Diff & Line numbering services
This commit is contained in:
parent
214a310069
commit
db25ac6bf4
@ -275,8 +275,11 @@
|
|||||||
<!-- Text -->
|
<!-- Text -->
|
||||||
<!-- TODO: this is a config variable. Read it out -->
|
<!-- TODO: this is a config variable. Read it out -->
|
||||||
<h3 translate>The assembly may decide:</h3>
|
<h3 translate>The assembly may decide:</h3>
|
||||||
<div *ngIf='motion && !editMotion'>
|
<div *ngIf='motion && !editMotion' class="motion-text"
|
||||||
<div [innerHtml]='getFormatedText()'></div>
|
[class.line-numbers-none]="isLineNumberingNone()"
|
||||||
|
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||||
|
[class.line-numbers-outside]="isLineNumberingOutside()">
|
||||||
|
<div [innerHtml]="getFormattedText()"></div>
|
||||||
</div>
|
</div>
|
||||||
<mat-form-field *ngIf="motion && editMotion" class="wide-form">
|
<mat-form-field *ngIf="motion && editMotion" class="wide-form">
|
||||||
<textarea matInput placeholder='Motion Text' formControlName='text' [value]='motionCopy.text'></textarea>
|
<textarea matInput placeholder='Motion Text' formControlName='text' [value]='motionCopy.text'></textarea>
|
||||||
|
@ -160,3 +160,90 @@ mat-expansion-panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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 {
|
||||||
|
color: green;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
del {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { MatExpansionPanel } from '@angular/material';
|
import { MatExpansionPanel } from '@angular/material';
|
||||||
|
|
||||||
import { BaseComponent } from '../../../../base.component';
|
import { BaseComponent } from '../../../../base.component';
|
||||||
import { Category } from '../../../../shared/models/motions/category';
|
import { Category } from '../../../../shared/models/motions/category';
|
||||||
import { ViewportService } from '../../../../core/services/viewport.service';
|
import { ViewportService } from '../../../../core/services/viewport.service';
|
||||||
import { MotionRepositoryService } from '../../services/motion-repository.service';
|
import { MotionRepositoryService } from '../../services/motion-repository.service';
|
||||||
import { ViewMotion } from '../../models/view-motion';
|
import { LineNumbering, ViewMotion } from '../../models/view-motion';
|
||||||
import { User } from '../../../../shared/models/users/user';
|
import { User } from '../../../../shared/models/users/user';
|
||||||
import { DataStoreService } from '../../../../core/services/data-store.service';
|
import { DataStoreService } from '../../../../core/services/data-store.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Motion } from '../../../../shared/models/motions/motion';
|
import { Motion } from '../../../../shared/models/motions/motion';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { SafeHtml } from '@angular/platform-browser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for the motion detail view
|
* Component for the motion detail view
|
||||||
@ -215,8 +216,13 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* get the formated motion text from the repository.
|
* get the formated motion text from the repository.
|
||||||
*/
|
*/
|
||||||
public getFormatedText(): string {
|
public getFormattedText(): SafeHtml {
|
||||||
return this.repo.formatMotion(this.motion.id, this.motion.lnMode, this.motion.crMode);
|
return this.repo.formatMotion(
|
||||||
|
this.motion.id,
|
||||||
|
this.motion.crMode,
|
||||||
|
this.motion.lineLength,
|
||||||
|
this.motion.highlightedLine
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -264,10 +270,31 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
|
|||||||
* Sets the motions line numbering mode
|
* Sets the motions line numbering mode
|
||||||
* @param mode Needs to fot to the enum defined in ViewMotion
|
* @param mode Needs to fot to the enum defined in ViewMotion
|
||||||
*/
|
*/
|
||||||
public setLineNumberingMode(mode: number): void {
|
public setLineNumberingMode(mode: LineNumbering): void {
|
||||||
this.motion.lnMode = mode;
|
this.motion.lnMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if no line numbers are to be shown.
|
||||||
|
*/
|
||||||
|
public isLineNumberingNone(): boolean {
|
||||||
|
return this.motion.lnMode === LineNumbering.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the line numbers are to be shown within the text with no line breaks.
|
||||||
|
*/
|
||||||
|
public isLineNumberingInline(): boolean {
|
||||||
|
return this.motion.lnMode === LineNumbering.Inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the line numbers are to be shown to the left of the text.
|
||||||
|
*/
|
||||||
|
public isLineNumberingOutside(): boolean {
|
||||||
|
return this.motion.lnMode === LineNumbering.Outside;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the motions change reco mode
|
* Sets the motions change reco mode
|
||||||
* @param mode Needs to fot to the enum defined in ViewMotion
|
* @param mode Needs to fot to the enum defined in ViewMotion
|
||||||
|
@ -6,7 +6,7 @@ import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
|||||||
import { BaseModel } from '../../../shared/models/base/base-model';
|
import { BaseModel } from '../../../shared/models/base/base-model';
|
||||||
import { BaseViewModel } from '../../base/base-view-model';
|
import { BaseViewModel } from '../../base/base-view-model';
|
||||||
|
|
||||||
enum LineNumbering {
|
export enum LineNumbering {
|
||||||
None,
|
None,
|
||||||
Inside,
|
Inside,
|
||||||
Outside
|
Outside
|
||||||
@ -46,6 +46,18 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
*/
|
*/
|
||||||
public crMode: ChangeReco;
|
public crMode: ChangeReco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the maximum line length as defined in the configuration.
|
||||||
|
* Needs to be accessed from outside
|
||||||
|
*/
|
||||||
|
public lineLength: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the currently highlighted line, if any.
|
||||||
|
* Needs to be accessed from outside
|
||||||
|
*/
|
||||||
|
public highlightedLine: number;
|
||||||
|
|
||||||
public get motion(): Motion {
|
public get motion(): Motion {
|
||||||
return this._motion;
|
return this._motion;
|
||||||
}
|
}
|
||||||
@ -179,6 +191,9 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
// TODO: Should be set using a a config variable
|
// TODO: Should be set using a a config variable
|
||||||
this.lnMode = LineNumbering.None;
|
this.lnMode = LineNumbering.None;
|
||||||
this.crMode = ChangeReco.Original;
|
this.crMode = ChangeReco.Original;
|
||||||
|
this.lineLength = 80;
|
||||||
|
|
||||||
|
this.highlightedLine = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTitle(): string {
|
public getTitle(): string {
|
||||||
|
1168
client/src/app/site/motions/services/diff.service.spec.ts
Normal file
1168
client/src/app/site/motions/services/diff.service.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
1986
client/src/app/site/motions/services/diff.service.ts
Normal file
1986
client/src/app/site/motions/services/diff.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,775 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LinenumberingService } from './linenumbering.service';
|
||||||
|
|
||||||
|
describe('LinenumberingService', () => {
|
||||||
|
const brMarkup = (no: number): string => {
|
||||||
|
return (
|
||||||
|
'<br class="os-line-break">' +
|
||||||
|
'<span class="os-line-number line-number-' +
|
||||||
|
no +
|
||||||
|
'" data-line-number="' +
|
||||||
|
no +
|
||||||
|
'" contenteditable="false"> </span>'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
noMarkup = (no: number): string => {
|
||||||
|
return (
|
||||||
|
'<span class="os-line-number line-number-' +
|
||||||
|
no +
|
||||||
|
'" data-line-number="' +
|
||||||
|
no +
|
||||||
|
'" contenteditable="false"> </span>'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
longstr = (length: number): string => {
|
||||||
|
let outstr = '';
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
outstr += String.fromCharCode(65 + (i % 26));
|
||||||
|
}
|
||||||
|
return outstr;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [LinenumberingService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('paragraph splitting', () => {
|
||||||
|
it('breaks simple DIVs', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const htmlIn = '<DIV class="testclass">Test <strong>1</strong></DIV>' + '\n' + '<p>Test <em>2</em> 3</p>';
|
||||||
|
const out = service.splitToParagraphs(htmlIn);
|
||||||
|
expect(out.length).toBe(2);
|
||||||
|
expect(out[0]).toBe('<div class="testclass">Test <strong>1</strong></div>');
|
||||||
|
expect(out[1]).toBe('<p>Test <em>2</em> 3</p>');
|
||||||
|
}));
|
||||||
|
it('ignores root-level text-nodes', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const htmlIn = '<DIV class="testclass">Test <strong>3</strong></DIV>' + '\n New line';
|
||||||
|
const out = service.splitToParagraphs(htmlIn);
|
||||||
|
expect(out.length).toBe(1);
|
||||||
|
expect(out[0]).toBe('<div class="testclass">Test <strong>3</strong></div>');
|
||||||
|
}));
|
||||||
|
it('splits UL-Lists', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const htmlIn =
|
||||||
|
"<UL class='testclass'>\n<li>Node 1</li>\n <li class='second'>Node <strong>2</strong></li><li><p>Node 3</p></li></UL>";
|
||||||
|
const out = service.splitToParagraphs(htmlIn);
|
||||||
|
expect(out.length).toBe(3);
|
||||||
|
expect(out[0]).toBe('<ul class="testclass"><li>Node 1</li></ul>');
|
||||||
|
expect(out[1]).toBe('<ul class="testclass"><li class="second">Node <strong>2</strong></li></ul>');
|
||||||
|
expect(out[2]).toBe('<ul class="testclass"><li><p>Node 3</p></li></ul>');
|
||||||
|
}));
|
||||||
|
it('splits OL-Lists', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const htmlIn =
|
||||||
|
"<OL start='2' class='testclass'>\n<li>Node 1</li>\n <li class='second'>Node <strong>2</strong></li><li><p>Node 3</p></li></OL>";
|
||||||
|
const out = service.splitToParagraphs(htmlIn);
|
||||||
|
expect(out.length).toBe(3);
|
||||||
|
expect(out[0]).toBe('<ol start="2" class="testclass"><li>Node 1</li></ol>');
|
||||||
|
expect(out[1]).toBe('<ol start="3" class="testclass"><li class="second">Node <strong>2</strong></li></ol>');
|
||||||
|
expect(out[2]).toBe('<ol start="4" class="testclass"><li><p>Node 3</p></li></ol>');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getting line number range', () => {
|
||||||
|
it('extracts the line number range, example 1', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const html =
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(2) +
|
||||||
|
'et accusam et justo duo dolores et ea <span style="color: #ff0000;"><strike>rebum </strike></span><span style="color: #006400;">Inserted Text</span>. Stet clita kasd ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'gubergren,</p>';
|
||||||
|
const range = service.getLineNumberRange(html);
|
||||||
|
expect(range).toEqual({ from: 2, to: 4 });
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('line numbering: test nodes', () => {
|
||||||
|
it('breaks very short lines', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const textNode = document.createTextNode('0123');
|
||||||
|
service.setInlineOffsetLineNumberForTests(0, 1);
|
||||||
|
const out = service.textNodeToLines(textNode, 5);
|
||||||
|
const outHtml = service.nodesToHtml(out);
|
||||||
|
expect(outHtml).toBe('0123');
|
||||||
|
expect(service.getInlineOffsetForTests()).toBe(4);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('breaks simple lines', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const textNode = document.createTextNode('012345678901234567');
|
||||||
|
service.setInlineOffsetLineNumberForTests(0, 1);
|
||||||
|
const out = service.textNodeToLines(textNode, 5);
|
||||||
|
const outHtml = service.nodesToHtml(out);
|
||||||
|
expect(outHtml).toBe('01234' + brMarkup(1) + '56789' + brMarkup(2) + '01234' + brMarkup(3) + '567');
|
||||||
|
expect(service.getInlineOffsetForTests()).toBe(3);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('breaks simple lines with offset', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const textNode = document.createTextNode('012345678901234567');
|
||||||
|
service.setInlineOffsetLineNumberForTests(2, 1);
|
||||||
|
const out = service.textNodeToLines(textNode, 5);
|
||||||
|
const outHtml = service.nodesToHtml(out);
|
||||||
|
expect(outHtml).toBe('012' + brMarkup(1) + '34567' + brMarkup(2) + '89012' + brMarkup(3) + '34567');
|
||||||
|
expect(service.getInlineOffsetForTests()).toBe(5);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('breaks simple lines with offset equaling to length', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const textNode = document.createTextNode('012345678901234567');
|
||||||
|
service.setInlineOffsetLineNumberForTests(5, 1);
|
||||||
|
const out = service.textNodeToLines(textNode, 5);
|
||||||
|
const outHtml = service.nodesToHtml(out);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
brMarkup(1) + '01234' + brMarkup(2) + '56789' + brMarkup(3) + '01234' + brMarkup(4) + '567'
|
||||||
|
);
|
||||||
|
expect(service.getInlineOffsetForTests()).toBe(3);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('breaks simple lines with spaces (1)', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const textNode = document.createTextNode('0123 45 67 89012 34 567');
|
||||||
|
service.setInlineOffsetLineNumberForTests(0, 1);
|
||||||
|
const out = service.textNodeToLines(textNode, 5);
|
||||||
|
const outHtml = service.nodesToHtml(out);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'0123 ' + brMarkup(1) + '45 67 ' + brMarkup(2) + '89012 ' + brMarkup(3) + '34 ' + brMarkup(4) + '567'
|
||||||
|
);
|
||||||
|
expect(service.getInlineOffsetForTests()).toBe(3);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('breaks simple lines with spaces (2)', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const textNode = document.createTextNode('0123 45 67 89012tes 344 ');
|
||||||
|
service.setInlineOffsetLineNumberForTests(0, 1);
|
||||||
|
const out = service.textNodeToLines(textNode, 5);
|
||||||
|
const outHtml = service.nodesToHtml(out);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'0123 ' + brMarkup(1) + '45 67 ' + brMarkup(2) + '89012' + brMarkup(3) + 'tes ' + brMarkup(4) + '344 '
|
||||||
|
);
|
||||||
|
expect(service.getInlineOffsetForTests()).toBe(4);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('breaks simple lines with spaces (3)', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const textNode = document.createTextNode("I'm a Demo-Text");
|
||||||
|
service.setInlineOffsetLineNumberForTests(0, 1);
|
||||||
|
const out = service.textNodeToLines(textNode, 5);
|
||||||
|
const outHtml = service.nodesToHtml(out);
|
||||||
|
expect(outHtml).toBe("I'm a " + brMarkup(1) + 'Demo-' + brMarkup(2) + 'Text');
|
||||||
|
expect(service.getInlineOffsetForTests()).toBe(4);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('breaks simple lines with spaces (4)', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const textNode = document.createTextNode("I'm a LongDemo-Text");
|
||||||
|
service.setInlineOffsetLineNumberForTests(0, 1);
|
||||||
|
const out = service.textNodeToLines(textNode, 5);
|
||||||
|
const outHtml = service.nodesToHtml(out);
|
||||||
|
expect(outHtml).toBe("I'm a " + brMarkup(1) + 'LongD' + brMarkup(2) + 'emo-' + brMarkup(3) + 'Text');
|
||||||
|
expect(service.getInlineOffsetForTests()).toBe(4);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('line numbering: inline nodes', () => {
|
||||||
|
it('leaves a simple SPAN untouched', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<span>Test</span>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 5);
|
||||||
|
expect(outHtml).toBe(noMarkup(1) + '<span>Test</span>');
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('breaks lines in a simple SPAN', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<span>Lorem ipsum dolorsit amet</span>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 5);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
noMarkup(1) +
|
||||||
|
'<span>Lorem ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'ipsum ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'dolor' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'sit ' +
|
||||||
|
brMarkup(5) +
|
||||||
|
'amet</span>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('breaks lines in nested inline elements', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<span>Lorem <strong>ipsum dolorsit</strong> amet</span>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 5);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
noMarkup(1) +
|
||||||
|
'<span>Lorem ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'<strong>ipsum ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'dolor' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'sit</strong> ' +
|
||||||
|
brMarkup(5) +
|
||||||
|
'amet</span>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('counts within DEL nodes', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '1234 <del>1234</del> 1234 1234';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 10);
|
||||||
|
expect(outHtml).toBe(noMarkup(1) + '1234 <del>1234</del> ' + brMarkup(2) + '1234 1234');
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('counts after DEL/INS-nodes', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml =
|
||||||
|
'<P>leo Testelefantgeweih Buchstabenwut als Achzehnzahlunginer. Hierbei <DEL>darf</DEL><INS>setzen</INS> bist der Deifi <DEL>das </DEL><INS>Dor Reh Wachtel da </INS>Subjunktivier <DEL>als Derftige Aal</DEL><INS>san</INS> Orthopädische<DEL>, der Arbeitsnachweisdiskus Bass der Tastatur </DEL><DEL>Weiter schreiben wie Tasse Wasser als</DEL><INS> dienen</INS>.</P>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 95);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'leo Testelefantgeweih Buchstabenwut als Achzehnzahlunginer. Hierbei <del>darf</del><ins>setzen</ins> bist der Deifi <del>das ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'</del><ins>Dor Reh Wachtel da </ins>Subjunktivier <del>als Derftige Aal</del><ins>san</ins> Orthopädische<del>, der Arbeitsnachweisdiskus Bass der Tastatur </del>' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'<del>Weiter schreiben wie Tasse Wasser als</del><ins> dienen</ins>.</p>'
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('handles STRIKE-tags', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml =
|
||||||
|
'<p>et accusam et justo duo dolores et ea <span style="color: #ff0000;"><strike>rebum </strike></span><span style="color: #006400;">Inserted Text</span>. Stet clita kasd gubergren,</p>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'et accusam et justo duo dolores et ea <span style="color: #ff0000;"><strike>rebum </strike></span><span style="color: #006400;">Inserted Text</span>. Stet clita kasd ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'gubergren,</p>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('treats ascii newline characters like spaces', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = '<p>Test 123\nTest1</p>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 5);
|
||||||
|
expect(outHtml).toBe('<p>' + noMarkup(1) + 'Test ' + brMarkup(2) + '123\n' + brMarkup(3) + 'Test1</p>');
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('line numbering: block nodes', () => {
|
||||||
|
it('leaves a simple DIV untouched', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<div>Test</div>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 5);
|
||||||
|
expect(outHtml).toBe('<div>' + noMarkup(1) + 'Test</div>');
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('breaks a DIV containing only inline elements', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = '<div>Test <span>Test1234</span>5678 Test</div>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 5);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<div>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'Test ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'<span>Test1' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'234</span>56' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'78 ' +
|
||||||
|
brMarkup(5) +
|
||||||
|
'Test</div>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('handles a DIV within a DIV correctly', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<div>Te<div>Te Test</div>Test</div>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 5);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<div>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'Te<div>' +
|
||||||
|
noMarkup(2) +
|
||||||
|
'Te ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'Test</div>' +
|
||||||
|
noMarkup(4) +
|
||||||
|
'Test</div>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('ignores white spaces between block element tags', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = '<ul>\n<li>Test</li>\n</ul>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe('<ul>\n<li>' + noMarkup(1) + 'Test</li>\n</ul>');
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('indentation for block elements', () => {
|
||||||
|
it('indents LI-elements', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<div>' + longstr(100) + '<ul><li>' + longstr(100) + '</li></ul>' + longstr(100) + '</div>';
|
||||||
|
const expected =
|
||||||
|
'<div>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'CDEFGHIJKLMNOPQRSTUV' +
|
||||||
|
'<ul><li>' +
|
||||||
|
noMarkup(3) +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVW' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'XYZABCDEFGHIJKLMNOPQRSTUV' +
|
||||||
|
'</li></ul>' +
|
||||||
|
noMarkup(5) +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' +
|
||||||
|
brMarkup(6) +
|
||||||
|
'CDEFGHIJKLMNOPQRSTUV</div>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(expected);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('indents BLOCKQUOTE-elements', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml =
|
||||||
|
'<div>' + longstr(100) + '<blockquote>' + longstr(100) + '</blockquote>' + longstr(100) + '</div>';
|
||||||
|
const expected =
|
||||||
|
'<div>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'CDEFGHIJKLMNOPQRSTUV' +
|
||||||
|
'<blockquote>' +
|
||||||
|
noMarkup(3) +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'IJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUV' +
|
||||||
|
'</blockquote>' +
|
||||||
|
noMarkup(5) +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' +
|
||||||
|
brMarkup(6) +
|
||||||
|
'CDEFGHIJKLMNOPQRSTUV</div>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(expected);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('shortens the line for H1-elements by 2/3', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = '<h1>' + longstr(80) + '</h1>';
|
||||||
|
const expected =
|
||||||
|
'<h1>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZA' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'BCDEFGHIJKLMNOPQRSTUVWXYZAB</h1>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(expected);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('shortens the line for H2-elements by 0.75', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = '<h2>' + longstr(80) + '</h2>';
|
||||||
|
const expected =
|
||||||
|
'<h2>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'IJKLMNOPQRSTUVWXYZAB</h2>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(expected);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('indents Ps with 30px-padding by 6 characters', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = '<div style="padding-left: 30px;">' + longstr(80) + '</div>';
|
||||||
|
const expected =
|
||||||
|
'<div style="padding-left: 30px;">' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUV' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'WXYZAB</div>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(expected);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('breaks before an inline element, if the first word of the new inline element is longer than the remaining line (1)', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml =
|
||||||
|
'<p>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie <strong>consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio</strong>.</p>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'<strong>consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'et iusto odio</strong>.</p>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('breaks before an inline element, if the first word of the new inline element is longer than the remaining line (2)', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml =
|
||||||
|
'<p><span>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie <strong>consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio</strong>.</span></p>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'<span>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'<strong>consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'et iusto odio</strong>.</span></p>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('does not fail in a weird case', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<ins>seid Noch</ins><p></p><p><ins>Test 123</ins></p>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
noMarkup(1) + '<ins>seid Noch</ins><p></p><p>' + noMarkup(2) + '<ins>Test 123</ins></p>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('line numbering in regard to the inline diff', () => {
|
||||||
|
it('does not count within INS nodes', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '1234 <ins>1234</ins> 1234 1234';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 10);
|
||||||
|
expect(outHtml).toBe(noMarkup(1) + '1234 <ins>1234</ins> 1234 ' + brMarkup(2) + '1234');
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('does not create a new line for a trailing INS', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml =
|
||||||
|
'<p>et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur<ins>dsfsdf23</ins></p>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur<ins>dsfsdf23</ins></p>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('inserts the line number before the INS, if INS is the first element of the paragraph', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml =
|
||||||
|
"<p><ins>lauthals </ins>'liebe Kinder, ich will hinaus in den Wald, seid auf der Hut vor dem Wolf!' Und noch etwas mehr Text bis zur nächsten Zeile</p>";
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
"<ins>lauthals </ins>'liebe Kinder, ich will hinaus in den Wald, seid auf der Hut vor dem Wolf!' Und " +
|
||||||
|
brMarkup(2) +
|
||||||
|
'noch etwas mehr Text bis zur nächsten Zeile</p>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('cancels newlines after br-elements', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<p>Test 123<br>\nTest 456</p>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe('<p>' + noMarkup(1) + 'Test 123<br>' + noMarkup(2) + 'Test 456</p>');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('does not force-break words right after an INS', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = '<p>' + noMarkup(1) + '012345 <ins>78 01 34567</ins>8901234567890123456789</p>';
|
||||||
|
const outHtml = service.insertLineBreaksWithoutNumbers(inHtml, 20, true);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<p>' +
|
||||||
|
noMarkup(1) +
|
||||||
|
'012345 <ins>78 01 <br class="os-line-break">34567</ins>890123456789012<br class="os-line-break">3456789</p>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('line breaking without adding line numbers', () => {
|
||||||
|
const plainBr = '<br class="os-line-break">';
|
||||||
|
|
||||||
|
it('breaks a DIV containing only inline elements', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = '<div>Test <span>Test1234</span>5678 Test</div>';
|
||||||
|
const outHtml = service.insertLineBreaksWithoutNumbers(inHtml, 5);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<div>Test ' +
|
||||||
|
plainBr +
|
||||||
|
'<span>Test1' +
|
||||||
|
plainBr +
|
||||||
|
'234</span>56' +
|
||||||
|
plainBr +
|
||||||
|
'78 ' +
|
||||||
|
plainBr +
|
||||||
|
'Test</div>'
|
||||||
|
);
|
||||||
|
expect(service.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('indents BLOCKQUOTE-elements', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml =
|
||||||
|
'<div>' + longstr(100) + '<blockquote>' + longstr(100) + '</blockquote>' + longstr(100) + '</div>';
|
||||||
|
const expected =
|
||||||
|
'<div>' +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' +
|
||||||
|
plainBr +
|
||||||
|
'CDEFGHIJKLMNOPQRSTUV' +
|
||||||
|
'<blockquote>' +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH' +
|
||||||
|
plainBr +
|
||||||
|
'IJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUV' +
|
||||||
|
'</blockquote>' +
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' +
|
||||||
|
plainBr +
|
||||||
|
'CDEFGHIJKLMNOPQRSTUV</div>';
|
||||||
|
const outHtml = service.insertLineBreaksWithoutNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe(expected);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('DOES count within INS nodes', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '1234 <ins>1234</ins> 1234 1234';
|
||||||
|
const outHtml = service.insertLineBreaksWithoutNumbers(inHtml, 10, true);
|
||||||
|
expect(outHtml).toBe('1234 <ins>1234</ins> ' + plainBr + '1234 1234');
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('does not create a new line for a trailing INS', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml =
|
||||||
|
'<p>et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur<ins>dsfsdf23</ins></p>';
|
||||||
|
const outHtml = service.insertLineBreaksWithoutNumbers(inHtml, 80, true);
|
||||||
|
expect(outHtml).toBe(
|
||||||
|
'<p>et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata ' +
|
||||||
|
plainBr +
|
||||||
|
'sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur' +
|
||||||
|
plainBr +
|
||||||
|
'<ins>dsfsdf23</ins></p>'
|
||||||
|
);
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('ignores witespaces by previously added line numbers', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = '<p>' + noMarkup(1) + longstr(10) + '</p>';
|
||||||
|
const outHtml = service.insertLineBreaksWithoutNumbers(inHtml, 10, true);
|
||||||
|
expect(outHtml).toBe('<p>' + noMarkup(1) + longstr(10) + '</p>');
|
||||||
|
expect(service.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('behavior regarding ckeditor', () => {
|
||||||
|
it('does not count empty lines, case 1', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<p>Line 1</p>\n\n<p>Line 2</p>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe('<p>' + noMarkup(1) + 'Line 1</p>' + '\n\n' + '<p>' + noMarkup(2) + 'Line 2</p>');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('does not count empty lines, case 2', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<ul>\n\n<li>Point 1</li>\n\n</ul>';
|
||||||
|
const outHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe('<ul>\n\n<li>' + noMarkup(1) + 'Point 1</li>\n\n</ul>');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('line highlighting', () => {
|
||||||
|
it('highlights a simple line', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = service.insertLineNumbers('<span>Lorem ipsum dolorsit amet</span>', 5);
|
||||||
|
const highlighted = service.highlightLine(inHtml, 2);
|
||||||
|
expect(highlighted).toBe(
|
||||||
|
noMarkup(1) +
|
||||||
|
'<span>Lorem ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'<span class="highlight">ipsum </span>' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'dolor' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'sit ' +
|
||||||
|
brMarkup(5) +
|
||||||
|
'amet</span>'
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('highlights a simple line with formattings', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = service.insertLineNumbers(
|
||||||
|
'<span>Lorem ipsum <strong>dolorsit amet Lorem</strong><em> ipsum dolorsit amet</em> Lorem ipsum dolorsit amet</span>',
|
||||||
|
20
|
||||||
|
);
|
||||||
|
expect(inHtml).toBe(
|
||||||
|
noMarkup(1) +
|
||||||
|
'<span>Lorem ipsum <strong>dolorsit ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'amet Lorem</strong><em> ipsum ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'dolorsit amet</em> Lorem ' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'ipsum dolorsit amet</span>'
|
||||||
|
);
|
||||||
|
|
||||||
|
const highlighted = service.highlightLine(inHtml, 2);
|
||||||
|
expect(highlighted).toBe(
|
||||||
|
noMarkup(1) +
|
||||||
|
'<span>Lorem ipsum <strong>dolorsit ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'<span class="highlight">amet Lorem</span></strong><em><span class="highlight"> ipsum </span>' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'dolorsit amet</em> Lorem ' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'ipsum dolorsit amet</span>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('highlights the last line', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = service.insertLineNumbers('<span>Lorem ipsum dolorsit amet</span>', 5);
|
||||||
|
const highlighted = service.highlightLine(inHtml, 5);
|
||||||
|
expect(highlighted).toBe(
|
||||||
|
noMarkup(1) +
|
||||||
|
'<span>Lorem ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'ipsum ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'dolor' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'sit ' +
|
||||||
|
brMarkup(5) +
|
||||||
|
'<span class="highlight">amet</span></span>'
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('highlights the first line', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = service.insertLineNumbers('<span>Lorem ipsum dolorsit amet</span>', 5);
|
||||||
|
const highlighted = service.highlightLine(inHtml, 1);
|
||||||
|
expect(highlighted).toBe(
|
||||||
|
noMarkup(1) +
|
||||||
|
'<span><span class="highlight">Lorem </span>' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'ipsum ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'dolor' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'sit ' +
|
||||||
|
brMarkup(5) +
|
||||||
|
'amet</span>'
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('does not change the string if the line number is not found', inject(
|
||||||
|
[LinenumberingService],
|
||||||
|
(service: LinenumberingService) => {
|
||||||
|
const inHtml = service.insertLineNumbers('<span>Lorem ipsum dolorsit amet</span>', 5);
|
||||||
|
const highlighted = service.highlightLine(inHtml, 8);
|
||||||
|
expect(highlighted).toBe(
|
||||||
|
noMarkup(1) +
|
||||||
|
'<span>Lorem ' +
|
||||||
|
brMarkup(2) +
|
||||||
|
'ipsum ' +
|
||||||
|
brMarkup(3) +
|
||||||
|
'dolor' +
|
||||||
|
brMarkup(4) +
|
||||||
|
'sit ' +
|
||||||
|
brMarkup(5) +
|
||||||
|
'amet</span>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('document structure parsing', () => {
|
||||||
|
it('detects the line numbers of headings', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
let inHtml =
|
||||||
|
'<p>Line 1</p>' +
|
||||||
|
'<h1>Heading 1</h1><p>Line 2</p><h2>Heading 1.1</h2><p>Line 3</p><h2>Heading 1.2</h2><p>Line 4</p>' +
|
||||||
|
'<h1>Heading 2</h1><h2>Heading 2.1</h2><p>Line 5</p>';
|
||||||
|
inHtml = service.insertLineNumbers(inHtml, 80);
|
||||||
|
const structure = service.getHeadingsWithLineNumbers(inHtml);
|
||||||
|
expect(structure).toEqual([
|
||||||
|
{ lineNumber: 2, level: 1, text: 'Heading 1' },
|
||||||
|
{ lineNumber: 4, level: 2, text: 'Heading 1.1' },
|
||||||
|
{ lineNumber: 6, level: 2, text: 'Heading 1.2' },
|
||||||
|
{ lineNumber: 8, level: 1, text: 'Heading 2' },
|
||||||
|
{ lineNumber: 9, level: 2, text: 'Heading 2.1' }
|
||||||
|
]);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('caching', () => {
|
||||||
|
it('caches based on line length', inject([LinenumberingService], (service: LinenumberingService) => {
|
||||||
|
const inHtml = '<p>' + longstr(100) + '</p>';
|
||||||
|
const outHtml80 = service.insertLineNumbers(inHtml, 80);
|
||||||
|
const outHtml70 = service.insertLineNumbers(inHtml, 70);
|
||||||
|
expect(outHtml70).not.toBe(outHtml80);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
1023
client/src/app/site/motions/services/linenumbering.service.ts
Normal file
1023
client/src/app/site/motions/services/linenumbering.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,8 @@ import { ViewMotion } from '../models/view-motion';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { BaseRepository } from '../../base/base-repository';
|
import { BaseRepository } from '../../base/base-repository';
|
||||||
import { DataStoreService } from '../../../core/services/data-store.service';
|
import { DataStoreService } from '../../../core/services/data-store.service';
|
||||||
|
import { LinenumberingService } from './linenumbering.service';
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository Services for motions (and potentially categories)
|
* Repository Services for motions (and potentially categories)
|
||||||
@ -30,9 +32,16 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
*
|
*
|
||||||
* Converts existing and incoming motions to ViewMotions
|
* Converts existing and incoming motions to ViewMotions
|
||||||
* Handles CRUD using an observer to the DataStore
|
* Handles CRUD using an observer to the DataStore
|
||||||
* @param DataSend
|
* @param DS
|
||||||
|
* @param dataSend
|
||||||
|
* @param lineNumbering
|
||||||
*/
|
*/
|
||||||
public constructor(DS: DataStoreService, private dataSend: DataSendService) {
|
public constructor(
|
||||||
|
DS: DataStoreService,
|
||||||
|
private dataSend: DataSendService,
|
||||||
|
private readonly lineNumbering: LinenumberingService,
|
||||||
|
private readonly sanitizer: DomSanitizer
|
||||||
|
) {
|
||||||
super(DS, Motion, [Category, User, Workflow]);
|
super(DS, Motion, [Category, User, Workflow]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,30 +111,20 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
* Format the motion text using the line numbering and change
|
* Format the motion text using the line numbering and change
|
||||||
* reco algorithm.
|
* reco algorithm.
|
||||||
*
|
*
|
||||||
* TODO: Call DiffView and LineNumbering Service here.
|
* TODO: Call DiffView Service here.
|
||||||
*
|
*
|
||||||
* Can be called from detail view and exporter
|
* Can be called from detail view and exporter
|
||||||
* @param id Motion ID - will be pulled from the repository
|
* @param id Motion ID - will be pulled from the repository
|
||||||
* @param lnMode indicator for the line numbering mode
|
|
||||||
* @param crMode indicator for the change reco mode
|
* @param crMode indicator for the change reco mode
|
||||||
|
* @param lineLength the current line
|
||||||
|
* @param highlightLine the currently highlighted line (default: none)
|
||||||
*/
|
*/
|
||||||
public formatMotion(id: number, lnMode: number, crMode: number): string {
|
public formatMotion(id: number, crMode: number, lineLength: number, highlightLine?: number): SafeHtml {
|
||||||
const targetMotion = this.getViewModel(id);
|
const targetMotion = this.getViewModel(id);
|
||||||
|
|
||||||
if (targetMotion && targetMotion.text) {
|
if (targetMotion && targetMotion.text) {
|
||||||
let motionText = targetMotion.text;
|
let motionText = targetMotion.text;
|
||||||
|
motionText = this.lineNumbering.insertLineNumbers(motionText, lineLength, highlightLine);
|
||||||
// TODO : Use Line numbering service here
|
|
||||||
switch (lnMode) {
|
|
||||||
case 0: // no line numbers
|
|
||||||
break;
|
|
||||||
case 1: // line number inside
|
|
||||||
motionText = 'Get line numbers outside';
|
|
||||||
break;
|
|
||||||
case 2: // line number outside
|
|
||||||
motionText = 'Get line numbers inside';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO : Use Diff Service here.
|
// TODO : Use Diff Service here.
|
||||||
// this will(currently) append the previous changes.
|
// this will(currently) append the previous changes.
|
||||||
@ -144,7 +143,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return motionText;
|
return this.sanitizer.bypassSecurityTrustHtml(motionText);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user