Compare commits
5 Commits
encoder-wi
...
v5.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0733f3a6c6 | ||
|
|
254adf15ab | ||
|
|
a07073a13c | ||
|
|
3facfec6b7 | ||
|
|
2b561c1856 |
@@ -1,3 +1,10 @@
|
||||
## [5.0.3](https://github.com/wickedest/Mergely/compare/v5.0.2...v5.0.3) (2023-08-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Updated docs with CDN example ([254adf1](https://github.com/wickedest/Mergely/commit/254adf15ab09fe9c5c3dff542d0a7f62ce2c9782))
|
||||
|
||||
## [5.0.2](https://github.com/wickedest/Mergely/compare/v5.0.1...v5.0.2) (2023-04-24)
|
||||
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -4,7 +4,7 @@
|
||||
|
||||
https://mergely.com
|
||||
|
||||
Mergely is a JavaScript component for differencing and merging files interactively in a browser (diff/merge). It provides a rich API that enables you to easily integrate Mergely into your existing web application. It is suitable for comparing text files online, for example, .txt, .html, .xml, .c, .cpp, .java, etc.
|
||||
Mergely is a JavaScript component for differencing and merging files interactively in a browser (diff/merge). It provides a rich API that enables you to easily integrate Mergely into your existing web application. It is suitable for comparing text files online, such as .txt, .html, .xml, .c, .cpp, .java, .js, etc.
|
||||
|
||||
Mergely has a JavaScript implementation of the Longest Common Subsequence (LCS) diff algorithm, and a customizable markup engine. It computes the diff within the browser.
|
||||
|
||||
@@ -36,11 +36,13 @@ rm -rf .git
|
||||
|
||||
### Usage via CDN
|
||||
|
||||
Unpack mergely.tgz into a folder, for example, `./lib`, and add the following to the `<head>` of your target HTML source file.
|
||||
Add the following to the `<head>` of your target HTML source file.
|
||||
|
||||
```html
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mergely/5.0.0/mergely.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mergely/5.0.0/mergely.css"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mergely/5.0.0/mergely.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mergely/5.0.0/mergely.css" />
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.css" />
|
||||
```
|
||||
|
||||
### Synchronous initialization
|
||||
@@ -93,8 +95,7 @@ Mergely will emit an `updated` event when the editor is first initialized, and e
|
||||
|<a name="ignorews"></a>ignorews|boolean|`false`|Ignores white-space.|
|
||||
|<a name="ignorecase"></a>ignorecase|boolean|`false`|Ignores case.|
|
||||
|<a name="ignoreaccents"></a>ignoreaccents|boolean|`false`|Ignores accented characters.|
|
||||
|<a name="inline"></a>inline|string|`chars`|The line-by-line (inline) type of diff. Valid values are: `none`, `chars`, `words`. When `none`, inline diff is disabled. When `chars` differientiation is done on a character-by-character basis. When `words` differentiation is done on a whitespace basis.|
|
||||
|<a name="lcs"></a>lcs|boolean|`true`|:warning: **Deprecated**, use [`inline`](#inline). Enables/disables LCS computation for paragraphs (char-by-char changes). Disabling can give a performance gain for large documents.|
|
||||
|<a name="lcs"></a>lcs|boolean|`true`|Enables/disables LCS computation for paragraphs (char-by-char changes). Disabling can give a performance gain for large documents.|
|
||||
|<a name="lhs"></a>lhs|boolean,`function handler(setValue)`|`null`|Sets the value of the editor on the left-hand side.|
|
||||
|<a name="license"></a>license|string|`lgpl`|The choice of license to use with Mergely. Valid values are: `lgpl`, `gpl`, `mpl` or `lgpl-separate-notice`, `gpl-separate-notice`, `mpl-separate-notice` (the license requirements are met in a separate notice file).|
|
||||
|<a name="line_numbers"></a>line_numbers|boolean|`true`|Enables/disables line numbers. Enabling line numbers will toggle the visibility of the line number margins.|
|
||||
|
||||
@@ -4,9 +4,15 @@ require('codemirror/addon/selection/mark-selection.js');
|
||||
require('codemirror/lib/codemirror.css');
|
||||
require('../src/mergely.css');
|
||||
|
||||
const lhs = `hello`;
|
||||
const lhs = `\
|
||||
the quick red fox
|
||||
jumped over the hairy dog
|
||||
`;
|
||||
|
||||
const rhs = `hello\ngoodbye`;
|
||||
const rhs = `\
|
||||
the quick brown fox
|
||||
jumped over the lazy dog
|
||||
`;
|
||||
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
@@ -16,7 +22,6 @@ document.onreadystatechange = function () {
|
||||
|
||||
const mergely = new Mergely('#compare', {
|
||||
license: 'lgpl',
|
||||
inline: 'words',
|
||||
lhs,
|
||||
rhs
|
||||
});
|
||||
|
||||
41
examples/cdn.html
Normal file
41
examples/cdn.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Mergely - Example full page editor</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1, IE=edge">
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
|
||||
<meta name="description" content="" />
|
||||
<meta name="keywords" content="mergely,diff,merge,compare" />
|
||||
<meta name="author" content="Jamie Peabody" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Mergely -->
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mergely/5.0.0/mergely.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mergely/5.0.0/mergely.css" />
|
||||
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.css" />
|
||||
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
#compare {
|
||||
height: inherit;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="compare"></div>
|
||||
|
||||
<script>
|
||||
const mergely = new Mergely('#compare');
|
||||
mergely.once('updated', () => {
|
||||
mergely.lhs('the quick red fox\njumped over the hairy dog');
|
||||
mergely.rhs('the quick brown fox\njumped over the lazy dog');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -304,6 +304,7 @@ dog
|
||||
and the postman
|
||||
`
|
||||
}];
|
||||
console.log(data.length);
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
const { lhs, rhs } = data[i];
|
||||
const darkModeOptions = i === 11 ? {
|
||||
|
||||
@@ -298,6 +298,7 @@
|
||||
rhs.style = 'color:initial';
|
||||
});
|
||||
jQuery('#search-text').on('keypress', (ev) => {
|
||||
console.log(ev.which)
|
||||
if (event.which === 13) {
|
||||
ev.preventDefault();
|
||||
jQuery('#search').click();
|
||||
|
||||
@@ -21,6 +21,9 @@ This example demonstrates the minimum amount of code required to use Mergely.
|
||||
<h1>Examples</h1>
|
||||
|
||||
<dl>
|
||||
<dt><a href="cdn.html">cdn.html</a></dt>
|
||||
<dd>Demonstrates how to use Mergely with CDN.</dd>
|
||||
|
||||
<dt><a href="editor.html">editor.html</a></dt>
|
||||
<dd>An example editor showcasing some of Mergely's API features.</dd>
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
"changelog" : {
|
||||
"commitTypes": [
|
||||
"docs",
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mergely",
|
||||
"version": "5.0.2",
|
||||
"version": "5.0.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mergely",
|
||||
"version": "5.0.2",
|
||||
"version": "5.0.3",
|
||||
"license": "(GPL-3.0 OR LGPL-3.0 OR MPL-1.1 OR SEE LICENSE IN LICENSE)",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.6",
|
||||
@@ -16564,9 +16564,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
|
||||
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.3.tgz",
|
||||
"integrity": "sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mergely",
|
||||
"version": "5.0.2",
|
||||
"version": "5.0.3",
|
||||
"description": "A javascript UI for diff/merge",
|
||||
"license": "(GPL-3.0 OR LGPL-3.0 OR MPL-1.1 OR SEE LICENSE IN LICENSE)",
|
||||
"author": {
|
||||
|
||||
@@ -941,6 +941,7 @@ CodeMirrorDiffView.prototype._markupLineChanges = function (changes) {
|
||||
for (let i = 0; i < changes.length; ++i) {
|
||||
const change = changes[i];
|
||||
const isCurrent = current_diff === i;
|
||||
const lineDiff = this.settings.lcs !== false;
|
||||
const lhsInView = this._isChangeInView('lhs', lhsvp, change);
|
||||
const rhsInView = this._isChangeInView('rhs', rhsvp, change);
|
||||
|
||||
@@ -951,7 +952,7 @@ CodeMirrorDiffView.prototype._markupLineChanges = function (changes) {
|
||||
|
||||
vdoc.addRender('lhs', change, i, {
|
||||
isCurrent,
|
||||
lineDiff: this.settings.inline !== 'none',
|
||||
lineDiff,
|
||||
// TODO: move out of loop
|
||||
getMergeHandler: (change, side, oside) => {
|
||||
return () => this._merge_change(change, side, oside);
|
||||
@@ -968,7 +969,7 @@ CodeMirrorDiffView.prototype._markupLineChanges = function (changes) {
|
||||
|
||||
vdoc.addRender('rhs', change, i, {
|
||||
isCurrent,
|
||||
lineDiff: this.settings.inline !== 'none',
|
||||
lineDiff,
|
||||
// TODO: move out of loop
|
||||
getMergeHandler: (change, side, oside) => {
|
||||
return () => this._merge_change(change, side, oside);
|
||||
@@ -978,14 +979,13 @@ CodeMirrorDiffView.prototype._markupLineChanges = function (changes) {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.settings.inline !== 'none'
|
||||
if (lineDiff
|
||||
&& (lhsInView || rhsInView)
|
||||
&& change.op === 'c') {
|
||||
vdoc.addInlineDiff(change, i, {
|
||||
ignoreaccents: this.settings.ignoreaccents,
|
||||
ignorews: this.settings.ignorews,
|
||||
ignorecase: this.settings.ignorecase,
|
||||
split: this.settings.inline,
|
||||
getText: (side, lineNum) => {
|
||||
if (side === 'lhs') {
|
||||
const text = led.getLine(lineNum);
|
||||
|
||||
180
src/diff.js
180
src/diff.js
@@ -1,52 +1,43 @@
|
||||
const Encoder = require('./encoder.js');
|
||||
|
||||
const SMS_TIMEOUT_SECONDS = 1.0;
|
||||
|
||||
function diff(lhs, rhs, opts) {
|
||||
function diff(lhs, rhs, options = {}) {
|
||||
const {
|
||||
ignorews = false,
|
||||
ignoreaccents = false,
|
||||
ignorecase = false,
|
||||
split = 'lines'
|
||||
} = opts || {};
|
||||
const options = {
|
||||
} = options;
|
||||
|
||||
this.codeify = new CodeifyText(lhs, rhs, {
|
||||
ignorews,
|
||||
ignoreaccents,
|
||||
ignorecase,
|
||||
split
|
||||
};
|
||||
|
||||
const encoder = new Encoder();
|
||||
const lhsCodes = encoder.encode(lhs, options);
|
||||
const rhsCodes = encoder.encode(rhs, options);
|
||||
const lhsCtx = {
|
||||
codes: lhsCodes.codes,
|
||||
length: lhsCodes.length,
|
||||
parts: lhsCodes.parts,
|
||||
});
|
||||
const lhs_ctx = {
|
||||
codes: this.codeify.getCodes('lhs'),
|
||||
modified: {}
|
||||
};
|
||||
const rhsCtx = {
|
||||
codes: rhsCodes.codes,
|
||||
length: rhsCodes.length,
|
||||
parts: rhsCodes.parts,
|
||||
const rhs_ctx = {
|
||||
codes: this.codeify.getCodes('rhs'),
|
||||
modified: {}
|
||||
};
|
||||
const vector_d = [];
|
||||
const vector_u = [];
|
||||
this._lcs(lhsCtx, 0, lhsCodes.length, rhsCtx, 0, rhsCodes.length, vector_u, vector_d);
|
||||
this._optimize(lhsCtx);
|
||||
this._optimize(rhsCtx);
|
||||
this.items = this._create_diffs(lhsCtx, rhsCtx, options);
|
||||
this.sides = {
|
||||
lhs: lhsCtx,
|
||||
rhs: rhsCtx
|
||||
};
|
||||
this._lcs(lhs_ctx, 0, lhs_ctx.codes.length, rhs_ctx, 0, rhs_ctx.codes.length, vector_u, vector_d);
|
||||
this._optimize(lhs_ctx);
|
||||
this._optimize(rhs_ctx);
|
||||
this.items = this._create_diffs(lhs_ctx, rhs_ctx);
|
||||
};
|
||||
|
||||
diff.prototype.changes = function() {
|
||||
return this.items;
|
||||
};
|
||||
|
||||
diff.prototype.getLines = function(side) {
|
||||
return this.codeify.getLines(side);
|
||||
};
|
||||
|
||||
diff.prototype.normal_form = function() {
|
||||
let nf = '';
|
||||
for (let index = 0; index < this.items.length; ++index) {
|
||||
@@ -66,8 +57,8 @@ diff.prototype.normal_form = function() {
|
||||
else rhs_str = (item.rhs_start + 1) + ',' + (item.rhs_start + item.rhs_inserted_count);
|
||||
nf += lhs_str + change + rhs_str + '\n';
|
||||
|
||||
const lhs_lines = this.sides.lhs.parts;
|
||||
const rhs_lines = this.sides.rhs.parts;
|
||||
const lhs_lines = this.getLines('lhs');
|
||||
const rhs_lines = this.getLines('rhs');
|
||||
if (rhs_lines && lhs_lines) {
|
||||
let i;
|
||||
// if rhs/lhs lines have been retained, output contextual diff
|
||||
@@ -111,7 +102,7 @@ diff.prototype._lcs = function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower
|
||||
|
||||
diff.prototype._sms = function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d) {
|
||||
const timeout = Date.now() + SMS_TIMEOUT_SECONDS * 1000;
|
||||
const max = lhs_ctx.length + rhs_ctx.length + 1;
|
||||
const max = lhs_ctx.codes.length + rhs_ctx.codes.length + 1;
|
||||
const kdown = lhs_lower - rhs_lower;
|
||||
const kup = lhs_upper - rhs_upper;
|
||||
const delta = (lhs_upper - lhs_lower) - (rhs_upper - rhs_lower);
|
||||
@@ -124,13 +115,12 @@ diff.prototype._sms = function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower
|
||||
const ret = { x:0, y:0 }
|
||||
let x;
|
||||
let y;
|
||||
let k;
|
||||
for (let d = 0; d <= maxd; ++d) {
|
||||
if (SMS_TIMEOUT_SECONDS && Date.now() > timeout) {
|
||||
// bail if taking too long
|
||||
return { x: lhs_lower, y: rhs_upper };
|
||||
}
|
||||
for (k = kdown - d; k <= kdown + d; k += 2) {
|
||||
for (let k = kdown - d; k <= kdown + d; k += 2) {
|
||||
if (k === kdown - d) {
|
||||
x = vector_d[ offset_down + k + 1 ];//down
|
||||
}
|
||||
@@ -188,15 +178,15 @@ diff.prototype._sms = function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower
|
||||
diff.prototype._optimize = function(ctx) {
|
||||
let start = 0;
|
||||
let end = 0;
|
||||
while (start < ctx.length) {
|
||||
while ((start < ctx.length) && (ctx.modified[start] === undefined || ctx.modified[start] === false)) {
|
||||
while (start < ctx.codes.length) {
|
||||
while ((start < ctx.codes.length) && (ctx.modified[start] === undefined || ctx.modified[start] === false)) {
|
||||
start++;
|
||||
}
|
||||
end = start;
|
||||
while ((end < ctx.length) && (ctx.modified[end] === true)) {
|
||||
while ((end < ctx.codes.length) && (ctx.modified[end] === true)) {
|
||||
end++;
|
||||
}
|
||||
if ((end < ctx.length) && (ctx.codes[start] === ctx.codes[end])) {
|
||||
if ((end < ctx.codes.length) && (ctx.codes[start] === ctx.codes[end])) {
|
||||
ctx.modified[start] = false;
|
||||
ctx.modified[end] = true;
|
||||
}
|
||||
@@ -206,16 +196,16 @@ diff.prototype._optimize = function(ctx) {
|
||||
}
|
||||
};
|
||||
|
||||
diff.prototype._create_diffs = function(lhs_ctx, rhs_ctx, options) {
|
||||
diff.prototype._create_diffs = function(lhs_ctx, rhs_ctx) {
|
||||
const items = [];
|
||||
let lhs_start = 0;
|
||||
let rhs_start = 0;
|
||||
let lhs_line = 0;
|
||||
let rhs_line = 0;
|
||||
|
||||
while (lhs_line < lhs_ctx.length || rhs_line < rhs_ctx.length) {
|
||||
if ((lhs_line < lhs_ctx.length) && (!lhs_ctx.modified[lhs_line])
|
||||
&& (rhs_line < rhs_ctx.length) && (!rhs_ctx.modified[rhs_line])) {
|
||||
while (lhs_line < lhs_ctx.codes.length || rhs_line < rhs_ctx.codes.length) {
|
||||
if ((lhs_line < lhs_ctx.codes.length) && (!lhs_ctx.modified[lhs_line])
|
||||
&& (rhs_line < rhs_ctx.codes.length) && (!rhs_ctx.modified[rhs_line])) {
|
||||
// equal lines
|
||||
lhs_line++;
|
||||
rhs_line++;
|
||||
@@ -225,50 +215,19 @@ diff.prototype._create_diffs = function(lhs_ctx, rhs_ctx, options) {
|
||||
lhs_start = lhs_line;
|
||||
rhs_start = rhs_line;
|
||||
|
||||
while (lhs_line < lhs_ctx.length && (rhs_line >= rhs_ctx.length || lhs_ctx.modified[lhs_line]))
|
||||
while (lhs_line < lhs_ctx.codes.length && (rhs_line >= rhs_ctx.codes.length || lhs_ctx.modified[lhs_line]))
|
||||
lhs_line++;
|
||||
|
||||
while (rhs_line < rhs_ctx.length && (lhs_line >= lhs_ctx.length || rhs_ctx.modified[rhs_line]))
|
||||
while (rhs_line < rhs_ctx.codes.length && (lhs_line >= lhs_ctx.codes.length || rhs_ctx.modified[rhs_line]))
|
||||
rhs_line++;
|
||||
|
||||
if ((lhs_start < lhs_line) || (rhs_start < rhs_line)) {
|
||||
// store a new difference-item
|
||||
let deleted_count;
|
||||
let inserted_count;
|
||||
let lhs_start = lhs_line;
|
||||
let rhs_start = rhs_line;
|
||||
if (options.split === 'lines') {
|
||||
lhs_start = lhs_line;
|
||||
rhs_start = rhs_line;
|
||||
deleted_count = lhs_line - lhs_start;
|
||||
inserted_count = rhs_line - rhs_start;
|
||||
} else {
|
||||
const ditem_lhs_start = (lhs_start >= lhs_ctx.length)
|
||||
? lhs_ctx.length
|
||||
: lhs_ctx.parts[lhs_start].from;
|
||||
const ditem_rhs_start = (rhs_start >= rhs_ctx.length)
|
||||
? rhs_ctx.length
|
||||
: rhs_ctx.parts[rhs_start].from;
|
||||
|
||||
const ditem_lhs_end = (lhs_line >= lhs_ctx.length)
|
||||
? lhs_ctx.length
|
||||
: lhs_ctx.parts[lhs_line].from;
|
||||
const ditem_rhs_end = (rhs_line >= rhs_ctx.length)
|
||||
? rhs_ctx.length
|
||||
: rhs_ctx.parts[rhs_line];
|
||||
// const delim_len = (options.split === 'words') ? 1 : 0;
|
||||
// const ditemLhs
|
||||
|
||||
deleted_count = ditem_lhs_end - ditem_lhs_start;
|
||||
inserted_count = ditem_rhs_end - ditem_rhs_start;
|
||||
lhs_start = ditem_lhs_start;
|
||||
rhs_start = ditem_rhs_start;
|
||||
}
|
||||
items.push({
|
||||
lhs_start: lhs_start,
|
||||
rhs_start: rhs_start,
|
||||
lhs_deleted_count: deleted_count,
|
||||
rhs_inserted_count: inserted_count
|
||||
lhs_deleted_count: lhs_line - lhs_start,
|
||||
rhs_inserted_count: rhs_line - rhs_start
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -276,4 +235,77 @@ diff.prototype._create_diffs = function(lhs_ctx, rhs_ctx, options) {
|
||||
return items;
|
||||
};
|
||||
|
||||
function CodeifyText(lhs, rhs, options) {
|
||||
this._max_code = 0;
|
||||
this._diff_codes = {};
|
||||
this.ctxs = {};
|
||||
this.options = options;
|
||||
this.options.split = this.options.split || 'lines';
|
||||
|
||||
if (typeof lhs === 'string') {
|
||||
if (this.options.split === 'chars') {
|
||||
this.lhs = lhs.split('');
|
||||
} else if (this.options.split === 'words') {
|
||||
this.lhs = lhs.split(/\s/);
|
||||
} else if (this.options.split === 'lines') {
|
||||
this.lhs = lhs.split('\n');
|
||||
}
|
||||
} else {
|
||||
this.lhs = lhs;
|
||||
}
|
||||
if (typeof rhs === 'string') {
|
||||
if (this.options.split === 'chars') {
|
||||
this.rhs = rhs.split('');
|
||||
} else if (this.options.split === 'words') {
|
||||
this.rhs = rhs.split(/\s/);
|
||||
} else if (this.options.split === 'lines') {
|
||||
this.rhs = rhs.split('\n');
|
||||
}
|
||||
} else {
|
||||
this.rhs = rhs;
|
||||
}
|
||||
};
|
||||
|
||||
CodeifyText.prototype.getCodes = function(side) {
|
||||
if (!this.ctxs.hasOwnProperty(side)) {
|
||||
var ctx = this._diff_ctx(this[side]);
|
||||
this.ctxs[side] = ctx;
|
||||
ctx.codes.length = Object.keys(ctx.codes).length;
|
||||
}
|
||||
return this.ctxs[side].codes;
|
||||
}
|
||||
|
||||
CodeifyText.prototype.getLines = function(side) {
|
||||
return this.ctxs[side].lines;
|
||||
}
|
||||
|
||||
CodeifyText.prototype._diff_ctx = function(lines) {
|
||||
var ctx = {i: 0, codes: {}, lines: lines};
|
||||
this._codeify(lines, ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
CodeifyText.prototype._codeify = function(lines, ctx) {
|
||||
for (let i = 0; i < lines.length; ++i) {
|
||||
let line = lines[i];
|
||||
if (this.options.ignorews) {
|
||||
line = line.replace(/\s+/g, '');
|
||||
}
|
||||
if (this.options.ignorecase) {
|
||||
line = line.toLowerCase();
|
||||
}
|
||||
if (this.options.ignoreaccents) {
|
||||
line = line.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||||
}
|
||||
const aCode = this._diff_codes[line];
|
||||
if (aCode !== undefined) {
|
||||
ctx.codes[i] = aCode;
|
||||
} else {
|
||||
++this._max_code;
|
||||
this._diff_codes[line] = this._max_code;
|
||||
ctx.codes[i] = this._max_code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = diff;
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
class Encoder {
|
||||
constructor() {
|
||||
this._maxCode = 0;
|
||||
this._codes = {};
|
||||
}
|
||||
|
||||
encode(text, options) {
|
||||
let exp;
|
||||
let fudge = 0;
|
||||
if (options.split === 'chars') {
|
||||
exp = /./g;
|
||||
fudge = 1;
|
||||
} else if (options.split === 'words') {
|
||||
exp = /\s+/g;
|
||||
} else {
|
||||
exp = /\n/g;
|
||||
}
|
||||
let match;
|
||||
let p0 = -1;
|
||||
const parts = [];
|
||||
while ((match = exp.exec(text)) !== null) {
|
||||
const from = (options.split === 'lines') ? parts.length : p0 + 1;
|
||||
const to = (options.split === 'lines') ? parts.length + 1 : match.index + fudge;
|
||||
const item = {
|
||||
from,
|
||||
to,
|
||||
text: text.substr(p0 + 1, match.index - p0 - 1 + fudge)
|
||||
};
|
||||
parts.push(item);
|
||||
p0 = match.index;
|
||||
}
|
||||
const from = (options.split === 'lines') ? parts.length : p0 + 1;
|
||||
const to = (options.split === 'lines') ? parts.length + 1 : text.length;
|
||||
parts.push({
|
||||
from,
|
||||
to,
|
||||
text: text.substr(p0 + 1)
|
||||
});
|
||||
const hash = this._hash(parts, options)
|
||||
return {
|
||||
codes: hash.codes,
|
||||
parts: hash.parts,
|
||||
length: Object.keys(hash.codes).length
|
||||
};
|
||||
}
|
||||
|
||||
_hash(parts, options) {
|
||||
const codes = {};
|
||||
let i = 0;
|
||||
for (const part of parts) {
|
||||
let text = part.text;
|
||||
|
||||
if (options.ignorews) {
|
||||
text = text.replace(/\s+/g, '');
|
||||
}
|
||||
if (options.ignorecase) {
|
||||
text = text.toLowerCase();
|
||||
}
|
||||
if (options.ignoreaccents) {
|
||||
text = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||||
}
|
||||
const code = this._codes[text];
|
||||
if (code !== undefined) {
|
||||
codes[i] = code;
|
||||
} else {
|
||||
++this._maxCode;
|
||||
this._codes[text] = this._maxCode;
|
||||
codes[i] = this._maxCode;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return {
|
||||
codes,
|
||||
parts
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Encoder;
|
||||
@@ -12,7 +12,6 @@ const defaultOptions = {
|
||||
wrap_lines: false,
|
||||
line_numbers: true,
|
||||
lcs: true,
|
||||
inline: 'chars',
|
||||
sidebar: true,
|
||||
viewport: false,
|
||||
ignorews: false,
|
||||
@@ -120,15 +119,13 @@ class Mergely {
|
||||
const colors = dom.getColors(this.el);
|
||||
this._options = {
|
||||
...defaultOptions,//lgpl
|
||||
...{
|
||||
// default inline based off `lcs`
|
||||
inline: options && options.lcs === false ? 'none' : 'chars'
|
||||
},
|
||||
...this._initOptions,
|
||||
...options,//lgpl-separate-notice
|
||||
...options//lgpl-separate-notice
|
||||
};
|
||||
this._viewOptions = {
|
||||
...this._options,
|
||||
...defaultOptions,
|
||||
...this._initOptions,
|
||||
...options,
|
||||
_colors: colors
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ class VDoc {
|
||||
this._setRenderedChange(side, changeId);
|
||||
}
|
||||
|
||||
addInlineDiff(change, changeId, { getText, ignorews, ignoreaccents, ignorecase, split = 'chars' }) {
|
||||
addInlineDiff(change, changeId, { getText, ignorews, ignoreaccents, ignorecase }) {
|
||||
if (this.options._debug) {
|
||||
trace('vdoc#addInlineDiff', changeId, change);
|
||||
}
|
||||
@@ -152,7 +152,7 @@ class VDoc {
|
||||
ignoreaccents,
|
||||
ignorews,
|
||||
ignorecase,
|
||||
split
|
||||
split: 'chars'
|
||||
});
|
||||
for (const change of results.changes()) {
|
||||
const {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
const diff = require('../src/diff');
|
||||
|
||||
describe('diff', () => {
|
||||
it('should insert one line when lhs is empty and rhs has no line ending', () => {
|
||||
const _diff = new diff('', 'hello', { split: 'lines' });
|
||||
const changes = _diff.changes();
|
||||
console.log(changes);
|
||||
// with lhs_start at 1, the insert is at the end
|
||||
expect(changes).to.deep.equal([{
|
||||
lhs_start: 1,
|
||||
rhs_start: 1,
|
||||
lhs_deleted_count: 0,
|
||||
rhs_inserted_count: 0
|
||||
}]);
|
||||
});
|
||||
});
|
||||
@@ -189,6 +189,7 @@ describe('markup', () => {
|
||||
expect(editor.querySelectorAll('.mergely.rhs')).to.have.length(0);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Changed lines (lhs)',
|
||||
lhs: 'the quick red fox\njumped over the hairy dog',
|
||||
@@ -232,46 +233,6 @@ describe('markup', () => {
|
||||
expect(rhs_spans[1].innerText).to.equal('h');
|
||||
expect(rhs_spans[2].innerText).to.equal('ir');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Changed lines with inline words (lhs)',
|
||||
lhs: 'the quick red fox\njumped over the hairy dog',
|
||||
rhs: 'the quick brown fox\njumped over the lazy dog',
|
||||
options: { inline: 'words' },
|
||||
check: (editor) => {
|
||||
expect(editor.querySelectorAll(LHS_CHANGE_START + '.cid-0')).to.have.length(1);
|
||||
expect(editor.querySelectorAll(LHS_CHANGE_END + '.cid-0')).to.have.length(1);
|
||||
expect(editor.querySelectorAll(RHS_CHANGE_START + '.cid-0')).to.have.length(1);
|
||||
expect(editor.querySelectorAll(RHS_CHANGE_END + '.cid-0')).to.have.length(1);
|
||||
const lhs_spans = editor.querySelectorAll(LHS_INLINE_TEXT + '.cid-0');
|
||||
expect(lhs_spans).to.have.length(2);
|
||||
expect(lhs_spans[0].innerText).to.equal('red');
|
||||
expect(lhs_spans[1].innerText).to.equal('hairy');
|
||||
const rhs_spans = editor.querySelectorAll(RHS_INLINE_TEXT + '.cid-0');
|
||||
expect(rhs_spans).to.have.length(2);
|
||||
expect(rhs_spans[0].innerText).to.equal('brown');
|
||||
expect(rhs_spans[1].innerText).to.equal('lazy');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Changed lines (rhs)',
|
||||
lhs: 'the quick brown fox\njumped over the lazy dog',
|
||||
rhs: 'the quick red fox\njumped over the hairy dog',
|
||||
options: { inline: 'words' },
|
||||
check: (editor) => {
|
||||
expect(editor.querySelectorAll(LHS_CHANGE_START + '.cid-0')).to.have.length(1);
|
||||
expect(editor.querySelectorAll(LHS_CHANGE_END + '.cid-0')).to.have.length(1);
|
||||
expect(editor.querySelectorAll(RHS_CHANGE_START + '.cid-0')).to.have.length(1);
|
||||
expect(editor.querySelectorAll(RHS_CHANGE_END + '.cid-0')).to.have.length(1);
|
||||
const lhs_spans = editor.querySelectorAll(LHS_INLINE_TEXT + '.cid-0');
|
||||
expect(lhs_spans).to.have.length(2);
|
||||
expect(lhs_spans[0].innerText).to.equal('brown');
|
||||
expect(lhs_spans[1].innerText).to.equal('lazy');
|
||||
const rhs_spans = editor.querySelectorAll(RHS_INLINE_TEXT + '.cid-0');
|
||||
expect(rhs_spans).to.have.length(2);
|
||||
expect(rhs_spans[0].innerText).to.equal('red');
|
||||
expect(rhs_spans[1].innerText).to.equal('hairy');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -285,9 +246,8 @@ describe('markup', () => {
|
||||
license: 'lgpl-separate-notice',
|
||||
change_timeout: 0,
|
||||
_debug: debug,
|
||||
lhs: opt.lhs,
|
||||
rhs: opt.rhs,
|
||||
...opt.options
|
||||
lhs: (setValue) => setValue(opt.lhs),
|
||||
rhs: (setValue) => setValue(opt.rhs)
|
||||
});
|
||||
const test = () => {
|
||||
try {
|
||||
|
||||
@@ -8,7 +8,6 @@ const defaultOptions = {
|
||||
rhs_margin: 'right',
|
||||
wrap_lines: false,
|
||||
line_numbers: true,
|
||||
inline: 'chars',
|
||||
lcs: true,
|
||||
sidebar: true,
|
||||
viewport: false,
|
||||
|
||||
Reference in New Issue
Block a user