From 79cb0ddd6a07886435b0e17b0bac33416e67716d Mon Sep 17 00:00:00 2001 From: Jamie Peabody Date: Sun, 21 Nov 2021 17:22:04 +0000 Subject: [PATCH] refactor: Split code into individual files and tidier code (#160) * refactor: split code into separate files * chore: removed unused test code * refactor: no longer depends on jQuery.extend * docs: merged changes into CHANGELOG.md Co-authored-by: Jamie Peabody --- CHANGELOG.md | 107 ++ CHANGES.md | 108 -- karma.conf.js | 28 +- package.json | 2 +- src/diff-parser.js | 28 + src/diff-view.js | 1479 ++++++++++++++ src/diff.js | 291 +++ src/lcs.js | 90 + src/mergely.js | 1837 +----------------- src/{Timer.js => timer.js} | 4 +- test/index.html | 34 - test/lib/jsunit/jsunit.css | 15 - test/lib/jsunit/jsunit.js | 198 -- test/lib/jsunit/stacktrace.js | 416 ---- test/lib/jsunit/tipTip.css | 113 -- test/lib/jsunit/tipTip.js | 191 -- test/lib/require/require.js | 31 - test/lib/require/text.js | 11 - test/macbeth.js | 3411 --------------------------------- test/main.js | 25 - test/tests.js | 1204 ------------ tests/mergely.spec.js | 81 +- 22 files changed, 2099 insertions(+), 7605 deletions(-) delete mode 100644 CHANGES.md create mode 100644 src/diff-parser.js create mode 100644 src/diff-view.js create mode 100644 src/diff.js create mode 100644 src/lcs.js rename src/{Timer.js => timer.js} (75%) delete mode 100755 test/index.html delete mode 100755 test/lib/jsunit/jsunit.css delete mode 100755 test/lib/jsunit/jsunit.js delete mode 100755 test/lib/jsunit/stacktrace.js delete mode 100755 test/lib/jsunit/tipTip.css delete mode 100755 test/lib/jsunit/tipTip.js delete mode 100755 test/lib/require/require.js delete mode 100755 test/lib/require/text.js delete mode 100755 test/macbeth.js delete mode 100755 test/main.js delete mode 100755 test/tests.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ef3ffe..c955cc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,3 +23,110 @@ All notable changes to this project will be documented in this file. See [standa ### General * updated dependencies ([85b02ad](https://github.com/wickedest/Mergely/commit/85b02add899b90e4d6ced4474cfdee98838d272a)) +---- +### 4.3.5 +* patch: fixed issue with build + +### 4.3.4 +* patch: Fixes inline diff rendering issue with whitespace [#139](https://github.com/wickedest/Mergely/issues/139). + +### 4.3.3 +* patch: Fixes resize issue when using zoom [#152](https://github.com/wickedest/Mergely/issues/152). + +### 4.3.2 +* patch: Reset the current change position when [setValue](https://mergely.com/doc##options_callbacks), [clear](https://mergely.com/doc#clear), [lhs](https://mergely.com/doc#lhs) or [rhs](https://mergely.com/doc#rhs) are called. + +### 4.3.1 +* patch: Updated README.md to fix incorrect option name. + +### 4.3.0 +* feat: Added `summary` method + +### 4.2.4 +* patch: fixes [#142](https://github.com/wickedest/Mergely/issues/142). Added README.md to examples. + +### 4.2.3 +* patch: fixes [#147](https://github.com/wickedest/Mergely/issues/147). Fixes the css style for the currently selected change. + +### 4.2.2: +* patch: fixes issue where initial change was not being set causing next/prev and merge actions to not work as expected. + +### 4.2.1: +* chore: updated dependencies, cleared security issues + +### 4.2.0: +* minor: added new option `ignoreaccents` to ignore accented characters. + +### 4.1.2: +* patch: fixes issue #134 where the readme had broken links. + +### 4.1.1: +* patch: fixes issue #95 where cursor was not focusing correctly on init. + +### 4.1.0: +* minor: emits 'updated' event after every change. +* patch: fixes `scrollTo` that no longer functioned after a codemirror update. +* patch: fixes `loaded` being called prematurely and after every resize, and is now is called once, after the 'updated' event. + +### 4.0.16: +* patch: fixes rendering beyond change constraint + +### 4.0.15 +* patch: removed unnecessary addon mark-selected + +### 4.0.14 +* patch: fixes issue #104 where diff text conflicted with selected text + +### 4.0.13 +* patch: fixed issue where `lhs_cmsettings` and `lhs_cmsettings` were ignored +* patch: updated documentation +* patch: fixed typos +* patch: fixes issue #115 merging deleted line(s) from lhs would munge rhs text +* patch: fixes two typos in README.md + +### 4.0.11 +* patch: fixes typo in example ajax.html + +### 4.0.10 +* patch: fixes bad 4.0.9 artifacts + +### 4.0.9 +* patch: fixes issue #106 merge edge-case with add + +### 4.0.8 +* chore: updated webpack + +### 4.0.7 +* chore: updated documentation + +### 4.0.6 + +* patch: fixes issue #89 missing merge buttons + +### 4.0.5 + +* patch: fixes issue #85 XSS vulnerability with DOM id + +### 4.0.2 + +* patch: fixes issue #83 poor rendering performance + +### 4.0.0 + +### Breaking changes + +* Now using `npm` as the preferred distribution method. +* Improved width/height options. Removed options `editor_width` and `editor_height`, and added `width` and `height`. The options are equivalent. +* Added license option to indicate which type of license to use. +* Only distributing the minimized version as `mergely.js` (was `mergely.min.js`). +* No longer bundling CodeMirror. You will need to get the appropriate source files from a CDN. + +### Minor features + +* Created `mergely-webpack` example. +* Lighter distribution (no longer bundling CodeMirror or the Mergely editor). +* Made it easier to set CodeMirror settings via `cmsettings` for both editors, then applies `lhs_cmsettings` for the left-hand editor, and `rhs_cmsettings` for the right-hand editor. + +### Fixes + +* Previously `width: 'auto'` would unnecessarily account for a scroll bar width, now it does not. diff --git a/CHANGES.md b/CHANGES.md deleted file mode 100644 index fcee4dd..0000000 --- a/CHANGES.md +++ /dev/null @@ -1,108 +0,0 @@ -# Changes - -## 4.3.5 -* patch: fixed issue with build - -## 4.3.4 -* patch: Fixes inline diff rendering issue with whitespace [#139](https://github.com/wickedest/Mergely/issues/139). - -## 4.3.3 -* patch: Fixes resize issue when using zoom [#152](https://github.com/wickedest/Mergely/issues/152). - -## 4.3.2 -* patch: Reset the current change position when [setValue](https://mergely.com/doc##options_callbacks), [clear](https://mergely.com/doc#clear), [lhs](https://mergely.com/doc#lhs) or [rhs](https://mergely.com/doc#rhs) are called. - -## 4.3.1 -* patch: Updated README.md to fix incorrect option name. - -## 4.3.0 -* feat: Added `summary` method - -## 4.2.4 -* patch: fixes [#142](https://github.com/wickedest/Mergely/issues/142). Added README.md to examples. - -## 4.2.3 -* patch: fixes [#147](https://github.com/wickedest/Mergely/issues/147). Fixes the css style for the currently selected change. - -## 4.2.2: -* patch: fixes issue where initial change was not being set causing next/prev and merge actions to not work as expected. - -## 4.2.1: -* chore: updated dependencies, cleared security issues - -## 4.2.0: -* minor: added new option `ignoreaccents` to ignore accented characters. - -## 4.1.2: -* patch: fixes issue #134 where the readme had broken links. - -## 4.1.1: -* patch: fixes issue #95 where cursor was not focusing correctly on init. - -## 4.1.0: -* minor: emits 'updated' event after every change. -* patch: fixes `scrollTo` that no longer functioned after a codemirror update. -* patch: fixes `loaded` being called prematurely and after every resize, and is now is called once, after the 'updated' event. - -## 4.0.16: -* patch: fixes rendering beyond change constraint - -## 4.0.15 -* patch: removed unnecessary addon mark-selected - -## 4.0.14 -* patch: fixes issue #104 where diff text conflicted with selected text - -## 4.0.13 -* patch: fixed issue where `lhs_cmsettings` and `lhs_cmsettings` were ignored -* patch: updated documentation -* patch: fixed typos -* patch: fixes issue #115 merging deleted line(s) from lhs would munge rhs text -* patch: fixes two typos in README.md - -## 4.0.11 -* patch: fixes typo in example ajax.html - -## 4.0.10 -* patch: fixes bad 4.0.9 artifacts - -## 4.0.9 -* patch: fixes issue #106 merge edge-case with add - -## 4.0.8 -* chore: updated webpack - -## 4.0.7 -* chore: updated documentation - -## 4.0.6 - -* patch: fixes issue #89 missing merge buttons - -## 4.0.5 - -* patch: fixes issue #85 XSS vulnerability with DOM id - -## 4.0.2 - -* patch: fixes issue #83 poor rendering performance - -## 4.0.0 - -### Breaking changes - -* Now using `npm` as the preferred distribution method. -* Improved width/height options. Removed options `editor_width` and `editor_height`, and added `width` and `height`. The options are equivalent. -* Added license option to indicate which type of license to use. -* Only distributing the minimized version as `mergely.js` (was `mergely.min.js`). -* No longer bundling CodeMirror. You will need to get the appropriate source files from a CDN. - -### Minor features - -* Created `mergely-webpack` example. -* Lighter distribution (no longer bundling CodeMirror or the Mergely editor). -* Made it easier to set CodeMirror settings via `cmsettings` for both editors, then applies `lhs_cmsettings` for the left-hand editor, and `rhs_cmsettings` for the right-hand editor. - -### Fixes - -* Previously `width: 'auto'` would unnecessarily account for a scroll bar width, now it does not. diff --git a/karma.conf.js b/karma.conf.js index 1887325..b181102 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,5 +1,6 @@ const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpackConfig = require('./webpack.dev.js'); module.exports = function(config) { config.set({ @@ -36,32 +37,7 @@ module.exports = function(config) { captureConsole: true, mocha: {} }, - webpack: { - entry: './src/mergely.js', - module: { - rules: [{ - test: /\.(js)$/, - exclude: /node_modules/, - use: ['babel-loader'] - }] - }, - resolve: { - extensions: ['.js'], - alias: { - CodeMirror: path.join(__dirname, 'node_modules', 'codemirror'), - jQuery: path.join(__dirname, 'node_modules', 'jquery') - } - }, - plugins: [ - new CopyWebpackPlugin({ - patterns: [{ - from: 'src/mergely.css', - to: 'mergely.css', - toType: 'file' - }] - }) - ] - }, + webpack: webpackConfig, webpackServer: { noInfo: true } diff --git a/package.json b/package.json index fc1d061..4abb00b 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "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", + "debug": "karma start --browsers=Chrome --single-run=false", "test": "karma start", - "test:chrome": "karma start --browsers Chrome --singleRun=false", "release": "standard-version -a" } } diff --git a/src/diff-parser.js b/src/diff-parser.js new file mode 100644 index 0000000..3c071b5 --- /dev/null +++ b/src/diff-parser.js @@ -0,0 +1,28 @@ +const changeExp = new RegExp(/(^(?![><\-])*\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/); + +function DiffParser(diff) { + const changes = []; + let change_id = 0; + // parse diff + const diff_lines = diff.split(/\n/); + for (var i = 0; i < diff_lines.length; ++i) { + if (diff_lines[i].length == 0) continue; + const change = {}; + const test = changeExp.exec(diff_lines[i]); + if (test == null) continue; + // lines are zero-based + const fr = test[1].split(','); + change['lhs-line-from'] = fr[0] - 1; + if (fr.length == 1) change['lhs-line-to'] = fr[0] - 1; + else change['lhs-line-to'] = fr[1] - 1; + const to = test[3].split(','); + change['rhs-line-from'] = to[0] - 1; + if (to.length == 1) change['rhs-line-to'] = to[0] - 1; + else change['rhs-line-to'] = to[1] - 1; + change['op'] = test[2]; + changes[change_id++] = change; + } + return changes; +}; + +module.exports = DiffParser; diff --git a/src/diff-view.js b/src/diff-view.js new file mode 100644 index 0000000..114219a --- /dev/null +++ b/src/diff-view.js @@ -0,0 +1,1479 @@ +const Timer = require('./timer'); +const diff = require('./diff'); +const DiffParser = require('./diff-parser'); +const LCS = require('./lcs'); + +function CodeMirrorDiffView(el, options, { jQuery, CodeMirror }) { + CodeMirror.defineExtension('centerOnCursor', function() { + var coords = this.cursorCoords(null, 'local'); + this.scrollTo(null, + (coords.top + coords.bottom) / 2 - (this.getScrollerElement().clientHeight / 2)); + }); + this.jQuery = jQuery; + this.CodeMirror = CodeMirror; + + this.init(el, options); +}; + +CodeMirrorDiffView.prototype.init = function(el, options) { + const { jQuery } = this; + this.settings = { + autoupdate: true, + autoresize: true, + rhs_margin: 'right', + wrap_lines: false, + line_numbers: true, + lcs: true, + sidebar: true, + viewport: false, + ignorews: false, + ignorecase: false, + ignoreaccents: false, + fadein: 'fast', + resize_timeout: 500, + change_timeout: 150, + fgcolor: {a:'#4ba3fa',c:'#a3a3a3',d:'#ff7f7f', // color for differences (soft color) + ca:'#4b73ff',cc:'#434343',cd:'#ff4f4f'}, // color for currently active difference (bright color) + bgcolor: '#eee', + vpcolor: 'rgba(0, 0, 200, 0.5)', + license: '', + width: 'auto', + height: 'auto', + cmsettings: { + styleSelectedText: true + }, + lhs_cmsettings: {}, + rhs_cmsettings: {}, + lhs: function(setValue) { }, + rhs: function(setValue) { }, + loaded: function() { }, + resize: function(init) { + const parent = jQuery(el).parent(); + let w; + let h; + if (this.width == 'auto') { + w = parent.width(); + } + else { + w = this.width; + } + if (this.height == 'auto') { + h = parent.height() - 2; + } + else { + h = this.height; + } + const content_width = w / 2.0 - 2 * 8 - 8; + const content_height = h; + const self = jQuery(el); + self.find('.mergely-column').css({ width: content_width + 'px' }); + self.find('.mergely-column, .mergely-canvas, .mergely-margin, .mergely-column textarea, .CodeMirror-scroll, .cm-s-default').css({ height: content_height + 'px' }); + self.find('.mergely-canvas').css({ height: content_height + 'px' }); + self.find('.mergely-column textarea').css({ width: content_width + 'px' }); + self.css({ width: w, height: h, clear: 'both' }); + if (self.css('display') === 'none') { + if (this.fadein != false) { + self.fadeIn(this.fadein); + } + else { + self.show(); + } + } + if (this.resized) this.resized(); + }, + _debug: '', //scroll,draw,calc,diff,markup,change,init + resized: function() { }, + // user supplied options + ...options + }; + // save this element for faster queries + this.element = jQuery(el); + + this.lhs_cmsettings = { + ...this.settings.cmsettings, + ...this.settings.lhs_cmsettings, + // these override any user-defined CodeMirror settings + lineWrapping: this.settings.wrap_lines, + lineNumbers: this.settings.line_numbers, + gutters: (this.settings.line_numbers && ['merge', 'CodeMirror-linenumbers']) || [], + }; + this.rhs_cmsettings = { + ...this.settings.cmsettings, + ...this.settings.rhs_cmsettings, + // these override any user-defined CodeMirror settings + lineWrapping: this.settings.wrap_lines, + lineNumbers: this.settings.line_numbers, + gutters: (this.settings.line_numbers && ['merge', 'CodeMirror-linenumbers']) || [], + }; + + // bind if the element is destroyed + this.element.bind('destroyed', jQuery.proxy(this.teardown, this)); + + // save this instance in jQuery data, binding this view to the node + jQuery.data(el, 'mergely', this); + + this._setOptions(options); +}; + +CodeMirrorDiffView.prototype.unbind = function() { + const { jQuery } = this; + if (this.changed_timeout != null) clearTimeout(this.changed_timeout); + this.editor[this.id + '-lhs'].toTextArea(); + this.editor[this.id + '-rhs'].toTextArea(); + jQuery(window).off('.mergely'); +}; + +CodeMirrorDiffView.prototype.destroy = function() { + this.element.unbind('destroyed', this.teardown); + this.teardown(); +}; + +CodeMirrorDiffView.prototype.teardown = function() { + this.unbind(); +}; + +CodeMirrorDiffView.prototype.lhs = function(text) { + // invalidate existing changes and current position + this.changes = []; + delete this._current_diff; + this.editor[this.id + '-lhs'].setValue(text); +}; + +CodeMirrorDiffView.prototype.rhs = function(text) { + // invalidate existing changes and current position + this.changes = []; + delete this._current_diff; + this.editor[this.id + '-rhs'].setValue(text); +}; + +CodeMirrorDiffView.prototype.update = function() { + this._changing(this.id + '-lhs', this.id + '-rhs'); +}; + +CodeMirrorDiffView.prototype.unmarkup = function() { + this._clear(); +}; + +CodeMirrorDiffView.prototype.scrollToDiff = function(direction) { + if (!this.changes.length) return; + if (direction == 'next') { + if (this._current_diff == this.changes.length - 1) { + this._current_diff = 0; + } else { + this._current_diff = Math.min(++this._current_diff, this.changes.length - 1); + } + } + else if (direction == 'prev') { + if (this._current_diff == 0) { + this._current_diff = this.changes.length - 1; + } else { + this._current_diff = Math.max(--this._current_diff, 0); + } + } + this._scroll_to_change(this.changes[this._current_diff]); + this._changed(this.id + '-lhs', this.id + '-rhs'); +}; + +CodeMirrorDiffView.prototype.mergeCurrentChange = function(side) { + if (!this.changes.length) return; + if (side == 'lhs' && !this.lhs_cmsettings.readOnly) { + this._merge_change(this.changes[this._current_diff], 'rhs', 'lhs'); + } + else if (side == 'rhs' && !this.rhs_cmsettings.readOnly) { + this._merge_change(this.changes[this._current_diff], 'lhs', 'rhs'); + } +}; + +CodeMirrorDiffView.prototype.scrollTo = function(side, num) { + var le = this.editor[this.id + '-lhs']; + var re = this.editor[this.id + '-rhs']; + if (side == 'lhs') { + le.setCursor(num); + le.centerOnCursor(); + } + else { + re.setCursor(num); + re.centerOnCursor(); + } +}; + +CodeMirrorDiffView.prototype._setOptions = function(opts) { + const { jQuery } = this; + this.settings = { + ...this.settings, + ...opts + }; + if (this.settings.hasOwnProperty('rhs_margin')) { + // dynami259*-6+cally swap the margin + if (this.settings.rhs_margin == 'left') { + this.element.find('.mergely-margin:last-child').insertAfter( + this.element.find('.mergely-canvas')); + } + else { + var target = this.element.find('.mergely-margin').last(); + target.appendTo(target.parent()); + } + } + if (this.settings.hasOwnProperty('sidebar')) { + // dynamically enable sidebars + if (this.settings.sidebar) { + this.element.find('.mergely-margin').css({display: 'block'}); + } + else { + this.element.find('.mergely-margin').css({display: 'none'}); + } + } + var le, re; + if (this.settings.hasOwnProperty('wrap_lines')) { + if (this.editor) { + le = this.editor[this.id + '-lhs']; + re = this.editor[this.id + '-rhs']; + le.setOption('lineWrapping', this.settings.wrap_lines); + re.setOption('lineWrapping', this.settings.wrap_lines); + } + } + if (this.settings.hasOwnProperty('line_numbers')) { + if (this.editor) { + le = this.editor[this.id + '-lhs']; + re = this.editor[this.id + '-rhs']; + le.setOption('lineNumbers', this.settings.line_numbers); + re.setOption('lineNumbers', this.settings.line_numbers); + } + } +}; + +CodeMirrorDiffView.prototype.options = function(opts) { + if (opts) { + this._setOptions(opts); + if (this.settings.autoresize) this.resize(); + if (this.settings.autoupdate) this.update(); + } + else { + return this.settings; + } +}; + +CodeMirrorDiffView.prototype.swap = function() { + if (this.lhs_cmsettings.readOnly || this.rhs_cmsettings.readOnly) return; + var le = this.editor[this.id + '-lhs']; + var re = this.editor[this.id + '-rhs']; + var tmp = re.getValue(); + re.setValue(le.getValue()); + le.setValue(tmp); +}; + +CodeMirrorDiffView.prototype.merge = function(side) { + var le = this.editor[this.id + '-lhs']; + var re = this.editor[this.id + '-rhs']; + if (side == 'lhs' && !this.lhs_cmsettings.readOnly) le.setValue(re.getValue()); + else if (!this.rhs_cmsettings.readOnly) re.setValue(le.getValue()); +}; + +CodeMirrorDiffView.prototype.summary = function() { + return { + numChanges: this.changes.length, + lhsLength: this.editor[this.id + '-lhs'].getValue().length, + rhsLength: this.editor[this.id + '-rhs'].getValue().length, + c: this.changes.filter(function (a) { + return a.op === 'c'; + }).length, + a: this.changes.filter(function (a) { + return a.op === 'a'; + }).length, + d: this.changes.filter(function (a) { + return a.op === 'd'; + }).length + } +}; + +CodeMirrorDiffView.prototype.get = function(side) { + var ed = this.editor[this.id + '-' + side]; + var t = ed.getValue(); + if (t == undefined) return ''; + return t; +}; + +CodeMirrorDiffView.prototype.clear = function(side) { + if (side == 'lhs' && this.lhs_cmsettings.readOnly) return; + if (side == 'rhs' && this.rhs_cmsettings.readOnly) return; + var ed = this.editor[this.id + '-' + side]; + ed.setValue(''); + delete this._current_diff; +}; + +CodeMirrorDiffView.prototype.cm = function(side) { + return this.editor[this.id + '-' + side]; +}; + +CodeMirrorDiffView.prototype.search = function(side, query, direction) { + var le = this.editor[this.id + '-lhs']; + var re = this.editor[this.id + '-rhs']; + var editor; + if (side == 'lhs') editor = le; + else editor = re; + direction = (direction == 'prev') ? 'findPrevious' : 'findNext'; + if ((editor.getSelection().length == 0) || (this.prev_query[side] != query)) { + this.cursor[this.id] = editor.getSearchCursor(query, { line: 0, ch: 0 }, false); + this.prev_query[side] = query; + } + var cursor = this.cursor[this.id]; + + if (cursor[direction]()) { + editor.setSelection(cursor.from(), cursor.to()); + } + else { + cursor = editor.getSearchCursor(query, { line: 0, ch: 0 }, false); + } +}; + +CodeMirrorDiffView.prototype.resize = function() { + // recalculate line height as it may be zoomed + this.em_height = null; + this.settings.resize(); + this._changing(this.id + '-lhs', this.id + '-rhs'); + this._set_top_offset(this.id + '-lhs'); +}; + +CodeMirrorDiffView.prototype.diff = function() { + var lhs = this.editor[this.id + '-lhs'].getValue(); + var rhs = this.editor[this.id + '-rhs'].getValue(); + var d = new diff(lhs, rhs, this.settings); + return d.normal_form(); +}; + +CodeMirrorDiffView.prototype.bind = function(el) { + const { jQuery, CodeMirror } = this; + this.trace('init', 'bind'); + this.element.hide(); + this.id = jQuery(el).attr('id'); + try { + // ensure the id is valid for jQuery + jQuery(`#${this.id}`); + } catch (ex) { + console.error(`jQuery failed to find mergely: #${this.id}`); + return; + } + this.changed_timeout = null; + this.chfns = {}; + this.chfns[this.id + '-lhs'] = []; + this.chfns[this.id + '-rhs'] = []; + this.prev_query = []; + this.cursor = []; + this._skipscroll = {}; + this.change_exp = new RegExp(/(\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/); + var merge_lhs_button; + var merge_rhs_button; + if (jQuery.button != undefined) { + //jquery ui + merge_lhs_button = ''; + merge_rhs_button = ''; + } + else { + // homebrew + var style = 'opacity:0.6;height:16px;background-color:#bfbfbf;cursor:pointer;text-align:center;color:#eee;border:1px solid #848484;margin-right:-15px;margin-top:-2px;'; + merge_lhs_button = '
<
'; + merge_rhs_button = '
>
'; + } + this.merge_rhs_button = jQuery(merge_rhs_button); + this.merge_lhs_button = jQuery(merge_lhs_button); + + // create the textarea and canvas elements + var height = '10px'; + var width = '10px'; + + var splash = jQuery('
'); + var canvasLhs = jQuery(`
`); + canvasLhs.find('#lhs-margin').attr('id', `${this.id}-lhs-margin`); + var editorLhs = jQuery(`
`); + editorLhs.eq(0).attr('id', `${this.id}-editor-lhs`); + editorLhs.find('#text-lhs').attr('id', `${this.id}-lhs`); + var canvasMid = jQuery(`
`); + canvasMid.find('#mergely-canvas').attr('id', `${this.id}-mergely-canvas`); + canvasMid.find('#lhs-rhs-canvas').attr('id', `${this.id}-lhs-${this.id}-rhs-canvas`); + + this.element.append(splash); + this.element.append(canvasLhs); + this.element.append(editorLhs); + this.element.append(canvasMid); + var canvasRhs = jQuery(`
`); + canvasRhs.find('#rhs-margin').attr('id', `${this.id}-rhs-margin`); + if (this.settings.rhs_margin == 'left') { + this.element.append(canvasRhs); + } + var editorRhs = jQuery(`
`); + editorRhs.eq(0).attr('id', `${this.id}-editor-rhs`); + editorRhs.find('#text-rhs').attr('id', `${this.id}-rhs`); + this.element.append(editorRhs); + if (this.settings.rhs_margin != 'left') { + this.element.append(canvasRhs); + } + if (!this.settings.sidebar) { + this.element.find('.mergely-margin').css({display: 'none'}); + } + if (['lgpl-separate-notice', 'gpl-separate-notice', 'mpl-separate-notice', 'commercial'].indexOf(this.settings.license) < 0) { + const _lic = { + 'lgpl': 'GNU LGPL v3.0', + 'gpl': 'GNU GPL v3.0', + 'mpl': 'MPL 1.1' + }; + var lic = _lic[this.settings.license]; + if (!lic) { + lic = _lic['lgpl']; + } + + const parenth = this.element.parent().height(); + const parentw = this.element.parent().width(); + const icon = 'data:image/png;base64,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'; + this.element.find('#mergely-splash').css({ + position: 'absolute', + zIndex: '100', + backgroundColor: '#fff', + border: '1px solid black', + height: '70px', + width: '300px', + left: (parentw - 300) / 2, + padding: '10px 10px 0 10px', + fontFamily: 'arial', + fontSize: '11px' + }).append('

mergelyThis software is a Combined Work using Mergely and is covered by the ' + lic + ' license. For the full license, see http://www.mergely.com/license.

'); + jQuery('body').one('click', function () { + jQuery('#mergely-splash').fadeOut(100, 'linear', function () { + jQuery('#mergely-splash').remove(); + }); + }); + } + + // check initialization + var rhstx; + try { + rhstx = this.element.find(`#${this.id}-rhs`).get(0); + } catch (ex) { + } + if (!rhstx) { + console.error('rhs textarea not defined - Mergely not initialized properly'); + return; + } + var lhstx; + try { + lhstx = this.element.find(`#${this.id}-lhs`).get(0); + } catch (ex) { + } + if (!lhstx) { + console.error('lhs textarea not defined - Mergely not initialized properly'); + return; + } + + // get current diff border color + var color = jQuery('