www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

merge.js (9602B)


      1 /*
      2     ***** BEGIN LICENSE BLOCK *****
      3     
      4     Copyright © 2009 Center for History and New Media
      5                      George Mason University, Fairfax, Virginia, USA
      6                      http://zotero.org
      7     
      8     This file is part of Zotero.
      9     
     10     Zotero is free software: you can redistribute it and/or modify
     11     it under the terms of the GNU Affero General Public License as published by
     12     the Free Software Foundation, either version 3 of the License, or
     13     (at your option) any later version.
     14     
     15     Zotero is distributed in the hope that it will be useful,
     16     but WITHOUT ANY WARRANTY; without even the implied warranty of
     17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18     GNU Affero General Public License for more details.
     19     
     20     You should have received a copy of the GNU Affero General Public License
     21     along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
     22     
     23     ***** END LICENSE BLOCK *****
     24 */
     25 
     26 var Zotero_Merge_Window = new function () {
     27 	var _wizard = null;
     28 	var _wizardPage = null;
     29 	var _mergeGroup = null;
     30 	var _numObjects = null;
     31 	
     32 	var _io = null;
     33 	var _conflicts = null;
     34 	var _merged = [];
     35 	var _pos = -1;
     36 	
     37 	this.init = function () {
     38 		_wizard = document.getElementsByTagName('wizard')[0];
     39 		_wizardPage = document.getElementsByTagName('wizardpage')[0];
     40 		_mergeGroup = document.getElementsByTagName('zoteromergegroup')[0];
     41 		
     42 		_wizard.setAttribute('width', Math.min(980, screen.width - 20));
     43 		_wizard.setAttribute('height', Math.min(718, screen.height - 30));
     44 		
     45 		// Set font size from pref
     46 		Zotero.setFontSize(_wizardPage);
     47 		
     48 		_wizard.getButton('cancel').setAttribute('label', Zotero.getString('sync.cancel'));
     49 		
     50 		_io = window.arguments[0];
     51 		// Not totally clear when this is necessary
     52 		if (window.arguments[0].wrappedJSObject) {
     53 			_io = window.arguments[0].wrappedJSObject;
     54 		}
     55 		_conflicts = _io.dataIn.conflicts;
     56 		if (!_conflicts.length) {
     57 			// TODO: handle no conflicts
     58 			return;
     59 		}
     60 		
     61 		if (_io.dataIn.type) {
     62 			_mergeGroup.type = _io.dataIn.type;
     63 		}
     64 		_mergeGroup.leftCaption = _io.dataIn.captions[0];
     65 		_mergeGroup.rightCaption = _io.dataIn.captions[1];
     66 		_mergeGroup.mergeCaption = _io.dataIn.captions[2];
     67 		
     68 		_resolveAllCheckbox = document.getElementById('resolve-all');
     69 		if (_conflicts.length == 1) {
     70 			_resolveAllCheckbox.hidden = true;
     71 		}
     72 		else {
     73 			_mergeGroup.onSelectionChange = _updateResolveAllCheckbox;
     74 		}
     75 		
     76 		_numObjects = document.getElementById('zotero-merge-num-objects');
     77 		document.getElementById('zotero-merge-total-objects').value = _conflicts.length;
     78 		
     79 		this.onNext();
     80 	}
     81 	
     82 	
     83 	this.onBack = function () {
     84 		_merged[_pos] = _getCurrentMergeInfo();
     85 		
     86 		_pos--;
     87 		
     88 		if (_pos == 0) {
     89 			_wizard.canRewind = false;
     90 		}
     91 		
     92 		_updateGroup();
     93 		
     94 		var nextButton = _wizard.getButton("next");
     95 		
     96 		if (Zotero.isMac) {
     97 			nextButton.setAttribute("hidden", "false");
     98 			_wizard.getButton("finish").setAttribute("hidden", "true");
     99 		}
    100 		else {
    101 			var buttons = document.getAnonymousElementByAttribute(_wizard, "anonid", "Buttons");
    102 			var deck = document.getAnonymousElementByAttribute(buttons, "anonid", "WizardButtonDeck");
    103 			deck.selectedIndex = 1;
    104 		}
    105 		
    106 		_setInstructionsString(nextButton.label);
    107 	}
    108 	
    109 	
    110 	this.onNext = function () {
    111 		// At end or resolving all
    112 		if (_pos + 1 == _conflicts.length || _resolveAllCheckbox.checked) {
    113 			return true;
    114 		}
    115 		
    116 		// First page
    117 		if (_pos == -1) {
    118 			_wizard.canRewind = false;
    119 		}
    120 		// Subsequent pages
    121 		else {
    122 			_wizard.canRewind = true;
    123 			_merged[_pos] = _getCurrentMergeInfo();
    124 		}
    125 		
    126 		_pos++;
    127 		
    128 		try {
    129 			_updateGroup();
    130 		}
    131 		catch (e) {
    132 			_error(e);
    133 			return;
    134 		}
    135 		
    136 		_updateResolveAllCheckbox();
    137 		
    138 		if (_isLastConflict()) {
    139 			_showFinishButton();
    140 		}
    141 		else {
    142 			_showNextButton();
    143 		}
    144 		
    145 		return false;
    146 	}
    147 	
    148 	
    149 	this.onFinish = function () {
    150 		// If using one side for all remaining, update merge object
    151 		if (!_isLastConflict() && _resolveAllCheckbox.checked) {
    152 			let side = _mergeGroup.rightpane.getAttribute("selected") == "true" ? 'right' : 'left'
    153 			for (let i = _pos; i < _conflicts.length; i++) {
    154 				_merged[i] = {
    155 					data: _getMergeDataWithSide(i, side),
    156 					selected: side
    157 				};
    158 			}
    159 		}
    160 		else {
    161 			_merged[_pos] = _getCurrentMergeInfo();
    162 		}
    163 		
    164 		_merged.forEach(function (x, i, a) {
    165 			// Add key
    166 			x.data.key = _conflicts[i].left.key || _conflicts[i].right.key;
    167 			// Add back version
    168 			if (x.data) {
    169 				x.data.version = _conflicts[i][x.selected].version;
    170 			}
    171 		})
    172 		
    173 		_io.dataOut = _merged;
    174 		return true;
    175 	}
    176 	
    177 	
    178 	this.onCancel = function () {
    179 		// if already merged, ask
    180 	}
    181 	
    182 	
    183 	this.onResolveAllChange = function (resolveAll) {
    184 		if (resolveAll || _isLastConflict()) {
    185 			_showFinishButton();
    186 		}
    187 		else {
    188 			_showNextButton();
    189 		}
    190 	}
    191 	
    192 	
    193 	function _updateGroup() {
    194 		// Adjust counter
    195 		_numObjects.value = _pos + 1;
    196 		
    197 		let data = {};
    198 		Object.assign(data, _conflicts[_pos]);
    199 		var mergeInfo = _getMergeInfo(_pos);
    200 		data.merge = mergeInfo.data;
    201 		data.selected = mergeInfo.selected;
    202 		if (!_conflicts[_pos].libraryID) {
    203 			throw new Error("libraryID not provided in conflict object");
    204 		}
    205 		_mergeGroup.libraryID = _conflicts[_pos].libraryID;
    206 		_mergeGroup.data = data;
    207 		
    208 		_updateResolveAllCheckbox();
    209 	}
    210 	
    211 	
    212 	function _getCurrentMergeInfo() {
    213 		return {
    214 			data: _mergeGroup.merged,
    215 			selected: _mergeGroup.leftpane.getAttribute("selected") == "true" ? "left" : "right"
    216 		};
    217 	}
    218 	
    219 	
    220 	/**
    221 	 * Get the default or previously chosen merge info for a given position
    222 	 *
    223 	 * @param {Integer} pos
    224 	 * @return {Object} - Object with 'data' (JSON field data) and 'selected' ('left', 'right') properties
    225 	 */
    226 	function _getMergeInfo(pos) {
    227 		// If data already selected, use that
    228 		if (_merged[pos]) {
    229 			return _merged[pos];
    230 		}
    231 		// If either side was deleted, use other side
    232 		if (_conflicts[pos].left.deleted) {
    233 			let mergeInfo = {
    234 				data: {},
    235 				selected: 'right'
    236 			};
    237 			Object.assign(mergeInfo.data, _conflicts[pos].right);
    238 			return mergeInfo;
    239 		}
    240 		if (_conflicts[pos].right.deleted) {
    241 			let mergeInfo = {
    242 				data: {},
    243 				selected: 'left'
    244 			};
    245 			Object.assign(mergeInfo.data, _conflicts[pos].left);
    246 			return mergeInfo;
    247 		}
    248 		// Apply changes from each side and pick most recent version for conflicting fields
    249 		var mergeInfo = {
    250 			data: {}
    251 		};
    252 		Object.assign(mergeInfo.data, _conflicts[pos].left)
    253 		Zotero.DataObjectUtilities.applyChanges(mergeInfo.data, _conflicts[pos].changes);
    254 		if (_conflicts[pos].left.dateModified > _conflicts[pos].right.dateModified) {
    255 			var side = 0;
    256 		}
    257 		// Use remote if remote Date Modified is later or same
    258 		else {
    259 			var side = 1;
    260 		}
    261 		Zotero.DataObjectUtilities.applyChanges(
    262 			mergeInfo.data, _conflicts[pos].conflicts.map(x => x[side])
    263 		);
    264 		mergeInfo.selected = side ? 'right' : 'left';
    265 		return mergeInfo;
    266 	}
    267 	
    268 	
    269 	/**
    270 	 * Get the merge data using a given side at a given position
    271 	 *
    272 	 * @param {Integer} pos
    273 	 * @param {String} side - 'left' or 'right'
    274 	 * @return {Object} - JSON field data
    275 	 */
    276 	function _getMergeDataWithSide(pos, side) {
    277 		if (!side) {
    278 			throw new Error("Side not provided");
    279 		}
    280 		
    281 		if (_conflicts[pos].left.deleted || _conflicts[pos].right.deleted) {
    282 			return _conflicts[pos][side];
    283 		}
    284 		
    285 		var data = {};
    286 		Object.assign(data, _conflicts[pos].left)
    287 		Zotero.DataObjectUtilities.applyChanges(data, _conflicts[pos].changes);
    288 		Zotero.DataObjectUtilities.applyChanges(
    289 			data, _conflicts[pos].conflicts.map(x => x[side == 'left' ? 0 : 1])
    290 		);
    291 		return data;
    292 	}
    293 	
    294 	
    295 	function _updateResolveAllCheckbox() {
    296 		if (_mergeGroup.type == 'file') {
    297 			if (_mergeGroup.rightpane.getAttribute("selected") == 'true') {
    298 				var label = 'resolveAllRemote';
    299 			}
    300 			else {
    301 				var label = 'resolveAllLocal';
    302 			}
    303 		}
    304 		else {
    305 			if (_mergeGroup.rightpane.getAttribute("selected") == 'true') {
    306 				var label = 'resolveAllRemoteFields';
    307 			}
    308 			else {
    309 				var label = 'resolveAllLocalFields';
    310 			}
    311 		}
    312 		_resolveAllCheckbox.label = Zotero.getString('sync.conflict.' + label);
    313 	}
    314 	
    315 	
    316 	function _isLastConflict() {
    317 		return (_pos + 1) == _conflicts.length;
    318 	}
    319 	
    320 	
    321 	function _showNextButton() {
    322 		var nextButton = _wizard.getButton("next");
    323 		
    324 		if (Zotero.isMac) {
    325 			nextButton.setAttribute("hidden", "false");
    326 			_wizard.getButton("finish").setAttribute("hidden", "true");
    327 		}
    328 		else {
    329 			var buttons = document.getAnonymousElementByAttribute(_wizard, "anonid", "Buttons");
    330 			var deck = document.getAnonymousElementByAttribute(buttons, "anonid", "WizardButtonDeck");
    331 			deck.selectedIndex = 1;
    332 		}
    333 		
    334 		_setInstructionsString(nextButton.label);
    335 	}
    336 	
    337 	
    338 	function _showFinishButton() {
    339 		var finishButton = _wizard.getButton("finish");
    340 		
    341 		if (Zotero.isMac) {
    342 			_wizard.getButton("next").setAttribute("hidden", "true");
    343 			finishButton.setAttribute("hidden", "false");
    344 		}
    345 		// Windows uses a deck to switch between the Next and Finish buttons
    346 		// TODO: check Linux
    347 		else {
    348 			var buttons = document.getAnonymousElementByAttribute(_wizard, "anonid", "Buttons");
    349 			var deck = document.getAnonymousElementByAttribute(buttons, "anonid", "WizardButtonDeck");
    350 			deck.selectedIndex = 0;
    351 		}
    352 		
    353 		_setInstructionsString(finishButton.label);
    354 	}
    355 	
    356 	
    357 	function _setInstructionsString(buttonName) {
    358 		switch (_mergeGroup.type) {
    359 			case 'file':
    360 				var msg = 'fileChanged';
    361 				break;
    362 			
    363 			default:
    364 				// TODO: maybe don't always call it 'item'
    365 				var msg = 'itemChanged';
    366 		}
    367 		
    368 		msg = Zotero.getString('sync.conflict.' + msg, buttonName)
    369 		document.getElementById('zotero-merge-instructions').value = msg;
    370 	}
    371 	
    372 	
    373 	function _error(e) {
    374 		Zotero.debug(e);
    375 		_io.error = e;
    376 		_wizard.getButton('cancel').click();
    377 	}
    378 }