forked from lxm_front/Mergely
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8794bfb055 | ||
|
|
6643f48b14 | ||
|
|
481390f732 | ||
|
|
534bedf355 | ||
|
|
f3e90b7588 | ||
|
|
650daaff63 |
2
.github/workflows/run-tests.yaml
vendored
2
.github/workflows/run-tests.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x] #, 12.x, 14.x, 15.x
|
||||
node-version: [16.x] #, 12.x, 14.x, 15.x
|
||||
|
||||
name: Node.js ${{ matrix.node-version }}
|
||||
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### 4.3.9 (2022-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **#161:** Fixed issue with `options.ignorews` ([#162](https://github.com/wickedest/Mergely/issues/162)) ([650daaf](https://github.com/wickedest/Mergely/commit/650daaff63503f6426f64b12d8b33cfca6ef7185))
|
||||
|
||||
### 4.3.8 (2021-11-21)
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ npm run test
|
||||
|
||||
## Conventional commits
|
||||
|
||||
Mergely is using [conventional commits](https://www.conventionalcommits.org/en/v1.0.0).
|
||||
Mergely is using [conventional commits](https://www.conventionalcommits.org/en/v1.0.0), and [standard-version](https://www.npmjs.com/package/standard-version).
|
||||
|
||||
To create a commit, run:
|
||||
```bash
|
||||
|
||||
102
examples/app-styles.html
Normal file
102
examples/app-styles.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<!--
|
||||
This example demonstrates the minimum amount of code required to use Mergely.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Mergely - Application example</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="" />
|
||||
<meta name="author" content="Jamie Peabody" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.column-layout {
|
||||
display: flex;
|
||||
}
|
||||
.column-layout > * {
|
||||
flex: 1;
|
||||
height: 90px;
|
||||
}
|
||||
.full-width-layout > * {
|
||||
height: 120px;
|
||||
}
|
||||
em {
|
||||
font-style: italic;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<em>Added and removed from the start and end.</em>
|
||||
<div class="column-layout">
|
||||
<div id="mergely0"></div>
|
||||
<div id="mergely1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<em>Added and removed from the middle.</em>
|
||||
<div class="column-layout">
|
||||
<div id="mergely2"></div>
|
||||
<div id="mergely3"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<em>One line with the other empty.</em>
|
||||
<div class="column-layout">
|
||||
<div id="mergely4"></div>
|
||||
<div id="mergely5"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<em>Multiple lines with the other empty.</em>
|
||||
<div class="column-layout">
|
||||
<div id="mergely6"></div>
|
||||
<div id="mergely7"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<em>Added and removed from the end.</em>
|
||||
<div class="column-layout">
|
||||
<div id="mergely8"></div>
|
||||
<div id="mergely9"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<em>No changes.</em>
|
||||
<div class="column-layout">
|
||||
<div id="mergely10"></div>
|
||||
<div id="mergely11"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<em>Changed lines</em>
|
||||
<div class="column-layout">
|
||||
<div id="mergely12"></div>
|
||||
<div id="mergely13"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<em>Options</em>
|
||||
<div class="full-width-layout">
|
||||
<div id="mergely14"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
113
examples/app-styles.js
Normal file
113
examples/app-styles.js
Normal file
@@ -0,0 +1,113 @@
|
||||
require('codemirror/addon/selection/mark-selection.js');
|
||||
require('codemirror/lib/codemirror.css');
|
||||
require('../src/mergely.css');
|
||||
|
||||
|
||||
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
if (document.readyState !== 'complete') {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
const doc = new Mergely('#compare', {
|
||||
license: 'lgpl',
|
||||
// ignorews: true,
|
||||
// wrap_lines: false,
|
||||
// change_timeout: 50,
|
||||
// viewport: true,
|
||||
// cmsettings: {
|
||||
// readOnly: true
|
||||
// },
|
||||
line_numbers: false,
|
||||
lhs: (setValue) => setValue(lhs),
|
||||
rhs: (setValue) => setValue(rhs)
|
||||
});
|
||||
|
||||
// On init, scroll to first diff
|
||||
doc.once('updated', () => {
|
||||
doc.scrollToDiff('next');
|
||||
});
|
||||
*/
|
||||
|
||||
new Mergely('#mergely0', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog\n'),
|
||||
rhs: (setValue) => setValue('\nthe quick red fox\njumped over the hairy dog\n')
|
||||
});
|
||||
new Mergely('#mergely1', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('\nthe quick red fox\njumped over the hairy dog\n'),
|
||||
rhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog\n')
|
||||
});
|
||||
|
||||
new Mergely('#mergely2', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('\nthe quick red fox\njumped over the hairy dog\n\n'),
|
||||
rhs: (setValue) => setValue('\n\nthe quick brown fox\njumped over the lazy dog\n')
|
||||
});
|
||||
new Mergely('#mergely3', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('\nthe quick brown fox\njumped over the lazy dog\n\n'),
|
||||
rhs: (setValue) => setValue('\n\nthe quick red fox\njumped over the hairy dog\n')
|
||||
});
|
||||
|
||||
new Mergely('#mergely4', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('the quick brown fox\n'),
|
||||
});
|
||||
new Mergely('#mergely5', {
|
||||
license: 'lgpl-separate-notice',
|
||||
rhs: (setValue) => setValue('the quick brown fox\n')
|
||||
});
|
||||
|
||||
new Mergely('#mergely6', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog\n'),
|
||||
});
|
||||
new Mergely('#mergely7', {
|
||||
license: 'lgpl-separate-notice',
|
||||
rhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog\n')
|
||||
});
|
||||
|
||||
new Mergely('#mergely8', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog'),
|
||||
rhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog\n\nand the fence')
|
||||
});
|
||||
new Mergely('#mergely9', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog\n\nand the fence'),
|
||||
rhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog')
|
||||
});
|
||||
|
||||
new Mergely('#mergely10', {
|
||||
license: 'lgpl-separate-notice',
|
||||
});
|
||||
new Mergely('#mergely11', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog'),
|
||||
rhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog')
|
||||
});
|
||||
|
||||
new Mergely('#mergely12', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('the quick red fox\njumped over the hairy dog'),
|
||||
rhs: (setValue) => setValue('the quick brown fox\njumped over the lazy dog')
|
||||
});
|
||||
new Mergely('#mergely13', {
|
||||
license: 'lgpl-separate-notice',
|
||||
lhs: (setValue) => setValue('\nthe quick red fox\njumped over the hairy dog'),
|
||||
rhs: (setValue) => setValue('\nthe quick brown fox\njumped over the lazy dog')
|
||||
});
|
||||
|
||||
new Mergely('#mergely14', {
|
||||
license: 'lgpl-separate-notice',
|
||||
ignorecase: true,
|
||||
ignorews: true,
|
||||
ignoreaccents: true,
|
||||
lhs: (setValue) => setValue('ignore ws\n\nignore CASE\n\nignore áccents\n'),
|
||||
rhs: (setValue) => setValue('ignore\tws\n\nignore case\n\nignore accents\n')
|
||||
});
|
||||
};
|
||||
@@ -2,8 +2,6 @@ require('codemirror/addon/selection/mark-selection.js');
|
||||
require('codemirror/lib/codemirror.css');
|
||||
require('../src/mergely.css');
|
||||
|
||||
const macbeth = require('../test/data/macbeth');
|
||||
|
||||
const lhs = `\
|
||||
the quick red fox
|
||||
jumped over the hairy dog
|
||||
@@ -14,6 +12,7 @@ the quick brown fox
|
||||
jumped over the lazy dog
|
||||
`;
|
||||
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
if (document.readyState !== 'complete') {
|
||||
return;
|
||||
@@ -21,14 +20,6 @@ document.onreadystatechange = function () {
|
||||
|
||||
const doc = new Mergely('#compare', {
|
||||
license: 'lgpl',
|
||||
// ignorews: true,
|
||||
// wrap_lines: false,
|
||||
// change_timeout: 50,
|
||||
// viewport: true,
|
||||
// cmsettings: {
|
||||
// readOnly: true
|
||||
// },
|
||||
line_numbers: false,
|
||||
lhs: (setValue) => setValue(lhs),
|
||||
rhs: (setValue) => setValue(rhs)
|
||||
});
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"build",
|
||||
"chore",
|
||||
"ci",
|
||||
"feat",
|
||||
"fix",
|
||||
"docs",
|
||||
"feat",
|
||||
"fix",
|
||||
|
||||
@@ -56,14 +56,15 @@
|
||||
"style-loader": "^3.3.1",
|
||||
"webpack": "^5.66.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.7.3"
|
||||
"webpack-dev-server": "^4.7.3",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run test && npm run build:dist",
|
||||
"build:dist": "rm -rf lib && webpack --config ./webpack.prod.js",
|
||||
"start": "webpack serve --config webpack.dev.js",
|
||||
"examples": "http-server ./ -p 3000 -o examples/index.html",
|
||||
"debug": "karma start --timeout=28000 --browsers=Chrome --single-run=false",
|
||||
"debug": "karma start --timeout=28000 --no-browsers --single-run=false",
|
||||
"test": "karma start",
|
||||
"release": "standard-version -a"
|
||||
}
|
||||
|
||||
@@ -3,16 +3,24 @@ require('codemirror/addon/selection/mark-selection.js');
|
||||
|
||||
const diff = require('./diff');
|
||||
const DiffParser = require('./diff-parser');
|
||||
const LCS = require('./lcs');
|
||||
const VDoc = require('./vdoc');
|
||||
const { default: DiffWorker } = require('./diff.worker.js');
|
||||
|
||||
|
||||
/**
|
||||
TODO:
|
||||
Update the README.md for new syntax.
|
||||
Linting
|
||||
Unbind loads of stuff
|
||||
Test: search
|
||||
Check: read-only modes
|
||||
|
||||
CHANGES:
|
||||
|
||||
BREAKING:
|
||||
Removed dependency on `jQuery`.
|
||||
Added `.mergely-editor` to the DOM to scope all the CSS changes.
|
||||
CSS now prefixes `.mergely-editor`.
|
||||
CSS now prefixes `.mergely-editor` and styles have changed significantly.
|
||||
Current active change gutter line number style changed from `.CodeMirror-linenumber` to `.CodeMirror-gutter-background`.
|
||||
Removed support for jquery-ui merge buttons.
|
||||
API switched from jQuery-style to object methods.
|
||||
@@ -25,7 +33,7 @@ Removed `options.width`
|
||||
Removed `options.height`
|
||||
Removed `options.resized`
|
||||
Removed function `mergely.update`
|
||||
Remove styles `.mergely-resizer`, `.mergely-full-screen-0`, and `.mergely-full-screen-8`.
|
||||
Remove styles `.mergely-resizer`, `.mergely-full-screen-0`, and `.mergely-full-screen-8`. The mergely container now requires an explicit height. Child elements are added to it.
|
||||
Default for `options.change_timeout` changed to 50.
|
||||
No longer necessary to separately require codemirror/addon/search/searchcursor
|
||||
No longer necessary to separately require codemirror/addon/selection/mark-selection
|
||||
@@ -47,11 +55,6 @@ Fixed performance issue with large sections of deleted/added text
|
||||
Fixed issue where initial render scrolled to first change, showing it at the bottom (as opposed to middle as expected)
|
||||
Fixed issue where line-diffs failed to diff non-alphanumeric characters
|
||||
|
||||
TODO:
|
||||
Linting
|
||||
Unbind loads of stuff
|
||||
Test: search
|
||||
Check: read-only modes
|
||||
*/
|
||||
|
||||
const NOTICES = [
|
||||
@@ -254,23 +257,20 @@ CodeMirrorDiffView.prototype._setOptions = function(opts) {
|
||||
...this.settings,
|
||||
...opts
|
||||
};
|
||||
if (this.settings.hasOwnProperty('sidebar')) {
|
||||
// dynamically enable sidebars
|
||||
if (this.settings.sidebar) {
|
||||
const divs = document.querySelectorAll(`#${this.id} .mergely-margin`);
|
||||
for (const div of divs) {
|
||||
div.style.visibility = 'visible';
|
||||
}
|
||||
}
|
||||
else {
|
||||
const divs = document.querySelectorAll(`#${this.id} .mergely-margin`);
|
||||
for (const div of divs) {
|
||||
div.style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
if (this.settings._debug) {
|
||||
trace('api#_setOptions', opts);
|
||||
}
|
||||
|
||||
// if options set after init
|
||||
if (this.editor) {
|
||||
if (opts.hasOwnProperty('sidebar')) {
|
||||
const divs = document.querySelectorAll(`#${this.id} .mergely-margin`);
|
||||
const visible = !!opts.sidebar;
|
||||
for (const div of divs) {
|
||||
div.style.visibility = visible ? 'visible' : 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
const le = this.editor.lhs;
|
||||
const re = this.editor.rhs;
|
||||
if (opts.hasOwnProperty('wrap_lines')) {
|
||||
@@ -502,11 +502,8 @@ CodeMirrorDiffView.prototype.bind = function(container) {
|
||||
el.append(canvasRhs);
|
||||
}
|
||||
if (!this.settings.sidebar) {
|
||||
// it would be better if this just used this.options()
|
||||
const divs = document.querySelectorAll(`#${this.id} .mergely-margin`);
|
||||
for (const div of divs) {
|
||||
div.style.visibility = 'hidden';
|
||||
}
|
||||
canvasLhs.style.visibility = 'hidden';
|
||||
canvasRhs.style.visibility = 'hidden';
|
||||
}
|
||||
container.append(el);
|
||||
|
||||
@@ -983,8 +980,8 @@ CodeMirrorDiffView.prototype._diff = function() {
|
||||
if (this.settings._debug) {
|
||||
trace('change#_diff creating diff worker');
|
||||
}
|
||||
this._diffWorker
|
||||
= new Worker(new URL('./diff-worker.js', import.meta.url));
|
||||
this._diffWorker = new DiffWorker();
|
||||
|
||||
this._diffWorker.onerror = (ev) => {
|
||||
console.error('Unexpected error with web worker', ev);
|
||||
}
|
||||
@@ -1045,6 +1042,11 @@ CodeMirrorDiffView.prototype._getViewportSide = function(side) {
|
||||
};
|
||||
|
||||
CodeMirrorDiffView.prototype._isChangeInView = function(side, vp, change) {
|
||||
if (change[`${side}-line-from`] < 0 || change[`${side}-line-to`]) {
|
||||
// handle case where the diff is "empty" - always in view
|
||||
return true;
|
||||
}
|
||||
|
||||
return (change[`${side}-line-from`] >= vp.from && change[`${side}-line-from`] <= vp.to) ||
|
||||
(change[`${side}-line-to`] >= vp.from && change[`${side}-line-to`] <= vp.to) ||
|
||||
(vp.from >= change[`${side}-line-from`] && vp.to <= change[`${side}-line-to`]);
|
||||
|
||||
175
src/mergely.css
175
src/mergely.css
@@ -12,61 +12,6 @@
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.a.rhs.start {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.a.lhs.start.end {
|
||||
border-top-width: 0px;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.a.lhs.start.end,
|
||||
.mergely-editor .mergely.a.rhs.end {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.d.lhs.end,
|
||||
.mergely-editor .mergely.d.rhs.start.end {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.d.rhs.start.end.first {
|
||||
border-bottom-width: 0;
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.d.lhs.start {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.c.lhs.start,
|
||||
.mergely-editor .mergely.c.rhs.start {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.c.lhs.end,
|
||||
.mergely-editor .mergely.c.rhs.end {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.current.start {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.mergely-editor .mergely.current.end {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.mergely-editor .CodeMirror-linenumber {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -107,7 +52,24 @@
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* common stles */
|
||||
.mergely-editor .mergely.CodeMirror-linebackground {
|
||||
border-style: solid;
|
||||
border-width: 0px;
|
||||
}
|
||||
.mergely-editor .mergely.start.CodeMirror-linebackground {
|
||||
border-top-width: 1px;
|
||||
}
|
||||
.mergely-editor .mergely.end.CodeMirror-linebackground {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
.mergely-editor .mergely.no-end.CodeMirror-linebackground {
|
||||
border-bottom-width: 0px;
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------- */
|
||||
|
||||
.mergely-editor .CodeMirror-code {
|
||||
color: #717171;
|
||||
}
|
||||
@@ -130,86 +92,49 @@
|
||||
.mergely-editor .current .merge-button:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.mergely-editor .mergely.a {
|
||||
border-color: #a3d1ff;
|
||||
|
||||
/* changed styles */
|
||||
.mergely-editor .mergely.c.CodeMirror-linebackground {
|
||||
border-color: #a3a3a3;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
.mergely-editor .mergely.a.rhs {
|
||||
background-color: #eff7ff;
|
||||
color: #0000ff;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.mergely-editor .mergely.d {
|
||||
|
||||
/* deleted from lhs styles */
|
||||
.mergely-editor .mergely.lhs.no-start.CodeMirror-linebackground,
|
||||
.mergely-editor .mergely.lhs.no-end.CodeMirror-linebackground,
|
||||
.mergely-editor .mergely.lhs.d.CodeMirror-linebackground {
|
||||
border-color: #ff7f7f;
|
||||
}
|
||||
.mergely-editor .mergely.d.lhs {
|
||||
.mergely-editor .mergely.lhs.d.CodeMirror-linebackground {
|
||||
background-color: #ffe9e9;
|
||||
}
|
||||
.mergely-editor .mergely.c {
|
||||
border-color: #a3a3a3;
|
||||
}
|
||||
.mergely-editor .mergely.ch.ind.lhs,
|
||||
.mergely-editor .mergely.ch.d.lhs {
|
||||
text-decoration: line-through;
|
||||
color: #ff0000;
|
||||
background-color: transparent;
|
||||
color: #ff3e3e;
|
||||
}
|
||||
.mergely-editor .mergely.current {
|
||||
border-color: #000;
|
||||
.mergely-editor .mergely.ch.ind.lhs {
|
||||
background-color: #ffdbdb;
|
||||
color: #ff3e3e;
|
||||
}
|
||||
/* ---------------------------------------------------------------------- */
|
||||
.mergely-editor.dark-mode {
|
||||
background-color: #0b0e10;
|
||||
|
||||
/* added to rhs styles */
|
||||
.mergely-editor .mergely.rhs.no-end.CodeMirror-linebackground,
|
||||
.mergely-editor .mergely.rhs.no-start.CodeMirror-linebackground,
|
||||
.mergely-editor .mergely.rhs.a.CodeMirror-linebackground {
|
||||
border-color: #a3d1ff;
|
||||
}
|
||||
.mergely-editor.dark-mode .CodeMirror {
|
||||
background-color: #12171b;
|
||||
.mergely-editor .mergely.rhs.a.CodeMirror-linebackground {
|
||||
background-color: #eff7ff;
|
||||
}
|
||||
.mergely-editor.dark-mode .CodeMirror-code {
|
||||
color: #bababa;
|
||||
.mergely-editor .mergely.ch.ina.rhs {
|
||||
background-color: #d3e9ff;
|
||||
}
|
||||
.mergely-editor.dark-mode .mergely.a.rhs {
|
||||
color: #00beff;
|
||||
background-color: transparent;
|
||||
.mergely-editor .mergely.ch.a.rhs {
|
||||
color: inherit;
|
||||
}
|
||||
.mergely-editor .mergely.a {
|
||||
border-color: #00beff;
|
||||
|
||||
.mergely-editor .mergely.current.lhs.CodeMirror-linebackground,
|
||||
.mergely-editor .mergely.current.rhs.CodeMirror-linebackground {
|
||||
border-color: black;
|
||||
}
|
||||
.mergely-editor.dark-mode .mergely.current {
|
||||
border-color: #616161;
|
||||
}
|
||||
.mergely-editor.dark-mode .mergely.c {
|
||||
border-color: #313131;
|
||||
}
|
||||
.mergely-editor.dark-mode .mergely.d {
|
||||
border-color: #9f3fff;
|
||||
}
|
||||
.mergely-editor .mergely.d.lhs {
|
||||
background-color: transparent;
|
||||
}
|
||||
.mergely-editor.dark-mode .mergely.ch.d.lhs {
|
||||
color: #bc4fff;
|
||||
}
|
||||
.mergely-editor.dark-mode .CodeMirror-gutters {
|
||||
background-color: transparent;
|
||||
border-right: 1px dotted #2e343a;
|
||||
}
|
||||
.mergely-editor.dark-mode .mergely.current .CodeMirror-linenumber {
|
||||
background-color: #6a89f7;
|
||||
}
|
||||
.mergely-editor.dark-mode .CodeMirror-linenumber {
|
||||
color: #999;
|
||||
}
|
||||
.mergely-editor.dark-mode .mergely-column {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.mergely-editor.dark-mode .mergely.current {
|
||||
border-color: #6a89f7;
|
||||
}
|
||||
.mergely-editor.dark-mode .CodeMirror-cursor {
|
||||
border-left: 2px solid #B19F73;
|
||||
}
|
||||
.mergely-editor.dark-mode .CodeMirror-selected {
|
||||
background-color: #424242;
|
||||
}
|
||||
.mergely-editor.dark-mode .CodeMirror-focused .CodeMirror-selected {
|
||||
background-color: #07468b;
|
||||
}
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
128
src/vdoc.js
128
src/vdoc.js
@@ -12,10 +12,6 @@ class VDoc {
|
||||
};
|
||||
}
|
||||
|
||||
hasRenderedChange(side, changeId) {
|
||||
return !!this._rendered[side][changeId];
|
||||
}
|
||||
|
||||
addRender(side, change, changeId, options) {
|
||||
const {
|
||||
isCurrent,
|
||||
@@ -24,21 +20,18 @@ class VDoc {
|
||||
getMergeHandler
|
||||
} = options;
|
||||
|
||||
if (this.hasRenderedChange(side, change)) {
|
||||
const alreadyRendered = !!this._rendered[side][changeId];
|
||||
|
||||
if (alreadyRendered) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oside = (side === 'lhs') ? 'rhs' : 'lhs';
|
||||
const bgClass = [ 'mergely', side, change.op, `cid-${changeId}` ];
|
||||
const bgClass = [ 'mergely', side, `cid-${changeId}` ];
|
||||
const { lf, lt, olf, olt } = getExtents(side, change);
|
||||
|
||||
if (change[lf] < 0) {
|
||||
bgClass.push('empty');
|
||||
}
|
||||
this._getLine(side, lf).addLineClass('background', 'start');
|
||||
this._getLine(side, lt).addLineClass('background', 'end');
|
||||
|
||||
if (isCurrent) {
|
||||
if (lf != lt) {
|
||||
if (lf !== lt) {
|
||||
this._getLine(side, lf).addLineClass('background', 'current');
|
||||
}
|
||||
this._getLine(side, lt).addLineClass('background', 'current');
|
||||
@@ -47,38 +40,66 @@ class VDoc {
|
||||
}
|
||||
}
|
||||
|
||||
if (lf == 0 && lt == 0 && olf == 0) {
|
||||
// test if this change is the first line of the side
|
||||
if (lf < 0) {
|
||||
// If this is the first, line it has start but no end
|
||||
bgClass.push('start');
|
||||
bgClass.push('no-end');
|
||||
this._getLine(side, 0).addLineClass('background', bgClass.join(' '));
|
||||
this._setRenderedChange(side, changeId);
|
||||
return;
|
||||
}
|
||||
if (side === 'lhs' && change['lhs-y-start'] === change['lhs-y-end']) {
|
||||
// if lhs, and start/end are the same, it has end but no-start
|
||||
bgClass.push('no-start');
|
||||
bgClass.push('end');
|
||||
this._getLine(side, lf).addLineClass('background', bgClass.join(' '));
|
||||
this._getLine(side, lf).addLineClass('background', 'first');
|
||||
// FIXME: need lineDiff here?
|
||||
} else {
|
||||
// apply change for each line in-between the changed lines
|
||||
for (let j = lf, k = olf; j <= lt; ++j, ++k) {
|
||||
this._getLine(side, j).addLineClass('background', bgClass.join(' '));
|
||||
this._setRenderedChange(side, changeId);
|
||||
return;
|
||||
}
|
||||
if (side === 'rhs' && change['rhs-y-start'] === change['rhs-y-end']) {
|
||||
// if rhs, and start/end are the same, it has end but no-start
|
||||
bgClass.push('no-start');
|
||||
bgClass.push('end');
|
||||
this._getLine(side, lf).addLineClass('background', bgClass.join(' '));
|
||||
this._setRenderedChange(side, changeId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lineDiff) {
|
||||
// inner line diffs are disabled, skip the rest
|
||||
continue;
|
||||
}
|
||||
this._getLine(side, lf).addLineClass('background', 'start');
|
||||
this._getLine(side, lt).addLineClass('background', 'end');
|
||||
|
||||
if (side === 'lhs'
|
||||
&& (change.op === 'd'
|
||||
|| (change.op === 'c' && k > olt)
|
||||
)) {
|
||||
// mark entire line text with deleted (strikeout) if the
|
||||
// change is a delete, or if it is changed text and the
|
||||
// line goes past the end of the other side.
|
||||
this._getLine(side, j).markText(0, undefined, 'mergely ch d lhs');
|
||||
} else if (side == 'rhs'
|
||||
&& (change.op === 'a'
|
||||
|| (change.op === 'c' && k > olt)
|
||||
)) {
|
||||
// mark entire line text with added if the change is an
|
||||
// add, or if it is changed text and the line goes past the
|
||||
// end of the other side.
|
||||
this._getLine(side, j).markText(0, undefined, 'mergely ch a rhs');
|
||||
}
|
||||
const bgChangeOp = {
|
||||
lhs: {
|
||||
d: 'd',
|
||||
a: 'd',
|
||||
c: 'c'
|
||||
},
|
||||
rhs: {
|
||||
d: 'a',
|
||||
a: 'a',
|
||||
c: 'c'
|
||||
}
|
||||
}[ side ][ change.op ];
|
||||
|
||||
for (let j = lf, k = olf; lf !== -1 && lt !== -1 && j <= lt; ++j, ++k) {
|
||||
this._getLine(side, j).addLineClass('background', bgChangeOp);
|
||||
this._getLine(side, j).addLineClass('background', bgClass.join(' '));
|
||||
|
||||
if (!lineDiff) {
|
||||
// inner line diffs are disabled, skip the rest
|
||||
continue;
|
||||
}
|
||||
|
||||
if (side === 'lhs' && (change.op === 'd')) {
|
||||
// mark entire line text with deleted (strikeout) if the
|
||||
// change is a delete, or if it is changed text and the
|
||||
// line goes past the end of the other side.
|
||||
this._getLine(side, j).markText(0, undefined, 'mergely ch d lhs');
|
||||
} else if (side === 'rhs' && (change.op === 'a')) {
|
||||
// mark entire line text with added if the change is an
|
||||
// add, or if it is changed text and the line goes past the
|
||||
// end of the other side.
|
||||
this._getLine(side, j).markText(0, undefined, 'mergely ch a rhs');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,18 +118,27 @@ class VDoc {
|
||||
for (let j = lf, k = olf;
|
||||
((j >= 0) && (j <= lt)) || ((k >= 0) && (k <= olt));
|
||||
++j, ++k) {
|
||||
|
||||
|
||||
// if both lhs line and rhs are within the change range with
|
||||
// respect to each other, do inline diff.
|
||||
if (j <= lt && k <= olt) {
|
||||
const lhsText = getText('lhs', j);
|
||||
const rhsText = getText('rhs', k);
|
||||
|
||||
const alreadyRendered
|
||||
= !!this._lines.lhs[j].markup.length
|
||||
|| !!this._lines.rhs[k].markup.length
|
||||
if (alreadyRendered) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: there is an LCS performance gain here if either side
|
||||
// is empty.
|
||||
const lcs = new LCS(lhsText, rhsText, {
|
||||
ignoreaccents,
|
||||
ignorews,
|
||||
ignorecase
|
||||
ignorecase,
|
||||
});
|
||||
|
||||
// TODO: there might be an LCS performance gain here to move
|
||||
@@ -117,11 +147,11 @@ class VDoc {
|
||||
lcs.diff(
|
||||
function _added(from, to) {
|
||||
const line = vdoc._getLine('rhs', k);
|
||||
line.markText(from, to, 'mergely ch a rhs');
|
||||
line.markText(from, to, 'mergely ch ina rhs');
|
||||
},
|
||||
function _removed(from, to) {
|
||||
const line = vdoc._getLine('lhs', j);
|
||||
line.markText(from, to, 'mergely ch d lhs');
|
||||
line.markText(from, to, 'mergely ch ind lhs');
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -267,10 +297,10 @@ class VLine {
|
||||
function getExtents(side, change) {
|
||||
const oside = (side === 'lhs') ? 'rhs' : 'lhs';
|
||||
return {
|
||||
lf: change[`${side}-line-from`] >= 0 ? change[`${side}-line-from`] : 0,
|
||||
lt: change[`${side}-line-to`] >= 0 ? change[`${side}-line-to`] : 0,
|
||||
olf: change[`${oside}-line-from`] >= 0 ? change[`${oside}-line-from`] : 0,
|
||||
olt: change[`${oside}-line-to`] >= 0 ? change[`${oside}-line-to`] : 0
|
||||
lf: change[`${side}-line-from`],
|
||||
lt: change[`${side}-line-to`],
|
||||
olf: change[`${oside}-line-from`],
|
||||
olt: change[`${oside}-line-to`]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const CodeMirror = require('CodeMirror');
|
||||
const simple = require('simple-mock');
|
||||
const Mergely = require('../src/mergely');
|
||||
const macbeth = require('./data/macbeth').join('\n');
|
||||
@@ -28,24 +27,24 @@ const defaultOptions = {
|
||||
|
||||
describe('mergely', function () {
|
||||
let editor;
|
||||
let editorId = 0;
|
||||
function init(options) {
|
||||
editor = new window.Mergely('#mergely', options);
|
||||
editor = new window.Mergely(`#mergely-${editorId - 1}`, options);
|
||||
return editor;
|
||||
};
|
||||
|
||||
before(() => {
|
||||
const container = document.createElement('div');
|
||||
container.style.height = '275px';
|
||||
beforeEach(() => {
|
||||
const div = document.createElement('div');
|
||||
div.id = 'mergely';
|
||||
div.id = `mergely-${editorId++}`;
|
||||
div.style.margin = '0px';
|
||||
container.append(div);
|
||||
document.querySelector('body').appendChild(container);
|
||||
div.style.height = '275px';
|
||||
document.querySelector('body').appendChild(div);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
editor && editor.unbind();
|
||||
simple.restore();
|
||||
editor.el.parentNode.removeChild(editor.el);
|
||||
});
|
||||
|
||||
describe('initialization', () => {
|
||||
@@ -53,7 +52,7 @@ describe('mergely', function () {
|
||||
const editor = init();
|
||||
expect(editor).to.exist;
|
||||
editor.once('updated', () => {
|
||||
const { children } = editor.el;
|
||||
const { children } = editor.el.children[0];
|
||||
const items = Array.from(children).map(a => a.className);
|
||||
// NOTE: if running karma debug, these tests can fail because
|
||||
// the debugger grabs the focus and the CodeMirror instance
|
||||
@@ -82,7 +81,7 @@ describe('mergely', function () {
|
||||
});
|
||||
expect(editor).to.exist;
|
||||
editor.once('updated', () => {
|
||||
const { children } = editor.el;
|
||||
const { children } = editor.el.children[0];
|
||||
const items = Array.from(children).map(a => a.className);
|
||||
// NOTE: if running karma debug, these tests can fail because
|
||||
// the debugger grabs the focus and the CodeMirror instance
|
||||
@@ -110,7 +109,7 @@ describe('mergely', function () {
|
||||
});
|
||||
expect(editor).to.exist;
|
||||
editor.once('updated', () => {
|
||||
const { children } = editor.el;
|
||||
const { children } = editor.el.children[0];
|
||||
const items = Array.from(children).map(a => a.className);
|
||||
// NOTE: if running karma debug, these tests can fail because
|
||||
// the debugger grabs the focus and the CodeMirror instance
|
||||
@@ -160,8 +159,8 @@ describe('mergely', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.only('initializes with and set lhs/rhs on update', function (done) {
|
||||
const editor = init({ _debug: true });
|
||||
it('initializes with and set lhs/rhs on update', function (done) {
|
||||
const editor = init();
|
||||
expect(editor).to.exist;
|
||||
const test = () => {
|
||||
done();
|
||||
@@ -720,6 +719,94 @@ describe('mergely', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe.only('markup', () => {
|
||||
const RHS_EMPTY = '.mergely.first.start.end.rhs.d.CodeMirror-linebackground';
|
||||
const LHS_EMPTY = '.mergely.first.start.end.lhs.a.CodeMirror-linebackground';
|
||||
const RHS_EMPTY_NOT_FIRST = '.mergely.start.end.rhs.d.CodeMirror-linebackground';
|
||||
const RHS_DELETED_AFTER = '.mergely.start.end.rhs.d.CodeMirror-linebackground';
|
||||
const LHS_DELETED_TEXT = '.mergely.ch.d.lhs';
|
||||
const RHS_ADDED_TEXT = '.mergely.ch.a.rhs';
|
||||
const LHS_ONE_EMPTY_LINE_DELETED = '.mergely.lhs.d.start.end.CodeMirror-linebackground';
|
||||
// start end mergely lhs d cid-0 CodeMirror-linebackground
|
||||
const opts = [{
|
||||
lhs: 'asdf\nasdf\n',
|
||||
rhs: '',
|
||||
name: 'lhs with 2 deleted lines',
|
||||
check: (mergely, editor) => {
|
||||
const lhs_spans = editor.querySelectorAll(LHS_DELETED_TEXT);
|
||||
expect(lhs_spans).to.have.length(2);
|
||||
const rhs_markup = editor.querySelectorAll(RHS_EMPTY);
|
||||
expect(rhs_markup).to.have.length(1);
|
||||
}
|
||||
}, {
|
||||
only: true,
|
||||
lhs: '',
|
||||
rhs: 'asdf\nasdf\n',
|
||||
name: 'rhs with 2 added lines',
|
||||
check: (mergely, editor) => {
|
||||
const lhs_spans = editor.querySelectorAll(LHS_EMPTY);
|
||||
expect(lhs_spans).to.have.length(2);
|
||||
const rhs_markup = editor.querySelectorAll(RHS_ADDED_TEXT);
|
||||
expect(rhs_markup).to.have.length(1);
|
||||
}
|
||||
}, {
|
||||
lhs: 'asdf\n\nasdf\n',
|
||||
rhs: '',
|
||||
name: 'lhs with 3 deleted lines',
|
||||
check: (mergely, editor) => {
|
||||
const lhs_spans = editor.querySelectorAll(LHS_DELETED_TEXT);
|
||||
expect(lhs_spans).to.have.length(2);
|
||||
const rhs_markup = editor.querySelectorAll(RHS_EMPTY_NOT_FIRST);
|
||||
expect(rhs_markup).to.have.length(1);
|
||||
}
|
||||
}, {
|
||||
lhs: 'asdf\n\nasdf\n',
|
||||
rhs: '\n',
|
||||
name: 'lhs with 2 separately deleted lines',
|
||||
check: (mergely, editor) => {
|
||||
const lhs_spans = editor.querySelectorAll(LHS_DELETED_TEXT);
|
||||
expect(lhs_spans).to.have.length(2);
|
||||
const rhs_markup = editor.querySelectorAll(RHS_EMPTY_NOT_FIRST);
|
||||
expect(rhs_markup).to.have.length(1);
|
||||
}
|
||||
}, {
|
||||
lhs: 'asdf\n',
|
||||
rhs: 'asdf',
|
||||
name: 'lhs with the last line deleted',
|
||||
check: (mergely, editor) => {
|
||||
const lhs_spans = editor.querySelectorAll(LHS_ONE_EMPTY_LINE_DELETED);
|
||||
expect(lhs_spans).to.have.length(1);
|
||||
const rhs_markup = editor.querySelectorAll(RHS_DELETED_AFTER);
|
||||
expect(rhs_markup).to.have.length(1);
|
||||
}
|
||||
}];
|
||||
opts.forEach((opt, i) => {
|
||||
// add `only: true` to a condition to run only it
|
||||
const itfn = opt.only ? it.only : it;
|
||||
itfn(`markup-case-${i} should markup ${opt.name}`, function (done) {
|
||||
const editor = init({
|
||||
height: 100,
|
||||
license: 'lgpl-separate-notice',
|
||||
change_timeout: 0,
|
||||
lhs: (setValue) => setValue(opt.lhs),
|
||||
rhs: (setValue) => setValue(opt.rhs)
|
||||
});
|
||||
const test = () => {
|
||||
try {
|
||||
opt.check(editor, editor.el);
|
||||
//!opt.only && done();
|
||||
done();
|
||||
} catch (ex) {
|
||||
//!opt.only && done(ex);
|
||||
done(ex);
|
||||
}
|
||||
};
|
||||
editor.once('updated', test);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('options', () => {
|
||||
it('should have default options', function (done) {
|
||||
const editor = init();
|
||||
|
||||
@@ -7,15 +7,15 @@ module.exports = {
|
||||
|
||||
module: {
|
||||
rules: [{
|
||||
test: /worker/,
|
||||
loader: 'worker-loader',
|
||||
options: { inline: 'no-fallback' }
|
||||
}, {
|
||||
include: [
|
||||
path.resolve(__dirname, 'src'),
|
||||
path.resolve(__dirname, 'examples')
|
||||
],
|
||||
test: /\.js$/
|
||||
}, {
|
||||
test: /\.(js)$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['babel-loader']
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
use: [{
|
||||
@@ -35,39 +35,49 @@ module.exports = {
|
||||
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.join(__dirname, 'examples', 'app.html')
|
||||
template: path.join(__dirname, 'examples', 'app.html'),
|
||||
filename: 'app.html',
|
||||
inject: true,
|
||||
chunks: [ 'app' ],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.join(__dirname, 'examples', 'app-styles.html'),
|
||||
filename: 'app-styles.html',
|
||||
inject: true,
|
||||
chunks: [ 'styles' ],
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
CodeMirror: 'codemirror'
|
||||
})
|
||||
}),
|
||||
{
|
||||
apply: (compiler) => {
|
||||
compiler.hooks.entryOption.tap('MyPlugin', (context, entry) => {
|
||||
console.log('-'.repeat(78));
|
||||
console.log('Applications:');
|
||||
console.log('http://localhost:8080/app.html');
|
||||
console.log('http://localhost:8080/app-styles.html');
|
||||
console.log('-'.repeat(78));
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
entry: {
|
||||
app: [
|
||||
'./examples/app',
|
||||
'./examples/app.js',
|
||||
'./src/mergely'
|
||||
],
|
||||
styles: [
|
||||
'./examples/app-styles.js',
|
||||
'./src/mergely'
|
||||
]
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: 'mergely.js'
|
||||
filename: '[name].mergely.js'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
chunkIds: 'named'
|
||||
// splitChunks: { chunks: 'all' }
|
||||
/*
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
priority: -10,
|
||||
test: /[\\/]node_modules[\\/]/
|
||||
}
|
||||
},
|
||||
chunks: 'async',
|
||||
minChunks: 1,
|
||||
minSize: 30000
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
// const dev = require('./webpack.dev');
|
||||
|
||||
const dev = {
|
||||
mode: 'development',
|
||||
entry: {
|
||||
@@ -19,9 +16,9 @@ const dev = {
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.(js)$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['babel-loader']
|
||||
test: /worker/,
|
||||
loader: 'worker-loader',
|
||||
options: { inline: 'no-fallback' }
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
|
||||
Reference in New Issue
Block a user