zotero/chrome/content/zotero/integration/quickFormat.js
Abe Jellinek f81b4b071f fx-compat: Correct create[XUL]Element[NS]() calls
This fixes the dictionary manager and various other places where a XUL
element was being created with createElement(), and also simplifies a lot
of createElementNS(HTML_NS, ...) calls.

This might cause some minor regressions but everything tested has
worked. Not touching preferences (#2659) or bindings.
2022-06-29 11:14:40 -04:00

1585 lines
50 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2011 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 *****
*/
Components.utils.import("resource://gre/modules/Services.jsm");
var Zotero_QuickFormat = new function () {
const pixelRe = /^([0-9]+)px$/
const specifiedLocatorRe = /^(?:,? *(p{1,2})(?:\. *| *)|:)([0-9\-]+) *$/;
const yearRe = /,? *([0-9]+) *(B[. ]*C[. ]*(?:E[. ]*)?|A[. ]*D[. ]*|C[. ]*E[. ]*)?$/i;
const locatorRe = /(?:,? *(p{0,2})\.?|(\:)) *([0-9\-]+)$/i;
const creatorSplitRe = /(?:,| *(?:and|\&)) +/;
const charRe = /[\w\u007F-\uFFFF]/;
const numRe = /^[0-9\-]+$/;
var initialized, io, qfs, qfi, qfiWindow, qfiDocument, qfe, qfb, qfbHeight, qfGuidance,
keepSorted, showEditor, referencePanel, referenceBox, referenceHeight = 0,
separatorHeight = 0, currentLocator, currentLocatorLabel, currentSearchTime, dragging,
panel, panelPrefix, panelSuffix, panelSuppressAuthor, panelLocatorLabel, panelLocator,
panelLibraryLink, panelInfo, panelRefersToBubble, panelFrameHeight = 0, accepted = false;
var locatorLocked = true;
var locatorNode = null;
var _searchPromise;
const SEARCH_TIMEOUT = 250;
const SHOWN_REFERENCES = 7;
/**
* Pre-initialization, when the dialog has loaded but has not yet appeared
*/
this.onDOMContentLoaded = function(event) {
if(event.target === document) {
initialized = true;
io = window.arguments[0].wrappedJSObject;
Zotero.debug(`Quick Format received citation:`);
Zotero.debug(JSON.stringify(io.citation.toJSON()));
if (io.disableClassicDialog) {
document.getElementById('classic-view').hidden = true;
}
// Only hide chrome on Windows or Mac
if(Zotero.isMac) {
document.documentElement.setAttribute("drawintitlebar", true);
} else if(Zotero.isWin) {
document.documentElement.setAttribute("hidechrome", true);
}
// Include a different key combo in message on Mac
if(Zotero.isMac) {
var qf = document.querySelector('.citation-dialog.guidance');
qf && qf.setAttribute('about', qf.getAttribute('about') + "Mac");
}
new WindowDraggingElement(document.querySelector("window.citation-dialog"), window);
qfs = document.querySelector(".citation-dialog.search");
qfi = document.querySelector(".citation-dialog.iframe");
qfb = document.querySelector(".citation-dialog.entry");
qfbHeight = qfb.scrollHeight;
referencePanel = document.querySelector(".citation-dialog.reference-panel");
referenceBox = document.querySelector(".citation-dialog.reference-list");
if (Zotero.isWin) {
referencePanel.style.marginTop = "-29px";
if (Zotero.Prefs.get('integration.keepAddCitationDialogRaised')) {
qfb.setAttribute("square", "true");
}
}
// With fx60 and drawintitlebar=true Firefox calculates the minHeight
// as titlebar+maincontent, so we have hack around that here.
else if (Zotero.isMac) {
qfb.style.marginBottom = "-28px";
}
keepSorted = document.getElementById("keep-sorted");
showEditor = document.getElementById("show-editor");
if(keepSorted && io.sortable) {
keepSorted.hidden = false;
if(!io.citation.properties.unsorted) {
keepSorted.setAttribute("checked", "true");
}
}
// Nodes for citation properties panel
panel = document.getElementById("citation-properties");
if (panel) {
panelPrefix = document.getElementById("prefix");
panelSuffix = document.getElementById("suffix");
panelSuppressAuthor = document.getElementById("suppress-author");
panelLocatorLabel = document.getElementById("locator-label");
panelLocator = document.getElementById("locator");
panelInfo = document.getElementById("citation-properties-info");
panelLibraryLink = document.getElementById("citation-properties-library-link");
// add labels to popup
var locators = Zotero.Cite.labels;
var menu = document.getElementById("locator-label");
var labelList = document.getElementById("locator-label-popup");
for(var locator of locators) {
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
// add to list of labels
var child = document.createXULElement("menuitem");
child.setAttribute("value", locator);
child.setAttribute("label", locatorLabel);
labelList.appendChild(child);
}
menu.selectedIndex = 0;
}
// Don't need to set noautohide dynamically on these platforms, so do it now
if(Zotero.isMac || Zotero.isWin) {
referencePanel.setAttribute("noautohide", true);
}
} else if (event.target === qfi.contentDocument) {
qfiWindow = qfi.contentWindow;
qfiDocument = qfi.contentDocument;
qfb.addEventListener("click", _onQuickSearchClick, false);
qfb.addEventListener("keypress", _onQuickSearchKeyPress, false);
qfe = qfiDocument.querySelector(".citation-dialog.editor");
qfe.addEventListener("drop", _onBubbleDrop, false);
qfe.addEventListener("paste", _onPaste, false);
if (Zotero_QuickFormat.citingNotes) {
_quickFormat();
}
}
}
/**
* Initialize add citation dialog
*/
this.onLoad = async function (event) {
try {
if (event.target !== document) return;
// make sure we are visible
let resizePromise = (async function () {
await Zotero.Promise.delay();
window.resizeTo(window.outerWidth, qfb.clientHeight);
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);
}
qfGuidance = document.querySelector('.citation-dialog.guidance');
qfGuidance && qfGuidance.show();
_refocusQfe();
})();
window.focus();
qfe.focus();
// load citation data
if (io.citation.citationItems.length) {
// hack to get spacing right
let event = new KeyboardEvent(
"keypress",
{
key: " ",
code: "Space",
bubbles: true,
cancelable: true,
}
);
qfe.dispatchEvent(event);
await resizePromise;
var node = qfe.firstChild;
node.nodeValue = "";
_showCitation(node);
_resize();
}
}
catch (e) {
Zotero.logError(e);
}
};
function _refocusQfe() {
referencePanel.blur();
window.focus();
qfe.focus();
}
/**
* Gets the content of the text node that the cursor is currently within
*/
function _getCurrentEditorTextNode() {
var selection = qfiWindow.getSelection();
if (!selection) return false;
var range = selection.getRangeAt(0);
var node = range.startContainer;
if(node !== range.endContainer) return false;
if(node.nodeType === Node.TEXT_NODE) return node;
// Range could be referenced to the body element
if(node === qfe) {
var offset = range.startOffset;
if(offset !== range.endOffset) return false;
node = qfe.childNodes[Math.min(qfe.childNodes.length-1, offset)];
if(node.nodeType === Node.TEXT_NODE) return node;
}
return false;
}
/**
* Gets text within the currently selected node
* @param {Boolean} [clear] If true, also remove these nodes
*/
function _getEditorContent(clear) {
var node = _getCurrentEditorTextNode();
return node ? node.wholeText : false;
}
/**
* Updates currentLocator based on a string
* @param {String} str String to search for locator
* @return {String} str without locator
*/
function _updateLocator(str) {
m = locatorRe.exec(str);
if(m && (m[1] || m[2] || m[3].length !== 4) && m.index > 0) {
currentLocator = m[3];
str = str.substr(0, m.index)+str.substring(m.index+m[0].length);
}
return str;
}
/**
* Does the dirty work of figuring out what the user meant to type
*/
var _quickFormat = Zotero.Promise.coroutine(function* () {
var str = _getEditorContent();
if (str && str.match(/\s$/)) {
locatorLocked = true;
}
var haveConditions = false;
const etAl = " et al.";
var m,
year = false,
isBC = false,
dateID = false;
currentLocator = false;
currentLocatorLabel = false;
// check for adding a number onto a previous page number
if(!locatorLocked && numRe.test(str)) {
// add to previous cite
var node = _getCurrentEditorTextNode();
let citationItem = JSON.parse(locatorNode && locatorNode.dataset.citationItem || "null");
if (citationItem) {
if (!("locator" in citationItem)) {
citationItem.locator = "";
}
citationItem.locator += str;
locatorNode.dataset.citationItem = JSON.stringify(citationItem);
locatorNode.textContent = _buildBubbleString(citationItem);
node.nodeValue = "";
_clearEntryList();
return;
}
}
if(str && str.length > 1) {
// check for specified locator
m = specifiedLocatorRe.exec(str);
if(m) {
if(m.index === 0) {
// add to previous cite
var node = _getCurrentEditorTextNode();
var prevNode = locatorLocked ? node.previousSibling : locatorNode;
let citationItem = JSON.parse(prevNode && prevNode.dataset.citationItem || "null");
if (citationItem) {
citationItem.locator = m[2];
prevNode.dataset.citationItem = JSON.stringify(citationItem);
prevNode.textContent = _buildBubbleString(citationItem);
node.nodeValue = "";
_clearEntryList();
locatorLocked = false;
locatorNode = prevNode;
return;
}
}
// TODO support types other than page
currentLocator = m[2];
str = str.substring(0, m.index);
}
str = _updateLocator(str);
// check for year and pages
m = yearRe.exec(str);
if(m) {
year = parseInt(m[1]);
isBC = m[2] && m[2][0] === "B";
str = str.substr(0, m.index)+str.substring(m.index+m[0].length);
}
if(year) str += " "+year;
var s = new Zotero.Search();
str = str.replace(/ (?:&|and) /g, " ", "g");
str = str.replace(/^,/, '');
if(charRe.test(str)) {
Zotero.debug("QuickFormat: QuickSearch: "+str);
// Exclude feeds
Zotero.Feeds.getAll()
.forEach(feed => s.addCondition("libraryID", "isNot", feed.libraryID));
if (Zotero_QuickFormat.citingNotes) {
s.addCondition("quicksearch-titleCreatorYearNote", "contains", str);
}
else {
s.addCondition("quicksearch-titleCreatorYear", "contains", str);
s.addCondition("itemType", "isNot", "attachment");
if (io.filterLibraryIDs) {
io.filterLibraryIDs.forEach(id => s.addCondition("libraryID", "is", id));
}
}
haveConditions = true;
}
}
if (!haveConditions && Zotero_QuickFormat.citingNotes) {
s = new Zotero.Search();
str = "";
s.addCondition("quicksearch-titleCreatorYearNote", "contains", str);
haveConditions = true;
}
if (haveConditions) {
var searchResultIDs = (haveConditions ? (yield s.search()) : []);
// Show items list without cited items to start
yield _updateItemList({ searchString: str, searchResultIDs });
// Check to see which search results match items already in the document
var citedItems, completed = !!Zotero_QuickFormat.citingNotes, isAsync = false;
// Save current search time so that when we get items, we know whether it's too late to
// process them or not
var lastSearchTime = currentSearchTime = Date.now();
// This may or may not be synchronous
if (!Zotero_QuickFormat.citingNotes) {
io.getItems().then(function(citedItems) {
// Don't do anything if panel is already closed
if(isAsync &&
((referencePanel.state !== "open" && referencePanel.state !== "showing")
|| lastSearchTime !== currentSearchTime)) return;
completed = true;
if(str.toLowerCase() === Zotero.getString("integration.ibid").toLowerCase()) {
// If "ibid" is entered, show all cited items
citedItemsMatchingSearch = citedItems;
} else {
Zotero.debug("Searching cited items");
// Search against items. We do this here because it's possible that some of these
// items are only in the doc, and not in the DB.
var splits = Zotero.Fulltext.semanticSplitter(str),
citedItemsMatchingSearch = [];
for(var i=0, iCount=citedItems.length; i<iCount; i++) {
// Generate a string to search for each item
let item = citedItems[i];
let itemStr = item.getCreators()
.map(creator => creator.firstName + " " + creator.lastName)
.concat([item.getField("title"), item.getField("date", true, true).substr(0, 4)])
.join(" ");
// See if words match
for(var j=0, jCount=splits.length; j<jCount; j++) {
var split = splits[j];
if(itemStr.toLowerCase().indexOf(split) === -1) break;
}
// If matched, add to citedItemsMatchingSearch
if(j === jCount) citedItemsMatchingSearch.push(item);
}
Zotero.debug("Searched cited items");
}
_updateItemList({
citedItems,
citedItemsMatchingSearch,
searchString: str,
searchResultIDs,
preserveSelection: isAsync
});
});
}
if(!completed) {
// We are going to have to wait until items have been retrieved from the document.
Zotero.debug("Getting cited items asynchronously");
isAsync = true;
} else {
Zotero.debug("Got cited items synchronously");
}
} else {
// No search conditions, so just clear the box
_updateItemList({ citedItems: [] });
}
});
/**
* Updates the item list
*/
var _updateItemList = async function (options = {}) {
options = Object.assign({
citedItems: false,
citedItemsMatchingSearch: false,
searchString: "",
searchResultIDs: [],
preserveSelection: false
}, options);
let { citedItems, citedItemsMatchingSearch, searchString,
searchResultIDs, preserveSelection } = options
var selectedIndex = 1, previousItemID;
if (Zotero_QuickFormat.citingNotes) citedItems = [];
// Do this so we can preserve the selected item after cited items have been loaded
if(preserveSelection && referenceBox.selectedIndex !== -1 && referenceBox.selectedIndex !== 2) {
previousItemID = parseInt(referenceBox.selectedItem.getAttribute("zotero-item"), 10);
}
while(referenceBox.hasChildNodes()) referenceBox.removeChild(referenceBox.firstChild);
var nCitedItemsFromLibrary = {};
if(!citedItems) {
// We don't know whether or not we have cited items, because we are waiting for document
// data
referenceBox.appendChild(_buildListSeparator(Zotero.getString("integration.cited.loading")));
selectedIndex = 2;
} else if(citedItems.length) {
// We have cited items
for(var i=0, n=citedItems.length; i<n; i++) {
var citedItem = citedItems[i];
// Tabulate number of items in document for each library
if(!citedItem.cslItemID) {
var libraryID = citedItem.libraryID;
if(libraryID in nCitedItemsFromLibrary) {
nCitedItemsFromLibrary[libraryID]++;
} else {
nCitedItemsFromLibrary[libraryID] = 1;
}
}
}
if(citedItemsMatchingSearch && citedItemsMatchingSearch.length) {
referenceBox.appendChild(_buildListSeparator(Zotero.getString("integration.cited")));
for(var i=0; i<Math.min(citedItemsMatchingSearch.length, 50); i++) {
var citedItem = citedItemsMatchingSearch[i];
referenceBox.appendChild(_buildListItem(citedItem));
}
}
}
// Also take into account items cited in this citation. This means that the sorting isn't
// exactly by # of items cited from each library, but maybe it's better this way.
_updateCitationObject();
for(var citationItem of io.citation.citationItems) {
var citedItem = io.customGetItem && io.customGetItem(citationItem) || Zotero.Cite.getItem(citationItem.id);
if(!citedItem.cslItemID) {
var libraryID = citedItem.libraryID;
if(libraryID in nCitedItemsFromLibrary) {
nCitedItemsFromLibrary[libraryID]++;
} else {
nCitedItemsFromLibrary[libraryID] = 1;
}
}
}
if(searchResultIDs.length && (!citedItemsMatchingSearch || citedItemsMatchingSearch.length < 50)) {
// Search results might be in an unloaded library, so get items asynchronously and load
// necessary data
var items = await Zotero.Items.getAsync(searchResultIDs);
await Zotero.Items.loadDataTypes(items);
searchString = searchString.toLowerCase();
var collation = Zotero.getLocaleCollation();
function _itemSort(a, b) {
var firstCreatorA = a.firstCreator, firstCreatorB = b.firstCreator;
// Favor left-bound name matches (e.g., "Baum" < "Appelbaum"),
// using last name of first author
if (firstCreatorA && firstCreatorB) {
let caStartsWith = firstCreatorA.toLowerCase().indexOf(searchString) == 0;
let cbStartsWith = firstCreatorB.toLowerCase().indexOf(searchString) == 0;
if (caStartsWith && !cbStartsWith) {
return -1;
}
else if (!caStartsWith && cbStartsWith) {
return 1;
}
}
var libA = a.libraryID, libB = b.libraryID;
if(libA !== libB) {
// Sort by number of cites for library
if(nCitedItemsFromLibrary[libA] && !nCitedItemsFromLibrary[libB]) {
return -1;
}
if(!nCitedItemsFromLibrary[libA] && nCitedItemsFromLibrary[libB]) {
return 1;
}
if(nCitedItemsFromLibrary[libA] !== nCitedItemsFromLibrary[libB]) {
return nCitedItemsFromLibrary[libB] - nCitedItemsFromLibrary[libA];
}
// Sort by ID even if number of cites is equal
return libA - libB;
}
// Sort by last name of first author
if (firstCreatorA !== "" && firstCreatorB === "") {
return -1;
} else if (firstCreatorA === "" && firstCreatorB !== "") {
return 1
} else if (firstCreatorA) {
return collation.compareString(1, firstCreatorA, firstCreatorB);
}
// Sort by date
var yearA = a.getField("date", true, true).substr(0, 4),
yearB = b.getField("date", true, true).substr(0, 4);
return yearA - yearB;
}
function _noteSort(a, b) {
return collation.compareString(
1, b.getField('dateModified'), a.getField('dateModified')
);
}
items.sort(Zotero_QuickFormat.citingNotes ? _noteSort : _itemSort);
var previousLibrary = -1;
for(var i=0, n=Math.min(items.length, citedItemsMatchingSearch ? 50-citedItemsMatchingSearch.length : 50); i<n; i++) {
var item = items[i], libraryID = item.libraryID;
if(previousLibrary != libraryID) {
var libraryName = libraryID ? Zotero.Libraries.getName(libraryID)
: Zotero.getString('pane.collections.library');
referenceBox.appendChild(_buildListSeparator(libraryName));
}
referenceBox.appendChild(_buildListItem(item));
previousLibrary = libraryID;
if(preserveSelection && (item.cslItemID ? item.cslItemID : item.id) === previousItemID) {
selectedIndex = referenceBox.childNodes.length-1;
}
}
}
_resize();
if((citedItemsMatchingSearch && citedItemsMatchingSearch.length) || searchResultIDs.length) {
referenceBox.selectedIndex = selectedIndex;
referenceBox.ensureIndexIsVisible(selectedIndex);
}
};
/**
* Builds a string describing an item. We avoid CSL here for speed.
*/
function _buildItemDescription(item, infoHbox) {
var nodes = [];
var str = "";
if (item.isNote()) {
var date = Zotero.Date.sqlToDate(item.dateModified, true);
date = Zotero.Date.toFriendlyDate(date);
str += date;
var text = item.note;
text = Zotero.Utilities.unescapeHTML(text);
text = text.trim();
text = text.slice(0, 500);
var parts = text.split('\n').map(x => x.trim()).filter(x => x.length);
if (parts[1]) str += " " + parts[1];
}
else {
var author, authorDate = "";
if(item.firstCreator) author = authorDate = item.firstCreator;
var date = item.getField("date", true, true);
if(date && (date = date.substr(0, 4)) !== "0000") {
authorDate += " (" + parseInt(date) + ")";
}
authorDate = authorDate.trim();
if(authorDate) nodes.push(authorDate);
var publicationTitle = item.getField("publicationTitle", false, true);
if(publicationTitle) {
var label = document.createXULElement("label");
label.setAttribute("value", publicationTitle);
label.setAttribute("crop", "end");
label.style.fontStyle = "italic";
nodes.push(label);
}
var volumeIssue = item.getField("volume");
var issue = item.getField("issue");
if(issue) volumeIssue += "("+issue+")";
if(volumeIssue) nodes.push(volumeIssue);
var publisherPlace = [], field;
if((field = item.getField("publisher"))) publisherPlace.push(field);
if((field = item.getField("place"))) publisherPlace.push(field);
if(publisherPlace.length) nodes.push(publisherPlace.join(": "));
var pages = item.getField("pages");
if(pages) nodes.push(pages);
if(!nodes.length) {
var url = item.getField("url");
if(url) nodes.push(url);
}
// compile everything together
for(var i=0, n=nodes.length; i<n; i++) {
var node = nodes[i];
if(i != 0) str += ", ";
if(typeof node === "object") {
var label = document.createXULElement("label");
label.setAttribute("value", str);
label.setAttribute("crop", "end");
infoHbox.appendChild(label);
infoHbox.appendChild(node);
str = "";
} else {
str += node;
}
}
if(nodes.length && (!str.length || str[str.length-1] !== ".")) str += ".";
}
var label = document.createXULElement("label");
label.setAttribute("value", str);
label.setAttribute("crop", "end");
label.setAttribute("flex", "1");
infoHbox.appendChild(label);
}
/**
* Creates an item to be added to the item list
*/
function _buildListItem(item) {
var titleNode = document.createXULElement("label");
titleNode.setAttribute("class", "citation-dialog title");
titleNode.setAttribute("flex", "1");
titleNode.setAttribute("crop", "end");
titleNode.setAttribute("value", item.getDisplayTitle());
var infoNode = document.createXULElement("hbox");
infoNode.setAttribute("class", "citation-dialog info");
_buildItemDescription(item, infoNode);
// add to rich list item
var rll = document.createElement("richlistitem");
rll.setAttribute("orient", "vertical");
rll.setAttribute("class", "citation-dialog item");
rll.setAttribute("zotero-item", item.cslItemID ? item.cslItemID : item.id);
rll.appendChild(titleNode);
rll.appendChild(infoNode);
rll.addEventListener("click", Zotero_QuickFormat._bubbleizeSelected, false);
return rll;
}
/**
* Creates a list separator to be added to the item list
*/
function _buildListSeparator(labelText, loading) {
var titleNode = document.createXULElement("label");
titleNode.setAttribute("class", "citation-dialog separator-title");
titleNode.setAttribute("flex", "1");
titleNode.setAttribute("crop", "end");
titleNode.setAttribute("value", labelText);
// add to rich list item
var rll = document.createElement("richlistitem");
rll.setAttribute("orient", "vertical");
rll.setAttribute("disabled", true);
rll.setAttribute("class", loading ? "citation-dialog loading" : "citation-dialog separator");
rll.appendChild(titleNode);
rll.addEventListener("mousedown", _ignoreClick, true);
rll.addEventListener("click", _ignoreClick, true);
return rll;
}
/**
* Builds the string to go inside a bubble
*/
function _buildBubbleString(citationItem) {
var item = io.customGetItem && io.customGetItem(citationItem) || Zotero.Cite.getItem(citationItem.id);
// create text for bubble
// Creator
var title, delimiter;
var str = item.getField("firstCreator");
// Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field)
title = item.getDisplayTitle();
if (item.isNote()) {
title = title.substr(0, 24) + '…';
}
if (!str) {
str = Zotero.getString("punctuation.openingQMark") + title + Zotero.getString("punctuation.closingQMark");
}
// Date
var date = item.getField("date", true, true);
if(date && (date = date.substr(0, 4)) !== "0000") {
str += ", " + parseInt(date);
}
// Locator
if(citationItem.locator) {
if(citationItem.label) {
// TODO localize and use short forms
var label = citationItem.label;
} else if(/[\-,]/.test(citationItem.locator)) {
var label = "pp.";
} else {
var label = "p."
}
str += ", "+label+" "+citationItem.locator;
}
// Prefix
if(citationItem.prefix && Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP) {
str = citationItem.prefix
+(Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP.test(citationItem.prefix) ? " " : "")
+str;
}
// Suffix
if(citationItem.suffix && Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP) {
str += (Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP.test(citationItem.suffix) ? " " : "")
+citationItem.suffix;
}
return str;
}
/**
* Insert a bubble into the DOM at a specified position
*/
function _insertBubble(citationItem, nextNode) {
var str = _buildBubbleString(citationItem);
// It's entirely unintuitive why, but after trying a bunch of things, it looks like using
// a XUL label for these things works best. A regular span causes issues with moving the
// cursor.
var bubble = qfiDocument.createElement("span");
bubble.setAttribute("class", "citation-dialog bubble");
bubble.setAttribute("draggable", "true");
bubble.textContent = str;
bubble.addEventListener("click", _onBubbleClick, false);
bubble.addEventListener("dragstart", _onBubbleDrag, false);
bubble.dataset.citationItem = JSON.stringify(citationItem);
if(nextNode && nextNode instanceof Range) {
nextNode.insertNode(bubble);
} else {
qfe.insertBefore(bubble, (nextNode ? nextNode : null));
}
// make sure that there are no rogue <br>s
var elements = qfe.getElementsByTagName("br");
while(elements.length) {
elements[0].parentNode.removeChild(elements[0]);
}
return bubble;
}
/**
* Clear list of bubbles
*/
function _clearEntryList() {
while(referenceBox.hasChildNodes()) referenceBox.removeChild(referenceBox.firstChild);
_resize();
}
/**
* Converts the selected item to a bubble
*/
this._bubbleizeSelected = Zotero.Promise.coroutine(function* () {
if(!referenceBox.hasChildNodes() || !referenceBox.selectedItem) return false;
var citationItem = {"id":referenceBox.selectedItem.getAttribute("zotero-item")};
if (typeof citationItem.id === "string" && citationItem.id.indexOf("/") !== -1) {
var item = Zotero.Cite.getItem(citationItem.id);
citationItem.uris = item.cslURIs;
citationItem.itemData = item.cslItemData;
}
else if (Zotero.Retractions.isRetracted({ id: parseInt(citationItem.id) })) {
citationItem.id = parseInt(citationItem.id);
if (Zotero.Retractions.shouldShowCitationWarning(citationItem)) {
referencePanel.hidden = true;
var ps = Services.prompt;
var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL
+ ps.BUTTON_POS_2 * ps.BUTTON_TITLE_IS_STRING;
var checkbox = { value: false };
var result = ps.confirmEx(null,
Zotero.getString('general.warning'),
Zotero.getString('retraction.citeWarning.text1') + '\n\n'
+ Zotero.getString('retraction.citeWarning.text2'),
buttonFlags,
Zotero.getString('general.continue'),
null,
Zotero.getString('pane.items.showItemInLibrary'),
Zotero.getString('retraction.citationWarning.dontWarn'), checkbox);
referencePanel.hidden = false;
if (result > 0) {
if (result == 2) {
Zotero_QuickFormat.showInLibrary(parseInt(citationItem.id));
}
return false;
}
if (checkbox.value) {
Zotero.Retractions.disableCitationWarningsForItem(citationItem);
}
}
citationItem.ignoreRetraction = true;
}
_updateLocator(_getEditorContent());
if(currentLocator) {
citationItem["locator"] = currentLocator;
if(currentLocatorLabel) {
citationItem["label"] = currentLocatorLabel;
}
}
locatorLocked = "locator" in citationItem;
// get next node and clear this one
var node = _getCurrentEditorTextNode();
node.nodeValue = "";
// We are setting a locator node here, but below 2 calls reset
// the bubble list for sorting, so we do some additional
// handling to maintain the correct locator node in
// _showCitation()
var bubble = locatorNode = _insertBubble(citationItem, node);
_clearEntryList();
yield _previewAndSort();
_refocusQfe();
return true;
});
/**
* Ignores clicks (for use on separators in the rich list box)
*/
function _ignoreClick(e) {
e.stopPropagation();
e.preventDefault();
}
/**
* Resizes window to fit content
*/
function _resize() {
var childNodes = referenceBox.childNodes, numReferences = 0, numSeparators = 0,
firstReference, firstSeparator, height;
for(var i=0, n=childNodes.length; i<n && numReferences < SHOWN_REFERENCES; i++) {
if(childNodes[i].className === "citation-dialog item") {
numReferences++;
if(!firstReference) {
firstReference = childNodes[i];
if(referenceBox.selectedIndex === -1) referenceBox.selectedIndex = i;
}
} else if(childNodes[i].className === "citation-dialog separator") {
numSeparators++;
if(!firstSeparator) firstSeparator = childNodes[i];
}
}
if(qfe.scrollHeight > 30) {
qfe.setAttribute("multiline", true);
qfs.setAttribute("multiline", true);
qfs.style.height = ((Zotero.isMac ? 6 : 4)+qfe.scrollHeight)+"px";
window.sizeToContent();
// the above line causes drawing artifacts to appear due to a bug with drawintitle property
// in fx60. this fixes the artifacting
if (Zotero.isMac && Zotero.platformMajorVersion >= 60) {
document.children[0].setAttribute('drawintitlebar', 'false');
document.children[0].setAttribute('drawintitlebar', 'true');
}
} else {
delete qfs.style.height;
qfe.removeAttribute("multiline");
qfs.removeAttribute("multiline");
window.sizeToContent();
if (Zotero.isMac && Zotero.platformMajorVersion >= 60) {
document.children[0].setAttribute('drawintitlebar', 'false');
document.children[0].setAttribute('drawintitlebar', 'true');
}
}
var panelShowing = referencePanel.state === "open" || referencePanel.state === "showing";
if(numReferences || numSeparators) {
if(((!referenceHeight && firstReference) || (!separatorHeight && firstSeparator)
|| !panelFrameHeight) && !panelShowing) {
_openReferencePanel();
panelShowing = true;
}
if(!referenceHeight && firstReference) {
referenceHeight = firstReference.scrollHeight + 1;
}
if(!separatorHeight && firstSeparator) {
separatorHeight = firstSeparator.scrollHeight + 1;
}
if(!panelFrameHeight) {
panelFrameHeight = referencePanel.boxObject.height - referencePanel.clientHeight;
var computedStyle = window.getComputedStyle(referenceBox, null);
for(var attr of ["border-top-width", "border-bottom-width"]) {
var val = computedStyle.getPropertyValue(attr);
if(val) {
var m = pixelRe.exec(val);
if(m) panelFrameHeight += parseInt(m[1], 10);
}
}
}
referencePanel.sizeTo(window.outerWidth-30,
numReferences*referenceHeight+numSeparators*separatorHeight+panelFrameHeight);
if(!panelShowing) _openReferencePanel();
} else if(panelShowing) {
referencePanel.hidePopup();
referencePanel.sizeTo(window.outerWidth-30, 0);
_refocusQfe();
}
}
/**
* Opens the reference panel and potentially refocuses the main text box
*/
function _openReferencePanel() {
var panelShowing = referencePanel.state === "open" || referencePanel.state === "showing";
if (!panelShowing && !Zotero.isMac && !Zotero.isWin) {
// noautohide and noautofocus are incompatible on Linux
// https://bugzilla.mozilla.org/show_bug.cgi?id=545265
referencePanel.setAttribute("noautohide", "false");
// reinstate noautohide after the window is shown
referencePanel.addEventListener("popupshowing", function() {
referencePanel.removeEventListener("popupshowing", arguments.callee, false);
referencePanel.setAttribute("noautohide", "true");
}, false);
}
referencePanel.openPopup(document.documentElement, "after_start", 15,
qfb.clientHeight-window.clientHeight, false, false, null);
}
/**
* Clears all citations
*/
function _clearCitation() {
var citations = qfe.getElementsByClassName("citation-dialog bubble");
while(citations.length) {
citations[0].parentNode.removeChild(citations[0]);
}
}
/**
* Shows citations in the citation object
*/
function _showCitation(insertBefore) {
if(!io.citation.properties.unsorted
&& keepSorted && keepSorted.hasAttribute("checked")
&& io.citation.sortedItems
&& io.citation.sortedItems.length) {
for(var i=0, n=io.citation.sortedItems.length; i<n; i++) {
const bubble = _insertBubble(io.citation.sortedItems[i][1], insertBefore);
if (locatorNode && bubble.textContent == locatorNode.textContent) {
locatorNode = bubble;
}
}
} else {
for(var i=0, n=io.citation.citationItems.length; i<n; i++) {
const bubble = _insertBubble(io.citation.citationItems[i], insertBefore);
if (locatorNode && bubble.textContent == locatorNode.textContent) {
locatorNode = bubble;
}
}
}
}
/**
* Populates the citation object
*/
function _updateCitationObject() {
var nodes = qfe.childNodes;
io.citation.citationItems = [];
for (let node of nodes) {
if (node.dataset && node.dataset.citationItem) {
io.citation.citationItems.push(JSON.parse(node.dataset.citationItem));
}
}
if(io.sortable) {
if(keepSorted && keepSorted.hasAttribute("checked")) {
delete io.citation.properties.unsorted;
} else {
io.citation.properties.unsorted = true;
}
}
}
/**
* Move cursor to end of the textbox
*/
function _moveCursorToEnd() {
var nodeRange = qfiDocument.createRange();
nodeRange.selectNode(qfe.lastChild);
nodeRange.collapse(false);
var selection = qfiWindow.getSelection();
selection.removeAllRanges();
selection.addRange(nodeRange);
}
/**
* Generates the preview and sorts citations
*/
var _previewAndSort = Zotero.Promise.coroutine(function* () {
var shouldKeepSorted = keepSorted && keepSorted.hasAttribute("checked"),
editorShowing = showEditor && showEditor.hasAttribute("checked");
if(!shouldKeepSorted && !editorShowing) return;
_updateCitationObject();
yield io.sort();
if(shouldKeepSorted) {
// means we need to resort citations
_clearCitation();
_showCitation();
// select past last citation
var lastBubble = qfe.getElementsByClassName("citation-dialog bubble");
lastBubble = lastBubble[lastBubble.length-1];
_moveCursorToEnd();
}
});
/**
* Shows the citation properties panel for a given bubble
*/
function _showCitationProperties(target) {
panelRefersToBubble = target;
let citationItem = JSON.parse(target.dataset.citationItem);
panelPrefix.value = citationItem["prefix"] ? citationItem["prefix"] : "";
panelSuffix.value = citationItem["suffix"] ? citationItem["suffix"] : "";
if(citationItem["label"]) {
var option = panelLocatorLabel.getElementsByAttribute("value", citationItem["label"]);
if(option.length) {
panelLocatorLabel.selectedItem = option[0];
} else {
panelLocatorLabel.selectedIndex = 0;
}
} else {
panelLocatorLabel.selectedIndex = 0;
}
panelLocator.value = citationItem["locator"] ? citationItem["locator"] : "";
panelSuppressAuthor.checked = !!citationItem["suppress-author"];
var item = io.customGetItem && io.customGetItem(citationItem) || Zotero.Cite.getItem(citationItem.id);
document.getElementById("citation-properties-title").textContent = item.getDisplayTitle();
while(panelInfo.hasChildNodes()) panelInfo.removeChild(panelInfo.firstChild);
_buildItemDescription(item, panelInfo);
panelLibraryLink.hidden = !item.id;
if(item.id) {
var libraryName = item.libraryID ? Zotero.Libraries.getName(item.libraryID)
: Zotero.getString('pane.collections.library');
panelLibraryLink.label = Zotero.getString("integration.openInLibrary", libraryName);
}
target.setAttribute("selected", "true");
panel.openPopup(target, "after_start",
target.clientWidth/2, 0, false, false, null);
panelLocator.focus();
}
/**
* Called when progress changes
*/
function _onProgress(percent) {
var meter = document.querySelector(".citation-dialog .progress-meter");
if(percent === null) {
meter.mode = "undetermined";
} else {
meter.mode = "determined";
meter.value = Math.round(percent);
}
}
/**
* Accepts current selection and adds citation
*/
this._accept = function() {
if(accepted) return;
accepted = true;
try {
_updateCitationObject();
document.querySelector(".citation-dialog.deck").selectedIndex = 1;
io.accept(_onProgress);
} catch(e) {
Zotero.debug(e);
}
}
/**
* Handles windows closed with the close box
*/
this.onUnload = function() {
if(accepted) return;
accepted = true;
io.citation.citationItems = [];
io.accept();
}
/**
* Handle escape for entire window
*/
this.onKeyPress = function (event) {
var keyCode = event.keyCode;
if (keyCode === event.DOM_VK_ESCAPE && !accepted) {
accepted = true;
io.citation.citationItems = [];
io.accept();
window.close();
}
};
/**
* Get bubbles within the current selection
*/
function _getSelectedBubble(right) {
var selection = qfiWindow.getSelection(),
range = selection.getRangeAt(0);
qfe.normalize();
// Check whether the bubble is selected
// Not sure whether this ever happens anymore
var container = range.startContainer;
if (container !== qfe) {
if (container.dataset && container.dataset.citationItem) {
return container;
} else if (container.nodeType === Node.TEXT_NODE && container.wholeText == "") {
if (container.parentNode === qfe) {
var node = container;
while (node = container.previousSibling) {
if (node.dataset.citationItem) {
return node;
}
}
} else if (container.parentNode.dataset && container.parentNode.dataset.citationItem) {
return container.parentNode;
}
}
return null;
}
// Check whether there is a bubble anywhere to the left of this one
var offset = range.startOffset,
childNodes = qfe.childNodes,
node = childNodes[offset-(right ? 0 : 1)];
if (node && node.dataset.citationItem) return node;
return null;
}
/**
* Reset timer that controls when search takes place. We use this to avoid searching after each
* keypress, since searches can be slow.
*/
function _resetSearchTimer() {
// Show spinner
var spinner = document.querySelector('.citation-dialog.spinner');
spinner.style.visibility = '';
// Cancel current search if active
if (_searchPromise && _searchPromise.isPending()) {
_searchPromise.cancel();
}
// Start new search
_searchPromise = Zotero.Promise.delay(SEARCH_TIMEOUT)
.then(() => _quickFormat())
.then(() => {
_searchPromise = null;
spinner.style.visibility = 'hidden';
});
}
async function _onQuickSearchClick(event) {
if (qfGuidance) qfGuidance.hide();
let bubble = _getSelectedBubble(false);
if (bubble) {
event.preventDefault();
var nodeRange = qfiDocument.createRange();
nodeRange.selectNode(bubble);
nodeRange.collapse(false);
var selection = qfiWindow.getSelection();
selection.removeAllRanges();
selection.addRange(nodeRange);
}
}
/**
* Handle return or escape
*/
var _onQuickSearchKeyPress = Zotero.Promise.coroutine(function* (event) {
// Prevent hang if another key is pressed after Enter
// https://forums.zotero.org/discussion/59157/
if (accepted) {
event.preventDefault();
return;
}
if(qfGuidance) qfGuidance.hide();
var keyCode = event.keyCode;
if (keyCode === event.DOM_VK_RETURN) {
event.preventDefault();
if(!(yield Zotero_QuickFormat._bubbleizeSelected()) && !_getEditorContent()) {
Zotero_QuickFormat._accept();
}
} else if (keyCode === event.DOM_VK_ESCAPE) {
// Handled in the event handler up, but we have to cancel it here
// so that we do not issue another _quickFormat call
return;
} else if(keyCode === event.DOM_VK_TAB || event.charCode === 59 /* ; */) {
event.preventDefault();
Zotero_QuickFormat._bubbleizeSelected();
} else if(keyCode === event.DOM_VK_BACK_SPACE || keyCode === event.DOM_VK_DELETE) {
var bubble = _getSelectedBubble(keyCode === event.DOM_VK_DELETE);
if(bubble) {
event.preventDefault();
bubble.parentNode.removeChild(bubble);
}
_resize();
_resetSearchTimer();
} else if(keyCode === event.DOM_VK_LEFT || keyCode === event.DOM_VK_RIGHT) {
locatorLocked = true;
var right = keyCode === event.DOM_VK_RIGHT,
bubble = _getSelectedBubble(right);
if(bubble) {
event.preventDefault();
var nodeRange = qfiDocument.createRange();
nodeRange.selectNode(bubble);
nodeRange.collapse(!right);
var selection = qfiWindow.getSelection();
selection.removeAllRanges();
selection.addRange(nodeRange);
}
} else if (["Home", "End"].includes(event.key)) {
locatorLocked = true;
setTimeout(() => {
right = event.key == "End";
bubble = _getSelectedBubble(right);
if (bubble) {
event.preventDefault();
var nodeRange = qfiDocument.createRange();
nodeRange.selectNode(bubble);
nodeRange.collapse(!right);
var selection = qfiWindow.getSelection();
selection.removeAllRanges();
selection.addRange(nodeRange);
}
})
} else if(keyCode === event.DOM_VK_UP && referencePanel.state === "open") {
locatorLocked = true;
var selectedItem = referenceBox.selectedItem;
var previousSibling;
// Seek the closet previous sibling that is not disabled
while((previousSibling = selectedItem.previousSibling) && previousSibling.hasAttribute("disabled")) {
selectedItem = previousSibling;
}
// If found, change to that
if(previousSibling) {
referenceBox.selectedItem = previousSibling;
// If there are separators before this item, ensure that they are visible
var visibleItem = previousSibling;
while(visibleItem.previousSibling && visibleItem.previousSibling.hasAttribute("disabled")) {
visibleItem = visibleItem.previousSibling;
}
referenceBox.ensureElementIsVisible(visibleItem);
};
event.preventDefault();
} else if(keyCode === event.DOM_VK_DOWN) {
locatorLocked = true;
if((Zotero.isMac ? event.metaKey : event.ctrlKey)) {
// If meta key is held down, show the citation properties panel
var bubble = _getSelectedBubble();
if(bubble) _showCitationProperties(bubble);
event.preventDefault();
} else if (referencePanel.state === "open") {
var selectedItem = referenceBox.selectedItem;
var nextSibling;
// Seek the closet next sibling that is not disabled
while((nextSibling = selectedItem.nextSibling) && nextSibling.hasAttribute("disabled")) {
selectedItem = nextSibling;
}
// If found, change to that
if(nextSibling){
referenceBox.selectedItem = nextSibling;
referenceBox.ensureElementIsVisible(nextSibling);
};
event.preventDefault();
}
} else {
_resetSearchTimer();
}
});
/**
* Adds a dummy element to make dragging work
*/
function _onBubbleDrag(event) {
dragging = event.currentTarget;
event.dataTransfer.setData("text/plain", '<span id="zotero-drag"/>');
event.stopPropagation();
}
/**
* Get index of bubble in citations
*/
function _getBubbleIndex(bubble) {
var nodes = qfe.childNodes, index = 0;
for (let node of nodes) {
if (node.dataset && node.dataset.citationItem) {
if (node == bubble) return index;
index++;
}
}
return -1;
}
/**
* Replaces the dummy element with a node to make dropping work
*/
var _onBubbleDrop = Zotero.Promise.coroutine(function* (event) {
event.preventDefault();
event.stopPropagation();
// Find old position in list
var oldPosition = _getBubbleIndex(dragging);
// Move bubble
var range = document.createRange();
range.setStartAfter(event.rangeParent);
dragging.parentNode.removeChild(dragging);
var bubble = _insertBubble(JSON.parse(dragging.dataset.citationItem), range);
// If moved out of order, turn off "Keep Sources Sorted"
if(io.sortable && keepSorted && keepSorted.hasAttribute("checked") && oldPosition !== -1 &&
oldPosition != _getBubbleIndex(bubble)) {
keepSorted.removeAttribute("checked");
}
yield _previewAndSort();
_moveCursorToEnd();
});
/**
* Handle a click on a bubble
*/
function _onBubbleClick(event) {
_moveCursorToEnd();
_showCitationProperties(event.currentTarget);
}
/**
* Called when the user attempts to paste
*/
function _onPaste(event) {
event.stopPropagation();
event.preventDefault();
var str = Zotero.Utilities.Internal.getClipboard("text/unicode");
if(str) {
var selection = qfiWindow.getSelection();
var range = selection.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(str.replace(/[\r\n]/g, " ").trim()));
range.collapse(false);
_resetSearchTimer();
}
}
/**
* Handle changes to citation properties
*/
this.onCitationPropertiesChanged = function(event) {
let citationItem = JSON.parse(panelRefersToBubble.dataset.citationItem || "{}");
if(panelPrefix.value) {
citationItem["prefix"] = panelPrefix.value;
} else {
delete citationItem["prefix"];
}
if(panelSuffix.value) {
citationItem["suffix"] = panelSuffix.value;
} else {
delete citationItem["suffix"];
}
if(panelLocatorLabel.selectedIndex !== 0) {
citationItem["label"] = panelLocatorLabel.selectedItem.value;
} else {
delete citationItem["label"];
}
if(panelLocator.value) {
citationItem["locator"] = panelLocator.value;
} else {
delete citationItem["locator"];
}
if(panelSuppressAuthor.checked) {
citationItem["suppress-author"] = true;
} else {
delete citationItem["suppress-author"];
}
locatorLocked = "locator" in citationItem;
locatorNode = panelRefersToBubble;
panelRefersToBubble.dataset.citationItem = JSON.stringify(citationItem);
panelRefersToBubble.textContent = _buildBubbleString(citationItem);
};
/**
* Handle closing citation properties panel
*/
this.onCitationPropertiesClosed = function(event) {
panelRefersToBubble.removeAttribute("selected");
Zotero_QuickFormat.onCitationPropertiesChanged();
}
/**
* Makes "Enter" work in the panel
*/
this.onPanelKeyPress = function(event) {
var keyCode = event.keyCode;
if (keyCode === event.DOM_VK_RETURN) {
document.getElementById("citation-properties").hidePopup();
}
};
/**
* Handle checking/unchecking "Keep Citations Sorted"
*/
this.onKeepSortedCommand = function(event) {
_previewAndSort();
};
/**
* Open classic Add Citation window
*/
this.onClassicViewCommand = function(event) {
_updateCitationObject();
var newWindow = window.newWindow = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null, 'chrome://zotero/content/integration/addCitationDialog.xul',
'', 'chrome,centerscreen,resizable', io);
newWindow.addEventListener("focus", function() {
newWindow.removeEventListener("focus", arguments.callee, true);
window.close();
}, true);
accepted = true;
}
/**
* Show an item in the library it came from
*/
this.showInLibrary = async function (itemID) {
let citationItem = JSON.parse(panelRefersToBubble.dataset.citationItem || "{}");
var id = itemID || citationItem.id;
var pane = Zotero.getActiveZoteroPane();
// Open main window if it's not open (Mac)
if (!pane) {
let win = Zotero.openMainWindow();
await new Zotero.Promise((resolve) => {
let onOpen = function () {
win.removeEventListener('load', onOpen);
resolve();
};
win.addEventListener('load', onOpen);
});
pane = win.ZoteroPane;
}
pane.selectItem(id);
// Pull window to foreground
Zotero.Utilities.Internal.activate(pane.document.defaultView);
}
/**
* Resizes windows
* @constructor
*/
var Resizer = function(panel, targetWidth, targetHeight, pixelsPerStep, stepsPerSecond) {
this.panel = panel;
this.curWidth = panel.clientWidth;
this.curHeight = panel.clientHeight;
this.difX = (targetWidth ? targetWidth - this.curWidth : 0);
this.difY = (targetHeight ? targetHeight - this.curHeight : 0);
this.step = 0;
this.steps = Math.ceil(Math.max(Math.abs(this.difX), Math.abs(this.difY))/pixelsPerStep);
this.timeout = (1000/stepsPerSecond);
var me = this;
this._animateCallback = function() { me.animate() };
};
/**
* Performs a step of the animation
*/
Resizer.prototype.animate = function() {
if(this.stopped) return;
this.step++;
this.panel.sizeTo(this.curWidth+Math.round(this.step*this.difX/this.steps),
this.curHeight+Math.round(this.step*this.difY/this.steps));
if(this.step !== this.steps) {
window.setTimeout(this._animateCallback, this.timeout);
}
};
/**
* Halts resizing
*/
Resizer.prototype.stop = function() {
this.stopped = true;
};
}
window.addEventListener("DOMContentLoaded", Zotero_QuickFormat.onDOMContentLoaded, false);
window.addEventListener("load", Zotero_QuickFormat.onLoad, false);