zotero/chrome/content/zotero/merge.js
Abe Jellinek 1dd3812e00 Deprecate setFontSize(), replace with new Zotero.UIProperties
This new XPCOM module handles font size and UI density and automatically keeps
registered roots up to date when prefs change. Roots receive a
UIPropertiesChanged event after their properties are updated.
2024-01-24 03:39:09 -05:00

352 lines
8.7 KiB
JavaScript

/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2023 Corporation for Digital Scholarship
Vienna, Virginia, USA
https://www.zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
var Zotero_Merge_Window = new function () {
var _wizard = null;
var _wizardPage = null;
var _mergeGroup = null;
var _numObjects = null;
var _io = null;
var _conflicts = null;
var _merged = [];
var _pos = -1;
this.init = function () {
_wizard = document.getElementById('merge-window');
_wizardPage = document.querySelector('wizardpage');
_mergeGroup = document.querySelector('merge-group');
_wizard.setAttribute('width', Math.min(980, screen.width - 20));
_wizard.setAttribute('height', Math.min(718, screen.height - 30));
_wizard.addEventListener('wizardback', this.onBack.bind(this));
_wizard.addEventListener('wizardnext', this.onNext.bind(this));
_wizard.addEventListener('wizardcancel', this.onCancel.bind(this));
_wizard.addEventListener('wizardfinish', this.onFinish.bind(this));
// Set font size from pref
Zotero.UIProperties.registerRoot(_wizardPage);
_wizard.getButton('cancel').setAttribute('label', Zotero.getString('sync.cancel'));
_io = window.arguments[0];
// Not totally clear when this is necessary
if (window.arguments[0].wrappedJSObject) {
_io = window.arguments[0].wrappedJSObject;
}
_conflicts = _io.dataIn.conflicts;
if (!_conflicts.length) {
// TODO: handle no conflicts
return;
}
if (_io.dataIn.type) {
_mergeGroup.type = _io.dataIn.type;
}
_mergeGroup.leftCaption = _io.dataIn.captions[0];
_mergeGroup.rightCaption = _io.dataIn.captions[1];
_mergeGroup.mergeCaption = _io.dataIn.captions[2];
_resolveAllCheckbox = document.getElementById('resolve-all');
if (_conflicts.length == 1) {
_resolveAllCheckbox.hidden = true;
}
else {
_mergeGroup.onSelectionChange = _updateResolveAllCheckbox;
}
_numObjects = document.getElementById('zotero-merge-num-objects');
document.getElementById('zotero-merge-total-objects').value = _conflicts.length;
this.onNext();
}
this.onBack = function () {
_merged[_pos] = _getCurrentMergeInfo();
_pos--;
if (_pos == 0) {
_wizard.canRewind = false;
}
_updateGroup();
this.updateNextFinish();
}
this.onNext = function () {
// First page
if (_pos == -1) {
_wizard.canRewind = false;
}
// Subsequent pages
else {
_wizard.canRewind = true;
_merged[_pos] = _getCurrentMergeInfo();
}
_pos++;
try {
_updateGroup();
}
catch (e) {
_error(e);
return;
}
_updateResolveAllCheckbox();
this.updateNextFinish();
return false;
}
this.onFinish = function () {
// If using one side for all remaining, update merge object
if (!_isLastConflict() && _resolveAllCheckbox.checked) {
let side = _mergeGroup.rightPane.getAttribute("selected") == "true" ? 'right' : 'left'
for (let i = _pos; i < _conflicts.length; i++) {
_merged[i] = {
data: _getMergeDataWithSide(i, side),
selected: side
};
}
}
else {
_merged[_pos] = _getCurrentMergeInfo();
}
_merged.forEach(function (x, i, a) {
// Add key
x.data.key = _conflicts[i].left.key || _conflicts[i].right.key;
// Add back version
if (x.data) {
x.data.version = _conflicts[i][x.selected].version;
}
})
_io.dataOut = _merged;
return true;
}
this.onCancel = function () {
// if already merged, ask
}
this.updateNextFinish = function () {
if (_isLastConflict() || _resolveAllCheckbox.checked) {
// The Mozilla wizard CE checks 'next' to determine if it's on the last page
_wizardPage.next = '';
_showFinishButton()
}
else {
_wizardPage.next = 'page-id';
_showNextButton();
}
}
function _updateGroup() {
// Adjust counter
_numObjects.value = _pos + 1;
let data = {};
Object.assign(data, _conflicts[_pos]);
var mergeInfo = _getMergeInfo(_pos);
data.merge = mergeInfo.data;
data.selected = mergeInfo.selected;
if (!_conflicts[_pos].libraryID) {
throw new Error("libraryID not provided in conflict object");
}
_mergeGroup.libraryID = _conflicts[_pos].libraryID;
_mergeGroup.data = data;
_updateResolveAllCheckbox();
}
function _getCurrentMergeInfo() {
return {
data: _mergeGroup.merged,
selected: _mergeGroup.leftPane.getAttribute("selected") == "true" ? "left" : "right"
};
}
/**
* Get the default or previously chosen merge info for a given position
*
* @param {Integer} pos
* @return {Object} - Object with 'data' (JSON field data) and 'selected' ('left', 'right') properties
*/
function _getMergeInfo(pos) {
// If data already selected, use that
if (_merged[pos]) {
return _merged[pos];
}
// If either side was deleted, use other side
if (_conflicts[pos].left.deleted) {
let mergeInfo = {
data: {},
selected: 'right'
};
Object.assign(mergeInfo.data, _conflicts[pos].right);
return mergeInfo;
}
if (_conflicts[pos].right.deleted) {
let mergeInfo = {
data: {},
selected: 'left'
};
Object.assign(mergeInfo.data, _conflicts[pos].left);
return mergeInfo;
}
// Apply changes from each side and pick most recent version for conflicting fields
var mergeInfo = {
data: {}
};
Object.assign(mergeInfo.data, _conflicts[pos].left)
Zotero.DataObjectUtilities.applyChanges(mergeInfo.data, _conflicts[pos].changes);
if (_conflicts[pos].left.dateModified > _conflicts[pos].right.dateModified) {
var side = 0;
}
// Use remote if remote Date Modified is later or same
else {
var side = 1;
}
Zotero.DataObjectUtilities.applyChanges(
mergeInfo.data, _conflicts[pos].conflicts.map(x => x[side])
);
mergeInfo.selected = side ? 'right' : 'left';
return mergeInfo;
}
/**
* Get the merge data using a given side at a given position
*
* @param {Integer} pos
* @param {String} side - 'left' or 'right'
* @return {Object} - JSON field data
*/
function _getMergeDataWithSide(pos, side) {
if (!side) {
throw new Error("Side not provided");
}
// If either side is deleted, nothing to merge
if (_conflicts[pos].left.deleted || _conflicts[pos].right.deleted) {
return _conflicts[pos][side];
}
var data = {};
Object.assign(data, _conflicts[pos].left)
Zotero.DataObjectUtilities.applyChanges(data, _conflicts[pos].changes);
Zotero.DataObjectUtilities.applyChanges(
data, _conflicts[pos].conflicts.map(x => x[side == 'left' ? 0 : 1])
);
return data;
}
function _updateResolveAllCheckbox() {
if (_mergeGroup.rightPane.getAttribute("selected") == 'true') {
var label = 'resolveAllRemote';
}
else {
var label = 'resolveAllLocal';
}
_resolveAllCheckbox.label = Zotero.getString('sync.conflict.' + label);
}
function _isLastConflict() {
return (_pos + 1) == _conflicts.length;
}
function _showNextButton() {
var nextButton = _wizard.getButton("next");
if (Zotero.isMac) {
nextButton.setAttribute("hidden", "false");
_wizard.getButton("finish").setAttribute("hidden", "true");
}
else {
let deck = document.querySelector(".wizard-next-deck");
deck.selectedIndex = 1;
}
_setInstructionsString(nextButton.label);
}
function _showFinishButton() {
var finishButton = _wizard.getButton("finish");
if (Zotero.isMac) {
_wizard.getButton("next").setAttribute("hidden", "true");
finishButton.setAttribute("hidden", "false");
}
// Windows uses a deck to switch between the Next and Finish buttons
// TODO: check Linux
else {
let deck = document.querySelector(".wizard-next-deck");
deck.selectedIndex = 0;
}
_setInstructionsString(finishButton.label);
}
function _setInstructionsString(buttonName) {
switch (_mergeGroup.type) {
case 'file':
var msg = 'fileChanged';
break;
default:
// TODO: maybe don't always call it 'item'
var msg = 'itemChanged';
}
msg = Zotero.getString('sync.conflict.' + msg, buttonName)
document.getElementById('zotero-merge-instructions').value = msg;
}
function _error(e) {
Zotero.debug(e);
_io.error = e;
_wizard.getButton('cancel').click();
}
}