Change recommendations for amendments
This commit is contained in:
parent
e0069f734a
commit
b51787129b
@ -18,6 +18,7 @@ import {
|
||||
import { ChangeRecoMode } from 'app/site/motions/motions.constants';
|
||||
import { BaseRepository } from '../base-repository';
|
||||
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 { 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 {ViewModelStoreService} viewModelStoreService
|
||||
* @param {TranslateService} translate
|
||||
* @param {RelationManagerService} relationManager
|
||||
* @param {DiffService} diffService
|
||||
* @param {LinenumberingService} lineNumbering Line numbering service
|
||||
*/
|
||||
public constructor(
|
||||
DS: DataStoreService,
|
||||
@ -59,7 +62,8 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
||||
viewModelStoreService: ViewModelStoreService,
|
||||
translate: TranslateService,
|
||||
relationManager: RelationManagerService,
|
||||
private diffService: DiffService
|
||||
private diffService: DiffService,
|
||||
private lineNumbering: LinenumberingService
|
||||
) {
|
||||
super(
|
||||
DS,
|
||||
@ -103,7 +107,7 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public getChangeRecoOfMotion(motion_id: number): ViewMotionChangeRecommendation[] {
|
||||
@ -171,22 +175,61 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
||||
* @param {LineRange} lineRange
|
||||
* @param {number} lineLength
|
||||
*/
|
||||
public createChangeRecommendationTemplate(
|
||||
public createMotionChangeRecommendationTemplate(
|
||||
motion: ViewMotion,
|
||||
lineRange: LineRange,
|
||||
lineLength: number
|
||||
): ViewMotionChangeRecommendation {
|
||||
const motionText = this.lineNumbering.insertLineNumbers(motion.text, lineLength);
|
||||
|
||||
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.text = this.diffService.extractMotionLineRange(motionText, lineRange, false, lineLength, null);
|
||||
changeReco.rejected = false;
|
||||
changeReco.motion_id = motion.id;
|
||||
|
||||
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.
|
||||
* 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 { CollectionStringMapperService } from '../../core-services/collection-string-mapper.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';
|
||||
|
||||
@ -201,11 +201,14 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
* @param DS The DataStore
|
||||
* @param mapperService Maps collection strings to classes
|
||||
* @param dataSend sending changed objects
|
||||
* @param viewModelStoreService ViewModelStoreService
|
||||
* @param translate
|
||||
* @param relationManager
|
||||
* @param httpService OpenSlides own Http service
|
||||
* @param lineNumbering Line numbering for motion text
|
||||
* @param diff Display changes in motion text as diff.
|
||||
* @param personalNoteService service fo personal notes
|
||||
* @param config ConfigService (subscribe to sorting config)
|
||||
* @param operator
|
||||
*/
|
||||
public constructor(
|
||||
DS: DataStoreService,
|
||||
@ -322,7 +325,16 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
ownKey: 'diffLines',
|
||||
get: (motion: Motion, viewMotion: ViewMotion) => {
|
||||
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
|
||||
@ -376,7 +388,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
/**
|
||||
* Set the state of motions in bulk
|
||||
*
|
||||
* @param viewMotion target motion
|
||||
* @param viewMotions target motions
|
||||
* @param stateId the number that indicates the state
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param viewMotion target motion
|
||||
* @param viewMotions target motions
|
||||
* @param motionblockId the number that indicates the motion block
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param viewMotion target motion
|
||||
* @param viewMotions target motions
|
||||
* @param categoryId the number that indicates the category
|
||||
*/
|
||||
public async setMultiCategory(viewMotions: ViewMotion[], categoryId: number): Promise<void> {
|
||||
@ -609,11 +621,12 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
case ChangeRecoMode.Diff:
|
||||
const text = [];
|
||||
const changesToShow = changes.filter(change => change.showInDiffView());
|
||||
const motionText = this.lineNumbering.insertLineNumbers(targetMotion.text, lineLength);
|
||||
|
||||
for (let i = 0; i < changesToShow.length; i++) {
|
||||
text.push(
|
||||
this.diff.extractMotionLineRange(
|
||||
targetMotion.text,
|
||||
motionText,
|
||||
{
|
||||
from: i === 0 ? 1 : changesToShow[i - 1].getLineTo(),
|
||||
to: changesToShow[i].getLineFrom()
|
||||
@ -624,18 +637,11 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
)
|
||||
);
|
||||
|
||||
text.push(
|
||||
this.diff.getChangeDiff(targetMotion.text, changesToShow[i], lineLength, highlightLine)
|
||||
);
|
||||
text.push(this.diff.getChangeDiff(motionText, changesToShow[i], lineLength, highlightLine));
|
||||
}
|
||||
|
||||
text.push(
|
||||
this.diff.getTextRemainderAfterLastChange(
|
||||
targetMotion.text,
|
||||
changesToShow,
|
||||
lineLength,
|
||||
highlightLine
|
||||
)
|
||||
this.diff.getTextRemainderAfterLastChange(motionText, changesToShow, lineLength, highlightLine)
|
||||
);
|
||||
return text.join('');
|
||||
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 {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
|
||||
* @returns {DiffLinesInParagraph}
|
||||
*/
|
||||
public getAmendmentParagraphs(
|
||||
public getAmendmentParagraphLines(
|
||||
amendment: ViewMotion,
|
||||
lineLength: number,
|
||||
crMode: ChangeRecoMode,
|
||||
changeRecommendations: ViewMotionChangeRecommendation[],
|
||||
includeUnchanged: boolean
|
||||
): DiffLinesInParagraph[] {
|
||||
const motion = amendment.parent;
|
||||
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(
|
||||
(newText: string, paraNo: number): DiffLinesInParagraph => {
|
||||
if (newText !== null) {
|
||||
return this.diff.getAmendmentParagraphsLinesByMode(
|
||||
return this.diff.getAmendmentParagraphsLines(
|
||||
paraNo,
|
||||
baseParagraphs[paraNo],
|
||||
newText,
|
||||
lineLength
|
||||
);
|
||||
} else {
|
||||
// Nothing has changed in this paragraph
|
||||
if (includeUnchanged) {
|
||||
return null; // Nothing has changed in this paragraph
|
||||
}
|
||||
}
|
||||
)
|
||||
.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]);
|
||||
return {
|
||||
paragraphNo: paraNo,
|
||||
@ -800,26 +892,47 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
textPost: ''
|
||||
} as DiffLinesInParagraph;
|
||||
} else {
|
||||
return null; // null will make this paragraph filtered out
|
||||
return diffLines;
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
.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.
|
||||
* 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 {number} lineLength
|
||||
* @param {ViewMotionChangeRecommendation[]} changeRecos
|
||||
* @returns {ViewMotionAmendedParagraph[]}
|
||||
*/
|
||||
public getAmendmentAmendedParagraphs(amendment: ViewMotion, lineLength: number): ViewMotionAmendedParagraph[] {
|
||||
public getAmendmentAmendedParagraphs(
|
||||
amendment: ViewMotion,
|
||||
lineLength: number,
|
||||
changeRecos: ViewMotionChangeRecommendation[]
|
||||
): ViewMotionAmendedParagraph[] {
|
||||
const motion = amendment.parent;
|
||||
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
|
||||
const changedAmendmentParagraphs = this.applyChangesToAmendment(amendment, lineLength, changeRecos, false);
|
||||
|
||||
return (amendment.amendment_paragraphs || [])
|
||||
return changedAmendmentParagraphs
|
||||
.map(
|
||||
(newText: string, paraNo: number): ViewMotionAmendedParagraph => {
|
||||
if (newText === null) {
|
||||
@ -844,6 +957,42 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
.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
|
||||
*
|
||||
|
@ -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', () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
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';
|
||||
|
||||
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 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} toLine
|
||||
* @returns {ExtractedContent}
|
||||
*/
|
||||
public extractRangeByLineNumbers(htmlIn: string, fromLine: number, toLine: number): ExtractedContent {
|
||||
public extractRangeByLineNumbers(htmlIn: LineNumberedString, fromLine: number, toLine: number): ExtractedContent {
|
||||
if (typeof htmlIn !== 'string') {
|
||||
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
|
||||
// We need to do this before removing </del><del> as done in one of the next statements
|
||||
diffUnnormalized = diffUnnormalized.replace(
|
||||
/<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 => {
|
||||
return (br !== undefined ? br : '') + span + ' </span>';
|
||||
/<del>(((<BR CLASS="os-line-break">)<\/del><del>)?(<span[^>]+os-line-number[^>]+?>)(\s|<\/?del>)*<\/span>)<\/del>/gi,
|
||||
(found: string, tag: string, brWithDel: string, plainBr: string, span: string): string => {
|
||||
return (plainBr !== undefined ? plainBr : '') + span + ' </span>';
|
||||
}
|
||||
);
|
||||
|
||||
// Merging individual insert/delete statements into bigger blocks
|
||||
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,
|
||||
// but only of these specific characters
|
||||
diffUnnormalized = diffUnnormalized.replace(
|
||||
@ -2138,7 +2151,7 @@ export class DiffService {
|
||||
* @param {number} lineLength the line length
|
||||
* @return {DiffLinesInParagraph|null}
|
||||
*/
|
||||
public getAmendmentParagraphsLinesByMode(
|
||||
public getAmendmentParagraphsLines(
|
||||
paragraphNo: number,
|
||||
origText: string,
|
||||
newText: string,
|
||||
@ -2190,20 +2203,18 @@ export class DiffService {
|
||||
* Returns the HTML with the changes, optionally with a highlighted line.
|
||||
* The original motion needs to be provided.
|
||||
*
|
||||
* @param {string} motionHtml
|
||||
* @param {LineNumberedString} html
|
||||
* @param {ViewUnifiedChange} change
|
||||
* @param {number} lineLength
|
||||
* @param {number} highlight
|
||||
* @returns {string}
|
||||
*/
|
||||
public getChangeDiff(
|
||||
motionHtml: string,
|
||||
html: LineNumberedString,
|
||||
change: ViewUnifiedChange,
|
||||
lineLength: number,
|
||||
highlight?: number
|
||||
): string {
|
||||
const html = this.lineNumberingService.insertLineNumbers(motionHtml, lineLength);
|
||||
|
||||
let data, oldText;
|
||||
|
||||
try {
|
||||
@ -2248,14 +2259,14 @@ export class DiffService {
|
||||
/**
|
||||
* Returns the remainder text of the motion after the last change
|
||||
*
|
||||
* @param {string} motionHtml
|
||||
* @param {LineNumberedString} motionHtml
|
||||
* @param {ViewUnifiedChange[]} changes
|
||||
* @param {number} lineLength
|
||||
* @param {number} highlight
|
||||
* @returns {string}
|
||||
*/
|
||||
public getTextRemainderAfterLastChange(
|
||||
motionHtml: string,
|
||||
motionHtml: LineNumberedString,
|
||||
changes: ViewUnifiedChange[],
|
||||
lineLength: number,
|
||||
highlight?: number
|
||||
@ -2267,15 +2278,14 @@ export class DiffService {
|
||||
}
|
||||
}, 0);
|
||||
|
||||
const numberedHtml = this.lineNumberingService.insertLineNumbers(motionHtml, lineLength, highlight);
|
||||
if (changes.length === 0) {
|
||||
return numberedHtml;
|
||||
return motionHtml;
|
||||
}
|
||||
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = this.extractRangeByLineNumbers(numberedHtml, maxLine, null);
|
||||
data = this.extractRangeByLineNumbers(motionHtml, maxLine, null);
|
||||
} catch (e) {
|
||||
// This only happens (as far as we know) when the motion text has been altered (shortened)
|
||||
// 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
|
||||
*
|
||||
* @param {string} motionText
|
||||
* @param {LineNumberedString} 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,
|
||||
motionText: LineNumberedString,
|
||||
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);
|
||||
const extracted = this.extractRangeByLineNumbers(motionText, lineRange.from, lineRange.to);
|
||||
let html =
|
||||
extracted.outerContextStart +
|
||||
extracted.innerContextStart +
|
||||
|
@ -3,6 +3,11 @@ import { Injectable } from '@angular/core';
|
||||
const ELEMENT_NODE = 1;
|
||||
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
|
||||
* exceeds the maximum line length.
|
||||
@ -894,7 +899,7 @@ export class LinenumberingService {
|
||||
highlight?: number,
|
||||
callback?: () => void,
|
||||
firstLine?: number
|
||||
): string {
|
||||
): LineNumberedString {
|
||||
let newHtml, newRoot;
|
||||
|
||||
if (highlight > 0) {
|
||||
|
@ -199,7 +199,6 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
* Extract the lines of the amendments
|
||||
* 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
|
||||
*/
|
||||
public getChangeLines(): string {
|
||||
|
@ -6,8 +6,10 @@ import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
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 { 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 { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change';
|
||||
import { mediumDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||
@ -84,7 +86,9 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
||||
* @param translate
|
||||
* @param matSnackBar
|
||||
* @param diff
|
||||
* @param lineNumbering
|
||||
* @param recoRepo
|
||||
* @param motionRepo
|
||||
* @param dialogService
|
||||
* @param configService
|
||||
* @param el
|
||||
@ -95,7 +99,9 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
||||
protected translate: TranslateService, // protected required for ng-translate-extract
|
||||
matSnackBar: MatSnackBar,
|
||||
private diff: DiffService,
|
||||
private lineNumbering: LinenumberingService,
|
||||
private recoRepo: ChangeRecommendationRepositoryService,
|
||||
private motionRepo: MotionRepositoryService,
|
||||
private dialogService: MatDialog,
|
||||
private configService: ConfigService,
|
||||
private el: ElementRef,
|
||||
@ -123,13 +129,16 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.diff.extractMotionLineRange(
|
||||
this.motion.text,
|
||||
lineRange,
|
||||
true,
|
||||
this.lineLength,
|
||||
this.highlightedLine
|
||||
);
|
||||
let baseText: LineNumberedString;
|
||||
if (this.motion.isParagraphBasedAmendment()) {
|
||||
baseText = this.motionRepo
|
||||
.getAllAmendmentParagraphsWithOriginalLineNumbers(this.motion, this.lineLength, true)
|
||||
.join('\n');
|
||||
} 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
|
||||
*/
|
||||
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) {
|
||||
return ''; // @TODO This happens in the test case when the lineLength-variable is not set
|
||||
}
|
||||
return this.diff.getTextRemainderAfterLastChange(
|
||||
this.motion.text,
|
||||
this.changes,
|
||||
this.lineLength,
|
||||
this.highlightedLine
|
||||
);
|
||||
let baseText: LineNumberedString;
|
||||
if (this.motion.isParagraphBasedAmendment()) {
|
||||
baseText = this.motionRepo
|
||||
.getAllAmendmentParagraphsWithOriginalLineNumbers(this.motion, this.lineLength, true)
|
||||
.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 {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
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';
|
||||
|
||||
/**
|
||||
* This component displays the original motion text with annotated change commendations
|
||||
* and a method to create new change recommendations from the line numbers to the left of the text.
|
||||
* It's called from motion-details for displaying the whole motion text as well as from the diff view to show the
|
||||
* unchanged parts of the motion.
|
||||
* This component displays either the original motion text or the original amendment diff text
|
||||
* with annotated change commendations and a method to create new change recommendations
|
||||
* from the line numbers to the left of the text.
|
||||
* 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
|
||||
* and native HTML elements.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* ```html
|
||||
@ -63,12 +71,25 @@ export class MotionDetailOriginalChangeRecommendationsComponent implements OnIni
|
||||
|
||||
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 {ElementRef} el
|
||||
* @param {ChangeDetectorRef} cd
|
||||
* @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));
|
||||
}
|
||||
|
||||
@ -107,7 +128,21 @@ export class MotionDetailOriginalChangeRecommendationsComponent implements OnIni
|
||||
}
|
||||
|
||||
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
|
||||
// is changing while the DOM updates
|
||||
window.setTimeout(() => {
|
||||
this.setLineNumberCache();
|
||||
this.showChangeRecommendations = true;
|
||||
this.cd.detectChanges();
|
||||
}, 1);
|
||||
}
|
||||
|
||||
|
@ -530,7 +530,6 @@
|
||||
[matMenuTriggerFor]="changeRecoMenu"
|
||||
*ngIf="
|
||||
motion &&
|
||||
!motion.isParagraphBasedAmendment() &&
|
||||
((allChangingObjects && allChangingObjects.length) || motion.modified_final_version)
|
||||
"
|
||||
>
|
||||
@ -874,33 +873,40 @@
|
||||
<div class="alert alert-info" *ngIf="motion.diffLines.length === 0">
|
||||
<span>{{ 'No changes at the text.' | translate }}</span>
|
||||
</div>
|
||||
<ng-container *ngIf="!isRecoMode(ChangeRecoMode.Diff) && !isFinalEdit">
|
||||
<div
|
||||
*ngFor="let paragraph of getAmendmentParagraphs(showAmendmentContext)"
|
||||
*ngFor="let paragraph of getAmendmentParagraphs()"
|
||||
class="motion-text motion-text-diff amendment-view"
|
||||
[class.line-numbers-none]="isLineNumberingNone()"
|
||||
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||
[class.amendment-context]="showAmendmentContext"
|
||||
>
|
||||
<!-- TODO: everything here is required for PDF as well. Should be in a service -->
|
||||
<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>
|
||||
<h3 class="amendment-line-header" *ngIf="!showAmendmentContext">{{ getAmendmentParagraphLinesTitle(paragraph) }}</h3>
|
||||
|
||||
<!-- TODO: Seems to be directly duplicated in the slide -->
|
||||
<div class="paragraphcontext" [innerHtml]="paragraph.textPre | trust: 'html'"></div>
|
||||
<div [innerHtml]="paragraph.text | trust: 'html'"></div>
|
||||
<div class="paragraphcontext" [innerHtml]="paragraph.textPost | trust: 'html'"></div>
|
||||
<os-motion-detail-original-change-recommendations
|
||||
*ngIf="isLineNumberingOutside() && (isRecoMode(ChangeRecoMode.Original) || isRecoMode(ChangeRecoMode.Changed))"
|
||||
[html]="getAmendmentDiffTextWithContext(paragraph)"
|
||||
[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>
|
||||
</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 *ngIf="!motion.diffLines">
|
||||
<span class="red-warning-text">{{
|
||||
@ -910,7 +916,7 @@
|
||||
</section>
|
||||
|
||||
<!-- Show entire motion text -->
|
||||
<div>
|
||||
<div *ngIf="isRecoMode(ChangeRecoMode.Original) || isRecoMode(ChangeRecoMode.Changed)">
|
||||
<mat-checkbox
|
||||
(change)="showAmendmentContext = !showAmendmentContext"
|
||||
*ngIf="motion && motion.isParagraphBasedAmendment()"
|
||||
@ -953,7 +959,10 @@
|
||||
</div>
|
||||
</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">
|
||||
<button
|
||||
mat-menu-item
|
||||
@ -982,13 +991,13 @@
|
||||
mat-menu-item
|
||||
(click)="setChangeRecoMode(ChangeRecoMode.Final)"
|
||||
[ngClass]="{ selected: crMode === ChangeRecoMode.Final }"
|
||||
*ngIf="allChangingObjects && allChangingObjects.length"
|
||||
*ngIf="motion && !motion.isParagraphBasedAmendment() && allChangingObjects && allChangingObjects.length"
|
||||
>
|
||||
{{ 'Final version' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngIf="motion && motion.modified_final_version"
|
||||
*ngIf="motion && motion.modified_final_version && !motion.isParagraphBasedAmendment()"
|
||||
(click)="setChangeRecoMode(ChangeRecoMode.ModifiedFinal)"
|
||||
[ngClass]="{ selected: crMode === ChangeRecoMode.ModifiedFinal }"
|
||||
>
|
||||
|
@ -215,10 +215,21 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
public changeRecommendations: ViewMotionChangeRecommendation[];
|
||||
|
||||
/**
|
||||
* All amendments to this motions
|
||||
* All amendments to this motion
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -416,25 +427,25 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* @param repo Motion Repository
|
||||
* @param changeRecoRepo Change Recommendation Repository
|
||||
* @param statuteRepo: Statute Paragraph Repository
|
||||
* @param mediafileRepo Mediafile Repository
|
||||
* @param DS The DataStoreService
|
||||
* @param configService The configuration provider
|
||||
* @param promptService ensure safe deletion
|
||||
* @param pdfExport export the motion to pdf
|
||||
* @param personalNoteService: personal comments and favorite marker
|
||||
* @param linenumberingService The line numbering service
|
||||
* @param categoryRepo Repository for categories
|
||||
* @param viewModelStore accessing view models
|
||||
* @param categoryRepo access the category repository
|
||||
* @param userRepo Repository for users
|
||||
* @param notifyService: NotifyService work with notification
|
||||
* @param tagRepo
|
||||
* @param mediaFilerepo
|
||||
* @param workflowRepo
|
||||
* @param blockRepo
|
||||
* @param itemRepo
|
||||
* @param motionSortService
|
||||
* @param motionFilterListService
|
||||
* @param amendmentSortService
|
||||
* @param motionFilterService
|
||||
* @param amendmentFilterService
|
||||
* @param cd ChangeDetectorRef
|
||||
* @param pollDialog
|
||||
* @param motionPollService
|
||||
*/
|
||||
public constructor(
|
||||
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.
|
||||
* Called each time one of these arrays changes.
|
||||
@ -614,8 +643,12 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
}
|
||||
if (this.amendments) {
|
||||
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
|
||||
.getAmendmentAmendedParagraphs(amendment, this.lineLength)
|
||||
.getAmendmentAmendedParagraphs(amendment, this.lineLength, toApplyChanges)
|
||||
.forEach((change: ViewUnifiedChange): void => {
|
||||
this.allChangingObjects.push(change);
|
||||
});
|
||||
@ -630,6 +663,14 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
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();
|
||||
}
|
||||
|
||||
@ -664,6 +705,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
|
||||
this.repo.amendmentsTo(motionId).subscribe((amendments: ViewMotion[]): void => {
|
||||
this.amendments = amendments;
|
||||
this.resetAmendmentChangeRecoListener();
|
||||
this.recalcUnifiedChanges();
|
||||
}),
|
||||
this.repo
|
||||
@ -908,15 +950,48 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
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.
|
||||
* TODO: Cleanup: repo function could be injected part of the model, to have easier access
|
||||
*
|
||||
* @param {boolean} includeUnchanged
|
||||
* @returns {DiffLinesInParagraph[]}
|
||||
*/
|
||||
public getAmendmentParagraphs(includeUnchanged: boolean): DiffLinesInParagraph[] {
|
||||
return this.repo.getAmendmentParagraphs(this.motion, this.lineLength, includeUnchanged);
|
||||
public getAmendmentParagraphs(): DiffLinesInParagraph[] {
|
||||
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,
|
||||
newChangeRecommendation: true,
|
||||
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,
|
||||
lineRange,
|
||||
this.lineLength
|
||||
)
|
||||
};
|
||||
);
|
||||
}
|
||||
this.dialogService.open(MotionChangeRecommendationDialogComponent, {
|
||||
...mediumDialogSettings,
|
||||
data: data
|
||||
@ -1329,7 +1419,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
/**
|
||||
* 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 {
|
||||
event.stopPropagation();
|
||||
@ -1497,7 +1588,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* Function to handle leaving persons and
|
||||
* 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 {
|
||||
this.otherWorkOnMotion = this.otherWorkOnMotion.filter(value => value !== senderName);
|
||||
|
@ -64,7 +64,14 @@ export class MotionCsvExportService {
|
||||
const amendments = this.motionRepo.getAmendmentsInstantly(motion.id);
|
||||
if (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) {
|
||||
changes.push(change as ViewUnifiedChange);
|
||||
}
|
||||
|
@ -576,17 +576,16 @@ export class MotionPdfService {
|
||||
if (motion.isParagraphBasedAmendment()) {
|
||||
// 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)) {
|
||||
if (paragraph.diffLineTo === paragraph.diffLineFrom + 1) {
|
||||
motionText += `<h3>
|
||||
${this.translate.instant('Line')} ${paragraph.diffLineFrom}:
|
||||
</h3>`;
|
||||
} else {
|
||||
motionText += `<h3>
|
||||
${this.translate.instant('Line')} ${paragraph.diffLineFrom} - ${paragraph.diffLineTo - 1}:
|
||||
</h3>`;
|
||||
}
|
||||
|
||||
const changeRecos = this.changeRecoRepo.getChangeRecoOfMotion(motion.id);
|
||||
const amendmentParas = this.motionRepo.getAmendmentParagraphLines(
|
||||
motion,
|
||||
lineLength,
|
||||
crMode,
|
||||
changeRecos,
|
||||
false
|
||||
);
|
||||
for (const paragraph of amendmentParas) {
|
||||
motionText += '<h3>' + this.motionRepo.getAmendmentParagraphLinesTitle(paragraph) + '</h3>';
|
||||
motionText += `<div class="paragraphcontext">${paragraph.textPre}</div>`;
|
||||
motionText += paragraph.text;
|
||||
motionText += `<div class="paragraphcontext">${paragraph.textPost}</div>`;
|
||||
@ -634,11 +633,12 @@ export class MotionPdfService {
|
||||
return this.changeRecoRepo
|
||||
.getChangeRecoOfMotion(motion.id)
|
||||
.concat(
|
||||
this.motionRepo
|
||||
.getAmendmentsInstantly(motion.id)
|
||||
.flatMap((amendment: ViewMotion) =>
|
||||
this.motionRepo.getAmendmentAmendedParagraphs(amendment, lineLength)
|
||||
)
|
||||
this.motionRepo.getAmendmentsInstantly(motion.id).flatMap((amendment: ViewMotion) => {
|
||||
const changeRecos = this.changeRecoRepo
|
||||
.getChangeRecoOfMotion(amendment.id)
|
||||
.filter(reco => reco.showInFinalView());
|
||||
return this.motionRepo.getAmendmentAmendedParagraphs(amendment, lineLength, changeRecos);
|
||||
})
|
||||
)
|
||||
.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 { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.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 { MotionTitleInformation } from 'app/site/motions/models/view-motion';
|
||||
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
|
||||
*
|
||||
* @param {string} motionHtml
|
||||
* @param {LineNumberedString} motionHtml
|
||||
* @param {LineRange} lineRange
|
||||
* @param {boolean} lineNumbers - weather to add line numbers to the returned HTML string
|
||||
* @param {number} lineLength
|
||||
*/
|
||||
public extractMotionLineRange(
|
||||
motionHtml: string,
|
||||
motionHtml: LineNumberedString,
|
||||
lineRange: LineRange,
|
||||
lineNumbers: boolean,
|
||||
lineLength: number
|
||||
): string {
|
||||
const origHtml = this.lineNumbering.insertLineNumbers(motionHtml, this.lineLength, this.highlightedLine);
|
||||
const extracted = this.diff.extractRangeByLineNumbers(origHtml, lineRange.from, lineRange.to);
|
||||
const extracted = this.diff.extractRangeByLineNumbers(motionHtml, lineRange.from, lineRange.to);
|
||||
let html =
|
||||
extracted.outerContextStart +
|
||||
extracted.innerContextStart +
|
||||
@ -343,21 +342,22 @@ export class MotionSlideComponent extends BaseMotionSlideComponent<MotionSlideDa
|
||||
const changes = this.getAllTextChangingObjects().filter(change => {
|
||||
return change.showInDiffView();
|
||||
});
|
||||
const motionText = this.lineNumbering.insertLineNumbers(motion.text, this.lineLength);
|
||||
changes.forEach((change: ViewUnifiedChange, idx: number) => {
|
||||
if (idx === 0) {
|
||||
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()) {
|
||||
const lineRange = {
|
||||
from: changes[idx - 1].getLineTo(),
|
||||
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(
|
||||
motion.text,
|
||||
motionText,
|
||||
changes,
|
||||
this.lineLength,
|
||||
this.highlightedLine
|
||||
@ -412,7 +412,7 @@ export class MotionSlideComponent extends BaseMotionSlideComponent<MotionSlideDa
|
||||
return null;
|
||||
}
|
||||
// Hint: can be either DiffLinesInParagraph or null, if no changes are made
|
||||
return this.diff.getAmendmentParagraphsLinesByMode(
|
||||
return this.diff.getAmendmentParagraphsLines(
|
||||
paraNo,
|
||||
baseParagraphs[paraNo],
|
||||
newText,
|
||||
|
Loading…
Reference in New Issue
Block a user