mirror of
https://github.com/wickedest/Mergely.git
synced 2026-03-24 09:48:39 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8075912b1b | ||
|
|
9834f0964a | ||
|
|
fa30d277e5 | ||
|
|
cc7df68aea | ||
|
|
6ed235c4b0 | ||
|
|
27778f0840 | ||
|
|
3651df9ad6 | ||
|
|
f1e58f58fe | ||
|
|
5cb3946182 | ||
|
|
69ecf76fe6 | ||
|
|
93eee752a7 | ||
|
|
00a79170a8 | ||
|
|
c90edb3150 | ||
|
|
1a2ee6af3d | ||
|
|
aa496078fd | ||
|
|
d6d8d06acc | ||
|
|
b9aed167f3 | ||
|
|
da71745c38 | ||
|
|
350e5af2e8 | ||
|
|
498746a831 | ||
|
|
83df02d967 |
70
CHANGELOG.md
70
CHANGELOG.md
@@ -1,3 +1,73 @@
|
|||||||
|
## [5.4.7](https://github.com/wickedest/Mergely/compare/v5.4.6...v5.4.7) (2026-02-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#228:** Fixes issue where a single scroll event would cause multiple updates and calculate view placement incorrectly ([9834f09](https://github.com/wickedest/Mergely/commit/9834f0964abeac88a4af83cde40b1deb29b8fe5d))
|
||||||
|
|
||||||
|
## [5.4.6](https://github.com/wickedest/Mergely/compare/v5.4.5...v5.4.6) (2026-02-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#228:** Clicking on edit marker in gutter now accounts for top position of editor. ([cc7df68](https://github.com/wickedest/Mergely/commit/cc7df68aea62e2e3a18c996fa56048f1e583faa5)), closes [#228](https://github.com/wickedest/Mergely/issues/228)
|
||||||
|
|
||||||
|
## [5.4.5](https://github.com/wickedest/Mergely/compare/v5.4.4...v5.4.5) (2026-02-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#222:** Fixed issue with poor rendering performance and marker misalignment ([27778f0](https://github.com/wickedest/Mergely/commit/27778f0840895bf5e029d8c962e99890b8a0a8f6)), closes [#222](https://github.com/wickedest/Mergely/issues/222) [#227](https://github.com/wickedest/Mergely/issues/227)
|
||||||
|
|
||||||
|
## [5.4.4](https://github.com/wickedest/Mergely/compare/v5.4.3...v5.4.4) (2026-02-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Updated examples README.md ([f1e58f5](https://github.com/wickedest/Mergely/commit/f1e58f58fea85628b1dd345b98b23923f05d8c2e))
|
||||||
|
|
||||||
|
## [5.4.3](https://github.com/wickedest/Mergely/compare/v5.4.2...v5.4.3) (2026-02-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Removed whitespace from example ([69ecf76](https://github.com/wickedest/Mergely/commit/69ecf76fe6066ded1efca06f9ee0ad985e8ed047))
|
||||||
|
|
||||||
|
## [5.4.2](https://github.com/wickedest/Mergely/compare/v5.4.1...v5.4.2) (2026-02-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Removed whitespace from example ([00a7917](https://github.com/wickedest/Mergely/commit/00a79170a8d4b701d6da98c1d6ea921b592fb3d1))
|
||||||
|
|
||||||
|
## [5.4.1](https://github.com/wickedest/Mergely/compare/v5.4.0...v5.4.1) (2026-02-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#225:** Fixes rendering issue when there are multiple editors ([1a2ee6a](https://github.com/wickedest/Mergely/commit/1a2ee6af3d7d87160e72e8faca5e38cf01b36fcb))
|
||||||
|
|
||||||
|
# [5.4.0](https://github.com/wickedest/Mergely/compare/v5.3.6...v5.4.0) (2025-11-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#220:** Exposes Mergely's instance of CodeMirror as Mergely.CodeMirror ([#221](https://github.com/wickedest/Mergely/issues/221)) ([d6d8d06](https://github.com/wickedest/Mergely/commit/d6d8d06accaa4fa3a4352b95620e3e456226d444)), closes [#220](https://github.com/wickedest/Mergely/issues/220)
|
||||||
|
|
||||||
|
## [5.3.6](https://github.com/wickedest/Mergely/compare/v5.3.5...v5.3.6) (2024-10-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#207:** Updated documentation for search method. ([#214](https://github.com/wickedest/Mergely/issues/214)) ([350e5af](https://github.com/wickedest/Mergely/commit/350e5af2e8b2e852364dc5a5729e03e3bc75358b)), closes [#207](https://github.com/wickedest/Mergely/issues/207)
|
||||||
|
|
||||||
|
## [5.3.5](https://github.com/wickedest/Mergely/compare/v5.3.4...v5.3.5) (2024-10-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* bump dependencies ([#213](https://github.com/wickedest/Mergely/issues/213)) ([83df02d](https://github.com/wickedest/Mergely/commit/83df02d967ce3100a33216f8ca77c2168bd53234))
|
||||||
|
|
||||||
## [5.3.4](https://github.com/wickedest/Mergely/compare/v5.3.3...v5.3.4) (2024-10-11)
|
## [5.3.4](https://github.com/wickedest/Mergely/compare/v5.3.3...v5.3.4) (2024-10-11)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -84,13 +84,15 @@ Mergely will emit an `updated` event when the editor is first initialized, and e
|
|||||||
|
|
||||||
### Visualization modes
|
### Visualization modes
|
||||||
|
|
||||||
Mergely supports the following CodeMirror visualizations for [mode](codemirror.net/5/doc/manual.html#option_mode):
|
Mergely does not support any CodeMirror highlighting for different languages (xml, html, javascript etc.) out of the box.
|
||||||
|
You can compile your own Mergely-Version with support for the desired modes by adding them to diff-view.js. For example:
|
||||||
* go
|
```
|
||||||
* javascript
|
require('codemirror/mode/go/go.js');
|
||||||
* htmlmixed
|
require('codemirror/mode/javascript/javascript.js');
|
||||||
* markdown
|
require('codemirror/mode/htmlmixed/htmlmixed.js');
|
||||||
* python
|
require('codemirror/mode/markdown/markdown.js');
|
||||||
|
require('codemirror/mode/python/python.js');
|
||||||
|
```
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# Mergely examples
|
# Mergely examples
|
||||||
|
|
||||||
|
|
||||||
## Mergely development server
|
## Mergely development server
|
||||||
|
|
||||||
To run the hot dev-server for development, you need to run:
|
To run the hot dev-server for development, you need to run:
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ require('codemirror/addon/selection/mark-selection.js');
|
|||||||
require('codemirror/lib/codemirror.css');
|
require('codemirror/lib/codemirror.css');
|
||||||
require('../src/mergely.css');
|
require('../src/mergely.css');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
document.onreadystatechange = function () {
|
document.onreadystatechange = function () {
|
||||||
if (document.readyState !== 'complete') {
|
if (document.readyState !== 'complete') {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ the quick brown fox
|
|||||||
jumped over the lazy dog
|
jumped over the lazy dog
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
document.onreadystatechange = function () {
|
document.onreadystatechange = function () {
|
||||||
if (document.readyState !== 'complete') {
|
if (document.readyState !== 'complete') {
|
||||||
return;
|
return;
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mergely",
|
"name": "mergely",
|
||||||
"version": "5.3.4",
|
"version": "5.4.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mergely",
|
"name": "mergely",
|
||||||
"version": "5.3.4",
|
"version": "5.4.7",
|
||||||
"license": "(GPL-3.0 OR LGPL-3.0 OR MPL-1.1 OR SEE LICENSE IN LICENSE)",
|
"license": "(GPL-3.0 OR LGPL-3.0 OR MPL-1.1 OR SEE LICENSE IN LICENSE)",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.1.6",
|
"@babel/core": "^7.1.6",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mergely",
|
"name": "mergely",
|
||||||
"version": "5.3.4",
|
"version": "5.4.7",
|
||||||
"description": "A javascript UI for diff/merge",
|
"description": "A javascript UI for diff/merge",
|
||||||
"license": "(GPL-3.0 OR LGPL-3.0 OR MPL-1.1 OR SEE LICENSE IN LICENSE)",
|
"license": "(GPL-3.0 OR LGPL-3.0 OR MPL-1.1 OR SEE LICENSE IN LICENSE)",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ function CodeMirrorDiffView(el, options) {
|
|||||||
const coords = this.cursorCoords(null, 'local');
|
const coords = this.cursorCoords(null, 'local');
|
||||||
this.scrollTo(null,
|
this.scrollTo(null,
|
||||||
(coords.top + coords.bottom) / 2 - (this.getScrollerElement().clientHeight / 2));
|
(coords.top + coords.bottom) / 2 - (this.getScrollerElement().clientHeight / 2));
|
||||||
});
|
});
|
||||||
this.init(el, options);
|
this.init(el, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CodeMirrorDiffView.CodeMirror = CodeMirror;
|
||||||
|
|
||||||
const trace = console.log;
|
const trace = console.log;
|
||||||
const traceTimeStart = console.time;
|
const traceTimeStart = console.time;
|
||||||
@@ -128,7 +130,6 @@ CodeMirrorDiffView.prototype.scrollToDiff = function(direction) {
|
|||||||
this.trace('change', 'current-diff', this._current_diff);
|
this.trace('change', 'current-diff', this._current_diff);
|
||||||
// _current_diff changed, refresh the view
|
// _current_diff changed, refresh the view
|
||||||
this._scroll_to_change(this.changes[this._current_diff]);
|
this._scroll_to_change(this.changes[this._current_diff]);
|
||||||
this.setChanges(this.changes);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CodeMirrorDiffView.prototype.mergeCurrentChange = function(side) {
|
CodeMirrorDiffView.prototype.mergeCurrentChange = function(side) {
|
||||||
@@ -147,7 +148,6 @@ CodeMirrorDiffView.prototype.scrollTo = function(side, num) {
|
|||||||
const ed = this.editor[side];
|
const ed = this.editor[side];
|
||||||
ed.setCursor(num);
|
ed.setCursor(num);
|
||||||
ed.centerOnCursor();
|
ed.centerOnCursor();
|
||||||
this._renderChanges();
|
|
||||||
this.el.dispatchEvent(new Event('updated'));
|
this.el.dispatchEvent(new Event('updated'));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ CodeMirrorDiffView.prototype.bind = function(container) {
|
|||||||
if (!notice) {
|
if (!notice) {
|
||||||
notice = noticeTypes.lgpl;
|
notice = noticeTypes.lgpl;
|
||||||
}
|
}
|
||||||
const editor = this._queryElement(`#${this.id}`);
|
const editor = this.el;
|
||||||
const splash = dom.getSplash({
|
const splash = dom.getSplash({
|
||||||
notice,
|
notice,
|
||||||
left: (editor.offsetWidth - 300) / 2,
|
left: (editor.offsetWidth - 300) / 2,
|
||||||
@@ -554,20 +554,12 @@ CodeMirrorDiffView.prototype._scroll_to_change = function(change) {
|
|||||||
if (!change) {
|
if (!change) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const {
|
|
||||||
lhs: led,
|
|
||||||
rhs: red
|
|
||||||
} = this.editor;
|
|
||||||
// set cursors
|
|
||||||
const llf = Math.max(change['lhs-line-from'], 0);
|
|
||||||
const rlf = Math.max(change['rhs-line-from'], 0);
|
|
||||||
led.setCursor(llf, 0);
|
|
||||||
red.setCursor(rlf, 0);
|
|
||||||
if (change['lhs-line-to'] >= 0) {
|
if (change['lhs-line-to'] >= 0) {
|
||||||
this.scrollTo('lhs', change['lhs-line-to']);
|
this.scrollTo('lhs', change['lhs-line-to']);
|
||||||
} else if (change['rhs-line-to'] >= 0) {
|
} else if (change['rhs-line-to'] >= 0) {
|
||||||
this.scrollTo('rhs', change['rhs-line-to']);
|
this.scrollTo('rhs', change['rhs-line-to']);
|
||||||
}
|
}
|
||||||
|
const { lhs: led } = this.editor;
|
||||||
led.focus();
|
led.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -738,6 +730,10 @@ CodeMirrorDiffView.prototype._isChangeInView = function(side, vp, change) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// there are 3 conditions to test
|
||||||
|
// 1: the change "from" is within the viewport from/to
|
||||||
|
// 2: the change "to" is within the viewport from/to
|
||||||
|
// 3: the change is so big that the viewport is within
|
||||||
return (change[`${side}-line-from`] >= vp.from && change[`${side}-line-from`] <= vp.to) ||
|
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) ||
|
(change[`${side}-line-to`] >= vp.from && change[`${side}-line-to`] <= vp.to) ||
|
||||||
(vp.from >= change[`${side}-line-from`] && vp.to <= change[`${side}-line-to`]);
|
(vp.from >= change[`${side}-line-from`] && vp.to <= change[`${side}-line-to`]);
|
||||||
@@ -1088,8 +1084,8 @@ CodeMirrorDiffView.prototype._renderDiff = function(changes) {
|
|||||||
ctx_lhs.strokeRect(1.5, mkr_lhs_y_start, 4.5, Math.max(mkr_lhs_y_end - mkr_lhs_y_start, 5));
|
ctx_lhs.strokeRect(1.5, mkr_lhs_y_start, 4.5, Math.max(mkr_lhs_y_end - mkr_lhs_y_start, 5));
|
||||||
ctx_lhs.stroke();
|
ctx_lhs.stroke();
|
||||||
|
|
||||||
const mkr_rhs_y_start = change['rhs-y-start'] * lratio;
|
const mkr_rhs_y_start = change['rhs-y-start'] * rratio;
|
||||||
const mkr_rhs_y_end = Math.max(change['rhs-y-end'] * lratio, 5);
|
const mkr_rhs_y_end = Math.max(change['rhs-y-end'] * rratio, 5);
|
||||||
ctx_rhs.beginPath();
|
ctx_rhs.beginPath();
|
||||||
ctx_rhs.fillStyle = '#a3a3a3';
|
ctx_rhs.fillStyle = '#a3a3a3';
|
||||||
ctx_rhs.strokeStyle = '#000';
|
ctx_rhs.strokeStyle = '#000';
|
||||||
@@ -1179,12 +1175,16 @@ CodeMirrorDiffView.prototype._renderDiff = function(changes) {
|
|||||||
ctx_rhs.fillRect(1.5, rfrom, 4.5, rto);
|
ctx_rhs.fillRect(1.5, rfrom, 4.5, rto);
|
||||||
|
|
||||||
this._handleLhsMarginClick = function (ev) {
|
this._handleLhsMarginClick = function (ev) {
|
||||||
const y = ev.pageY - ex.lhs_xyoffset.top - (lto / 2);
|
// `top` accounts for the editor starting at position other than 0 on page
|
||||||
|
const { top } = ev.currentTarget.offsetParent.getBoundingClientRect();
|
||||||
|
const y = (ev.pageY - top) - ex.lhs_xyoffset.top - (lto / 2);
|
||||||
const sto = Math.max(0, (y / mcanvas_lhs.height) * ex.lhs_scroller.scrollHeight);
|
const sto = Math.max(0, (y / mcanvas_lhs.height) * ex.lhs_scroller.scrollHeight);
|
||||||
ex.lhs_scroller.scrollTo({ top: sto });
|
ex.lhs_scroller.scrollTo({ top: sto });
|
||||||
};
|
};
|
||||||
this._handleRhsMarginClick = function (ev) {
|
this._handleRhsMarginClick = function (ev) {
|
||||||
const y = ev.pageY - ex.rhs_xyoffset.top - (rto / 2);
|
// `top` accounts for the editor starting at position other than 0 on page
|
||||||
|
const { top } = ev.currentTarget.offsetParent.getBoundingClientRect();
|
||||||
|
const y = (ev.pageY - top) - ex.rhs_xyoffset.top - (rto / 2);
|
||||||
const sto = Math.max(0, (y / mcanvas_rhs.height) * ex.rhs_scroller.scrollHeight);
|
const sto = Math.max(0, (y / mcanvas_rhs.height) * ex.rhs_scroller.scrollHeight);
|
||||||
ex.rhs_scroller.scrollTo({ top: sto });
|
ex.rhs_scroller.scrollTo({ top: sto });
|
||||||
};
|
};
|
||||||
@@ -1198,7 +1198,7 @@ CodeMirrorDiffView.prototype._renderDiff = function(changes) {
|
|||||||
|
|
||||||
CodeMirrorDiffView.prototype._queryElement = function(selector) {
|
CodeMirrorDiffView.prototype._queryElement = function(selector) {
|
||||||
const cacheName = `_element:${selector}`;
|
const cacheName = `_element:${selector}`;
|
||||||
const element = this[cacheName] || document.querySelector(selector);
|
const element = this[cacheName] || this.el.querySelector(selector);
|
||||||
if (!this[cacheName]) {
|
if (!this[cacheName]) {
|
||||||
this[cacheName] = element;
|
this[cacheName] = element;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ class Mergely {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mergely.CodeMirror = CodeMirrorDiffView.CodeMirror;
|
||||||
|
|
||||||
window.Mergely = Mergely;
|
window.Mergely = Mergely;
|
||||||
|
|
||||||
module.exports = Mergely;
|
module.exports = Mergely;
|
||||||
|
|||||||
23
src/vdoc.js
23
src/vdoc.js
@@ -205,9 +205,13 @@ class VDoc {
|
|||||||
if (this.options._debug) {
|
if (this.options._debug) {
|
||||||
trace('vdoc#update', side, editor, viewport);
|
trace('vdoc#update', side, editor, viewport);
|
||||||
}
|
}
|
||||||
const lines = Object.keys(this._lines[side]);
|
const keys = Object.keys(this._lines[side]);
|
||||||
for (let i = 0; i < lines.length; ++i) {
|
// while Mergely diffs unicode diacritic chars (letters+mark),
|
||||||
const id = lines[i];
|
// CM is by character, so diffs need to be mapped.
|
||||||
|
const mappedChars = mapLettersToChars(editor.getValue());
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
const { id } = this._lines[side][key];
|
||||||
if (id < viewport.from || id > viewport.to) {
|
if (id < viewport.from || id > viewport.to) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -216,7 +220,7 @@ class VDoc {
|
|||||||
if (vline.rendered) {
|
if (vline.rendered) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
vline.update(editor);
|
vline.update(editor, mappedChars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +261,7 @@ class VLine {
|
|||||||
this.markup.push([ charFrom, charTo, className ]);
|
this.markup.push([ charFrom, charTo, className ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(editor) {
|
update(editor, mappedChars) {
|
||||||
if (this.rendered) {
|
if (this.rendered) {
|
||||||
// FIXME: probably do not need this now
|
// FIXME: probably do not need this now
|
||||||
console.log('already rendered', this.id);
|
console.log('already rendered', this.id);
|
||||||
@@ -279,18 +283,17 @@ class VLine {
|
|||||||
editor.setGutterMarker(this.id, name, item);
|
editor.setGutterMarker(this.id, name, item);
|
||||||
}
|
}
|
||||||
if (this.markup.length) {
|
if (this.markup.length) {
|
||||||
// while Mergely diffs unicode chars (letters+mark), CM is by character,
|
// while Mergely diffs unicode diacritic chars (letters+mark),
|
||||||
// so diffs need to be mapped.
|
// CM is by character, so diffs need to be mapped.
|
||||||
const mapped = mapLettersToChars(editor.getValue());
|
|
||||||
for (const markup of this.markup) {
|
for (const markup of this.markup) {
|
||||||
const [ charFrom, charTo, className ] = markup;
|
const [ charFrom, charTo, className ] = markup;
|
||||||
const fromPos = { line: this.id };
|
const fromPos = { line: this.id };
|
||||||
const toPos = { line: this.id };
|
const toPos = { line: this.id };
|
||||||
if (charFrom >= 0) {
|
if (charFrom >= 0) {
|
||||||
fromPos.ch = mapped[charFrom];
|
fromPos.ch = mappedChars[charFrom];
|
||||||
}
|
}
|
||||||
if (charTo >= 0) {
|
if (charTo >= 0) {
|
||||||
toPos.ch = mapped[charTo];
|
toPos.ch = mappedChars[charTo];
|
||||||
}
|
}
|
||||||
this._clearMarkup.push(
|
this._clearMarkup.push(
|
||||||
editor.markText(fromPos, toPos, { className }));
|
editor.markText(fromPos, toPos, { className }));
|
||||||
|
|||||||
Reference in New Issue
Block a user