Merge pull request #5304 from CatoTH/change-recommendations-for-amendments
Change recommendations for amendments
This commit is contained in:
commit
f9cd3ebd89
@ -18,6 +18,7 @@ import {
|
|||||||
import { ChangeRecoMode } from 'app/site/motions/motions.constants';
|
import { ChangeRecoMode } from 'app/site/motions/motions.constants';
|
||||||
import { BaseRepository } from '../base-repository';
|
import { BaseRepository } from '../base-repository';
|
||||||
import { DiffService, LineRange, ModificationType } from '../../ui-services/diff.service';
|
import { DiffService, LineRange, ModificationType } from '../../ui-services/diff.service';
|
||||||
|
import { LinenumberingService } from '../../ui-services/linenumbering.service';
|
||||||
import { ViewMotion } from '../../../site/motions/models/view-motion';
|
import { ViewMotion } from '../../../site/motions/models/view-motion';
|
||||||
import { ViewUnifiedChange } from '../../../shared/models/motions/view-unified-change';
|
import { ViewUnifiedChange } from '../../../shared/models/motions/view-unified-change';
|
||||||
|
|
||||||
@ -50,7 +51,9 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
|||||||
* @param {CollectionStringMapperService} mapperService Maps collection strings to classes
|
* @param {CollectionStringMapperService} mapperService Maps collection strings to classes
|
||||||
* @param {ViewModelStoreService} viewModelStoreService
|
* @param {ViewModelStoreService} viewModelStoreService
|
||||||
* @param {TranslateService} translate
|
* @param {TranslateService} translate
|
||||||
|
* @param {RelationManagerService} relationManager
|
||||||
* @param {DiffService} diffService
|
* @param {DiffService} diffService
|
||||||
|
* @param {LinenumberingService} lineNumbering Line numbering service
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
DS: DataStoreService,
|
DS: DataStoreService,
|
||||||
@ -59,7 +62,8 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
|||||||
viewModelStoreService: ViewModelStoreService,
|
viewModelStoreService: ViewModelStoreService,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
relationManager: RelationManagerService,
|
relationManager: RelationManagerService,
|
||||||
private diffService: DiffService
|
private diffService: DiffService,
|
||||||
|
private lineNumbering: LinenumberingService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
DS,
|
DS,
|
||||||
@ -103,7 +107,7 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
|||||||
/**
|
/**
|
||||||
* Synchronously getting the change recommendations of the corresponding motion.
|
* Synchronously getting the change recommendations of the corresponding motion.
|
||||||
*
|
*
|
||||||
* @param motionId the id of the target motion
|
* @param motion_id the id of the target motion
|
||||||
* @returns the array of change recommendations to the motions.
|
* @returns the array of change recommendations to the motions.
|
||||||
*/
|
*/
|
||||||
public getChangeRecoOfMotion(motion_id: number): ViewMotionChangeRecommendation[] {
|
public getChangeRecoOfMotion(motion_id: number): ViewMotionChangeRecommendation[] {
|
||||||
@ -171,22 +175,61 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
|||||||
* @param {LineRange} lineRange
|
* @param {LineRange} lineRange
|
||||||
* @param {number} lineLength
|
* @param {number} lineLength
|
||||||
*/
|
*/
|
||||||
public createChangeRecommendationTemplate(
|
public createMotionChangeRecommendationTemplate(
|
||||||
motion: ViewMotion,
|
motion: ViewMotion,
|
||||||
lineRange: LineRange,
|
lineRange: LineRange,
|
||||||
lineLength: number
|
lineLength: number
|
||||||
): ViewMotionChangeRecommendation {
|
): ViewMotionChangeRecommendation {
|
||||||
|
const motionText = this.lineNumbering.insertLineNumbers(motion.text, lineLength);
|
||||||
|
|
||||||
const changeReco = new MotionChangeRecommendation();
|
const changeReco = new MotionChangeRecommendation();
|
||||||
changeReco.line_from = lineRange.from;
|
changeReco.line_from = lineRange.from;
|
||||||
changeReco.line_to = lineRange.to;
|
changeReco.line_to = lineRange.to;
|
||||||
changeReco.type = ModificationType.TYPE_REPLACEMENT;
|
changeReco.type = ModificationType.TYPE_REPLACEMENT;
|
||||||
changeReco.text = this.diffService.extractMotionLineRange(motion.text, lineRange, false, lineLength, null);
|
changeReco.text = this.diffService.extractMotionLineRange(motionText, lineRange, false, lineLength, null);
|
||||||
changeReco.rejected = false;
|
changeReco.rejected = false;
|
||||||
changeReco.motion_id = motion.id;
|
changeReco.motion_id = motion.id;
|
||||||
|
|
||||||
return new ViewMotionChangeRecommendation(changeReco);
|
return new ViewMotionChangeRecommendation(changeReco);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link ViewMotionChangeRecommendation} object based on the amendment ID, the precalculated
|
||||||
|
* paragraphs (because we don't have access to motion-repository serice here) 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} amendment
|
||||||
|
* @param {string[]} lineNumberedParagraphs
|
||||||
|
* @param {LineRange} lineRange
|
||||||
|
* @param {number} lineLength
|
||||||
|
*/
|
||||||
|
public createAmendmentChangeRecommendationTemplate(
|
||||||
|
amendment: ViewMotion,
|
||||||
|
lineNumberedParagraphs: string[],
|
||||||
|
lineRange: LineRange,
|
||||||
|
lineLength: number
|
||||||
|
): ViewMotionChangeRecommendation {
|
||||||
|
const consolidatedText = lineNumberedParagraphs.join('\n');
|
||||||
|
|
||||||
|
const extracted = this.diffService.extractRangeByLineNumbers(consolidatedText, lineRange.from, lineRange.to);
|
||||||
|
const extractedHtml =
|
||||||
|
extracted.outerContextStart +
|
||||||
|
extracted.innerContextStart +
|
||||||
|
extracted.html +
|
||||||
|
extracted.innerContextEnd +
|
||||||
|
extracted.outerContextEnd;
|
||||||
|
|
||||||
|
const changeReco = new MotionChangeRecommendation();
|
||||||
|
changeReco.line_from = lineRange.from;
|
||||||
|
changeReco.line_to = lineRange.to;
|
||||||
|
changeReco.type = ModificationType.TYPE_REPLACEMENT;
|
||||||
|
changeReco.rejected = false;
|
||||||
|
changeReco.motion_id = amendment.id;
|
||||||
|
changeReco.text = extractedHtml;
|
||||||
|
|
||||||
|
return new ViewMotionChangeRecommendation(changeReco);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link ViewMotionChangeRecommendation} object to change the title, based on the motion ID.
|
* 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.
|
* This object is not saved yet and does not yet have any changed title. It's meant to populate the UI form.
|
||||||
|
@ -37,7 +37,7 @@ import { BaseIsAgendaItemAndListOfSpeakersContentObjectRepository } from '../bas
|
|||||||
import { NestedModelDescriptors } from '../base-repository';
|
import { NestedModelDescriptors } from '../base-repository';
|
||||||
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
|
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
|
||||||
import { DataSendService } from '../../core-services/data-send.service';
|
import { DataSendService } from '../../core-services/data-send.service';
|
||||||
import { LinenumberingService, LineNumberRange } from '../../ui-services/linenumbering.service';
|
import { LineNumberedString, LinenumberingService, LineNumberRange } from '../../ui-services/linenumbering.service';
|
||||||
|
|
||||||
type SortProperty = 'weight' | 'identifier';
|
type SortProperty = 'weight' | 'identifier';
|
||||||
|
|
||||||
@ -201,11 +201,14 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
* @param DS The DataStore
|
* @param DS The DataStore
|
||||||
* @param mapperService Maps collection strings to classes
|
* @param mapperService Maps collection strings to classes
|
||||||
* @param dataSend sending changed objects
|
* @param dataSend sending changed objects
|
||||||
|
* @param viewModelStoreService ViewModelStoreService
|
||||||
|
* @param translate
|
||||||
|
* @param relationManager
|
||||||
* @param httpService OpenSlides own Http service
|
* @param httpService OpenSlides own Http service
|
||||||
* @param lineNumbering Line numbering for motion text
|
* @param lineNumbering Line numbering for motion text
|
||||||
* @param diff Display changes in motion text as diff.
|
* @param diff Display changes in motion text as diff.
|
||||||
* @param personalNoteService service fo personal notes
|
|
||||||
* @param config ConfigService (subscribe to sorting config)
|
* @param config ConfigService (subscribe to sorting config)
|
||||||
|
* @param operator
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
DS: DataStoreService,
|
DS: DataStoreService,
|
||||||
@ -322,7 +325,16 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
ownKey: 'diffLines',
|
ownKey: 'diffLines',
|
||||||
get: (motion: Motion, viewMotion: ViewMotion) => {
|
get: (motion: Motion, viewMotion: ViewMotion) => {
|
||||||
if (viewMotion.parent) {
|
if (viewMotion.parent) {
|
||||||
return this.getAmendmentParagraphs(viewMotion, this.motionLineLength, false);
|
const changeRecos = viewMotion.changeRecommendations.filter(changeReco =>
|
||||||
|
changeReco.showInFinalView()
|
||||||
|
);
|
||||||
|
return this.getAmendmentParagraphLines(
|
||||||
|
viewMotion,
|
||||||
|
this.motionLineLength,
|
||||||
|
ChangeRecoMode.Changed,
|
||||||
|
changeRecos,
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getCacheObjectToCheck: (viewMotion: ViewMotion) => viewMotion.parent
|
getCacheObjectToCheck: (viewMotion: ViewMotion) => viewMotion.parent
|
||||||
@ -376,7 +388,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
/**
|
/**
|
||||||
* Set the state of motions in bulk
|
* Set the state of motions in bulk
|
||||||
*
|
*
|
||||||
* @param viewMotion target motion
|
* @param viewMotions target motions
|
||||||
* @param stateId the number that indicates the state
|
* @param stateId the number that indicates the state
|
||||||
*/
|
*/
|
||||||
public async setMultiState(viewMotions: ViewMotion[], stateId: number): Promise<void> {
|
public async setMultiState(viewMotions: ViewMotion[], stateId: number): Promise<void> {
|
||||||
@ -390,7 +402,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
/**
|
/**
|
||||||
* Set the motion blocks of motions in bulk
|
* Set the motion blocks of motions in bulk
|
||||||
*
|
*
|
||||||
* @param viewMotion target motion
|
* @param viewMotions target motions
|
||||||
* @param motionblockId the number that indicates the motion block
|
* @param motionblockId the number that indicates the motion block
|
||||||
*/
|
*/
|
||||||
public async setMultiMotionBlock(viewMotions: ViewMotion[], motionblockId: number): Promise<void> {
|
public async setMultiMotionBlock(viewMotions: ViewMotion[], motionblockId: number): Promise<void> {
|
||||||
@ -404,7 +416,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
/**
|
/**
|
||||||
* Set the category of motions in bulk
|
* Set the category of motions in bulk
|
||||||
*
|
*
|
||||||
* @param viewMotion target motion
|
* @param viewMotions target motions
|
||||||
* @param categoryId the number that indicates the category
|
* @param categoryId the number that indicates the category
|
||||||
*/
|
*/
|
||||||
public async setMultiCategory(viewMotions: ViewMotion[], categoryId: number): Promise<void> {
|
public async setMultiCategory(viewMotions: ViewMotion[], categoryId: number): Promise<void> {
|
||||||
@ -609,11 +621,12 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
case ChangeRecoMode.Diff:
|
case ChangeRecoMode.Diff:
|
||||||
const text = [];
|
const text = [];
|
||||||
const changesToShow = changes.filter(change => change.showInDiffView());
|
const changesToShow = changes.filter(change => change.showInDiffView());
|
||||||
|
const motionText = this.lineNumbering.insertLineNumbers(targetMotion.text, lineLength);
|
||||||
|
|
||||||
for (let i = 0; i < changesToShow.length; i++) {
|
for (let i = 0; i < changesToShow.length; i++) {
|
||||||
text.push(
|
text.push(
|
||||||
this.diff.extractMotionLineRange(
|
this.diff.extractMotionLineRange(
|
||||||
targetMotion.text,
|
motionText,
|
||||||
{
|
{
|
||||||
from: i === 0 ? 1 : changesToShow[i - 1].getLineTo(),
|
from: i === 0 ? 1 : changesToShow[i - 1].getLineTo(),
|
||||||
to: changesToShow[i].getLineFrom()
|
to: changesToShow[i].getLineFrom()
|
||||||
@ -624,18 +637,11 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
text.push(
|
text.push(this.diff.getChangeDiff(motionText, changesToShow[i], lineLength, highlightLine));
|
||||||
this.diff.getChangeDiff(targetMotion.text, changesToShow[i], lineLength, highlightLine)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text.push(
|
text.push(
|
||||||
this.diff.getTextRemainderAfterLastChange(
|
this.diff.getTextRemainderAfterLastChange(motionText, changesToShow, lineLength, highlightLine)
|
||||||
targetMotion.text,
|
|
||||||
changesToShow,
|
|
||||||
lineLength,
|
|
||||||
highlightLine
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
return text.join('');
|
return text.join('');
|
||||||
case ChangeRecoMode.Final:
|
case ChangeRecoMode.Final:
|
||||||
@ -760,34 +766,120 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all paragraphs that are affected by the given amendment in diff-format
|
* Returns the amended paragraphs by an amendment. Correlates to the amendment_paragraphs field,
|
||||||
|
* but also considers relevant change recommendations.
|
||||||
|
* The returned array includes "null" values for paragraphs that have not been changed.
|
||||||
*
|
*
|
||||||
* @param {ViewMotion} amendment
|
* @param {ViewMotion} amendment
|
||||||
* @param {number} lineLength
|
* @param {number} lineLength
|
||||||
|
* @param {ViewMotionChangeRecommendation[]} changes
|
||||||
|
* @param {boolean} includeUnchanged
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public applyChangesToAmendment(
|
||||||
|
amendment: ViewMotion,
|
||||||
|
lineLength: number,
|
||||||
|
changes: ViewMotionChangeRecommendation[],
|
||||||
|
includeUnchanged: boolean
|
||||||
|
): string[] {
|
||||||
|
const motion = amendment.parent;
|
||||||
|
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
|
||||||
|
|
||||||
|
// Changes need to be applied from the bottom up, to prevent conflicts with changing line numbers.
|
||||||
|
changes.sort((change1: ViewUnifiedChange, change2: ViewUnifiedChange) => {
|
||||||
|
if (change1.getLineFrom() < change2.getLineFrom()) {
|
||||||
|
return 1;
|
||||||
|
} else if (change1.getLineFrom() > change2.getLineFrom()) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return amendment.amendment_paragraphs.map((newText: string, paraNo: number) => {
|
||||||
|
let paragraph: string;
|
||||||
|
let paragraphHasChanges;
|
||||||
|
|
||||||
|
if (newText === null) {
|
||||||
|
paragraph = baseParagraphs[paraNo];
|
||||||
|
paragraphHasChanges = false;
|
||||||
|
} else {
|
||||||
|
// Add line numbers to newText, relative to the baseParagraph, by creating a diff
|
||||||
|
// to the line numbered base version any applying it right away
|
||||||
|
const diff = this.diff.diff(baseParagraphs[paraNo], newText);
|
||||||
|
paragraph = this.diff.diffHtmlToFinalText(diff);
|
||||||
|
paragraphHasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const affected: LineNumberRange = this.lineNumbering.getLineNumberRange(paragraph);
|
||||||
|
|
||||||
|
changes.forEach((change: ViewMotionChangeRecommendation) => {
|
||||||
|
// Hint: this assumes that change recommendations only affect one specific paragraph, not multiple
|
||||||
|
if (change.line_from >= affected.from && change.line_from < affected.to) {
|
||||||
|
paragraph = this.diff.replaceLines(paragraph, change.text, change.line_from, change.line_to);
|
||||||
|
|
||||||
|
// Reapply relative line numbers
|
||||||
|
const diff = this.diff.diff(baseParagraphs[paraNo], paragraph);
|
||||||
|
paragraph = this.diff.diffHtmlToFinalText(diff);
|
||||||
|
|
||||||
|
paragraphHasChanges = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (paragraphHasChanges || includeUnchanged) {
|
||||||
|
return paragraph;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all paragraph lines that are affected by the given amendment in diff-format, including context
|
||||||
|
*
|
||||||
|
* @param {ViewMotion} amendment
|
||||||
|
* @param {number} lineLength
|
||||||
|
* @param {ChangeRecoMode} crMode
|
||||||
|
* @param {ViewMotionChangeRecommendation[]} changeRecommendations
|
||||||
* @param {boolean} includeUnchanged
|
* @param {boolean} includeUnchanged
|
||||||
* @returns {DiffLinesInParagraph}
|
* @returns {DiffLinesInParagraph}
|
||||||
*/
|
*/
|
||||||
public getAmendmentParagraphs(
|
public getAmendmentParagraphLines(
|
||||||
amendment: ViewMotion,
|
amendment: ViewMotion,
|
||||||
lineLength: number,
|
lineLength: number,
|
||||||
|
crMode: ChangeRecoMode,
|
||||||
|
changeRecommendations: ViewMotionChangeRecommendation[],
|
||||||
includeUnchanged: boolean
|
includeUnchanged: boolean
|
||||||
): DiffLinesInParagraph[] {
|
): DiffLinesInParagraph[] {
|
||||||
const motion = amendment.parent;
|
const motion = amendment.parent;
|
||||||
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
|
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
|
||||||
|
|
||||||
return (amendment.amendment_paragraphs || [])
|
let amendmentParagraphs;
|
||||||
|
if (crMode === ChangeRecoMode.Changed) {
|
||||||
|
amendmentParagraphs = this.applyChangesToAmendment(amendment, lineLength, changeRecommendations, true);
|
||||||
|
} else {
|
||||||
|
amendmentParagraphs = amendment.amendment_paragraphs || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return amendmentParagraphs
|
||||||
.map(
|
.map(
|
||||||
(newText: string, paraNo: number): DiffLinesInParagraph => {
|
(newText: string, paraNo: number): DiffLinesInParagraph => {
|
||||||
if (newText !== null) {
|
if (newText !== null) {
|
||||||
return this.diff.getAmendmentParagraphsLinesByMode(
|
return this.diff.getAmendmentParagraphsLines(
|
||||||
paraNo,
|
paraNo,
|
||||||
baseParagraphs[paraNo],
|
baseParagraphs[paraNo],
|
||||||
newText,
|
newText,
|
||||||
lineLength
|
lineLength
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Nothing has changed in this paragraph
|
return null; // Nothing has changed in this paragraph
|
||||||
if (includeUnchanged) {
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.map((diffLines: DiffLinesInParagraph, paraNo: number) => {
|
||||||
|
// If nothing has changed and we want to keep unchanged paragraphs for the context,
|
||||||
|
// return the original text in "textPre"
|
||||||
|
if (diffLines === null && includeUnchanged) {
|
||||||
const paragraph_line_range = this.lineNumbering.getLineNumberRange(baseParagraphs[paraNo]);
|
const paragraph_line_range = this.lineNumbering.getLineNumberRange(baseParagraphs[paraNo]);
|
||||||
return {
|
return {
|
||||||
paragraphNo: paraNo,
|
paragraphNo: paraNo,
|
||||||
@ -800,26 +892,47 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
textPost: ''
|
textPost: ''
|
||||||
} as DiffLinesInParagraph;
|
} as DiffLinesInParagraph;
|
||||||
} else {
|
} else {
|
||||||
return null; // null will make this paragraph filtered out
|
return diffLines;
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
.filter((para: DiffLinesInParagraph) => para !== null);
|
.filter((para: DiffLinesInParagraph) => para !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getAmendmentParagraphLinesTitle(paragraph: DiffLinesInParagraph): string {
|
||||||
|
if (paragraph.diffLineTo === paragraph.diffLineFrom + 1) {
|
||||||
|
return this.translate.instant('Line') + ' ' + paragraph.diffLineFrom.toString(10);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
this.translate.instant('Line') +
|
||||||
|
' ' +
|
||||||
|
paragraph.diffLineFrom.toString(10) +
|
||||||
|
' - ' +
|
||||||
|
(paragraph.diffLineTo - 1).toString(10)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all paragraphs that are affected by the given amendment as unified change objects.
|
* Returns all paragraphs that are affected by the given amendment as unified change objects.
|
||||||
|
* Only the affected part of each paragraph is returned.
|
||||||
|
* Change recommendations to this amendment are considered here, too. That is, if a change recommendation
|
||||||
|
* for an amendment exists and is not rejected, the changed amendment will be returned here.
|
||||||
*
|
*
|
||||||
* @param {ViewMotion} amendment
|
* @param {ViewMotion} amendment
|
||||||
* @param {number} lineLength
|
* @param {number} lineLength
|
||||||
|
* @param {ViewMotionChangeRecommendation[]} changeRecos
|
||||||
* @returns {ViewMotionAmendedParagraph[]}
|
* @returns {ViewMotionAmendedParagraph[]}
|
||||||
*/
|
*/
|
||||||
public getAmendmentAmendedParagraphs(amendment: ViewMotion, lineLength: number): ViewMotionAmendedParagraph[] {
|
public getAmendmentAmendedParagraphs(
|
||||||
|
amendment: ViewMotion,
|
||||||
|
lineLength: number,
|
||||||
|
changeRecos: ViewMotionChangeRecommendation[]
|
||||||
|
): ViewMotionAmendedParagraph[] {
|
||||||
const motion = amendment.parent;
|
const motion = amendment.parent;
|
||||||
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
|
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
|
||||||
|
const changedAmendmentParagraphs = this.applyChangesToAmendment(amendment, lineLength, changeRecos, false);
|
||||||
|
|
||||||
return (amendment.amendment_paragraphs || [])
|
return changedAmendmentParagraphs
|
||||||
.map(
|
.map(
|
||||||
(newText: string, paraNo: number): ViewMotionAmendedParagraph => {
|
(newText: string, paraNo: number): ViewMotionAmendedParagraph => {
|
||||||
if (newText === null) {
|
if (newText === null) {
|
||||||
@ -844,6 +957,42 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
|||||||
.filter((para: ViewMotionAmendedParagraph) => para !== null);
|
.filter((para: ViewMotionAmendedParagraph) => para !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For unchanged paragraphs, this returns the original motion paragraph, including line numbers.
|
||||||
|
* For changed paragraphs, this returns the content of the amendment_paragraphs-field,
|
||||||
|
* but including line numbers relative to the original motion line numbers,
|
||||||
|
* so they can be used for the amendment change recommendations
|
||||||
|
*
|
||||||
|
* @param {ViewMotion} amendment
|
||||||
|
* @param {number} lineLength
|
||||||
|
* @param {boolean} withDiff
|
||||||
|
* @returns {LineNumberedString[]}
|
||||||
|
*/
|
||||||
|
public getAllAmendmentParagraphsWithOriginalLineNumbers(
|
||||||
|
amendment: ViewMotion,
|
||||||
|
lineLength: number,
|
||||||
|
withDiff: boolean
|
||||||
|
): LineNumberedString[] {
|
||||||
|
const motion = amendment.parent;
|
||||||
|
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
|
||||||
|
|
||||||
|
return (amendment.amendment_paragraphs || []).map((newText: string, paraNo: number): string => {
|
||||||
|
const origText = baseParagraphs[paraNo];
|
||||||
|
|
||||||
|
if (newText === null) {
|
||||||
|
return origText;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = this.diff.diff(origText, newText);
|
||||||
|
|
||||||
|
if (withDiff) {
|
||||||
|
return diff;
|
||||||
|
} else {
|
||||||
|
return this.diff.diffHtmlToFinalText(diff);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the acceptance of the current recommendation to the server
|
* Signals the acceptance of the current recommendation to the server
|
||||||
*
|
*
|
||||||
|
@ -1172,6 +1172,28 @@ describe('DiffService', () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
it('detects a word replacement at the end of line correctly', inject([DiffService], (service: DiffService) => {
|
||||||
|
const before =
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'wuid Brotzeit? Pfenningguat Stubn bitt da, hog di hi fei nia need nia need Goaßmaß ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'gscheid kloan mim';
|
||||||
|
const after =
|
||||||
|
'<P>wuid Brotzeit? Pfenningguat Stubn bitt da, ' +
|
||||||
|
'hog di hi fei nia need nia need Radler gscheid kloan mim';
|
||||||
|
|
||||||
|
const diff = service.diff(before, after);
|
||||||
|
expect(diff).toBe(
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'wuid Brotzeit? Pfenningguat Stubn bitt da, ' +
|
||||||
|
'hog di hi fei nia need nia need <del>Goaßmaß </del><ins>Radler </ins>' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'gscheid kloan mim</p>'
|
||||||
|
);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('addCSSClassToFirstTag function', () => {
|
describe('addCSSClassToFirstTag function', () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { LinenumberingService } from './linenumbering.service';
|
import { LineNumberedString, LinenumberingService } from './linenumbering.service';
|
||||||
import { ViewUnifiedChange } from '../../shared/models/motions/view-unified-change';
|
import { ViewUnifiedChange } from '../../shared/models/motions/view-unified-change';
|
||||||
|
|
||||||
const ELEMENT_NODE = 1;
|
const ELEMENT_NODE = 1;
|
||||||
@ -1318,12 +1318,12 @@ export class DiffService {
|
|||||||
* - extracting line 2 to 3 results in <p class="os-split-after os-split-before">Line 2</p>
|
* - extracting line 2 to 3 results in <p class="os-split-after os-split-before">Line 2</p>
|
||||||
* - extracting line 3 to null/4 results in <p class="os-split-before">Line 3</p>
|
* - extracting line 3 to null/4 results in <p class="os-split-before">Line 3</p>
|
||||||
*
|
*
|
||||||
* @param {string} htmlIn
|
* @param {LineNumberedString} htmlIn
|
||||||
* @param {number} fromLine
|
* @param {number} fromLine
|
||||||
* @param {number} toLine
|
* @param {number} toLine
|
||||||
* @returns {ExtractedContent}
|
* @returns {ExtractedContent}
|
||||||
*/
|
*/
|
||||||
public extractRangeByLineNumbers(htmlIn: string, fromLine: number, toLine: number): ExtractedContent {
|
public extractRangeByLineNumbers(htmlIn: LineNumberedString, fromLine: number, toLine: number): ExtractedContent {
|
||||||
if (typeof htmlIn !== 'string') {
|
if (typeof htmlIn !== 'string') {
|
||||||
throw new Error('Invalid call - extractRangeByLineNumbers expects a string as first argument');
|
throw new Error('Invalid call - extractRangeByLineNumbers expects a string as first argument');
|
||||||
}
|
}
|
||||||
@ -1878,15 +1878,28 @@ export class DiffService {
|
|||||||
// Remove <del> tags that only delete line numbers
|
// Remove <del> tags that only delete line numbers
|
||||||
// We need to do this before removing </del><del> as done in one of the next statements
|
// We need to do this before removing </del><del> as done in one of the next statements
|
||||||
diffUnnormalized = diffUnnormalized.replace(
|
diffUnnormalized = diffUnnormalized.replace(
|
||||||
/<del>((<BR CLASS="os-line-break"><\/del><del>)?(<span[^>]+os-line-number[^>]+?>)(\s|<\/?del>)*<\/span>)<\/del>/gi,
|
/<del>(((<BR CLASS="os-line-break">)<\/del><del>)?(<span[^>]+os-line-number[^>]+?>)(\s|<\/?del>)*<\/span>)<\/del>/gi,
|
||||||
(found: string, tag: string, br: string, span: string): string => {
|
(found: string, tag: string, brWithDel: string, plainBr: string, span: string): string => {
|
||||||
return (br !== undefined ? br : '') + span + ' </span>';
|
return (plainBr !== undefined ? plainBr : '') + span + ' </span>';
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merging individual insert/delete statements into bigger blocks
|
// Merging individual insert/delete statements into bigger blocks
|
||||||
diffUnnormalized = diffUnnormalized.replace(/<\/ins><ins>/gi, '').replace(/<\/del><del>/gi, '');
|
diffUnnormalized = diffUnnormalized.replace(/<\/ins><ins>/gi, '').replace(/<\/del><del>/gi, '');
|
||||||
|
|
||||||
|
// If we have a <del>deleted word</del>LINEBREAK<ins>new word</ins>, let's assume that the insertion
|
||||||
|
// was actually done in the same line as the deletion.
|
||||||
|
// We don't have the LINEBREAK-markers in the new string, hence we can't be a 100% sure, but
|
||||||
|
// this will probably the more frequent case.
|
||||||
|
// This only really makes a differences for change recommendations anyway, where we split the text into lines
|
||||||
|
// Hint: if there is no deletion before the line break, we have the same issue, but cannot solve this here.
|
||||||
|
diffUnnormalized = diffUnnormalized.replace(
|
||||||
|
/(<\/del>)(<BR CLASS="os-line-break"><span[^>]+os-line-number[^>]+?>\s*<\/span>)(<ins>[\s\S]*?<\/ins>)/gi,
|
||||||
|
(found: string, del: string, br: string, ins: string): string => {
|
||||||
|
return del + ins + br;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// If only a few characters of a word have changed, don't display this as a replacement of the whole word,
|
// If only a few characters of a word have changed, don't display this as a replacement of the whole word,
|
||||||
// but only of these specific characters
|
// but only of these specific characters
|
||||||
diffUnnormalized = diffUnnormalized.replace(
|
diffUnnormalized = diffUnnormalized.replace(
|
||||||
@ -2138,7 +2151,7 @@ export class DiffService {
|
|||||||
* @param {number} lineLength the line length
|
* @param {number} lineLength the line length
|
||||||
* @return {DiffLinesInParagraph|null}
|
* @return {DiffLinesInParagraph|null}
|
||||||
*/
|
*/
|
||||||
public getAmendmentParagraphsLinesByMode(
|
public getAmendmentParagraphsLines(
|
||||||
paragraphNo: number,
|
paragraphNo: number,
|
||||||
origText: string,
|
origText: string,
|
||||||
newText: string,
|
newText: string,
|
||||||
@ -2190,20 +2203,18 @@ export class DiffService {
|
|||||||
* Returns the HTML with the changes, optionally with a highlighted line.
|
* Returns the HTML with the changes, optionally with a highlighted line.
|
||||||
* The original motion needs to be provided.
|
* The original motion needs to be provided.
|
||||||
*
|
*
|
||||||
* @param {string} motionHtml
|
* @param {LineNumberedString} html
|
||||||
* @param {ViewUnifiedChange} change
|
* @param {ViewUnifiedChange} change
|
||||||
* @param {number} lineLength
|
* @param {number} lineLength
|
||||||
* @param {number} highlight
|
* @param {number} highlight
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public getChangeDiff(
|
public getChangeDiff(
|
||||||
motionHtml: string,
|
html: LineNumberedString,
|
||||||
change: ViewUnifiedChange,
|
change: ViewUnifiedChange,
|
||||||
lineLength: number,
|
lineLength: number,
|
||||||
highlight?: number
|
highlight?: number
|
||||||
): string {
|
): string {
|
||||||
const html = this.lineNumberingService.insertLineNumbers(motionHtml, lineLength);
|
|
||||||
|
|
||||||
let data, oldText;
|
let data, oldText;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2248,14 +2259,14 @@ export class DiffService {
|
|||||||
/**
|
/**
|
||||||
* Returns the remainder text of the motion after the last change
|
* Returns the remainder text of the motion after the last change
|
||||||
*
|
*
|
||||||
* @param {string} motionHtml
|
* @param {LineNumberedString} motionHtml
|
||||||
* @param {ViewUnifiedChange[]} changes
|
* @param {ViewUnifiedChange[]} changes
|
||||||
* @param {number} lineLength
|
* @param {number} lineLength
|
||||||
* @param {number} highlight
|
* @param {number} highlight
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public getTextRemainderAfterLastChange(
|
public getTextRemainderAfterLastChange(
|
||||||
motionHtml: string,
|
motionHtml: LineNumberedString,
|
||||||
changes: ViewUnifiedChange[],
|
changes: ViewUnifiedChange[],
|
||||||
lineLength: number,
|
lineLength: number,
|
||||||
highlight?: number
|
highlight?: number
|
||||||
@ -2267,15 +2278,14 @@ export class DiffService {
|
|||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
const numberedHtml = this.lineNumberingService.insertLineNumbers(motionHtml, lineLength, highlight);
|
|
||||||
if (changes.length === 0) {
|
if (changes.length === 0) {
|
||||||
return numberedHtml;
|
return motionHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data = this.extractRangeByLineNumbers(numberedHtml, maxLine, null);
|
data = this.extractRangeByLineNumbers(motionHtml, maxLine, null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// This only happens (as far as we know) when the motion text has been altered (shortened)
|
// This only happens (as far as we know) when the motion text has been altered (shortened)
|
||||||
// without modifying the change recommendations accordingly.
|
// without modifying the change recommendations accordingly.
|
||||||
@ -2305,21 +2315,20 @@ export class DiffService {
|
|||||||
/**
|
/**
|
||||||
* Extracts a renderable HTML string representing the given line number range of this motion text
|
* Extracts a renderable HTML string representing the given line number range of this motion text
|
||||||
*
|
*
|
||||||
* @param {string} motionText
|
* @param {LineNumberedString} motionText
|
||||||
* @param {LineRange} lineRange
|
* @param {LineRange} lineRange
|
||||||
* @param {boolean} lineNumbers - weather to add line numbers to the returned HTML string
|
* @param {boolean} lineNumbers - weather to add line numbers to the returned HTML string
|
||||||
* @param {number} lineLength
|
* @param {number} lineLength
|
||||||
* @param {number|null} highlightedLine
|
* @param {number|null} highlightedLine
|
||||||
*/
|
*/
|
||||||
public extractMotionLineRange(
|
public extractMotionLineRange(
|
||||||
motionText: string,
|
motionText: LineNumberedString,
|
||||||
lineRange: LineRange,
|
lineRange: LineRange,
|
||||||
lineNumbers: boolean,
|
lineNumbers: boolean,
|
||||||
lineLength: number,
|
lineLength: number,
|
||||||
highlightedLine: number
|
highlightedLine: number
|
||||||
): string {
|
): string {
|
||||||
const origHtml = this.lineNumberingService.insertLineNumbers(motionText, lineLength, highlightedLine);
|
const extracted = this.extractRangeByLineNumbers(motionText, lineRange.from, lineRange.to);
|
||||||
const extracted = this.extractRangeByLineNumbers(origHtml, lineRange.from, lineRange.to);
|
|
||||||
let html =
|
let html =
|
||||||
extracted.outerContextStart +
|
extracted.outerContextStart +
|
||||||
extracted.innerContextStart +
|
extracted.innerContextStart +
|
||||||
|
@ -3,6 +3,11 @@ import { Injectable } from '@angular/core';
|
|||||||
const ELEMENT_NODE = 1;
|
const ELEMENT_NODE = 1;
|
||||||
const TEXT_NODE = 3;
|
const TEXT_NODE = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper to indicate that certain functions expect the provided HTML strings to contain line numbers
|
||||||
|
*/
|
||||||
|
export type LineNumberedString = string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies a point within a HTML Text Node where a line break might be possible, if the following word
|
* Specifies a point within a HTML Text Node where a line break might be possible, if the following word
|
||||||
* exceeds the maximum line length.
|
* exceeds the maximum line length.
|
||||||
@ -894,7 +899,7 @@ export class LinenumberingService {
|
|||||||
highlight?: number,
|
highlight?: number,
|
||||||
callback?: () => void,
|
callback?: () => void,
|
||||||
firstLine?: number
|
firstLine?: number
|
||||||
): string {
|
): LineNumberedString {
|
||||||
let newHtml, newRoot;
|
let newHtml, newRoot;
|
||||||
|
|
||||||
if (highlight > 0) {
|
if (highlight > 0) {
|
||||||
|
@ -199,7 +199,6 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
|||||||
* Extract the lines of the amendments
|
* Extract the lines of the amendments
|
||||||
* If an amendments has multiple changes, they will be printed like an array of strings
|
* If an amendments has multiple changes, they will be printed like an array of strings
|
||||||
*
|
*
|
||||||
* @param amendment the motion to create the amendment to
|
|
||||||
* @return The lines of the amendment
|
* @return The lines of the amendment
|
||||||
*/
|
*/
|
||||||
public getChangeLines(): string {
|
public getChangeLines(): string {
|
||||||
|
@ -6,8 +6,10 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service';
|
import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service';
|
||||||
|
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||||
import { DiffService, LineRange } from 'app/core/ui-services/diff.service';
|
import { DiffService, LineRange } from 'app/core/ui-services/diff.service';
|
||||||
|
import { LineNumberedString, LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change';
|
import { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change';
|
||||||
import { mediumDialogSettings } from 'app/shared/utils/dialog-settings';
|
import { mediumDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
@ -84,7 +86,9 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
|||||||
* @param translate
|
* @param translate
|
||||||
* @param matSnackBar
|
* @param matSnackBar
|
||||||
* @param diff
|
* @param diff
|
||||||
|
* @param lineNumbering
|
||||||
* @param recoRepo
|
* @param recoRepo
|
||||||
|
* @param motionRepo
|
||||||
* @param dialogService
|
* @param dialogService
|
||||||
* @param configService
|
* @param configService
|
||||||
* @param el
|
* @param el
|
||||||
@ -95,7 +99,9 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
|||||||
protected translate: TranslateService, // protected required for ng-translate-extract
|
protected translate: TranslateService, // protected required for ng-translate-extract
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
private diff: DiffService,
|
private diff: DiffService,
|
||||||
|
private lineNumbering: LinenumberingService,
|
||||||
private recoRepo: ChangeRecommendationRepositoryService,
|
private recoRepo: ChangeRecommendationRepositoryService,
|
||||||
|
private motionRepo: MotionRepositoryService,
|
||||||
private dialogService: MatDialog,
|
private dialogService: MatDialog,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private el: ElementRef,
|
private el: ElementRef,
|
||||||
@ -123,13 +129,16 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.diff.extractMotionLineRange(
|
let baseText: LineNumberedString;
|
||||||
this.motion.text,
|
if (this.motion.isParagraphBasedAmendment()) {
|
||||||
lineRange,
|
baseText = this.motionRepo
|
||||||
true,
|
.getAllAmendmentParagraphsWithOriginalLineNumbers(this.motion, this.lineLength, true)
|
||||||
this.lineLength,
|
.join('\n');
|
||||||
this.highlightedLine
|
} else {
|
||||||
);
|
baseText = this.lineNumbering.insertLineNumbers(this.motion.text, this.lineLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.diff.extractMotionLineRange(baseText, lineRange, true, this.lineLength, this.highlightedLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,7 +167,15 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
|||||||
* @param {ViewUnifiedChange} change
|
* @param {ViewUnifiedChange} change
|
||||||
*/
|
*/
|
||||||
public getDiff(change: ViewUnifiedChange): string {
|
public getDiff(change: ViewUnifiedChange): string {
|
||||||
return this.diff.getChangeDiff(this.motion.text, change, this.lineLength, this.highlightedLine);
|
let motionHtml: string;
|
||||||
|
if (this.motion.isParagraphBasedAmendment()) {
|
||||||
|
const parentMotion = this.motionRepo.getViewModel(this.motion.parent_id);
|
||||||
|
motionHtml = parentMotion.text;
|
||||||
|
} else {
|
||||||
|
motionHtml = this.motion.text;
|
||||||
|
}
|
||||||
|
const baseHtml = this.lineNumbering.insertLineNumbers(motionHtml, this.lineLength);
|
||||||
|
return this.diff.getChangeDiff(baseHtml, change, this.lineLength, this.highlightedLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,12 +185,15 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
|||||||
if (!this.lineLength) {
|
if (!this.lineLength) {
|
||||||
return ''; // @TODO This happens in the test case when the lineLength-variable is not set
|
return ''; // @TODO This happens in the test case when the lineLength-variable is not set
|
||||||
}
|
}
|
||||||
return this.diff.getTextRemainderAfterLastChange(
|
let baseText: LineNumberedString;
|
||||||
this.motion.text,
|
if (this.motion.isParagraphBasedAmendment()) {
|
||||||
this.changes,
|
baseText = this.motionRepo
|
||||||
this.lineLength,
|
.getAllAmendmentParagraphsWithOriginalLineNumbers(this.motion, this.lineLength, true)
|
||||||
this.highlightedLine
|
.join('\n');
|
||||||
);
|
} else {
|
||||||
|
baseText = this.lineNumbering.insertLineNumbers(this.motion.text, this.lineLength);
|
||||||
|
}
|
||||||
|
return this.diff.getTextRemainderAfterLastChange(baseText, this.changes, this.lineLength, this.highlightedLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
@ -15,16 +16,23 @@ import { LineRange, ModificationType } from 'app/core/ui-services/diff.service';
|
|||||||
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation';
|
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component displays the original motion text with annotated change commendations
|
* This component displays either the original motion text or the original amendment diff text
|
||||||
* and a method to create new change recommendations from the line numbers to the left of the text.
|
* with annotated change commendations and a method to create new change recommendations
|
||||||
* It's called from motion-details for displaying the whole motion text as well as from the diff view to show the
|
* from the line numbers to the left of the text.
|
||||||
* unchanged parts of the motion.
|
* It's called from motion-details for displaying the whole motion text as well as from the
|
||||||
|
* motion's or amendment's diff view to show the unchanged parts of the motion.
|
||||||
*
|
*
|
||||||
* The line numbers are provided within the pre-rendered HTML, so we have to work with raw HTML
|
* The line numbers are provided within the pre-rendered HTML, so we have to work with raw HTML
|
||||||
* and native HTML elements.
|
* and native HTML elements.
|
||||||
*
|
*
|
||||||
* It takes the styling from the parent component.
|
* It takes the styling from the parent component.
|
||||||
*
|
*
|
||||||
|
* Special hints regarding amendments:
|
||||||
|
* When used for paragraph-based amendments, this component is embedded once for each paragraph. Hence,
|
||||||
|
* not all changeRecommendations provided are relevant for this paragraph (as we put the decision about
|
||||||
|
* which changeRecommendations are relevant in this component, not the caller).
|
||||||
|
* TODO: Right now, only change recommendations affecting only one paragraph are supported
|
||||||
|
*
|
||||||
* ## Examples
|
* ## Examples
|
||||||
*
|
*
|
||||||
* ```html
|
* ```html
|
||||||
@ -63,12 +71,25 @@ export class MotionDetailOriginalChangeRecommendationsComponent implements OnIni
|
|||||||
|
|
||||||
public can_manage = false;
|
public can_manage = false;
|
||||||
|
|
||||||
|
// Calculated from the embedded line numbers after the text has been set.
|
||||||
|
// Hint: this numbering refers to the actual lines, not the line number markers;
|
||||||
|
// hence, if maxLineNo === 10, line no. 10 is still visible.
|
||||||
|
// This is semantically different from the diff algorithms.
|
||||||
|
private minLineNo: number = null;
|
||||||
|
private maxLineNo: number = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Renderer2} renderer
|
* @param {Renderer2} renderer
|
||||||
* @param {ElementRef} el
|
* @param {ElementRef} el
|
||||||
|
* @param {ChangeDetectorRef} cd
|
||||||
* @param {OperatorService} operator
|
* @param {OperatorService} operator
|
||||||
*/
|
*/
|
||||||
public constructor(private renderer: Renderer2, private el: ElementRef, private operator: OperatorService) {
|
public constructor(
|
||||||
|
private renderer: Renderer2,
|
||||||
|
private el: ElementRef,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
private operator: OperatorService
|
||||||
|
) {
|
||||||
this.operator.getUserObservable().subscribe(this.onPermissionsChanged.bind(this));
|
this.operator.getUserObservable().subscribe(this.onPermissionsChanged.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +128,21 @@ export class MotionDetailOriginalChangeRecommendationsComponent implements OnIni
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getTextChangeRecommendations(): ViewMotionChangeRecommendation[] {
|
public getTextChangeRecommendations(): ViewMotionChangeRecommendation[] {
|
||||||
return this.changeRecommendations.filter(reco => !reco.isTitleChange());
|
return this.changeRecommendations
|
||||||
|
.filter(reco => !reco.isTitleChange())
|
||||||
|
.filter(reco => reco.line_from >= this.minLineNo && reco.line_from <= this.maxLineNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setLineNumberCache(): void {
|
||||||
|
Array.from(this.element.querySelectorAll('.os-line-number')).forEach((lineNumberEl: Element) => {
|
||||||
|
const lineNumber = parseInt(lineNumberEl.getAttribute('data-line-number'), 10);
|
||||||
|
if (this.minLineNo === null || lineNumber < this.minLineNo) {
|
||||||
|
this.minLineNo = lineNumber;
|
||||||
|
}
|
||||||
|
if (this.maxLineNo === null || lineNumber > this.maxLineNo) {
|
||||||
|
this.maxLineNo = lineNumber;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -282,7 +317,9 @@ export class MotionDetailOriginalChangeRecommendationsComponent implements OnIni
|
|||||||
// If we show it right away, there will be nasty Angular warnings about changed values, as the position
|
// If we show it right away, there will be nasty Angular warnings about changed values, as the position
|
||||||
// is changing while the DOM updates
|
// is changing while the DOM updates
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
|
this.setLineNumberCache();
|
||||||
this.showChangeRecommendations = true;
|
this.showChangeRecommendations = true;
|
||||||
|
this.cd.detectChanges();
|
||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,7 +530,6 @@
|
|||||||
[matMenuTriggerFor]="changeRecoMenu"
|
[matMenuTriggerFor]="changeRecoMenu"
|
||||||
*ngIf="
|
*ngIf="
|
||||||
motion &&
|
motion &&
|
||||||
!motion.isParagraphBasedAmendment() &&
|
|
||||||
((allChangingObjects && allChangingObjects.length) || motion.modified_final_version)
|
((allChangingObjects && allChangingObjects.length) || motion.modified_final_version)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@ -874,33 +873,40 @@
|
|||||||
<div class="alert alert-info" *ngIf="motion.diffLines.length === 0">
|
<div class="alert alert-info" *ngIf="motion.diffLines.length === 0">
|
||||||
<span>{{ 'No changes at the text.' | translate }}</span>
|
<span>{{ 'No changes at the text.' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<ng-container *ngIf="!isRecoMode(ChangeRecoMode.Diff) && !isFinalEdit">
|
||||||
<div
|
<div
|
||||||
*ngFor="let paragraph of getAmendmentParagraphs(showAmendmentContext)"
|
*ngFor="let paragraph of getAmendmentParagraphs()"
|
||||||
class="motion-text motion-text-diff amendment-view"
|
class="motion-text motion-text-diff amendment-view"
|
||||||
[class.line-numbers-none]="isLineNumberingNone()"
|
[class.line-numbers-none]="isLineNumberingNone()"
|
||||||
[class.line-numbers-inline]="isLineNumberingInline()"
|
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||||
[class.line-numbers-outside]="isLineNumberingOutside()"
|
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||||
[class.amendment-context]="showAmendmentContext"
|
[class.amendment-context]="showAmendmentContext"
|
||||||
>
|
>
|
||||||
<!-- TODO: everything here is required for PDF as well. Should be in a service -->
|
<h3 class="amendment-line-header" *ngIf="!showAmendmentContext">{{ getAmendmentParagraphLinesTitle(paragraph) }}</h3>
|
||||||
<h3
|
|
||||||
*ngIf="paragraph.diffLineTo === paragraph.diffLineFrom + 1 && !showAmendmentContext"
|
|
||||||
class="amendment-line-header"
|
|
||||||
>
|
|
||||||
{{ 'Line' | translate }} {{ paragraph.diffLineFrom }}:
|
|
||||||
</h3>
|
|
||||||
<h3
|
|
||||||
*ngIf="paragraph.diffLineTo !== paragraph.diffLineFrom + 1 && !showAmendmentContext"
|
|
||||||
class="amendment-line-header"
|
|
||||||
>
|
|
||||||
{{ 'Line' | translate }} {{ paragraph.diffLineFrom }} - {{ paragraph.diffLineTo - 1 }}:
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<!-- TODO: Seems to be directly duplicated in the slide -->
|
<os-motion-detail-original-change-recommendations
|
||||||
<div class="paragraphcontext" [innerHtml]="paragraph.textPre | trust: 'html'"></div>
|
*ngIf="isLineNumberingOutside() && (isRecoMode(ChangeRecoMode.Original) || isRecoMode(ChangeRecoMode.Changed))"
|
||||||
<div [innerHtml]="paragraph.text | trust: 'html'"></div>
|
[html]="getAmendmentDiffTextWithContext(paragraph)"
|
||||||
<div class="paragraphcontext" [innerHtml]="paragraph.textPost | trust: 'html'"></div>
|
[changeRecommendations]="changeRecommendations"
|
||||||
|
(createChangeRecommendation)="createChangeRecommendation($event)"
|
||||||
|
(gotoChangeRecommendation)="gotoChangeRecommendation($event)"
|
||||||
|
></os-motion-detail-original-change-recommendations>
|
||||||
|
<div
|
||||||
|
*ngIf="!isLineNumberingOutside() || !(isRecoMode(ChangeRecoMode.Original) || isRecoMode(ChangeRecoMode.Changed))"
|
||||||
|
[outerHTML]="getAmendmentDiffTextWithContext(paragraph) | trust: 'html'"
|
||||||
|
></div><!-- the <div> element is only a placeholder -> outerHTML to replace it -->
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<os-motion-detail-diff
|
||||||
|
*ngIf="isRecoMode(ChangeRecoMode.Diff)"
|
||||||
|
[motion]="motion"
|
||||||
|
[changes]="getChangesForDiffMode()"
|
||||||
|
[scrollToChange]="scrollToChange"
|
||||||
|
[highlightedLine]="highlightedLine"
|
||||||
|
[lineNumberingMode]="lnMode"
|
||||||
|
(createChangeRecommendation)="createChangeRecommendation($event)"
|
||||||
|
></os-motion-detail-diff>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!motion.diffLines">
|
<div *ngIf="!motion.diffLines">
|
||||||
<span class="red-warning-text">{{
|
<span class="red-warning-text">{{
|
||||||
@ -910,7 +916,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Show entire motion text -->
|
<!-- Show entire motion text -->
|
||||||
<div>
|
<div *ngIf="isRecoMode(ChangeRecoMode.Original) || isRecoMode(ChangeRecoMode.Changed)">
|
||||||
<mat-checkbox
|
<mat-checkbox
|
||||||
(change)="showAmendmentContext = !showAmendmentContext"
|
(change)="showAmendmentContext = !showAmendmentContext"
|
||||||
*ngIf="motion && motion.isParagraphBasedAmendment()"
|
*ngIf="motion && motion.isParagraphBasedAmendment()"
|
||||||
@ -953,7 +959,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<!-- Diff View Menu -->
|
<!-- Diff View Menu
|
||||||
|
For motions, all items are available if there are changing objects. The final print template only after is has been created.
|
||||||
|
For paragraph-based amendments, only the original and the diff version is available.
|
||||||
|
-->
|
||||||
<mat-menu #changeRecoMenu="matMenu">
|
<mat-menu #changeRecoMenu="matMenu">
|
||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
@ -982,13 +991,13 @@
|
|||||||
mat-menu-item
|
mat-menu-item
|
||||||
(click)="setChangeRecoMode(ChangeRecoMode.Final)"
|
(click)="setChangeRecoMode(ChangeRecoMode.Final)"
|
||||||
[ngClass]="{ selected: crMode === ChangeRecoMode.Final }"
|
[ngClass]="{ selected: crMode === ChangeRecoMode.Final }"
|
||||||
*ngIf="allChangingObjects && allChangingObjects.length"
|
*ngIf="motion && !motion.isParagraphBasedAmendment() && allChangingObjects && allChangingObjects.length"
|
||||||
>
|
>
|
||||||
{{ 'Final version' | translate }}
|
{{ 'Final version' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
*ngIf="motion && motion.modified_final_version"
|
*ngIf="motion && motion.modified_final_version && !motion.isParagraphBasedAmendment()"
|
||||||
(click)="setChangeRecoMode(ChangeRecoMode.ModifiedFinal)"
|
(click)="setChangeRecoMode(ChangeRecoMode.ModifiedFinal)"
|
||||||
[ngClass]="{ selected: crMode === ChangeRecoMode.ModifiedFinal }"
|
[ngClass]="{ selected: crMode === ChangeRecoMode.ModifiedFinal }"
|
||||||
>
|
>
|
||||||
|
@ -215,10 +215,21 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
public changeRecommendations: ViewMotionChangeRecommendation[];
|
public changeRecommendations: ViewMotionChangeRecommendation[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All amendments to this motions
|
* All amendments to this motion
|
||||||
*/
|
*/
|
||||||
public amendments: ViewMotion[];
|
public amendments: ViewMotion[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The change recommendations to amendments to this motion
|
||||||
|
*/
|
||||||
|
public amendmentChangeRecos: { [amendmentId: string]: ViewMotionChangeRecommendation[] } = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The observables for the `amendmentChangeRecos` field above.
|
||||||
|
* Necessary to track which amendments' change recommendations we have already subscribed to.
|
||||||
|
*/
|
||||||
|
public amendmentChangeRecoSubscriptions: { [amendmentId: string]: Subscription } = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All change recommendations AND amendments, sorted by line number.
|
* All change recommendations AND amendments, sorted by line number.
|
||||||
*/
|
*/
|
||||||
@ -416,25 +427,25 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
* @param repo Motion Repository
|
* @param repo Motion Repository
|
||||||
* @param changeRecoRepo Change Recommendation Repository
|
* @param changeRecoRepo Change Recommendation Repository
|
||||||
* @param statuteRepo: Statute Paragraph Repository
|
* @param statuteRepo: Statute Paragraph Repository
|
||||||
* @param mediafileRepo Mediafile Repository
|
|
||||||
* @param DS The DataStoreService
|
|
||||||
* @param configService The configuration provider
|
* @param configService The configuration provider
|
||||||
* @param promptService ensure safe deletion
|
* @param promptService ensure safe deletion
|
||||||
* @param pdfExport export the motion to pdf
|
* @param pdfExport export the motion to pdf
|
||||||
* @param personalNoteService: personal comments and favorite marker
|
* @param personalNoteService: personal comments and favorite marker
|
||||||
* @param linenumberingService The line numbering service
|
* @param linenumberingService The line numbering service
|
||||||
* @param categoryRepo Repository for categories
|
* @param categoryRepo Repository for categories
|
||||||
* @param viewModelStore accessing view models
|
|
||||||
* @param categoryRepo access the category repository
|
|
||||||
* @param userRepo Repository for users
|
* @param userRepo Repository for users
|
||||||
* @param notifyService: NotifyService work with notification
|
* @param notifyService: NotifyService work with notification
|
||||||
* @param tagRepo
|
* @param tagRepo
|
||||||
* @param mediaFilerepo
|
|
||||||
* @param workflowRepo
|
* @param workflowRepo
|
||||||
* @param blockRepo
|
* @param blockRepo
|
||||||
* @param itemRepo
|
* @param itemRepo
|
||||||
* @param motionSortService
|
* @param motionSortService
|
||||||
* @param motionFilterListService
|
* @param amendmentSortService
|
||||||
|
* @param motionFilterService
|
||||||
|
* @param amendmentFilterService
|
||||||
|
* @param cd ChangeDetectorRef
|
||||||
|
* @param pollDialog
|
||||||
|
* @param motionPollService
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
@ -590,6 +601,24 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to all new amendment's change recommendations so we can access their data for the diff view
|
||||||
|
*/
|
||||||
|
private resetAmendmentChangeRecoListener(): void {
|
||||||
|
this.amendments.forEach((amendment: ViewMotion) => {
|
||||||
|
if (this.amendmentChangeRecoSubscriptions[amendment.id] === undefined) {
|
||||||
|
this.amendmentChangeRecoSubscriptions[
|
||||||
|
amendment.id
|
||||||
|
] = this.changeRecoRepo
|
||||||
|
.getChangeRecosOfMotionObservable(amendment.id)
|
||||||
|
.subscribe((changeRecos: ViewMotionChangeRecommendation[]): void => {
|
||||||
|
this.amendmentChangeRecos[amendment.id] = changeRecos;
|
||||||
|
this.recalcUnifiedChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges amendments and change recommendations and sorts them by the line numbers.
|
* Merges amendments and change recommendations and sorts them by the line numbers.
|
||||||
* Called each time one of these arrays changes.
|
* Called each time one of these arrays changes.
|
||||||
@ -614,8 +643,12 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
}
|
}
|
||||||
if (this.amendments) {
|
if (this.amendments) {
|
||||||
this.amendments.forEach((amendment: ViewMotion): void => {
|
this.amendments.forEach((amendment: ViewMotion): void => {
|
||||||
|
const toApplyChanges = (this.amendmentChangeRecos[amendment.id] || []).filter(
|
||||||
|
// The rejected change recommendations for amendments should not be considered
|
||||||
|
change => change.showInFinalView()
|
||||||
|
);
|
||||||
this.repo
|
this.repo
|
||||||
.getAmendmentAmendedParagraphs(amendment, this.lineLength)
|
.getAmendmentAmendedParagraphs(amendment, this.lineLength, toApplyChanges)
|
||||||
.forEach((change: ViewUnifiedChange): void => {
|
.forEach((change: ViewUnifiedChange): void => {
|
||||||
this.allChangingObjects.push(change);
|
this.allChangingObjects.push(change);
|
||||||
});
|
});
|
||||||
@ -630,6 +663,14 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When "diff" is the default view mode, the crMode might have been set
|
||||||
|
// before the motion and allChangingObjects have been loaded. As the availability of "diff" depends on
|
||||||
|
// allChangingObjects, we set "diff" first in this case (in the config-listener) and perform the actual
|
||||||
|
// check if "diff" is possible now.
|
||||||
|
// Test: "diff" as default view. Open a motion, create an amendment. "Original" should be set automatically.
|
||||||
|
this.crMode = this.determineCrMode(this.crMode);
|
||||||
|
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,6 +705,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
|
|
||||||
this.repo.amendmentsTo(motionId).subscribe((amendments: ViewMotion[]): void => {
|
this.repo.amendmentsTo(motionId).subscribe((amendments: ViewMotion[]): void => {
|
||||||
this.amendments = amendments;
|
this.amendments = amendments;
|
||||||
|
this.resetAmendmentChangeRecoListener();
|
||||||
this.recalcUnifiedChanges();
|
this.recalcUnifiedChanges();
|
||||||
}),
|
}),
|
||||||
this.repo
|
this.repo
|
||||||
@ -908,15 +950,48 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
return formatedText;
|
return formatedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the plain HTML of a changed area in an amendment, including its context,
|
||||||
|
* for the purpose of piping it into <motion-detail-original-change-recommendations>.
|
||||||
|
* This component works with plain HTML, hence we are composing plain HTML here, too.
|
||||||
|
*
|
||||||
|
* @param {DiffLinesInParagraph} paragraph
|
||||||
|
* @returns {string}
|
||||||
|
*
|
||||||
|
* TODO: Seems to be directly duplicated in the slide
|
||||||
|
*/
|
||||||
|
public getAmendmentDiffTextWithContext(paragraph: DiffLinesInParagraph): string {
|
||||||
|
return (
|
||||||
|
'<div class="paragraphcontext">' +
|
||||||
|
paragraph.textPre +
|
||||||
|
'</div>' +
|
||||||
|
'<div>' +
|
||||||
|
paragraph.text +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="paragraphcontext">' +
|
||||||
|
paragraph.textPost +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If `this.motion` is an amendment, this returns the list of all changed paragraphs.
|
* If `this.motion` is an amendment, this returns the list of all changed paragraphs.
|
||||||
* TODO: Cleanup: repo function could be injected part of the model, to have easier access
|
* TODO: Cleanup: repo function could be injected part of the model, to have easier access
|
||||||
*
|
*
|
||||||
* @param {boolean} includeUnchanged
|
|
||||||
* @returns {DiffLinesInParagraph[]}
|
* @returns {DiffLinesInParagraph[]}
|
||||||
*/
|
*/
|
||||||
public getAmendmentParagraphs(includeUnchanged: boolean): DiffLinesInParagraph[] {
|
public getAmendmentParagraphs(): DiffLinesInParagraph[] {
|
||||||
return this.repo.getAmendmentParagraphs(this.motion, this.lineLength, includeUnchanged);
|
return this.repo.getAmendmentParagraphLines(
|
||||||
|
this.motion,
|
||||||
|
this.lineLength,
|
||||||
|
this.crMode,
|
||||||
|
this.changeRecommendations,
|
||||||
|
this.showAmendmentContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAmendmentParagraphLinesTitle(paragraph: DiffLinesInParagraph): string {
|
||||||
|
return this.repo.getAmendmentParagraphLinesTitle(paragraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1063,12 +1138,27 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
editChangeRecommendation: false,
|
editChangeRecommendation: false,
|
||||||
newChangeRecommendation: true,
|
newChangeRecommendation: true,
|
||||||
lineRange: lineRange,
|
lineRange: lineRange,
|
||||||
changeRecommendation: this.changeRecoRepo.createChangeRecommendationTemplate(
|
changeRecommendation: null
|
||||||
|
};
|
||||||
|
if (this.motion.isParagraphBasedAmendment()) {
|
||||||
|
const lineNumberedParagraphs = this.repo.getAllAmendmentParagraphsWithOriginalLineNumbers(
|
||||||
|
this.motion,
|
||||||
|
this.lineLength,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
data.changeRecommendation = this.changeRecoRepo.createAmendmentChangeRecommendationTemplate(
|
||||||
|
this.motion,
|
||||||
|
lineNumberedParagraphs,
|
||||||
|
lineRange,
|
||||||
|
this.lineLength
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
data.changeRecommendation = this.changeRecoRepo.createMotionChangeRecommendationTemplate(
|
||||||
this.motion,
|
this.motion,
|
||||||
lineRange,
|
lineRange,
|
||||||
this.lineLength
|
this.lineLength
|
||||||
)
|
);
|
||||||
};
|
}
|
||||||
this.dialogService.open(MotionChangeRecommendationDialogComponent, {
|
this.dialogService.open(MotionChangeRecommendationDialogComponent, {
|
||||||
...mediumDialogSettings,
|
...mediumDialogSettings,
|
||||||
data: data
|
data: data
|
||||||
@ -1329,7 +1419,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
/**
|
/**
|
||||||
* Adds or removes a tag to the current motion
|
* Adds or removes a tag to the current motion
|
||||||
*
|
*
|
||||||
* @param id Motion tag id
|
* @param {MouseEvent} event
|
||||||
|
* @param {number} id Motion tag id
|
||||||
*/
|
*/
|
||||||
public setTag(event: MouseEvent, id: number): void {
|
public setTag(event: MouseEvent, id: number): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -1497,7 +1588,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
* Function to handle leaving persons and
|
* Function to handle leaving persons and
|
||||||
* recognize if there is no other person editing the same motion anymore.
|
* recognize if there is no other person editing the same motion anymore.
|
||||||
*
|
*
|
||||||
* @param senderId The id of the sender who has left the editing-view.
|
* @param senderName The name of the sender who has left the editing-view.
|
||||||
*/
|
*/
|
||||||
private recognizeOtherWorkerOnMotion(senderName: string): void {
|
private recognizeOtherWorkerOnMotion(senderName: string): void {
|
||||||
this.otherWorkOnMotion = this.otherWorkOnMotion.filter(value => value !== senderName);
|
this.otherWorkOnMotion = this.otherWorkOnMotion.filter(value => value !== senderName);
|
||||||
|
@ -64,7 +64,14 @@ export class MotionCsvExportService {
|
|||||||
const amendments = this.motionRepo.getAmendmentsInstantly(motion.id);
|
const amendments = this.motionRepo.getAmendmentsInstantly(motion.id);
|
||||||
if (amendments) {
|
if (amendments) {
|
||||||
for (const amendment of amendments) {
|
for (const amendment of amendments) {
|
||||||
const changedParagraphs = this.motionRepo.getAmendmentAmendedParagraphs(amendment, lineLength);
|
const changeRecos = this.changeRecoRepo
|
||||||
|
.getChangeRecoOfMotion(amendment.id)
|
||||||
|
.filter(reco => reco.showInFinalView());
|
||||||
|
const changedParagraphs = this.motionRepo.getAmendmentAmendedParagraphs(
|
||||||
|
amendment,
|
||||||
|
lineLength,
|
||||||
|
changeRecos
|
||||||
|
);
|
||||||
for (const change of changedParagraphs) {
|
for (const change of changedParagraphs) {
|
||||||
changes.push(change as ViewUnifiedChange);
|
changes.push(change as ViewUnifiedChange);
|
||||||
}
|
}
|
||||||
|
@ -576,17 +576,16 @@ export class MotionPdfService {
|
|||||||
if (motion.isParagraphBasedAmendment()) {
|
if (motion.isParagraphBasedAmendment()) {
|
||||||
// this is logically redundant with the formation of amendments in the motion-detail html.
|
// 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
|
// 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)) {
|
const changeRecos = this.changeRecoRepo.getChangeRecoOfMotion(motion.id);
|
||||||
if (paragraph.diffLineTo === paragraph.diffLineFrom + 1) {
|
const amendmentParas = this.motionRepo.getAmendmentParagraphLines(
|
||||||
motionText += `<h3>
|
motion,
|
||||||
${this.translate.instant('Line')} ${paragraph.diffLineFrom}:
|
lineLength,
|
||||||
</h3>`;
|
crMode,
|
||||||
} else {
|
changeRecos,
|
||||||
motionText += `<h3>
|
false
|
||||||
${this.translate.instant('Line')} ${paragraph.diffLineFrom} - ${paragraph.diffLineTo - 1}:
|
);
|
||||||
</h3>`;
|
for (const paragraph of amendmentParas) {
|
||||||
}
|
motionText += '<h3>' + this.motionRepo.getAmendmentParagraphLinesTitle(paragraph) + '</h3>';
|
||||||
|
|
||||||
motionText += `<div class="paragraphcontext">${paragraph.textPre}</div>`;
|
motionText += `<div class="paragraphcontext">${paragraph.textPre}</div>`;
|
||||||
motionText += paragraph.text;
|
motionText += paragraph.text;
|
||||||
motionText += `<div class="paragraphcontext">${paragraph.textPost}</div>`;
|
motionText += `<div class="paragraphcontext">${paragraph.textPost}</div>`;
|
||||||
@ -634,11 +633,12 @@ export class MotionPdfService {
|
|||||||
return this.changeRecoRepo
|
return this.changeRecoRepo
|
||||||
.getChangeRecoOfMotion(motion.id)
|
.getChangeRecoOfMotion(motion.id)
|
||||||
.concat(
|
.concat(
|
||||||
this.motionRepo
|
this.motionRepo.getAmendmentsInstantly(motion.id).flatMap((amendment: ViewMotion) => {
|
||||||
.getAmendmentsInstantly(motion.id)
|
const changeRecos = this.changeRecoRepo
|
||||||
.flatMap((amendment: ViewMotion) =>
|
.getChangeRecoOfMotion(amendment.id)
|
||||||
this.motionRepo.getAmendmentAmendedParagraphs(amendment, lineLength)
|
.filter(reco => reco.showInFinalView());
|
||||||
)
|
return this.motionRepo.getAmendmentAmendedParagraphs(amendment, lineLength, changeRecos);
|
||||||
|
})
|
||||||
)
|
)
|
||||||
.sort((a, b) => a.getLineFrom() - b.getLineFrom()) as ViewUnifiedChange[];
|
.sort((a, b) => a.getLineFrom() - b.getLineFrom()) as ViewUnifiedChange[];
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import { SlideData } from 'app/core/core-services/projector-data.service';
|
|||||||
import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service';
|
import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service';
|
||||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
import { DiffLinesInParagraph, DiffService, LineRange } from 'app/core/ui-services/diff.service';
|
import { DiffLinesInParagraph, DiffService, LineRange } from 'app/core/ui-services/diff.service';
|
||||||
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
import { LineNumberedString, LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
||||||
import { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change';
|
import { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change';
|
||||||
import { MotionTitleInformation } from 'app/site/motions/models/view-motion';
|
import { MotionTitleInformation } from 'app/site/motions/models/view-motion';
|
||||||
import { ChangeRecoMode, LineNumberingMode } from 'app/site/motions/motions.constants';
|
import { ChangeRecoMode, LineNumberingMode } from 'app/site/motions/motions.constants';
|
||||||
@ -270,19 +270,18 @@ export class MotionSlideComponent extends BaseMotionSlideComponent<MotionSlideDa
|
|||||||
/**
|
/**
|
||||||
* Extracts a renderable HTML string representing the given line number range of this motion
|
* Extracts a renderable HTML string representing the given line number range of this motion
|
||||||
*
|
*
|
||||||
* @param {string} motionHtml
|
* @param {LineNumberedString} motionHtml
|
||||||
* @param {LineRange} lineRange
|
* @param {LineRange} lineRange
|
||||||
* @param {boolean} lineNumbers - weather to add line numbers to the returned HTML string
|
* @param {boolean} lineNumbers - weather to add line numbers to the returned HTML string
|
||||||
* @param {number} lineLength
|
* @param {number} lineLength
|
||||||
*/
|
*/
|
||||||
public extractMotionLineRange(
|
public extractMotionLineRange(
|
||||||
motionHtml: string,
|
motionHtml: LineNumberedString,
|
||||||
lineRange: LineRange,
|
lineRange: LineRange,
|
||||||
lineNumbers: boolean,
|
lineNumbers: boolean,
|
||||||
lineLength: number
|
lineLength: number
|
||||||
): string {
|
): string {
|
||||||
const origHtml = this.lineNumbering.insertLineNumbers(motionHtml, this.lineLength, this.highlightedLine);
|
const extracted = this.diff.extractRangeByLineNumbers(motionHtml, lineRange.from, lineRange.to);
|
||||||
const extracted = this.diff.extractRangeByLineNumbers(origHtml, lineRange.from, lineRange.to);
|
|
||||||
let html =
|
let html =
|
||||||
extracted.outerContextStart +
|
extracted.outerContextStart +
|
||||||
extracted.innerContextStart +
|
extracted.innerContextStart +
|
||||||
@ -343,21 +342,22 @@ export class MotionSlideComponent extends BaseMotionSlideComponent<MotionSlideDa
|
|||||||
const changes = this.getAllTextChangingObjects().filter(change => {
|
const changes = this.getAllTextChangingObjects().filter(change => {
|
||||||
return change.showInDiffView();
|
return change.showInDiffView();
|
||||||
});
|
});
|
||||||
|
const motionText = this.lineNumbering.insertLineNumbers(motion.text, this.lineLength);
|
||||||
changes.forEach((change: ViewUnifiedChange, idx: number) => {
|
changes.forEach((change: ViewUnifiedChange, idx: number) => {
|
||||||
if (idx === 0) {
|
if (idx === 0) {
|
||||||
const lineRange = { from: 1, to: change.getLineFrom() };
|
const lineRange = { from: 1, to: change.getLineFrom() };
|
||||||
text += this.extractMotionLineRange(motion.text, lineRange, true, this.lineLength);
|
text += this.extractMotionLineRange(motionText, lineRange, true, this.lineLength);
|
||||||
} else if (changes[idx - 1].getLineTo() < change.getLineFrom()) {
|
} else if (changes[idx - 1].getLineTo() < change.getLineFrom()) {
|
||||||
const lineRange = {
|
const lineRange = {
|
||||||
from: changes[idx - 1].getLineTo(),
|
from: changes[idx - 1].getLineTo(),
|
||||||
to: change.getLineFrom()
|
to: change.getLineFrom()
|
||||||
};
|
};
|
||||||
text += this.extractMotionLineRange(motion.text, lineRange, true, this.lineLength);
|
text += this.extractMotionLineRange(motionText, lineRange, true, this.lineLength);
|
||||||
}
|
}
|
||||||
text += this.diff.getChangeDiff(motion.text, change, this.lineLength, this.highlightedLine);
|
text += this.diff.getChangeDiff(motionText, change, this.lineLength, this.highlightedLine);
|
||||||
});
|
});
|
||||||
text += this.diff.getTextRemainderAfterLastChange(
|
text += this.diff.getTextRemainderAfterLastChange(
|
||||||
motion.text,
|
motionText,
|
||||||
changes,
|
changes,
|
||||||
this.lineLength,
|
this.lineLength,
|
||||||
this.highlightedLine
|
this.highlightedLine
|
||||||
@ -412,7 +412,7 @@ export class MotionSlideComponent extends BaseMotionSlideComponent<MotionSlideDa
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Hint: can be either DiffLinesInParagraph or null, if no changes are made
|
// Hint: can be either DiffLinesInParagraph or null, if no changes are made
|
||||||
return this.diff.getAmendmentParagraphsLinesByMode(
|
return this.diff.getAmendmentParagraphsLines(
|
||||||
paraNo,
|
paraNo,
|
||||||
baseParagraphs[paraNo],
|
baseParagraphs[paraNo],
|
||||||
newText,
|
newText,
|
||||||
|
Loading…
Reference in New Issue
Block a user