zotero/chrome/content/zotero/integration/addCitationDialog.js

805 lines
24 KiB
JavaScript

/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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_Citation_Dialog = new function () {
// Array value [0] is property name.
// Array value [1] is default value of property.
var _preserveData = {
"prefix":["value", ""],
"suffix":["value", ""],
"label":["selectedIndex", 0],
"locator":["value", ""],
"suppress-author":["checked", false]
};
var _accepted = false;
var _itemData = new Object();
var _multipleSourcesOn = false;
var _lastSelected = null;
var _previewShown = false;
var _suppressNextTreeSelect = false;
var _suppressNextListSelect = false;
var _locatorIndexArray = {};
var _locatorNameArray = {};
var _autoRegeneratePref;
var _acceptButton;
var _multipleSourceButton;
var _sortCheckbox;
var _citationList;
var _originalHTML;
var serial_number;
var io;
this.load = load;
this.toggleMultipleSources = toggleMultipleSources;
this.toggleEditor = toggleEditor;
this.treeItemSelected = treeItemSelected;
this.listItemSelected = listItemSelected;
this.up = up;
this.down = down;
this.add = add;
this.remove = remove;
this.setSortToggle = setSortToggle;
this.citationSortUnsort = citationSortUnsort;
this.confirmRegenerate = confirmRegenerate;
this.accept = accept;
this.cancel = cancel;
/*
* initialize add citation dialog
*/
function load() {
// make sure we are visible
window.setTimeout(function() {
var screenX = window.screenX;
var screenY = window.screenY;
var xRange = [window.screen.availLeft, window.screen.width-window.outerWidth];
var yRange = [window.screen.availTop, window.screen.height-window.outerHeight];
if(screenX < xRange[0] || screenX > xRange[1] || screenY < yRange[0] || screenY > yRange[1]) {
var targetX = Math.max(Math.min(screenX, xRange[1]), xRange[0]);
var targetY = Math.max(Math.min(screenY, yRange[1]), yRange[0]);
Zotero.debug("Moving window to "+targetX+", "+targetY);
window.moveTo(targetX, targetY);
}
}, 0);
document.documentElement.getButton("extra1").label = Zotero.getString("citation.multipleSources");
document.documentElement.getButton("extra2").label = Zotero.getString("citation.showEditor");
io = window.arguments[0].wrappedJSObject;
// find accept button
_acceptButton = document.getElementById("zotero-add-citation-dialog").getButton("accept");
_multipleSourceButton = document.documentElement.getButton("extra1");
_autoRegeneratePref = Zotero.Prefs.get("integration.autoRegenerate");
_citationList = document.getElementById("citation-list");
// Manipulated by _addItem(). Discriminates between cite instances
// based on the same item in the same citation. Internal throwaway variable,
// reset each time _multipleSourcesOn is set to true.
serial_number = 0;
// if a style with sortable citations, present checkbox
if(io.sortable) {
_sortCheckbox = document.getElementById("keepSorted");
_sortCheckbox.hidden = false;
_sortCheckbox.checked = !io.citation.properties.unsorted;
}
// load locators
var locators = Zotero.Cite.labels;
var menu = document.getElementById("label");
var label_list = document.getElementById("locator-type-popup");
var i = 0;
for(var value in locators) {
var locator = locators[value];
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
// add to list of labels
var child = document.createElement("menuitem");
child.setAttribute("value", value);
child.setAttribute("label", locatorLabel);
label_list.appendChild(child);
// add to array
_locatorIndexArray[locator] = i;
_locatorNameArray[i] = locator;
i++;
}
menu.selectedIndex = 0;
// load (from selectItemsDialog.js)
doLoad();
// if we already have a citation, load data from it
document.getElementById('editor').format = "RTF";
if(io.citation.citationItems.length) {
if(io.citation.citationItems.length === 1) {
// single citation
toggleMultipleSources(false);
_suppressNextTreeSelect = true;
// If we're in a different library, switch libraries
var id = io.citation.citationItems[0].id;
var collectionTreeRow = collectionsView.selectedTreeRow;
var item = Zotero.Items.get(id);
if(item.libraryID != collectionTreeRow.ref.libraryID) {
collectionsView.selectLibrary(item.libraryID);
}
var selected = itemsView.selectItem(id);
if(!selected) {
// If item wasn't found in current view, select library root
// and try again (in case we were in a collection of correct library)
collectionsView.selectLibrary(item.libraryID);
itemsView.selectItem(id);
}
for(var box in _preserveData) {
var property = _preserveData[box][0];
if(io.citation.citationItems[0][box]) {
if(box === "label") {
document.getElementById(box)[property] = _locatorIndexArray[io.citation.citationItems[0][box]];
} else {
document.getElementById(box)[property] = io.citation.citationItems[0][box];
}
}
}
} else {
// multiple citations
toggleMultipleSources(true);
var _itemData = {};
// There is a little thrashing here, with repeated writes and
// overwrites of node content. But sticking to the same
// workflow for all updates (node -> array -> io.citation) makes
// debugging a little less painful.
for(var i=0; i<io.citation.citationItems.length; i++) {
var item = Zotero.Items.get(io.citation.citationItems[i].id);
if(item) {
var itemNode = _addItem(item);
var itemDataID = itemNode.getAttribute("value");
_itemData[itemDataID] = {};
for(var box in _preserveData) {
var domBox = document.getElementById(box);
var property = _preserveData[box][0];
if("undefined" !== typeof io.citation.citationItems[i][box]) {
if(box === "label") {
domBox[property] = _locatorIndexArray[io.citation.citationItems[i][box]];
} else {
domBox[property] = io.citation.citationItems[i][box];
}
} else {
domBox[property] = _preserveData[box][1];
}
}
_itemSelected(itemDataID, true);
}
}
for (var box in _preserveData) {
document.getElementById(box).disabled = true;
}
}
// show user-editable edited citation
if(io.citation.properties.custom) {
toggleEditor(io.citation.properties.custom);
delete io.citation.properties.custom;
}
_updateAccept();
} else {
toggleMultipleSources(false);
}
}
/*
* turn on/off multiple sources item list
*/
function toggleMultipleSources(mode) {
if (mode === false || mode === true) {
_multipleSourcesOn = !mode;
}
_multipleSourcesOn = !_multipleSourcesOn;
var popup = document.defaultView;
var dialog = document.getElementById("zotero-add-citation-dialog");
if (dialog.getAttribute("height") == 1) {
popup.sizeToContent();
}
if(_multipleSourcesOn) {
_multipleSourceButton.label = Zotero.getString("citation.singleSource");
document.getElementById("multiple-sources").setAttribute("hidden", false);
if(dialog.getAttribute("width") <= 600) {
popup.resizeTo(750, dialog.getAttribute("height"));
}
//popup.moveBy((600 - 750)/2, 0);
serial_number = 0;
// The mode is forced only when run from load(), in which case
// the adding of items is done separately.
if (mode !== true) {
this.add(true);
}
} else {
_multipleSourceButton.label = Zotero.getString("citation.multipleSources");
document.getElementById("multiple-sources").setAttribute("hidden", true);
//popup.resizeTo(600, dialog.getAttribute("height"));
//popup.moveBy((750 - 600)/2, 0);
// enable all fields
for(var box in _preserveData) {
document.getElementById(box).disabled = false;
}
var itemID = false;
if (_citationList.selectedIndex > -1) {
var itemDataID = _citationList.getSelectedItem(0).getAttribute("value");
itemID = itemDataID.slice(0, itemDataID.indexOf(":"));
}
// delete item list
_itemData = new Object();
// delete all items
_clearCitationList();
// refresh
if (itemID) {
itemsView.wrappedJSObject.selectItem(itemID);
}
_updateAccept();
_updatePreview();
}
}
/*
* called when an item in the item selection tree is clicked
*/
function treeItemSelected() {
if(_suppressNextTreeSelect) {
_suppressNextTreeSelect = false;
_updateAccept();
return;
}
var items = itemsView.getSelectedItems(true); // treeview from xpcom/itemTreeView.js
var itemID = (items.length ? items[0] : false);
if(_multipleSourcesOn) {
// We can safely use itemID here, because none of these operations
// affect selected items; this is all about the tree and navigation.
// turn off highlight in selected item list
_suppressNextListSelect = true;
document.getElementById("citation-list").selectedIndex = -1;
// disable all fields
for(var box in _preserveData) {
document.getElementById(box).disabled = true;
}
// disable adding nothing
document.getElementById("add").disabled = !itemID;
document.getElementById("remove").disabled = true;
document.getElementById("up").disabled = true;
document.getElementById("down").disabled = true;
} else {
for(var box in _preserveData) {
document.getElementById(box).disabled = !itemID;
}
_updateAccept();
_updatePreview();
}
}
/*
* called when an item in the selected items list is clicked
*/
function listItemSelected() {
if(_suppressNextListSelect) {
_suppressNextListSelect = false;
_updateAccept();
return;
}
var selectedListItem = _citationList.getSelectedItem(0);
var selectedListIndex = _citationList.selectedIndex;
var itemDataID = (selectedListItem ? selectedListItem.getAttribute("value") : false);
_itemSelected(itemDataID);
// turn off highlight in item tree
_suppressNextTreeSelect = true;
document.getElementById("zotero-items-tree").view.selection.clearSelection();
document.getElementById("remove").disabled = !itemDataID;
document.getElementById("add").disabled = true;
_configListPosition(!itemDataID, selectedListIndex);
}
function _configListPosition(flag, selectedListIndex) {
if (selectedListIndex > 0) {
document.getElementById("up").disabled = flag;
} else {
document.getElementById("up").disabled = true;
}
if (-1 < selectedListIndex && selectedListIndex < (_citationList.getRowCount() - 1)) {
document.getElementById("down").disabled = flag;
} else {
document.getElementById("down").disabled = true;
}
}
function _move(direction) {
// automatically uncheck sorted checkbox if user is rearranging citation
if(_sortCheckbox && _sortCheckbox.checked) {
_sortCheckbox.checked = false;
setSortToggle();
}
var insertBeforeItem;
var selectedListItem = _citationList.getSelectedItem(0);
var selectedListIndex = _citationList.selectedIndex;
var itemDataID = selectedListItem.getAttribute("value");
if (direction === -1) {
insertBeforeItem = selectedListItem.previousSibling;
} else {
insertBeforeItem = selectedListItem.nextSibling.nextSibling;
}
var listItem = _citationList.removeChild(selectedListItem);
_citationList.insertBefore(listItem, insertBeforeItem);
_citationList.selectedIndex = (selectedListIndex + direction);
_itemSelected(itemDataID);
_updatePreview();
_configListPosition(false, (selectedListIndex + direction));
}
function up() {
_move(-1);
}
function down() {
_move(1);
}
/*
* Adds an item to the multipleSources list
*/
function add(first_item) {
var pos, len;
var item = itemsView.getSelectedItems()[0]; // treeview from xpcom/itemTreeView.js
if (!item) {
sortCitation();
_updateAccept();
_updatePreview();
return;
}
// Add to selection list and generate a new itemDataID for this cite.
var selectionNode = _addItem(item);
var itemDataID = selectionNode.getAttribute("value");
document.getElementById("add").disabled = !itemDataID;
// Save existing locator and affix field content, if any.
if (first_item) {
_itemSelected(itemDataID, true);
} else {
_itemSelected();
// set to defaults
for(var box in _preserveData) {
var property = _preserveData[box][0];
var default_value = _preserveData[box][1];
document.getElementById(box)[property] = default_value;
}
// Save default locator and affix element values to this multi-item.
_itemSelected(itemDataID, true);
}
for(var box in _preserveData) {
document.getElementById(box).disabled = true;
}
_citationList.ensureElementIsVisible(selectionNode);
// allow user to press OK
selectionNode = sortCitation(selectionNode);
_citationList.selectItem(selectionNode);
_updateAccept();
_updatePreview();
}
/*
* Deletes a citation from the multipleSources list
*/
function remove() {
var selectedListItem = _citationList.getSelectedItem(0);
var selectedListIndex = _citationList.selectedIndex;
var itemDataID = selectedListItem.getAttribute("value");
// remove from _itemData
delete _itemData[itemDataID];
_itemData[itemDataID] = undefined;
_lastSelected = null;
// remove from list
_citationList.removeChild(selectedListItem);
if (selectedListIndex >= _citationList.getRowCount()) {
selectedListIndex = _citationList.getRowCount() - 1;
}
_citationList.selectedIndex = selectedListIndex;
_updateAccept();
_updatePreview();
}
/*
* Sorts preview citations, if preview is open.
*/
function citationSortUnsort() {
setSortToggle();
sortCitation();
_updatePreview();
}
/*
* Sets the current sort toggle state persistently on the citation.
*/
function setSortToggle() {
if(!_sortCheckbox) return;
if(!_sortCheckbox.checked) {
io.citation.properties.unsorted = true;
} else {
io.citation.properties.unsorted = false;
}
return;
}
/*
* Sorts the list of citations
*/
function sortCitation(scrollToItem) {
if(!_sortCheckbox) return scrollToItem;
if(!_sortCheckbox.checked) {
io.citation.properties.unsorted = true;
return scrollToItem;
}
var scrollToItemID = false;
if (scrollToItem) {
scrollToItemID = scrollToItem.getAttribute("value");
}
_getCitation();
// delete all existing items from list
_clearCitationList();
// run preview function to re-sort, if it hasn't already been
// run
io.sort();
// add items back to list
scrollToItem = null;
for(var i=0; i<io.citation.sortedItems.length; i++) {
var itemID = io.citation.sortedItems[i][0].id;
var itemDataID = io.citation.sortedItems[i][1].tmpItemDataID;
var item = Zotero.Items.get(itemID);
// Don't increment serial_number, and use the
// existing itemDataID stored on the item in sortedItems
var itemNode = _addItem(item, itemDataID);
if(itemDataID == scrollToItemID) _citationList.selectedIndex = i;
if(scrollToItemID && itemDataID == scrollToItemID) scrollToItem = itemNode;
}
if(scrollToItem) _citationList.ensureElementIsVisible(scrollToItem);
return scrollToItem;
}
/*
* Ask whether to modifiy the preview
*/
function confirmRegenerate(focusShifted) {
if(document.getElementById('editor').value == _originalHTML || _originalHTML === undefined) {
// no changes; just update without asking
_updatePreview();
return;
}
if(_autoRegeneratePref == -1) {
if(focusShifted) { // only ask after onchange event; oninput is too
// frequent for this to be worthwhile
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var saveBehavior = { value: false };
var regenerate = promptService.confirmEx(
this.window,
Zotero.getString('integration.regenerate.title'),
Zotero.getString('integration.regenerate.body'),
promptService.STD_YES_NO_BUTTONS,
null, null, null,
Zotero.getString('integration.regenerate.saveBehavior'),
saveBehavior
);
if(saveBehavior.value) {
_autoRegeneratePref = (regenerate == 0 ? 1 : 0);
Zotero.Prefs.set("integration.autoRegenerate", _autoRegeneratePref);
}
if(regenerate == 0) {
_updatePreview();
}
}
} else if(_autoRegeneratePref == 1) {
_updatePreview();
}
}
/*
* Shows the edit pane
*/
function toggleEditor(text) {
var warning = document.getElementById('zotero-editor-warning');
var editor = document.getElementById('editor');
warning.hidden = _previewShown;
editor.hidden = _previewShown;
_previewShown = !_previewShown;
if(_previewShown) {
document.documentElement.getButton("extra2").label = Zotero.getString("citation.hideEditor");
if(text) {
io.preview().then(function(preview) {
_originalHTML = preview;
editor.value = text;
}).done();
} else {
_updatePreview();
}
} else {
document.documentElement.getButton("extra2").label = Zotero.getString("citation.showEditor");
}
}
/*
* called when accept button is clicked
*/
function accept() {
if(_accepted) return true;
_getCitation();
var isCustom = _previewShown && io.citation.citationItems.length // if a citation is selected
&& _originalHTML
&& document.getElementById('editor').value != _originalHTML // and citation has been edited
if(isCustom) {
var citation = document.getElementById('editor').value;
if(Zotero.Utilities.trim(citation) == "") {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var insert = promptService.confirm(window,
Zotero.getString("integration.emptyCitationWarning.title"),
Zotero.getString("integration.emptyCitationWarning.body"));
if(!insert) return false;
}
io.citation.properties.custom = citation;
}
io.accept();
_accepted = true;
return true;
}
/*
* called when cancel button is clicked
*/
function cancel() {
if(_accepted) return true;
io.citation.citationItems = new Array();
io.accept();
_accepted = true;
return true;
}
/*
* Updates the contents of the preview pane
*/
function _updatePreview() {
if(_previewShown) {
var editor = document.getElementById('editor');
_getCitation();
editor.readonly = !io.citation.citationItems.length;
if(io.citation.citationItems.length) {
io.preview().then(function(preview) {
editor.value = preview;
if(editor.initialized) {
_originalHTML = editor.value;
} else {
var eventListener = function() {
_originalHTML = editor.value;
editor.removeEventListener("tinymceInitialized", eventListener, false);
};
editor.addEventListener("tinymceInitialized", eventListener, false);
}
});
} else {
editor.value = "";
_originalHTML = "";
}
}
}
/*
* Controls whether the accept (OK) button should be enabled
*/
function _updateAccept(status) {
if(_multipleSourcesOn) {
_acceptButton.disabled = !_citationList.getRowCount();
// To prevent accidental data loss, do not allow change to
// single citation mode if multiple items are in selection
// list.
if (_citationList.getRowCount() > 1) {
_multipleSourceButton.disabled = true;
} else {
_multipleSourceButton.disabled = false;
}
} else {
collectionsView.addEventListener('load', () => {
_acceptButton.disabled = !itemsView.getSelectedItems().length;
});
}
}
/*
* called when an item is selected; if itemDataID is false, disables fields; if
* itemDataID is undefined, only updates _itemData array
*
* Note: This function no longer disables fields. That operation is
* now performed separately by explicit code.
*/
function _itemSelected(itemDataID, forceSave) {
if (forceSave) {
_lastSelected = itemDataID;
}
if(_lastSelected && !_itemData[_lastSelected]) {
_itemData[_lastSelected] = new Object();
}
for(var box in _preserveData) {
var domBox = document.getElementById(box);
var property = _preserveData[box][0];
// save property
if(_lastSelected) {
if(property == "label") {
_itemData[_lastSelected][box] = _locatorNameArray[domBox.selectedIndex];
} else {
_itemData[_lastSelected][box] = domBox[property];
}
}
// restore previous property
if(itemDataID) {
domBox.disabled = false;
if(_itemData[itemDataID] && _itemData[itemDataID][box] !== undefined) {
if(property == "label") {
domBox[property] = _locatorIndexArray[_itemData[itemDataID][box]];
} else {
domBox[property] = _itemData[itemDataID][box];
}
}
}
}
if(itemDataID !== undefined) _lastSelected = itemDataID;
}
/*
* updates io.citation to reflect selected items
*/
function _getCitation() {
var key;
io.citation.citationItems = new Array();
// use to map selectedIndexes back to page/paragraph/line
var locatorTypeElements = document.getElementById("label").getElementsByTagName("menuitem");
if(_multipleSourcesOn) {
_itemSelected(); // store locator info
var listLength = _citationList.getRowCount();
if(listLength) {
// generate citationItems
for(var i=0; i<listLength; i++) {
var itemDataID = _citationList.getItemAtIndex(i).getAttribute("value");
var citationItem = {};
for (key in _itemData[itemDataID]) {
// label is special everywhere
if (key === "label") {
citationItem.label = _locatorNameArray[_itemData[itemDataID].label];
} else if (_itemData[itemDataID][key]) {
citationItem[key] = _itemData[itemDataID][key];
}
}
citationItem["tmpItemDataID"] = itemDataID;
var itemID = itemDataID.slice(0, itemDataID.indexOf(":"));
citationItem.id = itemID;
io.citation.citationItems.push(citationItem);
}
}
} else {
var items = itemsView.getSelectedItems(true); // treeview from xpcom/itemTreeView.js
if(items.length) {
var citationItem = {};
citationItem.id = items[0];
for(var box in _preserveData) {
var property = _preserveData[box][0];
if(box == "label") {
citationItem[box] = _locatorNameArray[document.getElementById(box).selectedIndex];
} else {
var prop = document.getElementById(box)[property];
if(prop !== "" && prop !== false) citationItem[box] = prop;
}
}
if(!citationItem["locator"]) {
delete citationItem["locator"];
delete citationItem["label"];
}
io.citation.citationItems = [citationItem];
} else {
io.citation.citationItems = [];
}
}
}
/*
* Add an item to the item list (multiple sources only)
*/
function _addItem(item, forceID) {
var itemNode = document.createElement("listitem");
var itemDataID;
if (!forceID) {
serial_number += 1;
itemDataID = item.id + ":" + serial_number;
} else {
itemDataID = forceID;
}
itemNode.setAttribute("value", itemDataID);
itemNode.setAttribute("label", item.getDisplayTitle());
itemNode.setAttribute("class", "listitem-iconic");
itemNode.setAttribute("image", item.getImageSrc());
_citationList.appendChild(itemNode);
return itemNode;
}
/*
* Removes all items from the multiple sources list
*/
function _clearCitationList() {
while(_citationList.firstChild) _citationList.removeChild(_citationList.firstChild);
}
}