Merge pull request #4246 from CatoTH/OS3-Slides-LineNumbering-Diff
Initial support for line numbering and CR in Projector
This commit is contained in:
commit
69539cacbc
@ -27,7 +27,7 @@ import { TreeService } from 'app/core/ui-services/tree.service';
|
|||||||
import { User } from 'app/shared/models/users/user';
|
import { User } from 'app/shared/models/users/user';
|
||||||
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-change-recommendation';
|
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-change-recommendation';
|
||||||
import { ViewMotionAmendedParagraph } from 'app/site/motions/models/view-motion-amended-paragraph';
|
import { ViewMotionAmendedParagraph } from 'app/site/motions/models/view-motion-amended-paragraph';
|
||||||
import { ViewUnifiedChange } from 'app/site/motions/models/view-unified-change';
|
import { ViewUnifiedChange } from '../../../shared/models/motions/view-unified-change';
|
||||||
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
|
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
|
||||||
import { Workflow } from 'app/shared/models/motions/workflow';
|
import { Workflow } from 'app/shared/models/motions/workflow';
|
||||||
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
||||||
@ -397,7 +397,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
case ChangeRecoMode.Original:
|
case ChangeRecoMode.Original:
|
||||||
return this.lineNumbering.insertLineNumbers(targetMotion.text, lineLength, highlightLine);
|
return this.lineNumbering.insertLineNumbers(targetMotion.text, lineLength, highlightLine);
|
||||||
case ChangeRecoMode.Changed:
|
case ChangeRecoMode.Changed:
|
||||||
return this.diff.getTextWithChanges(targetMotion, changes, lineLength, highlightLine);
|
return this.diff.getTextWithChanges(targetMotion.text, changes, lineLength, highlightLine);
|
||||||
case ChangeRecoMode.Diff:
|
case ChangeRecoMode.Diff:
|
||||||
let text = '';
|
let text = '';
|
||||||
changes.forEach((change: ViewUnifiedChange, idx: number) => {
|
changes.forEach((change: ViewUnifiedChange, idx: number) => {
|
||||||
@ -424,13 +424,18 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
highlightLine
|
highlightLine
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
text += this.getChangeDiff(targetMotion, change, lineLength, highlightLine);
|
text += this.diff.getChangeDiff(targetMotion.text, change, lineLength, highlightLine);
|
||||||
});
|
});
|
||||||
text += this.getTextRemainderAfterLastChange(targetMotion, changes, lineLength, highlightLine);
|
text += this.diff.getTextRemainderAfterLastChange(
|
||||||
|
targetMotion.text,
|
||||||
|
changes,
|
||||||
|
lineLength,
|
||||||
|
highlightLine
|
||||||
|
);
|
||||||
return text;
|
return text;
|
||||||
case ChangeRecoMode.Final:
|
case ChangeRecoMode.Final:
|
||||||
const appliedChanges: ViewUnifiedChange[] = changes.filter(change => change.isAccepted());
|
const appliedChanges: ViewUnifiedChange[] = changes.filter(change => change.isAccepted());
|
||||||
return this.diff.getTextWithChanges(targetMotion, appliedChanges, lineLength, highlightLine);
|
return this.diff.getTextWithChanges(targetMotion.text, appliedChanges, lineLength, highlightLine);
|
||||||
case ChangeRecoMode.ModifiedFinal:
|
case ChangeRecoMode.ModifiedFinal:
|
||||||
if (targetMotion.modified_final_version) {
|
if (targetMotion.modified_final_version) {
|
||||||
return this.lineNumbering.insertLineNumbers(
|
return this.lineNumbering.insertLineNumbers(
|
||||||
@ -496,63 +501,6 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the remainder text of the motion after the last change
|
|
||||||
*
|
|
||||||
* @param {ViewMotion} motion
|
|
||||||
* @param {ViewUnifiedChange[]} changes
|
|
||||||
* @param {number} lineLength
|
|
||||||
* @param {number} highlight
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
public getTextRemainderAfterLastChange(
|
|
||||||
motion: ViewMotion,
|
|
||||||
changes: ViewUnifiedChange[],
|
|
||||||
lineLength: number,
|
|
||||||
highlight?: number
|
|
||||||
): string {
|
|
||||||
let maxLine = 1;
|
|
||||||
changes.forEach((change: ViewUnifiedChange) => {
|
|
||||||
if (change.getLineTo() > maxLine) {
|
|
||||||
maxLine = change.getLineTo();
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
const numberedHtml = this.lineNumbering.insertLineNumbers(motion.text, lineLength);
|
|
||||||
if (changes.length === 0) {
|
|
||||||
return numberedHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data;
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = this.diff.extractRangeByLineNumbers(numberedHtml, 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.
|
|
||||||
// That's a pretty serious inconsistency that should not happen at all,
|
|
||||||
// we're just doing some basic damage control here.
|
|
||||||
const msg =
|
|
||||||
'Inconsistent data. A change recommendation is probably referring to a non-existant line number.';
|
|
||||||
return '<em style="color: red; font-weight: bold;">' + msg + '</em>';
|
|
||||||
}
|
|
||||||
|
|
||||||
let html;
|
|
||||||
if (data.html !== '') {
|
|
||||||
// Add "merge-before"-css-class if the first line begins in the middle of a paragraph. Used for PDF.
|
|
||||||
html =
|
|
||||||
this.diff.addCSSClassToFirstTag(data.outerContextStart + data.innerContextStart, 'merge-before') +
|
|
||||||
data.html +
|
|
||||||
data.innerContextEnd +
|
|
||||||
data.outerContextEnd;
|
|
||||||
html = this.lineNumbering.insertLineNumbers(html, lineLength, highlight, null, maxLine);
|
|
||||||
} else {
|
|
||||||
// Prevents empty lines at the end of the motion
|
|
||||||
html = '';
|
|
||||||
}
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the last line number of a motion
|
* Returns the last line number of a motion
|
||||||
*
|
*
|
||||||
@ -567,7 +515,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link ViewChangeReco} object based on the motion ID and the given lange range.
|
* Creates a {@link ViewMotionChangeRecommendation} object based on the motion ID and the given lange range.
|
||||||
* This object is not saved yet and does not yet have any changed HTML. It's meant to populate the UI form.
|
* This object is not saved yet and does not yet have any changed HTML. It's meant to populate the UI form.
|
||||||
*
|
*
|
||||||
* @param {number} motionId
|
* @param {number} motionId
|
||||||
@ -590,66 +538,6 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
return new ViewMotionChangeRecommendation(changeReco);
|
return new ViewMotionChangeRecommendation(changeReco);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the HTML with the changes, optionally with a highlighted line.
|
|
||||||
* The original motion needs to be provided.
|
|
||||||
*
|
|
||||||
* @param {ViewMotion} motion
|
|
||||||
* @param {ViewUnifiedChange} change
|
|
||||||
* @param {number} lineLength
|
|
||||||
* @param {number} highlight
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
public getChangeDiff(
|
|
||||||
motion: ViewMotion,
|
|
||||||
change: ViewUnifiedChange,
|
|
||||||
lineLength: number,
|
|
||||||
highlight?: number
|
|
||||||
): string {
|
|
||||||
const html = this.lineNumbering.insertLineNumbers(motion.text, lineLength);
|
|
||||||
|
|
||||||
let data, oldText;
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = this.diff.extractRangeByLineNumbers(html, change.getLineFrom(), change.getLineTo());
|
|
||||||
oldText =
|
|
||||||
data.outerContextStart +
|
|
||||||
data.innerContextStart +
|
|
||||||
data.html +
|
|
||||||
data.innerContextEnd +
|
|
||||||
data.outerContextEnd;
|
|
||||||
} catch (e) {
|
|
||||||
// This only happens (as far as we know) when the motion text has been altered (shortened)
|
|
||||||
// without modifying the change recommendations accordingly.
|
|
||||||
// That's a pretty serious inconsistency that should not happen at all,
|
|
||||||
// we're just doing some basic damage control here.
|
|
||||||
const msg =
|
|
||||||
'Inconsistent data. A change recommendation is probably referring to a non-existant line number.';
|
|
||||||
return '<em style="color: red; font-weight: bold;">' + msg + '</em>';
|
|
||||||
}
|
|
||||||
|
|
||||||
oldText = this.lineNumbering.insertLineNumbers(oldText, lineLength, null, null, change.getLineFrom());
|
|
||||||
let diff = this.diff.diff(oldText, change.getChangeNewText());
|
|
||||||
|
|
||||||
// If an insertion makes the line longer than the line length limit, we need two line breaking runs:
|
|
||||||
// - First, for the official line numbers, ignoring insertions (that's been done some lines before)
|
|
||||||
// - Second, another one to prevent the displayed including insertions to exceed the page width
|
|
||||||
diff = this.lineNumbering.insertLineBreaksWithoutNumbers(diff, lineLength, true);
|
|
||||||
|
|
||||||
if (highlight > 0) {
|
|
||||||
diff = this.lineNumbering.highlightLine(diff, highlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
const origBeginning = data.outerContextStart + data.innerContextStart;
|
|
||||||
if (diff.toLowerCase().indexOf(origBeginning.toLowerCase()) === 0) {
|
|
||||||
// Add "merge-before"-css-class if the first line begins in the middle of a paragraph. Used for PDF.
|
|
||||||
diff =
|
|
||||||
this.diff.addCSSClassToFirstTag(origBeginning, 'merge-before') + diff.substring(origBeginning.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an amendment, this returns the motion affected by this amendments
|
* Given an amendment, this returns the motion affected by this amendments
|
||||||
*
|
*
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { LinenumberingService } from './linenumbering.service';
|
import { LinenumberingService } from './linenumbering.service';
|
||||||
import { ViewMotion } from '../../site/motions/models/view-motion';
|
import { ViewUnifiedChange } from '../../shared/models/motions/view-unified-change';
|
||||||
import { ViewUnifiedChange } from '../../site/motions/models/view-unified-change';
|
|
||||||
|
|
||||||
const ELEMENT_NODE = 1;
|
const ELEMENT_NODE = 1;
|
||||||
const TEXT_NODE = 3;
|
const TEXT_NODE = 3;
|
||||||
@ -2036,18 +2035,18 @@ export class DiffService {
|
|||||||
/**
|
/**
|
||||||
* Applies all given changes to the motion and returns the (line-numbered) text
|
* Applies all given changes to the motion and returns the (line-numbered) text
|
||||||
*
|
*
|
||||||
* @param {ViewMotion} motion
|
* @param {string} motionHtml
|
||||||
* @param {ViewUnifiedChange[]} changes
|
* @param {ViewUnifiedChange[]} changes
|
||||||
* @param {number} lineLength
|
* @param {number} lineLength
|
||||||
* @param {number} highlightLine
|
* @param {number} highlightLine
|
||||||
*/
|
*/
|
||||||
public getTextWithChanges(
|
public getTextWithChanges(
|
||||||
motion: ViewMotion,
|
motionHtml: string,
|
||||||
changes: ViewUnifiedChange[],
|
changes: ViewUnifiedChange[],
|
||||||
lineLength: number,
|
lineLength: number,
|
||||||
highlightLine: number
|
highlightLine: number
|
||||||
): string {
|
): string {
|
||||||
let html = motion.text;
|
let html = motionHtml;
|
||||||
|
|
||||||
// Changes need to be applied from the bottom up, to prevent conflicts with changing line numbers.
|
// Changes need to be applied from the bottom up, to prevent conflicts with changing line numbers.
|
||||||
changes.sort((change1: ViewUnifiedChange, change2: ViewUnifiedChange) => {
|
changes.sort((change1: ViewUnifiedChange, change2: ViewUnifiedChange) => {
|
||||||
@ -2127,4 +2126,120 @@ export class DiffService {
|
|||||||
textPost: textPost
|
textPost: textPost
|
||||||
} as DiffLinesInParagraph;
|
} as DiffLinesInParagraph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the HTML with the changes, optionally with a highlighted line.
|
||||||
|
* The original motion needs to be provided.
|
||||||
|
*
|
||||||
|
* @param {string} motionHtml
|
||||||
|
* @param {ViewUnifiedChange} change
|
||||||
|
* @param {number} lineLength
|
||||||
|
* @param {number} highlight
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public getChangeDiff(
|
||||||
|
motionHtml: string,
|
||||||
|
change: ViewUnifiedChange,
|
||||||
|
lineLength: number,
|
||||||
|
highlight?: number
|
||||||
|
): string {
|
||||||
|
const html = this.lineNumberingService.insertLineNumbers(motionHtml, lineLength);
|
||||||
|
|
||||||
|
let data, oldText;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = this.extractRangeByLineNumbers(html, change.getLineFrom(), change.getLineTo());
|
||||||
|
oldText =
|
||||||
|
data.outerContextStart +
|
||||||
|
data.innerContextStart +
|
||||||
|
data.html +
|
||||||
|
data.innerContextEnd +
|
||||||
|
data.outerContextEnd;
|
||||||
|
} catch (e) {
|
||||||
|
// This only happens (as far as we know) when the motion text has been altered (shortened)
|
||||||
|
// without modifying the change recommendations accordingly.
|
||||||
|
// That's a pretty serious inconsistency that should not happen at all,
|
||||||
|
// we're just doing some basic damage control here.
|
||||||
|
const msg =
|
||||||
|
'Inconsistent data. A change recommendation is probably referring to a non-existant line number.';
|
||||||
|
return '<em style="color: red; font-weight: bold;">' + msg + '</em>';
|
||||||
|
}
|
||||||
|
|
||||||
|
oldText = this.lineNumberingService.insertLineNumbers(oldText, lineLength, null, null, change.getLineFrom());
|
||||||
|
let diff = this.diff(oldText, change.getChangeNewText());
|
||||||
|
|
||||||
|
// If an insertion makes the line longer than the line length limit, we need two line breaking runs:
|
||||||
|
// - First, for the official line numbers, ignoring insertions (that's been done some lines before)
|
||||||
|
// - Second, another one to prevent the displayed including insertions to exceed the page width
|
||||||
|
diff = this.lineNumberingService.insertLineBreaksWithoutNumbers(diff, lineLength, true);
|
||||||
|
|
||||||
|
if (highlight > 0) {
|
||||||
|
diff = this.lineNumberingService.highlightLine(diff, highlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
const origBeginning = data.outerContextStart + data.innerContextStart;
|
||||||
|
if (diff.toLowerCase().indexOf(origBeginning.toLowerCase()) === 0) {
|
||||||
|
// Add "merge-before"-css-class if the first line begins in the middle of a paragraph. Used for PDF.
|
||||||
|
diff = this.addCSSClassToFirstTag(origBeginning, 'merge-before') + diff.substring(origBeginning.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the remainder text of the motion after the last change
|
||||||
|
*
|
||||||
|
* @param {string} motionHtml
|
||||||
|
* @param {ViewUnifiedChange[]} changes
|
||||||
|
* @param {number} lineLength
|
||||||
|
* @param {number} highlight
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public getTextRemainderAfterLastChange(
|
||||||
|
motionHtml: string,
|
||||||
|
changes: ViewUnifiedChange[],
|
||||||
|
lineLength: number,
|
||||||
|
highlight?: number
|
||||||
|
): string {
|
||||||
|
let maxLine = 1;
|
||||||
|
changes.forEach((change: ViewUnifiedChange) => {
|
||||||
|
if (change.getLineTo() > maxLine) {
|
||||||
|
maxLine = change.getLineTo();
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const numberedHtml = this.lineNumberingService.insertLineNumbers(motionHtml, lineLength);
|
||||||
|
if (changes.length === 0) {
|
||||||
|
return numberedHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = this.extractRangeByLineNumbers(numberedHtml, 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.
|
||||||
|
// That's a pretty serious inconsistency that should not happen at all,
|
||||||
|
// we're just doing some basic damage control here.
|
||||||
|
const msg =
|
||||||
|
'Inconsistent data. A change recommendation is probably referring to a non-existant line number.';
|
||||||
|
return '<em style="color: red; font-weight: bold;">' + msg + '</em>';
|
||||||
|
}
|
||||||
|
|
||||||
|
let html;
|
||||||
|
if (data.html !== '') {
|
||||||
|
// Add "merge-before"-css-class if the first line begins in the middle of a paragraph. Used for PDF.
|
||||||
|
html =
|
||||||
|
this.addCSSClassToFirstTag(data.outerContextStart + data.innerContextStart, 'merge-before') +
|
||||||
|
data.html +
|
||||||
|
data.innerContextEnd +
|
||||||
|
data.outerContextEnd;
|
||||||
|
html = this.lineNumberingService.insertLineNumbers(html, lineLength, highlight, null, maxLine);
|
||||||
|
} else {
|
||||||
|
// Prevents empty lines at the end of the motion
|
||||||
|
html = '';
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { Component } from '@angular/core';
|
|||||||
import { LineNumberingMode, ViewMotion } from '../../models/view-motion';
|
import { LineNumberingMode, ViewMotion } from '../../models/view-motion';
|
||||||
import { MotionDetailDiffComponent } from './motion-detail-diff.component';
|
import { MotionDetailDiffComponent } from './motion-detail-diff.component';
|
||||||
import { MotionDetailOriginalChangeRecommendationsComponent } from '../motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component';
|
import { MotionDetailOriginalChangeRecommendationsComponent } from '../motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component';
|
||||||
import { ViewUnifiedChange } from '../../models/view-unified-change';
|
import { ViewUnifiedChange } from '../../../../shared/models/motions/view-unified-change';
|
||||||
import { Motion } from 'app/shared/models/motions/motion';
|
import { Motion } from 'app/shared/models/motions/motion';
|
||||||
import { ViewMotionChangeRecommendation } from '../../models/view-change-recommendation';
|
import { ViewMotionChangeRecommendation } from '../../models/view-change-recommendation';
|
||||||
|
|
||||||
|
@ -5,9 +5,9 @@ import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { LineNumberingMode, ViewMotion } from '../../models/view-motion';
|
import { LineNumberingMode, ViewMotion } from '../../models/view-motion';
|
||||||
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../models/view-unified-change';
|
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../../shared/models/motions/view-unified-change';
|
||||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
import { LineRange, ModificationType } from 'app/core/ui-services/diff.service';
|
import { DiffService, LineRange, ModificationType } from 'app/core/ui-services/diff.service';
|
||||||
import { ViewMotionChangeRecommendation } from '../../models/view-change-recommendation';
|
import { ViewMotionChangeRecommendation } from '../../models/view-change-recommendation';
|
||||||
import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service';
|
import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service';
|
||||||
import {
|
import {
|
||||||
@ -69,6 +69,7 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
|||||||
* @param matSnackBar
|
* @param matSnackBar
|
||||||
* @param sanitizer
|
* @param sanitizer
|
||||||
* @param motionRepo
|
* @param motionRepo
|
||||||
|
* @param diff
|
||||||
* @param recoRepo
|
* @param recoRepo
|
||||||
* @param dialogService
|
* @param dialogService
|
||||||
* @param configService
|
* @param configService
|
||||||
@ -80,6 +81,7 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
|||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
private motionRepo: MotionRepositoryService,
|
private motionRepo: MotionRepositoryService,
|
||||||
|
private diff: DiffService,
|
||||||
private recoRepo: ChangeRecommendationRepositoryService,
|
private recoRepo: ChangeRecommendationRepositoryService,
|
||||||
private dialogService: MatDialog,
|
private dialogService: MatDialog,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
@ -142,7 +144,7 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
|||||||
* @param {ViewUnifiedChange} change
|
* @param {ViewUnifiedChange} change
|
||||||
*/
|
*/
|
||||||
public getDiff(change: ViewUnifiedChange): SafeHtml {
|
public getDiff(change: ViewUnifiedChange): SafeHtml {
|
||||||
const html = this.motionRepo.getChangeDiff(this.motion, change, this.lineLength, this.highlightedLine);
|
const html = this.diff.getChangeDiff(this.motion.text, change, this.lineLength, this.highlightedLine);
|
||||||
return this.sanitizer.bypassSecurityTrustHtml(html);
|
return this.sanitizer.bypassSecurityTrustHtml(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,8 +155,8 @@ 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.motionRepo.getTextRemainderAfterLastChange(
|
return this.diff.getTextRemainderAfterLastChange(
|
||||||
this.motion,
|
this.motion.text,
|
||||||
this.changes,
|
this.changes,
|
||||||
this.lineLength,
|
this.lineLength,
|
||||||
this.highlightedLine
|
this.highlightedLine
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import '../../../../../assets/styles/motion-styles-common';
|
||||||
|
|
||||||
span {
|
span {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@ -150,138 +152,6 @@ span {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Line numbers */
|
|
||||||
// :host ::ng-deep is needed as this styling applies to the motion html that is injected using innerHTML,
|
|
||||||
// which doesn't have the [ngcontent]-attributes necessary for regular styles.
|
|
||||||
// An alternative approach (in case ::ng-deep gets removed) might be to change the view encapsulation.
|
|
||||||
:host ::ng-deep .motion-text {
|
|
||||||
ins,
|
|
||||||
.insert {
|
|
||||||
color: green;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
del,
|
|
||||||
.delete {
|
|
||||||
color: red;
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul {
|
|
||||||
margin-left: 15px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight {
|
|
||||||
background-color: #ff0;
|
|
||||||
}
|
|
||||||
&.line-numbers-outside {
|
|
||||||
padding-left: 40px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.os-line-number {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 0;
|
|
||||||
line-height: 0;
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
padding-right: 55px;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: attr(data-line-number);
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
vertical-align: top;
|
|
||||||
color: gray;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selectable:hover:before,
|
|
||||||
&.selected:before {
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
left: 20px;
|
|
||||||
display: inline-block;
|
|
||||||
cursor: pointer;
|
|
||||||
content: '';
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z" fill="%23337ab7"/><path d="M0 0h24v24H0z" fill="none"/></svg>');
|
|
||||||
background-size: 16px 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.line-numbers-inline {
|
|
||||||
.os-line-break {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.os-line-number {
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
display: inline-block;
|
|
||||||
content: attr(data-line-number);
|
|
||||||
vertical-align: top;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: normal;
|
|
||||||
color: gray;
|
|
||||||
margin-top: -3px;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.line-numbers-none {
|
|
||||||
.os-line-break {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.os-line-number {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.os-split-before {
|
|
||||||
margin-top: 0;
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.os-split-after {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.os-split-before {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .amendment-view {
|
|
||||||
.os-split-after {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.os-split-before {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
.paragraphcontext {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
&.amendment-context .paragraphcontext {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-nav-color {
|
.main-nav-color {
|
||||||
color: rgba(0, 0, 0, 0.54);
|
color: rgba(0, 0, 0, 0.54);
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ import { StatuteParagraphRepositoryService } from 'app/core/repositories/motions
|
|||||||
import { ViewMotionChangeRecommendation } from '../../models/view-change-recommendation';
|
import { ViewMotionChangeRecommendation } from '../../models/view-change-recommendation';
|
||||||
import { ViewCreateMotion } from '../../models/view-create-motion';
|
import { ViewCreateMotion } from '../../models/view-create-motion';
|
||||||
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
||||||
import { ViewUnifiedChange } from '../../models/view-unified-change';
|
import { ViewUnifiedChange } from '../../../../shared/models/motions/view-unified-change';
|
||||||
import { ViewStatuteParagraph } from '../../models/view-statute-paragraph';
|
import { ViewStatuteParagraph } from '../../models/view-statute-paragraph';
|
||||||
import { Workflow } from 'app/shared/models/motions/workflow';
|
import { Workflow } from 'app/shared/models/motions/workflow';
|
||||||
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
||||||
@ -343,6 +343,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
* @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 categoryRepo
|
* @param categoryRepo
|
||||||
* @param userRepo
|
* @param userRepo
|
||||||
*/
|
*/
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { BaseViewModel } from '../../base/base-view-model';
|
import { BaseViewModel } from '../../base/base-view-model';
|
||||||
import { ModificationType } from 'app/core/ui-services/diff.service';
|
import { ModificationType } from 'app/core/ui-services/diff.service';
|
||||||
import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco';
|
import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco';
|
||||||
import { ViewUnifiedChange, ViewUnifiedChangeType } from './view-unified-change';
|
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change recommendation class for the View
|
* Change recommendation class for the View
|
||||||
*
|
*
|
||||||
* Stores a motion including all (implicit) references
|
* Stores a motion including all (implicit) references
|
||||||
* Provides "safe" access to variables and functions in {@link MotionChangeReco}
|
* Provides "safe" access to variables and functions in {@link MotionChangeRecommendation}
|
||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
export class ViewMotionChangeRecommendation extends BaseViewModel implements ViewUnifiedChange {
|
export class ViewMotionChangeRecommendation extends BaseViewModel implements ViewUnifiedChange {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ViewUnifiedChange, ViewUnifiedChangeType } from './view-unified-change';
|
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change';
|
||||||
import { ViewMotion } from './view-motion';
|
import { ViewMotion } from './view-motion';
|
||||||
import { LineRange } from 'app/core/ui-services/diff.service';
|
import { LineRange } from 'app/core/ui-services/diff.service';
|
||||||
import { MergeAmendment } from 'app/shared/models/motions/workflow-state';
|
import { MergeAmendment } from 'app/shared/models/motions/workflow-state';
|
||||||
@ -41,6 +41,8 @@ export class ViewMotionAmendedParagraph implements ViewUnifiedChange {
|
|||||||
* The state and recommendation of this amendment is considered.
|
* The state and recommendation of this amendment is considered.
|
||||||
* The state takes precedence.
|
* The state takes precedence.
|
||||||
*
|
*
|
||||||
|
* HINT: This implementation should be consistent with get_amendment_merge_into_motion() in projector.py
|
||||||
|
*
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
public isAccepted(): boolean {
|
public isAccepted(): boolean {
|
||||||
|
@ -9,9 +9,9 @@ import { MotionPollService, CalculablePollKey } from './motion-poll.service';
|
|||||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
import { StatuteParagraphRepositoryService } from 'app/core/repositories/motions/statute-paragraph-repository.service';
|
import { StatuteParagraphRepositoryService } from 'app/core/repositories/motions/statute-paragraph-repository.service';
|
||||||
import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-motion';
|
import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-motion';
|
||||||
import { ViewUnifiedChange } from '../models/view-unified-change';
|
|
||||||
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
||||||
import { MotionCommentSectionRepositoryService } from 'app/core/repositories/motions/motion-comment-section-repository.service';
|
import { MotionCommentSectionRepositoryService } from 'app/core/repositories/motions/motion-comment-section-repository.service';
|
||||||
|
import { ViewUnifiedChange } from 'app/shared/models/motions/view-unified-change';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type declaring which strings are valid options for metainfos to be exported into a pdf
|
* Type declaring which strings are valid options for metainfos to be exported into a pdf
|
||||||
|
@ -1,6 +1,62 @@
|
|||||||
|
import { ChangeRecoMode, LineNumberingMode } from '../../../site/motions/models/view-motion';
|
||||||
|
import { MergeAmendment } from '../../../shared/models/motions/workflow-state';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface describes the data returned by the server about an amendment.
|
||||||
|
* This object is used if actually the motion is shown and the amendment is shown in the context of the motion.
|
||||||
|
*/
|
||||||
|
export interface MotionsMotionSlideDataAmendment {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
amendment_paragraphs: string[];
|
||||||
|
merge_amendment_into_final: MergeAmendment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface describes the data returned by the server about a motion that is changed by an amendment.
|
||||||
|
* It only contains the data necessary for rendering the amendment's diff.
|
||||||
|
*/
|
||||||
|
export interface MotionsMotionSlideDataBaseMotion {
|
||||||
|
identifier: string;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface describes the data returned by the server about a statute paragraph that is changed by an amendment.
|
||||||
|
* It only contains the data necessary for rendering the amendment's diff.
|
||||||
|
*/
|
||||||
|
export interface MotionsMotionSlideDataBaseStatute {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface describes the data returned by the server about a change recommendation.
|
||||||
|
*/
|
||||||
|
export interface MotionsMotionSlideDataChangeReco {
|
||||||
|
creation_time: string;
|
||||||
|
id: number;
|
||||||
|
internal: boolean;
|
||||||
|
line_from: number;
|
||||||
|
line_to: number;
|
||||||
|
motion_id: number;
|
||||||
|
other_description: string;
|
||||||
|
rejected: false;
|
||||||
|
text: string;
|
||||||
|
type: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hint: defined on server-side in the file /openslides/motions/projector.py
|
||||||
|
*
|
||||||
|
* This interface describes either an motion (with all amendments and change recommendations enbedded)
|
||||||
|
* or an amendment (with the bas motion embedded).
|
||||||
|
*/
|
||||||
export interface MotionsMotionSlideData {
|
export interface MotionsMotionSlideData {
|
||||||
identifier: string;
|
identifier: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
preamble: string;
|
||||||
text: string;
|
text: string;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
is_child: boolean;
|
is_child: boolean;
|
||||||
@ -9,7 +65,13 @@ export interface MotionsMotionSlideData {
|
|||||||
recommender?: string;
|
recommender?: string;
|
||||||
recommendation?: string;
|
recommendation?: string;
|
||||||
recommendation_extension?: string;
|
recommendation_extension?: string;
|
||||||
amendment_paragraphs: { paragraph: string }[];
|
base_motion?: MotionsMotionSlideDataBaseMotion;
|
||||||
change_recommendations: object[];
|
base_statute?: MotionsMotionSlideDataBaseStatute;
|
||||||
|
amendment_paragraphs: string[];
|
||||||
|
change_recommendations: MotionsMotionSlideDataChangeReco[];
|
||||||
|
amendments: MotionsMotionSlideDataAmendment[];
|
||||||
modified_final_version?: string;
|
modified_final_version?: string;
|
||||||
|
line_length: number;
|
||||||
|
line_numbering_mode: LineNumberingMode;
|
||||||
|
change_recommendation_mode: ChangeRecoMode;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change';
|
||||||
|
import { MotionsMotionSlideDataAmendment } from './motions-motion-slide-data';
|
||||||
|
import { MergeAmendment } from '../../../shared/models/motions/workflow-state';
|
||||||
|
import { LineRange } from '../../../core/ui-services/diff.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class adds methods to the MotionsMotionSlideDataChangeReco data object
|
||||||
|
* necessary for use it as a UnifiedChange in the Diff-Functions
|
||||||
|
*/
|
||||||
|
export class MotionsMotionSlideObjAmendmentParagraph implements ViewUnifiedChange {
|
||||||
|
public id: number;
|
||||||
|
public type: number;
|
||||||
|
public merge_amendment_into_final: MergeAmendment;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
data: MotionsMotionSlideDataAmendment,
|
||||||
|
private paragraphNo: number,
|
||||||
|
private newText: string,
|
||||||
|
private lineRange: LineRange
|
||||||
|
) {
|
||||||
|
this.id = data.id;
|
||||||
|
this.merge_amendment_into_final = data.merge_amendment_into_final;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChangeId(): string {
|
||||||
|
return 'amendment-' + this.id.toString(10) + '-' + this.paragraphNo.toString(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChangeType(): ViewUnifiedChangeType {
|
||||||
|
return ViewUnifiedChangeType.TYPE_AMENDMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChangeNewText(): string {
|
||||||
|
return this.newText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLineFrom(): number {
|
||||||
|
return this.lineRange.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLineTo(): number {
|
||||||
|
return this.lineRange.to;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAccepted(): boolean {
|
||||||
|
return this.merge_amendment_into_final === MergeAmendment.YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRejected(): boolean {
|
||||||
|
return this.merge_amendment_into_final === MergeAmendment.NO;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change';
|
||||||
|
import { MotionsMotionSlideDataChangeReco } from './motions-motion-slide-data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class adds methods to the MotionsMotionSlideDataChangeReco data object
|
||||||
|
* necessary for use it as a UnifiedChange in the Diff-Functions
|
||||||
|
*/
|
||||||
|
export class MotionsMotionSlideObjChangeReco implements MotionsMotionSlideDataChangeReco, ViewUnifiedChange {
|
||||||
|
public creation_time: string;
|
||||||
|
public id: number;
|
||||||
|
public internal: boolean;
|
||||||
|
public line_from: number;
|
||||||
|
public line_to: number;
|
||||||
|
public motion_id: number;
|
||||||
|
public other_description: string;
|
||||||
|
public rejected: false;
|
||||||
|
public text: string;
|
||||||
|
public type: number;
|
||||||
|
|
||||||
|
public constructor(data: MotionsMotionSlideDataChangeReco) {
|
||||||
|
Object.assign(this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChangeId(): string {
|
||||||
|
return 'recommendation-' + this.id.toString(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChangeNewText(): string {
|
||||||
|
return this.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChangeType(): ViewUnifiedChangeType {
|
||||||
|
return ViewUnifiedChangeType.TYPE_CHANGE_RECOMMENDATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLineFrom(): number {
|
||||||
|
return this.line_from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLineTo(): number {
|
||||||
|
return this.line_to;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAccepted(): boolean {
|
||||||
|
return !this.rejected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRejected(): boolean {
|
||||||
|
return this.rejected;
|
||||||
|
}
|
||||||
|
}
|
@ -20,12 +20,52 @@
|
|||||||
<h2><span translate>Motion</span> {{ data.data.identifier }}</h2>
|
<h2><span translate>Motion</span> {{ data.data.identifier }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Text (original) -->
|
<!-- Text -->
|
||||||
<div *ngIf="!data.data.is_child" [innerHTML]="data.data.text"></div>
|
<span class="text-prefix-label">{{ preamble | translate }}</span>
|
||||||
|
|
||||||
|
<!-- Regular motions or traditional amendments -->
|
||||||
|
<ng-container *ngIf="!isStatuteAmendment() && !isParagraphBasedAmendment()">
|
||||||
|
<div
|
||||||
|
class="motion-text"
|
||||||
|
[class.line-numbers-none]="isLineNumberingNone()"
|
||||||
|
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||||
|
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||||
|
>
|
||||||
|
<div [innerHTML]="sanitizedText(getFormattedText())"></div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Statute amendments -->
|
||||||
|
<div
|
||||||
|
class="motion-text line-numbers-none"
|
||||||
|
*ngIf="isStatuteAmendment()"
|
||||||
|
[innerHTML]="getFormattedStatuteAmendment()"
|
||||||
|
></div>
|
||||||
|
|
||||||
<!-- Amendment text -->
|
<!-- Amendment text -->
|
||||||
<div *ngIf="data.data.is_child && data.data.amendment_paragraphs"
|
<section class="text-holder" *ngIf="isParagraphBasedAmendment()">
|
||||||
[innerHTML]="data.data.amendment_paragraphs[0]"></div>
|
<div class="alert alert-info" *ngIf="getAmendedParagraphs().length === 0">
|
||||||
|
<span translate>No changes at the text.</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
*ngFor="let paragraph of getAmendedParagraphs()"
|
||||||
|
class="motion-text motion-text-diff amendment-view"
|
||||||
|
[class.line-numbers-none]="isLineNumberingNone()"
|
||||||
|
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||||
|
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||||
|
>
|
||||||
|
<h3 *ngIf="paragraph.diffLineTo === paragraph.diffLineFrom + 1" class="amendment-line-header">
|
||||||
|
<span translate>Line</span> {{ paragraph.diffLineFrom }}:
|
||||||
|
</h3>
|
||||||
|
<h3 *ngIf="paragraph.diffLineTo !== paragraph.diffLineFrom + 1" class="amendment-line-header">
|
||||||
|
<span translate>Line</span> {{ paragraph.diffLineFrom }} - {{ paragraph.diffLineTo - 1 }}:
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="paragraph-context" [innerHtml]="sanitizedText(paragraph.textPre)"></div>
|
||||||
|
<div [innerHtml]="sanitizedText(paragraph.text)"></div>
|
||||||
|
<div class="paragraph-context" [innerHtml]="sanitizedText(paragraph.textPost)"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Reason -->
|
<!-- Reason -->
|
||||||
<div *ngIf="data.data.reason">
|
<div *ngIf="data.data.reason">
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
@import '../../../../assets/styles/motion-styles-common';
|
||||||
|
|
||||||
|
::ng-deep .paragraph-context {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
#sidebox {
|
#sidebox {
|
||||||
width: 260px;
|
width: 260px;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
import { MotionsMotionSlideData } from './motions-motion-slide-data';
|
import { MotionsMotionSlideData, MotionsMotionSlideDataAmendment } from './motions-motion-slide-data';
|
||||||
|
import { ChangeRecoMode, LineNumberingMode } from '../../../site/motions/models/view-motion';
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
import { DiffLinesInParagraph, DiffService, LineRange } from '../../../core/ui-services/diff.service';
|
||||||
|
import { LinenumberingService } from '../../../core/ui-services/linenumbering.service';
|
||||||
|
import { ViewUnifiedChange } from '../../../shared/models/motions/view-unified-change';
|
||||||
|
import { MotionsMotionSlideObjChangeReco } from './motions-motion-slide-obj-change-reco';
|
||||||
|
import { SlideData } from '../../../site/projector/services/projector-data.service';
|
||||||
|
import { MotionsMotionSlideObjAmendmentParagraph } from './motions-motion-slide-obj-amendment-paragraph';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-motions-motion-slide',
|
selector: 'os-motions-motion-slide',
|
||||||
@ -8,7 +16,336 @@ import { MotionsMotionSlideData } from './motions-motion-slide-data';
|
|||||||
styleUrls: ['./motions-motion-slide.component.scss']
|
styleUrls: ['./motions-motion-slide.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionsMotionSlideComponent extends BaseSlideComponent<MotionsMotionSlideData> {
|
export class MotionsMotionSlideComponent extends BaseSlideComponent<MotionsMotionSlideData> {
|
||||||
public constructor() {
|
/**
|
||||||
|
* Indicates the LineNumberingMode Mode.
|
||||||
|
*/
|
||||||
|
public lnMode: LineNumberingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the Change reco Mode.
|
||||||
|
*/
|
||||||
|
public crMode: ChangeRecoMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the maximum line length as defined in the configuration.
|
||||||
|
*/
|
||||||
|
public lineLength: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the currently highlighted line, if any.
|
||||||
|
* @TODO Read value from the backend
|
||||||
|
*/
|
||||||
|
public highlightedLine: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the config variable `motions_preamble`
|
||||||
|
*/
|
||||||
|
public preamble: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All change recommendations AND amendments, sorted by line number.
|
||||||
|
*/
|
||||||
|
public allChangingObjects: ViewUnifiedChange[];
|
||||||
|
|
||||||
|
private _data: SlideData<MotionsMotionSlideData>;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public set data(value: SlideData<MotionsMotionSlideData>) {
|
||||||
|
this._data = value;
|
||||||
|
this.lnMode = value.data.line_numbering_mode;
|
||||||
|
this.lineLength = value.data.line_length;
|
||||||
|
this.preamble = value.data.preamble;
|
||||||
|
this.crMode = value.element.mode || 'original';
|
||||||
|
console.log(this.crMode);
|
||||||
|
|
||||||
|
this.recalcUnifiedChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get data(): SlideData<MotionsMotionSlideData> {
|
||||||
|
return this._data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private sanitizer: DomSanitizer,
|
||||||
|
private lineNumbering: LinenumberingService,
|
||||||
|
private diff: DiffService
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all paragraphs that are affected by the given amendment as unified change objects.
|
||||||
|
*
|
||||||
|
* @param {MotionsMotionSlideDataAmendment} amendment
|
||||||
|
* @returns {MotionsMotionSlideObjAmendmentParagraph[]}
|
||||||
|
*/
|
||||||
|
public getAmendmentAmendedParagraphs(
|
||||||
|
amendment: MotionsMotionSlideDataAmendment
|
||||||
|
): MotionsMotionSlideObjAmendmentParagraph[] {
|
||||||
|
let baseHtml = this.data.data.text;
|
||||||
|
baseHtml = this.lineNumbering.insertLineNumbers(baseHtml, this.lineLength);
|
||||||
|
const baseParagraphs = this.lineNumbering.splitToParagraphs(baseHtml);
|
||||||
|
|
||||||
|
return amendment.amendment_paragraphs
|
||||||
|
.map(
|
||||||
|
(newText: string, paraNo: number): MotionsMotionSlideObjAmendmentParagraph => {
|
||||||
|
if (newText === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const origText = baseParagraphs[paraNo],
|
||||||
|
paragraphLines = this.lineNumbering.getLineNumberRange(origText),
|
||||||
|
diff = this.diff.diff(origText, newText),
|
||||||
|
affectedLines = this.diff.detectAffectedLineRange(diff);
|
||||||
|
|
||||||
|
if (affectedLines === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newTextLines = this.lineNumbering.insertLineNumbers(
|
||||||
|
newText,
|
||||||
|
this.lineLength,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
paragraphLines.from
|
||||||
|
);
|
||||||
|
newTextLines = this.diff.formatDiff(
|
||||||
|
this.diff.extractRangeByLineNumbers(newTextLines, affectedLines.from, affectedLines.to)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new MotionsMotionSlideObjAmendmentParagraph(amendment, paraNo, newTextLines, affectedLines);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.filter((para: MotionsMotionSlideObjAmendmentParagraph) => para !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges amendments and change recommendations and sorts them by the line numbers.
|
||||||
|
* Called each time one of these arrays changes.
|
||||||
|
*/
|
||||||
|
private recalcUnifiedChanges(): void {
|
||||||
|
this.allChangingObjects = [];
|
||||||
|
|
||||||
|
if (this.data.data.change_recommendations) {
|
||||||
|
this.data.data.change_recommendations.forEach(change => {
|
||||||
|
this.allChangingObjects.push(new MotionsMotionSlideObjChangeReco(change));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.data.data.amendments) {
|
||||||
|
this.data.data.amendments.forEach(amendment => {
|
||||||
|
const paras = this.getAmendmentAmendedParagraphs(amendment);
|
||||||
|
paras.forEach(para => this.allChangingObjects.push(para));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.allChangingObjects.sort((a: ViewUnifiedChange, b: ViewUnifiedChange) => {
|
||||||
|
if (a.getLineFrom() < b.getLineFrom()) {
|
||||||
|
return -1;
|
||||||
|
} else if (a.getLineFrom() > b.getLineFrom()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true, if this is a statute amendment
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isStatuteAmendment(): boolean {
|
||||||
|
return !!this.data.data.base_statute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true, if this is an paragraph-based amendment
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isParagraphBasedAmendment(): boolean {
|
||||||
|
return (
|
||||||
|
this.data.data.is_child &&
|
||||||
|
this.data.data.amendment_paragraphs &&
|
||||||
|
this.data.data.amendment_paragraphs.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if no line numbers are to be shown.
|
||||||
|
*
|
||||||
|
* @returns whether there are line numbers at all
|
||||||
|
*/
|
||||||
|
public isLineNumberingNone(): boolean {
|
||||||
|
return this.lnMode === LineNumberingMode.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the line numbers are to be shown within the text with no line breaks.
|
||||||
|
*
|
||||||
|
* @returns whether the line numberings are inside
|
||||||
|
*/
|
||||||
|
public isLineNumberingInline(): boolean {
|
||||||
|
return this.lnMode === LineNumberingMode.Inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the line numbers are to be shown to the left of the text.
|
||||||
|
*
|
||||||
|
* @returns whether the line numberings are outside
|
||||||
|
*/
|
||||||
|
public isLineNumberingOutside(): boolean {
|
||||||
|
return this.lnMode === LineNumberingMode.Outside;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the template to make a HTML string compatible with [innerHTML]
|
||||||
|
* (otherwise line-number-data-attributes would be stripped out)
|
||||||
|
*
|
||||||
|
* @param {string} text
|
||||||
|
* @returns {SafeHtml}
|
||||||
|
*/
|
||||||
|
public sanitizedText(text: string): SafeHtml {
|
||||||
|
return this.sanitizer.bypassSecurityTrustHtml(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a renderable HTML string representing the given line number range of this motion
|
||||||
|
*
|
||||||
|
* @param {string} motionHtml
|
||||||
|
* @param {LineRange} lineRange
|
||||||
|
* @param {boolean} lineNumbers - weather to add line numbers to the returned HTML string
|
||||||
|
* @param {number} lineLength
|
||||||
|
*/
|
||||||
|
public extractMotionLineRange(
|
||||||
|
motionHtml: string,
|
||||||
|
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);
|
||||||
|
let html =
|
||||||
|
extracted.outerContextStart +
|
||||||
|
extracted.innerContextStart +
|
||||||
|
extracted.html +
|
||||||
|
extracted.innerContextEnd +
|
||||||
|
extracted.outerContextEnd;
|
||||||
|
if (lineNumbers) {
|
||||||
|
html = this.lineNumbering.insertLineNumbers(html, lineLength, null, null, lineRange.from);
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the formated motion text from the repository.
|
||||||
|
*
|
||||||
|
* @returns formated motion texts
|
||||||
|
*/
|
||||||
|
public getFormattedText(): string {
|
||||||
|
// Prevent this.allChangingObjects to be reordered from within formatMotion
|
||||||
|
// const changes: ViewUnifiedChange[] = Object.assign([], this.allChangingObjects);
|
||||||
|
const motion = this.data.data;
|
||||||
|
|
||||||
|
switch (this.crMode) {
|
||||||
|
case ChangeRecoMode.Original:
|
||||||
|
return this.lineNumbering.insertLineNumbers(motion.text, this.lineLength, this.highlightedLine);
|
||||||
|
case ChangeRecoMode.Changed:
|
||||||
|
return this.diff.getTextWithChanges(
|
||||||
|
motion.text,
|
||||||
|
this.allChangingObjects,
|
||||||
|
this.lineLength,
|
||||||
|
this.highlightedLine
|
||||||
|
);
|
||||||
|
case ChangeRecoMode.Diff:
|
||||||
|
let text = '';
|
||||||
|
this.allChangingObjects.forEach((change: ViewUnifiedChange, idx: number) => {
|
||||||
|
if (idx === 0) {
|
||||||
|
const lineRange = { from: 1, to: change.getLineFrom() };
|
||||||
|
text += this.extractMotionLineRange(motion.text, lineRange, true, this.lineLength);
|
||||||
|
} else if (this.allChangingObjects[idx - 1].getLineTo() < change.getLineFrom()) {
|
||||||
|
const lineRange = {
|
||||||
|
from: this.allChangingObjects[idx - 1].getLineTo(),
|
||||||
|
to: change.getLineFrom()
|
||||||
|
};
|
||||||
|
text += this.extractMotionLineRange(motion.text, lineRange, true, this.lineLength);
|
||||||
|
}
|
||||||
|
text += this.diff.getChangeDiff(motion.text, change, this.lineLength, this.highlightedLine);
|
||||||
|
});
|
||||||
|
text += this.diff.getTextRemainderAfterLastChange(
|
||||||
|
motion.text,
|
||||||
|
this.allChangingObjects,
|
||||||
|
this.lineLength,
|
||||||
|
this.highlightedLine
|
||||||
|
);
|
||||||
|
return text;
|
||||||
|
case ChangeRecoMode.Final:
|
||||||
|
const appliedChanges: ViewUnifiedChange[] = this.allChangingObjects.filter(change =>
|
||||||
|
change.isAccepted()
|
||||||
|
);
|
||||||
|
return this.diff.getTextWithChanges(motion.text, appliedChanges, this.lineLength, this.highlightedLine);
|
||||||
|
case ChangeRecoMode.ModifiedFinal:
|
||||||
|
if (motion.modified_final_version) {
|
||||||
|
return this.lineNumbering.insertLineNumbers(
|
||||||
|
motion.modified_final_version,
|
||||||
|
this.lineLength,
|
||||||
|
this.highlightedLine,
|
||||||
|
null,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Use the final version as fallback, if the modified does not exist.
|
||||||
|
const appliedChangeObjects: ViewUnifiedChange[] = this.allChangingObjects.filter(change =>
|
||||||
|
change.isAccepted()
|
||||||
|
);
|
||||||
|
return this.diff.getTextWithChanges(
|
||||||
|
motion.text,
|
||||||
|
appliedChangeObjects,
|
||||||
|
this.lineLength,
|
||||||
|
this.highlightedLine
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
console.error('unrecognized ChangeRecoMode option (' + this.crMode + ')');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `this.data.data` is an amendment, this returns the list of all changed paragraphs.
|
||||||
|
*
|
||||||
|
* @returns {DiffLinesInParagraph[]}
|
||||||
|
*/
|
||||||
|
public getAmendedParagraphs(): DiffLinesInParagraph[] {
|
||||||
|
let baseHtml = this.data.data.base_motion.text;
|
||||||
|
baseHtml = this.lineNumbering.insertLineNumbers(baseHtml, this.lineLength);
|
||||||
|
const baseParagraphs = this.lineNumbering.splitToParagraphs(baseHtml);
|
||||||
|
|
||||||
|
return this.data.data.amendment_paragraphs
|
||||||
|
.map(
|
||||||
|
(newText: string, paraNo: number): DiffLinesInParagraph => {
|
||||||
|
if (newText === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Hint: can be either DiffLinesInParagraph or null, if no changes are made
|
||||||
|
return this.diff.getAmendmentParagraphsLinesByMode(
|
||||||
|
paraNo,
|
||||||
|
baseParagraphs[paraNo],
|
||||||
|
newText,
|
||||||
|
this.lineLength
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.filter((para: DiffLinesInParagraph) => para !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the diff html from the statute amendment, as SafeHTML for [innerHTML]
|
||||||
|
*
|
||||||
|
* @returns safe html strings
|
||||||
|
*/
|
||||||
|
public getFormattedStatuteAmendment(): SafeHtml {
|
||||||
|
let diffHtml = this.diff.diff(this.data.data.base_statute.text, this.data.data.text);
|
||||||
|
diffHtml = this.lineNumbering.insertLineBreaksWithoutNumbers(diffHtml, this.lineLength, true);
|
||||||
|
return this.sanitizer.bypassSecurityTrustHtml(diffHtml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
131
client/src/assets/styles/motion-styles-common.scss
Normal file
131
client/src/assets/styles/motion-styles-common.scss
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/* Line numbers */
|
||||||
|
// :host ::ng-deep is needed as this styling applies to the motion html that is injected using innerHTML,
|
||||||
|
// which doesn't have the [ngcontent]-attributes necessary for regular styles.
|
||||||
|
// An alternative approach (in case ::ng-deep gets removed) might be to change the view encapsulation.
|
||||||
|
:host ::ng-deep .motion-text {
|
||||||
|
ins,
|
||||||
|
.insert {
|
||||||
|
color: green;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
del,
|
||||||
|
.delete {
|
||||||
|
color: red;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: #ff0;
|
||||||
|
}
|
||||||
|
&.line-numbers-outside {
|
||||||
|
padding-left: 40px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.os-line-number {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0;
|
||||||
|
line-height: 0;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
padding-right: 55px;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: attr(data-line-number);
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
vertical-align: top;
|
||||||
|
color: gray;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selectable:hover:before,
|
||||||
|
&.selected:before {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: 20px;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
content: '';
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z" fill="%23337ab7"/><path d="M0 0h24v24H0z" fill="none"/></svg>');
|
||||||
|
background-size: 16px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.line-numbers-inline {
|
||||||
|
.os-line-break {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-line-number {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: inline-block;
|
||||||
|
content: attr(data-line-number);
|
||||||
|
vertical-align: top;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: gray;
|
||||||
|
margin-top: -3px;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.line-numbers-none {
|
||||||
|
.os-line-break {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-line-number {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-split-before {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-split-after {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.os-split-before {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep .amendment-view {
|
||||||
|
.os-split-after {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.os-split-before {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.paragraphcontext {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
&.amendment-context .paragraphcontext {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,71 @@ def get_state(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_amendment_merge_into_motion(all_data, motion, amendment):
|
||||||
|
"""
|
||||||
|
HINT: This implementation should be consistent to isAccepted() in ViewMotionAmendedParagraph.ts
|
||||||
|
"""
|
||||||
|
if amendment["state_id"] is None:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
state = get_state(all_data, motion, amendment["state_id"])
|
||||||
|
if (
|
||||||
|
state["merge_amendment_into_final"] == -1
|
||||||
|
or state["merge_amendment_into_final"] == 1
|
||||||
|
):
|
||||||
|
return state["merge_amendment_into_final"]
|
||||||
|
|
||||||
|
if amendment["recommendation_id"] is None:
|
||||||
|
return 0
|
||||||
|
recommendation = get_state(all_data, motion, amendment["recommendation_id"])
|
||||||
|
return recommendation["merge_amendment_into_final"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_amendments_for_motion(motion, all_data):
|
||||||
|
amendment_data = []
|
||||||
|
for amendment_id, amendment in all_data["motions/motion"].items():
|
||||||
|
if amendment["parent_id"] == motion["id"]:
|
||||||
|
merge_amendment_into_final = get_amendment_merge_into_motion(
|
||||||
|
all_data, motion, amendment
|
||||||
|
)
|
||||||
|
amendment_data.append(
|
||||||
|
{
|
||||||
|
"id": amendment["id"],
|
||||||
|
"identifier": amendment["identifier"],
|
||||||
|
"title": amendment["title"],
|
||||||
|
"amendment_paragraphs": amendment["amendment_paragraphs"],
|
||||||
|
"merge_amendment_into_final": merge_amendment_into_final,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return amendment_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_amendment_base_motion(amendment, all_data):
|
||||||
|
try:
|
||||||
|
motion = all_data["motions/motion"][amendment["parent_id"]]
|
||||||
|
except KeyError:
|
||||||
|
motion_id = amendment["parent_id"]
|
||||||
|
raise ProjectorElementException(f"motion with id {motion_id} does not exist")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"identifier": motion["identifier"],
|
||||||
|
"title": motion["title"],
|
||||||
|
"text": motion["text"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_amendment_base_statute(amendment, all_data):
|
||||||
|
try:
|
||||||
|
statute = all_data["motions/statute-paragraph"][
|
||||||
|
amendment["statute_paragraph_id"]
|
||||||
|
]
|
||||||
|
except KeyError:
|
||||||
|
statute_id = amendment["statute_paragraph_id"]
|
||||||
|
raise ProjectorElementException(f"statute with id {statute_id} does not exist")
|
||||||
|
|
||||||
|
return {"title": statute["title"], "text": statute["text"]}
|
||||||
|
|
||||||
|
|
||||||
def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Motion slide.
|
Motion slide.
|
||||||
@ -51,7 +116,7 @@ def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
* change_recommendations
|
* change_recommendations
|
||||||
* submitter
|
* submitter
|
||||||
"""
|
"""
|
||||||
mode = element.get("mode")
|
mode = element.get("mode", get_config(all_data, "motions_recommendation_text_mode"))
|
||||||
motion_id = element.get("id")
|
motion_id = element.get("id")
|
||||||
|
|
||||||
if motion_id is None:
|
if motion_id is None:
|
||||||
@ -63,14 +128,44 @@ def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
raise ProjectorElementException(f"motion with id {motion_id} does not exist")
|
raise ProjectorElementException(f"motion with id {motion_id} does not exist")
|
||||||
|
|
||||||
show_meta_box = not get_config(all_data, "motions_disable_sidebox_on_projector")
|
show_meta_box = not get_config(all_data, "motions_disable_sidebox_on_projector")
|
||||||
|
line_length = get_config(all_data, "motions_line_length")
|
||||||
|
line_numbering_mode = get_config(all_data, "motions_default_line_numbering")
|
||||||
|
motions_preamble = get_config(all_data, "motions_preamble")
|
||||||
|
|
||||||
|
if motion["statute_paragraph_id"]:
|
||||||
|
change_recommendations = [] # type: ignore
|
||||||
|
amendments = [] # type: ignore
|
||||||
|
base_motion = None
|
||||||
|
base_statute = get_amendment_base_statute(motion, all_data)
|
||||||
|
elif bool(motion["parent_id"]) and motion["amendment_paragraphs"]:
|
||||||
|
change_recommendations = []
|
||||||
|
amendments = []
|
||||||
|
base_motion = get_amendment_base_motion(motion, all_data)
|
||||||
|
base_statute = None
|
||||||
|
else:
|
||||||
|
change_recommendations = list(
|
||||||
|
filter(
|
||||||
|
lambda reco: reco["internal"] is False, motion["change_recommendations"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
amendments = get_amendments_for_motion(motion, all_data)
|
||||||
|
base_motion = None
|
||||||
|
base_statute = None
|
||||||
|
|
||||||
return_value = {
|
return_value = {
|
||||||
"identifier": motion["identifier"],
|
"identifier": motion["identifier"],
|
||||||
"title": motion["title"],
|
"title": motion["title"],
|
||||||
|
"preamble": motions_preamble,
|
||||||
"text": motion["text"],
|
"text": motion["text"],
|
||||||
"amendment_paragraphs": motion["amendment_paragraphs"],
|
"amendment_paragraphs": motion["amendment_paragraphs"],
|
||||||
|
"base_motion": base_motion,
|
||||||
|
"base_statute": base_statute,
|
||||||
"is_child": bool(motion["parent_id"]),
|
"is_child": bool(motion["parent_id"]),
|
||||||
"show_meta_box": show_meta_box,
|
"show_meta_box": show_meta_box,
|
||||||
|
"change_recommendations": change_recommendations,
|
||||||
|
"amendments": amendments,
|
||||||
|
"line_length": line_length,
|
||||||
|
"line_numbering_mode": line_numbering_mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
if not get_config(all_data, "motions_disable_reason_on_projector"):
|
if not get_config(all_data, "motions_disable_reason_on_projector"):
|
||||||
@ -98,7 +193,6 @@ def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
return_value["recommender"] = get_config(
|
return_value["recommender"] = get_config(
|
||||||
all_data, "motions_recommendations_by"
|
all_data, "motions_recommendations_by"
|
||||||
)
|
)
|
||||||
return_value["change_recommendations"] = motion["change_recommendations"]
|
|
||||||
|
|
||||||
return_value["submitter"] = [
|
return_value["submitter"] = [
|
||||||
get_user_name(all_data, submitter["user_id"])
|
get_user_name(all_data, submitter["user_id"])
|
||||||
|
@ -74,7 +74,99 @@ def all_data():
|
|||||||
"weight": 10000,
|
"weight": 10000,
|
||||||
"created": "2019-01-19T18:37:34.741336+01:00",
|
"created": "2019-01-19T18:37:34.741336+01:00",
|
||||||
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
||||||
}
|
"change_recommendations": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"motion_id": 1,
|
||||||
|
"rejected": False,
|
||||||
|
"internal": True,
|
||||||
|
"type": 0,
|
||||||
|
"other_description": "",
|
||||||
|
"line_from": 1,
|
||||||
|
"line_to": 2,
|
||||||
|
"text": "internal new motion text",
|
||||||
|
"creation_time": "2019-02-09T09:54:06.256378+01:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"motion_id": 1,
|
||||||
|
"rejected": False,
|
||||||
|
"internal": False,
|
||||||
|
"type": 0,
|
||||||
|
"other_description": "",
|
||||||
|
"line_from": 1,
|
||||||
|
"line_to": 2,
|
||||||
|
"text": "public new motion text",
|
||||||
|
"creation_time": "2019-02-09T09:54:06.256378+01:00",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
"id": 2,
|
||||||
|
"identifier": "Ä1",
|
||||||
|
"title": "Amendment for 12345",
|
||||||
|
"text": "",
|
||||||
|
"amendment_paragraphs": ["New motion text"],
|
||||||
|
"modified_final_version": "",
|
||||||
|
"reason": "",
|
||||||
|
"parent_id": 1,
|
||||||
|
"category_id": None,
|
||||||
|
"comments": [],
|
||||||
|
"motion_block_id": None,
|
||||||
|
"origin": "",
|
||||||
|
"submitters": [{"id": 4, "user_id": 1, "motion_id": 1, "weight": 1}],
|
||||||
|
"supporters_id": [],
|
||||||
|
"state_id": 1,
|
||||||
|
"state_extension": None,
|
||||||
|
"state_access_level": 0,
|
||||||
|
"statute_paragraph_id": None,
|
||||||
|
"workflow_id": 1,
|
||||||
|
"recommendation_id": None,
|
||||||
|
"recommendation_extension": None,
|
||||||
|
"tags_id": [],
|
||||||
|
"attachments_id": [],
|
||||||
|
"polls": [],
|
||||||
|
"agenda_item_id": 4,
|
||||||
|
"log_messages": [],
|
||||||
|
"sort_parent_id": None,
|
||||||
|
"weight": 10000,
|
||||||
|
"created": "2019-01-19T18:37:34.741336+01:00",
|
||||||
|
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
||||||
|
"change_recommendations": [],
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
"id": 3,
|
||||||
|
"identifier": None,
|
||||||
|
"title": "Statute amendment for §1 Preamble",
|
||||||
|
"text": "<p>Some other preamble text</p>",
|
||||||
|
"amendment_paragraphs": None,
|
||||||
|
"modified_final_version": "",
|
||||||
|
"reason": "",
|
||||||
|
"parent_id": None,
|
||||||
|
"category_id": None,
|
||||||
|
"comments": [],
|
||||||
|
"motion_block_id": None,
|
||||||
|
"origin": "",
|
||||||
|
"submitters": [{"id": 4, "user_id": 1, "motion_id": 1, "weight": 1}],
|
||||||
|
"supporters_id": [],
|
||||||
|
"state_id": 1,
|
||||||
|
"state_extension": None,
|
||||||
|
"state_access_level": 0,
|
||||||
|
"statute_paragraph_id": 1,
|
||||||
|
"workflow_id": 1,
|
||||||
|
"recommendation_id": None,
|
||||||
|
"recommendation_extension": None,
|
||||||
|
"tags_id": [],
|
||||||
|
"attachments_id": [],
|
||||||
|
"polls": [],
|
||||||
|
"agenda_item_id": 4,
|
||||||
|
"log_messages": [],
|
||||||
|
"sort_parent_id": None,
|
||||||
|
"weight": 10000,
|
||||||
|
"created": "2019-01-19T18:37:34.741336+01:00",
|
||||||
|
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
||||||
|
"change_recommendations": [],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return_value["motions/workflow"] = {
|
return_value["motions/workflow"] = {
|
||||||
1: {
|
1: {
|
||||||
@ -149,6 +241,14 @@ def all_data():
|
|||||||
"first_state_id": 1,
|
"first_state_id": 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return_value["motions/statute-paragraph"] = {
|
||||||
|
1: {
|
||||||
|
"id": 1,
|
||||||
|
"title": "§1 Preamble",
|
||||||
|
"text": "<p>Some preamble text</p>",
|
||||||
|
"weight": 10000,
|
||||||
|
}
|
||||||
|
}
|
||||||
return_value["motions/motion-change-recommendation"] = {}
|
return_value["motions/motion-change-recommendation"] = {}
|
||||||
return return_value
|
return return_value
|
||||||
|
|
||||||
@ -162,9 +262,85 @@ def test_motion_slide(all_data):
|
|||||||
"identifier": "4",
|
"identifier": "4",
|
||||||
"title": "12345",
|
"title": "12345",
|
||||||
"text": "motion text",
|
"text": "motion text",
|
||||||
|
"amendments": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Amendment for 12345",
|
||||||
|
"amendment_paragraphs": ["New motion text"],
|
||||||
|
"identifier": "Ä1",
|
||||||
|
"merge_amendment_into_final": 0,
|
||||||
|
}
|
||||||
|
],
|
||||||
"amendment_paragraphs": None,
|
"amendment_paragraphs": None,
|
||||||
|
"change_recommendations": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"motion_id": 1,
|
||||||
|
"rejected": False,
|
||||||
|
"internal": False,
|
||||||
|
"type": 0,
|
||||||
|
"other_description": "",
|
||||||
|
"line_from": 1,
|
||||||
|
"line_to": 2,
|
||||||
|
"text": "public new motion text",
|
||||||
|
"creation_time": "2019-02-09T09:54:06.256378+01:00",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"base_motion": None,
|
||||||
|
"base_statute": None,
|
||||||
"is_child": False,
|
"is_child": False,
|
||||||
"show_meta_box": True,
|
"show_meta_box": True,
|
||||||
"reason": "",
|
"reason": "",
|
||||||
"submitter": ["Administrator"],
|
"submitter": ["Administrator"],
|
||||||
|
"line_length": 90,
|
||||||
|
"line_numbering_mode": "none",
|
||||||
|
"preamble": "The assembly may decide:",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_amendment_slide(all_data):
|
||||||
|
element: Dict[str, Any] = {"id": 2}
|
||||||
|
|
||||||
|
data = projector.motion_slide(all_data, element)
|
||||||
|
|
||||||
|
assert data == {
|
||||||
|
"identifier": "Ä1",
|
||||||
|
"title": "Amendment for 12345",
|
||||||
|
"text": "",
|
||||||
|
"amendments": [],
|
||||||
|
"amendment_paragraphs": ["New motion text"],
|
||||||
|
"change_recommendations": [],
|
||||||
|
"base_motion": {"identifier": "4", "text": "motion text", "title": "12345"},
|
||||||
|
"base_statute": None,
|
||||||
|
"is_child": True,
|
||||||
|
"show_meta_box": True,
|
||||||
|
"reason": "",
|
||||||
|
"submitter": ["Administrator"],
|
||||||
|
"line_length": 90,
|
||||||
|
"line_numbering_mode": "none",
|
||||||
|
"preamble": "The assembly may decide:",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_statute_amendment_slide(all_data):
|
||||||
|
element: Dict[str, Any] = {"id": 3}
|
||||||
|
|
||||||
|
data = projector.motion_slide(all_data, element)
|
||||||
|
|
||||||
|
assert data == {
|
||||||
|
"identifier": None,
|
||||||
|
"title": "Statute amendment for §1 Preamble",
|
||||||
|
"text": "<p>Some other preamble text</p>",
|
||||||
|
"amendments": [],
|
||||||
|
"amendment_paragraphs": None,
|
||||||
|
"change_recommendations": [],
|
||||||
|
"base_motion": None,
|
||||||
|
"base_statute": {"title": "§1 Preamble", "text": "<p>Some preamble text</p>"},
|
||||||
|
"is_child": False,
|
||||||
|
"show_meta_box": True,
|
||||||
|
"reason": "",
|
||||||
|
"submitter": ["Administrator"],
|
||||||
|
"line_length": 90,
|
||||||
|
"line_numbering_mode": "none",
|
||||||
|
"preamble": "The assembly may decide:",
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user