Async DB megacommit

Promise-based rewrite of most of the codebase, with asynchronous database and file access -- see https://github.com/zotero/zotero/issues/518 for details.

WARNING: This includes backwards-incompatible schema changes.

An incomplete list of other changes:

- Schema overhaul
  - Replace main tables with new versions with updated schema
  - Enable real foreign key support and remove previous triggers
  - Don't use NULLs for local libraryID, which broke the UNIQUE index
    preventing object key duplication. All code (Zotero and third-party)
    using NULL for the local library will need to be updated to use 0
    instead (already done for Zotero code)
  - Add 'compatibility' DB version that can be incremented manually to break DB
    compatibility with previous versions. 'userdata' upgrades will no longer
    automatically break compatibility.
  - Demote creators and tags from first-class objects to item properties
- New API syncing properties
  - 'synced'/'version' properties to data objects
  - 'etag' to groups
  - 'version' to libraries
- Create Zotero.DataObject that other objects inherit from
- Consolidate data object loading into Zotero.DataObjects
- Change object reloading so that only the loaded and changed parts of objects are reloaded, instead of reloading all data from the database (with some exceptions, including item primary data)
- Items and collections now have .parentItem and .parentKey properties, replacing item.getSource() and item.getSourceKey()
- New function Zotero.serial(fn), to wrap an async function such that all calls are run serially
- New function Zotero.Utilities.Internal.forEachChunkAsync(arr, chunkSize, func)
- Add tag selector loading message
- Various API and name changes, since everything was breaking anyway

Known broken things:

- Syncing (will be completely rewritten for API syncing)
- Translation architecture (needs promise-based rewrite)
- Duplicates view
- DB integrity check (from schema changes)
- Dragging (may be difficult to fix)

Lots of other big and little things are certainly broken, particularly with the UI, which can be affected by async code in all sorts of subtle ways.
This commit is contained in:
Dan Stillman 2014-08-06 17:38:05 -04:00
parent 4ea5e2d426
commit db0fa3c33e
88 changed files with 17286 additions and 15771 deletions

View file

@ -53,32 +53,24 @@ var ZoteroAdvancedSearch = new function() {
_searchBox.updateSearch();
_searchBox.active = true;
// A minimal implementation of Zotero.CollectionTreeView
var itemGroup = {
// A minimal implementation of Zotero.CollectionTreeRow
var collectionTreeRow = {
ref: _searchBox.search,
isSearchMode: function() { return true; },
getItems: function () {
var search = _searchBox.search.clone();
getItems: Zotero.Promise.coroutine(function* () {
var search = yield _searchBox.search.clone();
// Hack to create a condition for the search's library --
// this logic should really go in the search itself instead of here
// and in collectionTreeView.js
var conditions = search.getSearchConditions();
if (!conditions.some(function (condition) condition.condition == 'libraryID')) {
let libraryID = _searchBox.search.libraryID;
// TEMP: libraryIDInt
if (libraryID) {
search.addCondition('libraryID', 'is', libraryID);
}
else {
let groups = Zotero.Groups.getAll();
for (let i=0; i<groups.length; i++) {
search.addCondition('libraryID', 'isNot', groups[i].libraryID);
}
}
yield search.addCondition('libraryID', 'is', _searchBox.search.libraryID);
}
return Zotero.Items.get(search.search());
},
var ids = yield search.search();
return Zotero.Items.get(ids);
}),
isLibrary: function () { return false; },
isCollection: function () { return false; },
isSearch: function () { return true; },
@ -90,7 +82,7 @@ var ZoteroAdvancedSearch = new function() {
this.itemsView.unregister();
}
this.itemsView = new Zotero.ItemTreeView(itemGroup, false);
this.itemsView = new Zotero.ItemTreeView(collectionTreeRow, false);
document.getElementById('zotero-items-tree').view = this.itemsView;
}
@ -104,9 +96,11 @@ var ZoteroAdvancedSearch = new function() {
var s = new Zotero.Search();
// Don't clear the selected library
s.libraryID = _searchBox.search.libraryID;
s.addCondition('title', 'contains', '');
_searchBox.search = s;
_searchBox.active = false;
s.addCondition('title', 'contains', '')
.then(function () {
_searchBox.search = s;
_searchBox.active = false;
});
}
@ -138,11 +132,14 @@ var ZoteroAdvancedSearch = new function() {
name.value = untitled;
}
var s = _searchBox.search.clone();
s.name = name.value;
s.save();
window.close();
return _searchBox.search.clone()
.then(function (s) {
s.name = name.value;
return s.save();
})
.then(function () {
window.close()
});
}

View file

@ -138,201 +138,203 @@
<!-- Private properties -->
<method name="refresh">
<body>
<![CDATA[
Zotero.debug('Refreshing attachment box');
var attachmentBox = document.getAnonymousNodes(this)[0];
var title = this._id('title');
var fileNameRow = this._id('fileNameRow');
var urlField = this._id('url');
var accessed = this._id('accessedRow');
var pagesRow = this._id('pagesRow');
var dateModifiedRow = this._id('dateModifiedRow');
var indexStatusRow = this._id('indexStatusRow');
var selectButton = this._id('select-button');
// DEBUG: this is annoying -- we really want to use an abstracted
// version of createValueElement() from itemPane.js
// (ideally in an XBL binding)
// Wrap title to multiple lines if necessary
while (title.hasChildNodes()) {
title.removeChild(title.firstChild);
}
var val = this.item.getField('title');
if (typeof val != 'string') {
val += "";
}
var firstSpace = val.indexOf(" ");
// Crop long uninterrupted text
if ((firstSpace == -1 && val.length > 29 ) || firstSpace > 29) {
title.setAttribute('crop', 'end');
title.setAttribute('value', val);
}
// Create a <description> element, essentially
else {
title.removeAttribute('value');
title.appendChild(document.createTextNode(val));
}
if (this.editable) {
title.className = 'zotero-clicky';
<body><![CDATA[
Zotero.spawn(function* () {
Zotero.debug('Refreshing attachment box');
// For the time being, use a silly little popup
title.addEventListener('click', this.editTitle, false);
}
var isImportedURL = this.item.attachmentLinkMode ==
Zotero.Attachments.LINK_MODE_IMPORTED_URL;
// Metadata for URL's
if (this.item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL
|| isImportedURL) {
yield [this.item.loadItemData(), this.item.loadNote()];
// URL
if (this.displayURL) {
var urlSpec = this.item.getField('url');
urlField.setAttribute('value', urlSpec);
urlField.setAttribute('hidden', false);
if (this.clickableLink) {
urlField.onclick = function (event) {
ZoteroPane_Local.loadURI(this.value, event)
};
urlField.className = 'zotero-text-link';
var attachmentBox = document.getAnonymousNodes(this)[0];
var title = this._id('title');
var fileNameRow = this._id('fileNameRow');
var urlField = this._id('url');
var accessed = this._id('accessedRow');
var pagesRow = this._id('pagesRow');
var dateModifiedRow = this._id('dateModifiedRow');
var indexStatusRow = this._id('indexStatusRow');
var selectButton = this._id('select-button');
// DEBUG: this is annoying -- we really want to use an abstracted
// version of createValueElement() from itemPane.js
// (ideally in an XBL binding)
// Wrap title to multiple lines if necessary
while (title.hasChildNodes()) {
title.removeChild(title.firstChild);
}
var val = this.item.getField('title');
if (typeof val != 'string') {
val += "";
}
var firstSpace = val.indexOf(" ");
// Crop long uninterrupted text
if ((firstSpace == -1 && val.length > 29 ) || firstSpace > 29) {
title.setAttribute('crop', 'end');
title.setAttribute('value', val);
}
// Create a <description> element, essentially
else {
title.removeAttribute('value');
title.appendChild(document.createTextNode(val));
}
if (this.editable) {
title.className = 'zotero-clicky';
// For the time being, use a silly little popup
title.addEventListener('click', this.editTitle, false);
}
var isImportedURL = this.item.attachmentLinkMode ==
Zotero.Attachments.LINK_MODE_IMPORTED_URL;
// Metadata for URL's
if (this.item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL
|| isImportedURL) {
// URL
if (this.displayURL) {
var urlSpec = this.item.getField('url');
urlField.setAttribute('value', urlSpec);
urlField.setAttribute('hidden', false);
if (this.clickableLink) {
urlField.onclick = function (event) {
ZoteroPane_Local.loadURI(this.value, event)
};
urlField.className = 'zotero-text-link';
}
else {
urlField.className = '';
}
urlField.hidden = false;
}
else {
urlField.className = '';
urlField.hidden = true;
}
// Access date
if (this.displayAccessed) {
this._id("accessed-label").value = Zotero.getString('itemFields.accessDate')
+ Zotero.getString('punctuation.colon');
this._id("accessed").value = Zotero.Date.sqlToDate(
this.item.getField('accessDate'), true
).toLocaleString();
accessed.hidden = false;
}
else {
accessed.hidden = true;
}
urlField.hidden = false;
}
// Metadata for files
else {
urlField.hidden = true;
}
// Access date
if (this.displayAccessed) {
this._id("accessed-label").value = Zotero.getString('itemFields.accessDate')
+ Zotero.getString('punctuation.colon');
this._id("accessed").value = Zotero.Date.sqlToDate(
this.item.getField('accessDate'), true
).toLocaleString();
accessed.hidden = false;
}
else {
accessed.hidden = true;
}
}
// Metadata for files
else {
urlField.hidden = true;
accessed.hidden = true;
}
if (this.item.attachmentLinkMode
!= Zotero.Attachments.LINK_MODE_LINKED_URL
&& this.displayFileName) {
var fileName = this.item.getFilename();
if (fileName) {
this._id("fileName-label").value = Zotero.getString('pane.item.attachments.filename')
+ Zotero.getString('punctuation.colon');
this._id("fileName").value = fileName;
fileNameRow.hidden = false;
if (this.item.attachmentLinkMode
!= Zotero.Attachments.LINK_MODE_LINKED_URL
&& this.displayFileName) {
var fileName = this.item.getFilename();
if (fileName) {
this._id("fileName-label").value = Zotero.getString('pane.item.attachments.filename')
+ Zotero.getString('punctuation.colon');
this._id("fileName").value = fileName;
fileNameRow.hidden = false;
}
else {
fileNameRow.hidden = true;
}
}
else {
fileNameRow.hidden = true;
}
}
else {
fileNameRow.hidden = true;
}
// Page count
if (this.displayPages) {
var pages = Zotero.Fulltext.getPages(this.item.id);
var pages = pages ? pages.total : null;
if (pages) {
this._id("pages-label").value = Zotero.getString('itemFields.pages')
+ Zotero.getString('punctuation.colon');
this._id("pages").value = pages;
pagesRow.hidden = false;
// Page count
if (this.displayPages) {
var pages = yield Zotero.Fulltext.getPages(this.item.id);
var pages = pages ? pages.total : null;
if (pages) {
this._id("pages-label").value = Zotero.getString('itemFields.pages')
+ Zotero.getString('punctuation.colon');
this._id("pages").value = pages;
pagesRow.hidden = false;
}
else {
pagesRow.hidden = true;
}
}
else {
pagesRow.hidden = true;
}
}
else {
pagesRow.hidden = true;
}
if (this.displayDateModified) {
this._id("dateModified-label").value = Zotero.getString('itemFields.dateModified')
+ Zotero.getString('punctuation.colon');
var mtime = this.item.attachmentModificationTime;
if (mtime) {
this._id("dateModified").value = new Date(mtime).toLocaleString();
}
// Use the item's mod time as a backup (e.g., when sync
// passes in the mod time for the nonexistent remote file)
else {
this._id("dateModified").value = Zotero.Date.sqlToDate(
this.item.getField('dateModified'), true
).toLocaleString();
}
dateModifiedRow.hidden = false;
}
else {
dateModifiedRow.hidden = true;
}
// Full-text index information
if (this.displayIndexed) {
this.updateItemIndexedState();
indexStatusRow.hidden = false;
}
else {
indexStatusRow.hidden = true;
}
// Note editor
var noteEditor = this._id('attachment-note-editor');
if (this.displayNote) {
if (this.displayNoteIfEmpty || this.item.getNote() != '') {
Zotero.debug("setting links on top");
noteEditor.linksOnTop = true;
noteEditor.hidden = false;
// Don't make note editable (at least for now)
if (this.mode == 'merge' || this.mode == 'mergeedit') {
noteEditor.mode = 'merge';
noteEditor.displayButton = false;
}
else {
noteEditor.mode = this.mode;
}
noteEditor.parent = null;
noteEditor.item = this.item;
}
}
else {
noteEditor.hidden = true;
}
if (this.displayButton) {
selectButton.label = this.buttonCaption;
selectButton.hidden = false;
selectButton.setAttribute('oncommand',
'document.getBindingParent(this).clickHandler(this)');
}
else {
selectButton.hidden = true;
}
]]>
</body>
if (this.displayDateModified) {
this._id("dateModified-label").value = Zotero.getString('itemFields.dateModified')
+ Zotero.getString('punctuation.colon');
var mtime = this.item.attachmentModificationTime;
if (mtime) {
this._id("dateModified").value = new Date(mtime).toLocaleString();
}
// Use the item's mod time as a backup (e.g., when sync
// passes in the mod time for the nonexistent remote file)
else {
this._id("dateModified").value = Zotero.Date.sqlToDate(
this.item.getField('dateModified'), true
).toLocaleString();
}
dateModifiedRow.hidden = false;
}
else {
dateModifiedRow.hidden = true;
}
// Full-text index information
if (this.displayIndexed) {
yield this.updateItemIndexedState();
indexStatusRow.hidden = false;
}
else {
indexStatusRow.hidden = true;
}
// Note editor
var noteEditor = this._id('attachment-note-editor');
if (this.displayNote) {
if (this.displayNoteIfEmpty || this.item.getNote() != '') {
Zotero.debug("setting links on top");
noteEditor.linksOnTop = true;
noteEditor.hidden = false;
// Don't make note editable (at least for now)
if (this.mode == 'merge' || this.mode == 'mergeedit') {
noteEditor.mode = 'merge';
noteEditor.displayButton = false;
}
else {
noteEditor.mode = this.mode;
}
noteEditor.parent = null;
noteEditor.item = this.item;
}
}
else {
noteEditor.hidden = true;
}
if (this.displayButton) {
selectButton.label = this.buttonCaption;
selectButton.hidden = false;
selectButton.setAttribute('oncommand',
'document.getBindingParent(this).clickHandler(this)');
}
else {
selectButton.hidden = true;
}
}, this);
]]></body>
</method>
@ -464,43 +466,43 @@
Update Indexed: (Yes|No|Partial) line
-->
<method name="updateItemIndexedState">
<body>
<![CDATA[
var indexStatus = this._id('index-status');
var reindexButton = this._id('reindex');
var status = Zotero.Fulltext.getIndexedState(this.item.id);
var str = 'fulltext.indexState.';
switch (status) {
case Zotero.Fulltext.INDEX_STATE_UNAVAILABLE:
str += 'unavailable';
break;
case Zotero.Fulltext.INDEX_STATE_UNINDEXED:
str = 'general.no';
break;
case Zotero.Fulltext.INDEX_STATE_PARTIAL:
str += 'partial';
break;
case Zotero.Fulltext.INDEX_STATE_INDEXED:
str = 'general.yes';
break;
}
this._id("index-status-label").value = Zotero.getString('fulltext.indexState.indexed')
+ Zotero.getString('punctuation.colon');
indexStatus.value = Zotero.getString(str);
// Reindex button tooltip (string stored in zotero.properties)
var str = Zotero.getString('pane.items.menu.reindexItem');
reindexButton.setAttribute('tooltiptext', str);
if (this.editable && Zotero.Fulltext.canReindex(this.item.id)) {
reindexButton.setAttribute('hidden', false);
}
else {
reindexButton.setAttribute('hidden', true);
}
]]>
</body>
<body><![CDATA[
return Zotero.spawn(function* () {
var indexStatus = this._id('index-status');
var reindexButton = this._id('reindex');
var status = yield Zotero.Fulltext.getIndexedState(this.item);
var str = 'fulltext.indexState.';
switch (status) {
case Zotero.Fulltext.INDEX_STATE_UNAVAILABLE:
str += 'unavailable';
break;
case Zotero.Fulltext.INDEX_STATE_UNINDEXED:
str = 'general.no';
break;
case Zotero.Fulltext.INDEX_STATE_PARTIAL:
str += 'partial';
break;
case Zotero.Fulltext.INDEX_STATE_INDEXED:
str = 'general.yes';
break;
}
this._id("index-status-label").value = Zotero.getString('fulltext.indexState.indexed')
+ Zotero.getString('punctuation.colon');
indexStatus.value = Zotero.getString(str);
// Reindex button tooltip (string stored in zotero.properties)
var str = Zotero.getString('pane.items.menu.reindexItem');
reindexButton.setAttribute('tooltiptext', str);
if (this.editable && (yield Zotero.Fulltext.canReindex(this.item))) {
reindexButton.setAttribute('hidden', false);
}
else {
reindexButton.setAttribute('hidden', true);
}
}, this);
]]></body>
</method>

View file

@ -347,7 +347,7 @@
// createValueElement() adds the itemTypeID as an attribute
// and converts it to a localized string for display
if (fieldName == 'itemType') {
val = this.item.getField('itemTypeID');
val = this.item.itemTypeID;
}
else {
val = this.item.getField(fieldName);
@ -530,8 +530,8 @@
max = num;
}
for (var i = 0; i < max; i++) {
this.addCreatorRow(this.item.getCreator(i).ref,
this.item.getCreator(i).creatorTypeID);
let data = this.item.getCreator(i);
this.addCreatorRow(data, data.creatorType);
// Display "+" button on all but last row
if (i == max - 2) {
@ -553,7 +553,7 @@
this._displayAllCreators = true;
if (this._addCreatorRow) {
this.addCreatorRow(false, this.item.getCreator(max-1).creatorTypeID, true);
this.addCreatorRow(false, this.item.getCreator(max-1).creatorType, true);
this._addCreatorRow = false;
this.disableCreatorAddButtons();
}
@ -659,8 +659,8 @@
<method name="addCreatorRow">
<parameter name="creator"/>
<parameter name="creatorTypeID"/>
<parameter name="creatorData"/>
<parameter name="creatorTypeIDOrName"/>
<parameter name="unsaved"/>
<parameter name="defaultRow"/>
<body>
@ -668,26 +668,34 @@
// getCreatorFields(), switchCreatorMode() and handleCreatorAutoCompleteSelect()
// may need need to be adjusted if this DOM structure changes
if (!creator) {
creator = {
firstName: '',
lastName: '',
fieldMode: Zotero.Prefs.get('lastCreatorFieldMode')
};
var fieldMode = Zotero.Prefs.get('lastCreatorFieldMode');
var firstName = '';
var lastName = '';
if (creatorData) {
fieldMode = creatorData.fieldMode;
firstName = creatorData.firstName;
lastName = creatorData.lastName;
}
if (creator.fieldMode == 1) {
var firstName = '';
var lastName = creator.lastName ? creator.lastName : this._defaultFullName;
// Sub in placeholder text for empty fields
if (fieldMode == 1) {
if (lastName === "") {
lastName = this._defaultFullName;
}
}
else {
var firstName = creator.firstName ? creator.firstName : this._defaultFirstName;
var lastName = creator.lastName ? creator.lastName : this._defaultLastName;
if (firstName === "") {
firstName = this._defaultFirstName;
}
if (lastName === "") {
lastName = this._defaultLastName;
}
}
// Use the first entry in the drop-down for the default type if none specified
var typeID = creatorTypeID ?
creatorTypeID : this._creatorTypeMenu.childNodes[0].getAttribute('typeid');
var typeID = creatorTypeIDOrName
? Zotero.CreatorTypes.getID(creatorTypeIDOrName)
: this._creatorTypeMenu.childNodes[0].getAttribute('typeid');
var typeBox = document.createElement("hbox");
typeBox.setAttribute("typeid", typeID);
@ -738,7 +746,7 @@
tabindex + 1
)
);
if (creator.fieldMode) {
if (fieldMode) {
firstlast.lastChild.setAttribute('hidden', true);
}
@ -782,7 +790,7 @@
this.disableButton(addButton);
}
else {
this._enablePlusButton(addButton, typeID, creator.fieldMode);
this._enablePlusButton(addButton, typeID, fieldMode);
}
hbox.appendChild(addButton);
@ -797,7 +805,7 @@
this.addDynamicRow(typeBox, hbox, true);
// Set single/double field toggle mode
if (creator.fieldMode) {
if (fieldMode) {
this.switchCreatorMode(hbox.parentNode, 1, true);
}
else {
@ -1169,16 +1177,11 @@
<body>
<![CDATA[
button.setAttribute('disabled', false);
button.setAttribute("onclick",
"var parent = document.getBindingParent(this); "
+ "parent.disableButton(this); "
+ "var creator = new Zotero.Creator; "
+ "creator.fieldMode = " + (fieldMode ? fieldMode : 0) + "; "
+ "parent.addCreatorRow("
+ "creator, "
+ (creatorTypeID ? creatorTypeID : 'false') + ", true"
+ ");"
);
button.onclick = function () {
var parent = document.getBindingParent(this);
parent.disableButton(this);
parent.addCreatorRow(null, creatorTypeID, true);
};
]]>
</body>
</method>
@ -1373,8 +1376,10 @@
var [field, creatorIndex, creatorField] = fieldName.split('-');
if (field == 'creator') {
var c = this.item.getCreator(creatorIndex);
var value = c ? c.ref[creatorField] : '';
var value = this.item.getCreator(creatorIndex)[creatorField];
if (value === undefined) {
value = "";
}
var itemID = this.item.id;
}
else {
@ -1487,72 +1492,73 @@
-->
<method name="handleCreatorAutoCompleteSelect">
<parameter name="textbox"/>
<body>
<![CDATA[
var comment = false;
var controller = textbox.controller;
if (!controller.matchCount) return;
for (var i=0; i<controller.matchCount; i++)
{
if (controller.getValueAt(i) == textbox.value)
<body><![CDATA[
return Zotero.spawn(function* () {
var comment = false;
var controller = textbox.controller;
if (!controller.matchCount) return;
for (var i=0; i<controller.matchCount; i++)
{
comment = controller.getCommentAt(i);
break;
}
}
var [creatorID, numFields] = comment.split('-');
// If result uses two fields, save both
if (numFields==2)
{
// Manually clear autocomplete controller's reference to
// textbox to prevent error next time around
textbox.mController.input = null;
var [field, creatorIndex, creatorField] =
textbox.getAttribute('fieldname').split('-');
// Stay focused
this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex')) - 1;
this._tabDirection = 1;
var creator = Zotero.Creators.get(creatorID);
var otherField = creatorField == 'lastName' ? 'firstName' : 'lastName';
// Update this textbox
textbox.setAttribute('value', creator[creatorField]);
textbox.value = creator[creatorField];
// Update the other label
if (otherField=='firstName'){
var label = textbox.nextSibling.nextSibling;
}
else if (otherField=='lastName'){
var label = textbox.previousSibling.previousSibling;
if (controller.getValueAt(i) == textbox.value)
{
comment = controller.getCommentAt(i);
break;
}
}
//this._setFieldValue(label, creator[otherField]);
if (label.firstChild){
label.firstChild.nodeValue = creator[otherField];
}
else {
label.value = creator[otherField];
Zotero.debug(comment);
var [creatorID, numFields] = comment.split('-');
// If result uses two fields, save both
if (numFields==2)
{
// Manually clear autocomplete controller's reference to
// textbox to prevent error next time around
textbox.mController.input = null;
var [field, creatorIndex, creatorField] =
textbox.getAttribute('fieldname').split('-');
// Stay focused
this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex')) - 1;
this._tabDirection = 1;
var creator = yield Zotero.Creators.getAsync(creatorID);
var otherField = creatorField == 'lastName' ? 'firstName' : 'lastName';
// Update this textbox
textbox.setAttribute('value', creator[creatorField]);
textbox.value = creator[creatorField];
// Update the other label
if (otherField=='firstName'){
var label = textbox.nextSibling.nextSibling;
}
else if (otherField=='lastName'){
var label = textbox.previousSibling.previousSibling;
}
//this._setFieldValue(label, creator[otherField]);
if (label.firstChild){
label.firstChild.nodeValue = creator[otherField];
}
else {
label.value = creator[otherField];
}
var row = Zotero.getAncestorByTagName(textbox, 'row');
var fields = this.getCreatorFields(row);
fields[creatorField] = creator[creatorField];
fields[otherField] = creator[otherField];
yield this.modifyCreator(creatorIndex, fields);
}
var row = Zotero.getAncestorByTagName(textbox, 'row');
var fields = this.getCreatorFields(row);
fields[creatorField] = creator[creatorField];
fields[otherField] = creator[otherField];
this.modifyCreator(creatorIndex, fields);
}
// Otherwise let the autocomplete popup handle matters
]]>
</body>
// Otherwise let the autocomplete popup handle matters
}, this);
]]></body>
</method>
@ -1719,8 +1725,8 @@
//Shift existing creators
for (var i=initNumCreators-1; i>=creatorIndex; i--) {
var shiftedCreator = this.item.getCreator(i);
this.item.setCreator(nameArray.length+i,shiftedCreator.ref,shiftedCreator.creatorTypeID);
let shiftedCreatorData = this.item.getCreator(i);
this.item.setCreator(nameArray.length + i, shiftedCreatorData);
}
}
@ -1753,7 +1759,7 @@
}
var val = this.item.getCreator(creatorIndex);
val = val ? val.ref[creatorField] : null;
val = val ? val[creatorField] : null;
if (!val) {
// Reset to '(first)'/'(last)'/'(name)'
@ -1977,10 +1983,8 @@
var label2 = label1.parentNode.lastChild;
var fields = {
lastName: label1.firstChild ? label1.firstChild.nodeValue
: label1.value,
firstName: label2.firstChild ? label2.firstChild.nodeValue
: label2.value,
lastName: label1.firstChild ? label1.firstChild.nodeValue : label1.value,
firstName: label2.firstChild ? label2.firstChild.nodeValue : label2.value,
fieldMode: label1.getAttribute('fieldMode')
? parseInt(label1.getAttribute('fieldMode')) : 0,
creatorTypeID: parseInt(typeID),
@ -1996,6 +2000,9 @@
fields.lastName = '';
}
Zotero.debug("RETURNING FIELDS");
Zotero.debug(fields);
return fields;
]]>
</body>
@ -2006,14 +2013,11 @@
<parameter name="index"/>
<parameter name="fields"/>
<parameter name="changeGlobally"/>
<body>
<![CDATA[
try {
<body><![CDATA[
return Zotero.Promise.try(function () {
var libraryID = this.item.libraryID;
var firstName = fields.firstName;
var lastName = fields.lastName;
//var shortName = fields.shortName;
var fieldMode = fields.fieldMode;
var creatorTypeID = fields.creatorTypeID;
@ -2025,130 +2029,27 @@
return;
}
this.item.removeCreator(index);
this.item.save();
if (this.saveOnEdit) {
return this.item.save();
}
return;
}
Zotero.DB.beginTransaction();
var newCreator = new Zotero.Creator;
newCreator.libraryID = libraryID;
newCreator.setFields(fields);
var newLinkedCreators = [];
var creatorDataID = Zotero.Creators.getDataID(fields);
if (creatorDataID) {
newLinkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID, libraryID);
var changed = this.item.setCreator(index, fields);
if (changed && this.saveOnEdit) {
return this.item.save();
}
if (oldCreator) {
if (oldCreator.ref.equals(newCreator) || (oldCreator.ref.libraryID != newCreator.libraryID)) {
if (oldCreator.creatorTypeID == creatorTypeID) {
Zotero.debug("Creator " + oldCreator.ref.id + " hasn't changed");
}
// Just change creatorTypeID
else {
this.item.setCreator(index, oldCreator.ref, creatorTypeID);
if (this.saveOnEdit) {
this.item.save();
}
}
Zotero.DB.commitTransaction();
return;
}
oldCreator = oldCreator.ref;
}
var creator;
var creatorID;
if (oldCreator) {
var numLinkedItems = oldCreator.countLinkedItems();
// Creator is linked only to the current item
if (numLinkedItems == 1) {
if (newLinkedCreators.length) {
// Use the first creator found with this data
// TODO: support choosing among options
creatorID = newLinkedCreators[0];
creator = Zotero.Creators.get(creatorID);
}
else {
oldCreator.setFields(fields);
//creatorID = oldCreator.save();
creator = oldCreator;
}
}
// Creator is linked to multiple items with changeGlobally off
else if (!changeGlobally) {
if (newLinkedCreators.length) {
// Use the first creator found with this data
// TODO: support choosing among options
creatorID = newLinkedCreators[0];
creator = Zotero.Creators.get(creatorID);
}
else {
//creatorID = newCreator.save();
creator = newCreator;
}
}
// Creator is linked to multiple items with changeGlobally on
else {
throw ('changeGlobally unimplemented');
if (newLinkedCreators.length) {
// Use the first creator found with this data
// TODO: support choosing among options
creatorID = newLinkedCreators[0];
// TODO: switch all linked items to this creator
}
else {
creatorID = newCreator.save();
// TODO: switch all linked items to new creatorID
}
}
}
// No existing creator
else {
if (newLinkedCreators.length) {
creatorID = newLinkedCreators[0];
creator = Zotero.Creators.get(creatorID);
}
else {
//creatorID = newCreator.save();
creator = newCreator;
}
}
this.item.setCreator(index, creator, creatorTypeID);
if (this.saveOnEdit) {
try {
this.item.save();
}
catch (e) {
// DEBUG: Errors aren't being logged in Fx3.1b4pre without this
Zotero.debug(e);
Components.utils.reportError(e);
throw (e);
}
}
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
throw (e);
}
]]>
</body>
}.bind(this));
]]></body>
</method>
<!--
@return {Promise}
-->
<method name="swapNames">
<body><![CDATA[
return Zotero.Promise.try(function () {
var row = Zotero.getAncestorByTagName(document.popupNode, 'row');
var typeBox = row.getElementsByAttribute('popup', 'creator-type-menu')[0];
var creatorIndex = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
@ -2157,16 +2058,20 @@
var firstName = fields.firstName;
fields.lastName = firstName;
fields.firstName = lastName;
this.modifyCreator(creatorIndex, fields);
this.item.save();
return this.modifyCreator(creatorIndex, fields);
}.bind(this));
]]></body>
</method>
<!--
@return {Promise}
-->
<method name="moveCreator">
<parameter name="index"/>
<parameter name="moveUp"/>
<body>
<![CDATA[
return Zotero.Promise.try(function () {
if (index == 0 && moveUp) {
Zotero.debug("Can't move up creator 0");
return;
@ -2177,13 +2082,14 @@
}
var newIndex = moveUp ? index - 1 : index + 1;
var creator = this.item.getCreator(index);
var swapCreator = this.item.getCreator(newIndex);
this.item.setCreator(newIndex, creator.ref, creator.creatorTypeID);
this.item.setCreator(index, swapCreator.ref, swapCreator.creatorTypeID);
var a = this.item.getCreator(index);
var b = this.item.getCreator(newIndex);
this.item.setCreator(newIndex, a);
this.item.setCreator(index, b);
if (this.saveOnEdit) {
this.item.save();
return this.item.save();
}
}.bind(this));
]]>
</body>
</method>
@ -2207,24 +2113,6 @@
</body>
</method>
<!--
/*
function modifyCreatorByID(index, creatorID, creatorTypeID) {
throw ('Unimplemented');
var oldCreator = _itemBeingEdited.getCreator(index);
if (creator) {
oldCreator = creator.ref;
var oldCreatorID = oldCreator.creatorID;
}
Zotero.debug("Old creatorID is " + oldCreatorID);
_itemBeingEdited.setCreator(index, firstName, lastName, typeID, fieldMode);
_itemBeingEdited.save();
}
*/
-->
<method name="focusFirstField">
<body>
@ -2329,14 +2217,14 @@
<method name="blurOpenField">
<body>
<![CDATA[
var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
if (textboxes && textboxes.length) {
textboxes[0].inputField.blur();
}
]]>
</body>
<body><![CDATA[
return Zotero.spawn(function* () {
var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
if (textboxes && textboxes.length) {
yield this.blurHandler(textboxes[0].inputField);
}
}, this);
]]></body>
</method>
@ -2455,7 +2343,7 @@
var item = document.getBindingParent(this).item;
var exists = item.hasCreatorAt(index);
if (exists) {
var fieldMode = item.getCreator(index).ref.fieldMode;
var fieldMode = item.getCreator(index).name !== undefined ? 1 : 0;
}
var hideTransforms = !exists || !!fieldMode;
return !hideTransforms;">

View file

@ -98,11 +98,11 @@
</setter>
</property>
<field name="_parent"/>
<property name="parent" onget="return this._parent;">
<field name="_parentItem"/>
<property name="parentItem" onget="return this._parentItem;">
<setter>
<![CDATA[
this._parent = this._id('links').parent = val;
this._parentItem = this._id('links').parentItem = val;
]]>
</setter>
</property>
@ -112,20 +112,22 @@
<field name="_item"/>
<property name="item" onget="return this._item;">
<setter>
<![CDATA[
<![CDATA[
Zotero.spawn(function* () {
this._item = val;
// TODO: use clientDateModified instead
this._mtime = val.getField('dateModified');
var parent = this.item.getSourceKey();
if (parent) {
this.parent = Zotero.Items.getByLibraryAndKey(this.item.libraryID, parent);
var parentKey = this.item.parentKey;
if (parentKey) {
this.parentItem = yield Zotero.Items.getByLibraryAndKey(this.item.libraryID, parentKey);
}
this._id('links').item = this.item;
this.refresh();
]]>
yield this.refresh();
}, this);
]]>
</setter>
</property>
@ -155,109 +157,112 @@
<property name="value" onget="return this._id('noteField').value;" onset="this._id('noteField').value = val;"/>
<method name="refresh">
<body>
<![CDATA[
Zotero.debug('Refreshing note editor');
var textbox = this._id('noteField');
var textboxReadOnly = this._id('noteFieldReadOnly');
var button = this._id('goButton');
if (this.editable) {
textbox.hidden = false;
textboxReadOnly.hidden = true;
}
else {
textbox.hidden = true;
textboxReadOnly.hidden = false;
textbox = textboxReadOnly;
}
//var scrollPos = textbox.inputField.scrollTop;
if (this.item) {
textbox.value = this.item.getNote();
}
else {
textbox.value = ''
}
//textbox.inputField.scrollTop = scrollPos;
this._id('linksbox').hidden = !(this.displayTags && this.displayRelated);
if (this.keyDownHandler) {
textbox.setAttribute('onkeydown',
'document.getBindingParent(this).handleKeyDown(event)');
}
else {
textbox.removeAttribute('onkeydown');
}
if (this.commandHandler) {
textbox.setAttribute('oncommand',
'document.getBindingParent(this).commandHandler()');
}
else {
textbox.removeAttribute('oncommand');
}
if (this.displayButton) {
button.label = this.buttonCaption;
button.hidden = false;
button.setAttribute('oncommand',
'document.getBindingParent(this).clickHandler(this)');
}
else {
button.hidden = true;
}
]]>
</body>
<body><![CDATA[
return Zotero.spawn(function* () {
Zotero.debug('Refreshing note editor');
var textbox = this._id('noteField');
var textboxReadOnly = this._id('noteFieldReadOnly');
var button = this._id('goButton');
if (this.editable) {
textbox.hidden = false;
textboxReadOnly.hidden = true;
}
else {
textbox.hidden = true;
textboxReadOnly.hidden = false;
textbox = textboxReadOnly;
}
//var scrollPos = textbox.inputField.scrollTop;
if (this.item) {
yield this.item.loadNote();
textbox.value = this.item.getNote();
}
else {
textbox.value = '';
}
//textbox.inputField.scrollTop = scrollPos;
this._id('linksbox').hidden = !(this.displayTags && this.displayRelated);
if (this.keyDownHandler) {
textbox.setAttribute('onkeydown',
'document.getBindingParent(this).handleKeyDown(event)');
}
else {
textbox.removeAttribute('onkeydown');
}
if (this.commandHandler) {
textbox.setAttribute('oncommand',
'document.getBindingParent(this).commandHandler()');
}
else {
textbox.removeAttribute('oncommand');
}
if (this.displayButton) {
button.label = this.buttonCaption;
button.hidden = false;
button.setAttribute('oncommand',
'document.getBindingParent(this).clickHandler(this)');
}
else {
button.hidden = true;
}
}, this);
]]></body>
</method>
<method name="save">
<body>
<![CDATA[
if (this._mode == 'view') {
Zotero.debug("Not saving read-only note");
return;
}
// Update note
var noteField = this._id('noteField');
if (this.item) {
// If note is reselected automatically after save
// from external note window, don't overwrite content
//
// TODO: use clientDateModified instead
if (this.item.getField('dateModified') != this._mtime) {
Zotero.debug("Note has already been changed", 4);
return Zotero.spawn(function* () {
if (this._mode == 'view') {
Zotero.debug("Not saving read-only note");
return;
}
this.item.setNote(noteField.value);
if (this.saveOnEdit) {
this.item.save();
// Update note
var noteField = this._id('noteField');
if (this.item) {
// If note is reselected automatically after save
// from external note window, don't overwrite content
//
// TODO: use clientDateModified instead
if (this.item.getField('dateModified') != this._mtime) {
Zotero.debug("Note has already been changed", 4);
return;
}
let changed = this.item.setNote(noteField.value);
if (changed && this.saveOnEdit) {
yield this.item.save();
}
return;
}
return;
}
// Create new note
var item = new Zotero.Item('note');
if (this.parent) {
item.libraryID = this.parent.libraryID;
}
item.setNote(noteField.value);
if (this.parent) {
item.setSource(this.parent.id);
}
if (this.saveOnEdit) {
var id = item.save();
if (!this.parent && this.collection) {
this.collection.addItem(id);
// Create new note
var item = new Zotero.Item('note');
if (this.parentItem) {
item.libraryID = this.parentItem.libraryID;
}
}
this.item = Zotero.Items.get(id);
item.setNote(noteField.value);
if (this.parentItem) {
item.parentKey = this.parentItem.key;
}
if (this.saveOnEdit) {
var id = yield item.save();
if (!this.parentItem && this.collection) {
this.collection.addItem(id);
}
}
this.item = yield Zotero.Items.getAsync(id);
}.bind(this));
]]>
</body>
</method>
@ -378,11 +383,11 @@
]]>
</setter>
</property>
<field name="_parent"/>
<property name="parent" onget="return this._parent;">
<field name="_parentItem"/>
<property name="parentItem" onget="return this._parentItem;">
<setter>
<![CDATA[
this._parent = val;
this._parentItem = val;
var parentText = this.id('parentText');
if (parentText.firstChild) {
@ -392,25 +397,25 @@
if (this._parent && this.getAttribute('notitle') != '1') {
this.id('parent-row').hidden = undefined;
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
parentText.appendChild(document.createTextNode(this._parent.getDisplayTitle(true)));
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
}
]]>
</setter>
</property>
<method name="tagsClick">
<body>
<![CDATA[
this.id('tags').reload();
<body><![CDATA[
Zotero.spawn(function* () {
yield this.id('tags').reload();
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('tagsPopup').openPopupAtScreen(x, y, false);
]]>
</body>
}, this);
]]></body>
</method>
<method name="updateTagsSummary">
<body>
<![CDATA[
var v = this.id('tags').summary;
<body><![CDATA[
Zotero.spawn(function* () {
var v = yield this.id('tags').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
@ -419,13 +424,14 @@
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
+ Zotero.getString('punctuation.colon');
this.id('tagsClick').value = v;
]]>
</body>
}, this);
]]></body>
</method>
<method name="seeAlsoClick">
<body>
<![CDATA[
var relatedList = this.item.relatedItemsBidirectional;
<body><![CDATA[
Zotero.spawn(function* () {
yield this.item.loadRelations();
var relatedList = this.item.relatedItems;
if (relatedList.length > 0) {
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
@ -434,13 +440,13 @@
else {
this.id('seeAlso').add();
}
]]>
</body>
}, this);
]]></body>
</method>
<method name="updateSeeAlsoSummary">
<body>
<![CDATA[
var v = this.id('seeAlso').summary;
<body><![CDATA[
Zotero.spawn(function* () {
var v = yield this.id('seeAlso').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
@ -449,8 +455,8 @@
this.id('seeAlsoLabel').value = Zotero.getString('itemFields.related')
+ Zotero.getString('punctuation.colon');
this.id('seeAlsoClick').value = v;
]]>
</body>
}, this)
]]></body>
</method>
<method name="parentClick">
<body>
@ -479,9 +485,11 @@
var zp = lastWin.ZoteroPane;
}
var parentID = this.item.getSource();
zp.clearQuicksearch();
zp.selectItem(parentID);
Zotero.spawn(function* () {
var parentID = this.item.parentID;
yield zp.clearQuicksearch();
zp.selectItem(parentID);
}, this);
]]>
</body>
</method>

View file

@ -73,27 +73,31 @@
</property>
<property name="summary">
<getter>
<![CDATA[
<![CDATA[
return Zotero.spawn(function* () {
var r = "";
if (this.item) {
var related = this.item.relatedItemsBidirectional;
yield this.item.loadRelations();
var related = this.item.relatedItems;
if (related) {
related = Zotero.Items.get(related);
related = yield Zotero.Items.getAsync(related);
for(var i = 0; i < related.length; i++) {
r = r + related[i].getDisplayTitle() + ", ";
}
r = r.substr(0,r.length-2);
}
}
return r;
]]>
}, this);
]]>
</getter>
</property>
<method name="reload">
<body>
<![CDATA[
<![CDATA[
return Zotero.spawn(function* () {
var addButton = this.id('addButton');
addButton.hidden = !this.editable;
@ -102,9 +106,10 @@
rows.removeChild(rows.firstChild);
if (this.item) {
var related = this.item.relatedItemsBidirectional;
yield this.item.loadRelations();
var related = this.item.relatedItems;
if (related) {
related = Zotero.Items.get(related);
related = yield Zotero.Items.getAsync(related);
for (var i = 0; i < related.length; i++) {
var icon= document.createElement("image");
icon.className = "zotero-box-icon";
@ -169,12 +174,13 @@
this.updateCount();
}
}
]]>
}, this);
]]>
</body>
</method>
<method name="add">
<body>
<![CDATA[
<body><![CDATA[
return Zotero.spawn(function* () {
var io = {dataIn: null, dataOut: null};
window.openDialog('chrome://zotero/content/selectItemsDialog.xul', '',
@ -182,7 +188,7 @@
if(io.dataOut) {
if (io.dataOut.length) {
var relItem = Zotero.Items.get(io.dataOut[0]);
var relItem = yield Zotero.Items.getAsync(io.dataOut[0]);
if (relItem.libraryID != this.item.libraryID) {
// FIXME
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
@ -194,27 +200,27 @@
for(var i = 0; i < io.dataOut.length; i++) {
this.item.addRelatedItem(io.dataOut[i]);
}
this.item.save();
yield this.item.save();
}
]]>
</body>
}, this);
]]></body>
</method>
<method name="remove">
<parameter name="id"/>
<body>
<![CDATA[
<body><![CDATA[
return Zotero.spawn(function* () {
if(id) {
// TODO: set attribute on reload to determine
// which of these is necessary
this.item.removeRelatedItem(id);
this.item.save();
yield this.item.save();
var item = Zotero.Items.get(id);
var item = yield Zotero.Items.getAsync(id);
item.removeRelatedItem(this.item.id);
item.save();
yield item.save();
}
]]>
</body>
});
]]></body>
</method>
<method name="showItem">
<parameter name="id"/>
@ -257,7 +263,7 @@
<body>
<![CDATA[
if (count == null) {
var count = this.item.relatedItemsBidirectional.length;
var count = this.item.relatedItems.length;
}
var str = 'pane.item.related.count.';

View file

@ -329,6 +329,8 @@
html = '<div style="'+bodyStyle+'"><p>'+html+"</p></div>";
}
Zotero.debug("SETTING CONTENT TO " + html);
this._editor.setContent(html);
return val;
]]></setter>

View file

@ -90,26 +90,25 @@
<property name="count"/>
<property name="summary">
<getter>
<![CDATA[
<getter><![CDATA[
return Zotero.spawn(function* () {
var r = "";
if(this.item)
{
if (this.item) {
yield this.item.loadTags();
var tags = this.item.getTags();
if(tags)
{
if (tags) {
for(var i = 0; i < tags.length; i++)
{
r = r + tags[i].name + ", ";
r = r + tags[i].tag + ", ";
}
r = r.substr(0,r.length-2);
}
}
return r;
]]>
</getter>
}, this);
]]></getter>
</property>
<constructor>
@ -134,6 +133,7 @@
<parameter name="event"/>
<parameter name="type"/>
<parameter name="ids"/>
<parameter name="extraData"/>
<body>
<![CDATA[
if (type == 'setting') {
@ -143,15 +143,16 @@
return;
}
else if (type == 'item-tag') {
let itemID, tagID;
let itemID, tagName;
for (var i=0; i<ids.length; i++) {
[itemID, tagID] = ids[i].split('-');
for (let i=0; i<ids.length; i++) {
[itemID, tagName] = ids[i].match(/^([0-9]+)-(.+)/).slice(1);
if (!this.item || itemID != this.item.id) {
continue;
}
if (event == 'add') {
var newTabIndex = this.add(tagID);
var newTabIndex = this.add(tagName);
if (newTabIndex == -1) {
return;
}
@ -166,8 +167,15 @@
}
}
}
else if (event == 'modify') {
Zotero.debug("EXTRA");
Zotero.debug(extraData);
let oldTagName = extraData[tagName].old.tag;
this.remove(oldTagName);
this.add(tagName);
}
else if (event == 'remove') {
var oldTabIndex = this.remove(tagID);
var oldTabIndex = this.remove(tagName);
if (oldTabIndex == -1) {
return;
}
@ -197,53 +205,50 @@
<method name="reload">
<body>
<![CDATA[
Zotero.debug('Reloading tags');
<body><![CDATA[
return Zotero.spawn(function* () {
Zotero.debug('Reloading tags box');
yield this.item.loadTags();
// Cancel field focusing while we're updating
this._reloading = true;
this.id('addButton').hidden = !this.editable;
var self = this;
return Zotero.Tags.getColors(self.item.libraryID)
.then(function (colors) {
self._tagColors = colors;
var rows = self.id('tagRows');
while(rows.hasChildNodes()) {
rows.removeChild(rows.firstChild);
}
var tags = self.item.getTags() || [];
for (var i=0; i<tags.length; i++) {
self.addDynamicRow(tags[i], i+1);
}
self.updateCount(tags.length);
self._reloading = false;
self._focusField();
})
.done();
]]>
</body>
this._tagColors = yield Zotero.Tags.getColors(this.item.libraryID)
var rows = this.id('tagRows');
while(rows.hasChildNodes()) {
rows.removeChild(rows.firstChild);
}
var tags = this.item.getTags();
// Sort tags alphabetically
var collation = Zotero.getLocaleCollation();
tags.sort(function (a, b) collation.compareString(1, a.tag, b.tag));
for (let i=0; i<tags.length; i++) {
this.addDynamicRow(tags[i], i+1);
}
this.updateCount(tags.length);
this._reloading = false;
this._focusField();
}, this);
]]></body>
</method>
<method name="addDynamicRow">
<parameter name="tagObj"/>
<parameter name="tagData"/>
<parameter name="tabindex"/>
<parameter name="skipAppend"/>
<body>
<![CDATA[
if (tagObj) {
var tagID = tagObj.id;
var name = tagObj.name;
var type = tagObj.type;
}
if (!name) {
name = '';
}
var isNew = !tagData;
var name = tagData ? tagData.tag : "";
var type = tagData ? tagData.type : 0;
if (!tabindex) {
tabindex = this.id('tagRows').childNodes.length + 1;
@ -265,13 +270,16 @@
}
var row = document.createElement("row");
if (isNew) {
row.setAttribute('isNew', true);
}
row.appendChild(icon);
row.appendChild(label);
if (this.editable) {
row.appendChild(remove);
}
this.updateRow(row, tagObj);
this.updateRow(row, tagData);
if (!skipAppend) {
this.id('tagRows').appendChild(row);
@ -289,12 +297,10 @@
-->
<method name="updateRow">
<parameter name="row"/>
<parameter name="tagObj"/>
<parameter name="tagData"/>
<body><![CDATA[
if (tagObj) {
var tagID = tagObj.id;
var type = tagObj.type;
}
var tagName = tagData ? tagData.tag : "";
var tagType = (tagData && tagData.type) ? tagData.type : 0;
var icon = row.firstChild;
var label = row.firstChild.nextSibling;
@ -303,17 +309,15 @@
}
// Row
if (tagObj) {
row.setAttribute('id', 'tag-' + tagID);
row.setAttribute('tagType', type);
}
row.setAttribute('tagName', tagName);
row.setAttribute('tagType', tagType);
// Icon
var iconFile = 'tag';
if (type == 0) {
if (!tagData || tagType == 0) {
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.user'));
}
else if (type == 1) {
else if (tagType == 1) {
iconFile += '-automatic';
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.automatic'));
}
@ -321,23 +325,24 @@
// "-" button
if (this.editable) {
if (tagID) {
remove.setAttribute('disabled', false);
var self = this;
remove.addEventListener('click', function () {
remove.setAttribute('disabled', false);
var self = this;
remove.addEventListener('click', function () {
Zotero.spawn(function* () {
self._lastTabIndex = false;
document.getBindingParent(this).item.removeTag(tagID);
if (tagData) {
let item = document.getBindingParent(this).item
item.removeTag(tagName);
yield item.save()
}
// Return focus to items pane
var tree = document.getElementById('zotero-items-tree');
if (tree) {
tree.focus();
}
});
}
else {
remove.setAttribute('disabled', true);
}
}.bind(this));
});
}
]]></body>
</method>
@ -362,7 +367,7 @@
if (event.button) {
return;
}
document.getBindingParent(this).clickHandler(this);
document.getBindingParent(this).clickHandler(this, 1, valueText);
}, false);
valueElement.className += ' zotero-clicky';
}
@ -402,8 +407,7 @@
<parameter name="elem"/>
<parameter name="rows"/>
<parameter name="value"/>
<body>
<![CDATA[
<body><![CDATA[
// Blur any active fields
/*
if (this._dynamicFields) {
@ -416,10 +420,6 @@
var fieldName = 'tag';
var tabindex = elem.getAttribute('ztabindex');
var tagID = elem.parentNode.getAttribute('id').split('-')[1];
if (!value) {
var value = tagID ? Zotero.Tags.getName(tagID) : '';
}
var itemID = Zotero.getAncestorByTagName(elem, 'tagsbox').item.id;
var t = document.createElement("textbox");
@ -466,114 +466,117 @@
t.select();
return t;
]]>
</body>
]]></body>
</method>
<method name="handleKeyPress">
<parameter name="event"/>
<body>
<![CDATA[
var target = event.target;
var focused = document.commandDispatcher.focusedElement;
switch (event.keyCode) {
case event.DOM_VK_RETURN:
var multiline = target.getAttribute('multiline');
var empty = target.value == "";
if (event.shiftKey) {
if (!multiline) {
var self = this;
setTimeout(function () {
var val = target.value;
if (val !== "") {
val += "\n";
}
self.makeMultiline(target, val, 6);
}, 0);
return false;
<body><![CDATA[
return Zotero.spawn(function* () {
var target = event.target;
var focused = document.commandDispatcher.focusedElement;
switch (event.keyCode) {
case event.DOM_VK_RETURN:
var multiline = target.getAttribute('multiline');
var empty = target.value == "";
if (event.shiftKey) {
if (!multiline) {
var self = this;
setTimeout(function () {
var val = target.value;
if (val !== "") {
val += "\n";
}
self.makeMultiline(target, val, 6);
}, 0);
return false;
}
// Submit
}
// Submit
}
else if (multiline) {
return true;
}
var fieldname = 'tag';
// If last tag row, create new one
var row = Zotero.getAncestorByTagName(target, 'row');
// If non-empty last row, add new row
if (row == row.parentNode.lastChild && !empty) {
var focusField = true;
this._tabDirection = 1;
}
// If empty non-last row, refocus current row
else if (row != row.parentNode.lastChild && empty) {
var focusField = true;
}
// If non-empty non-last row, return focus to items pane
else {
var focusField = false;
else if (multiline) {
return true;
}
var fieldname = 'tag';
var row = Zotero.getAncestorByTagName(target, 'row');
// If non-empty last row, add new row
if (row == row.parentNode.lastChild && !empty) {
var focusField = true;
this._tabDirection = 1;
}
// If empty non-last row, refocus current row
else if (row != row.parentNode.lastChild && empty) {
var focusField = true;
}
// If non-empty non-last row, return focus to items pane
else {
var focusField = false;
this._lastTabIndex = false;
}
target.onblur = null;
yield this.blurHandler(target);
if (focusField) {
Zotero.debug("FOCUSING FIELD");
this._focusField();
}
// Return focus to items pane
else {
Zotero.debug("FOCUSING ITEM PANE");
var tree = document.getElementById('zotero-items-tree');
if (tree) {
tree.focus();
}
}
return false;
case event.DOM_VK_ESCAPE:
// Reset field to original value
target.value = target.getAttribute('value');
var tagsbox = Zotero.getAncestorByTagName(focused, 'tagsbox');
this._lastTabIndex = false;
}
focused.blur();
if (focusField) {
this._focusField();
}
// Return focus to items pane
else {
target.onblur = null;
yield this.blurHandler(target);
if (tagsbox) {
tagsbox.closePopup();
}
// Return focus to items pane
var tree = document.getElementById('zotero-items-tree');
if (tree) {
tree.focus();
}
}
return false;
case event.DOM_VK_ESCAPE:
// Reset field to original value
target.value = target.getAttribute('value');
var tagsbox = Zotero.getAncestorByTagName(focused, 'tagsbox');
this._lastTabIndex = false;
focused.blur();
if (tagsbox) {
tagsbox.closePopup();
}
// Return focus to items pane
var tree = document.getElementById('zotero-items-tree');
if (tree) {
tree.focus();
}
return false;
case event.DOM_VK_TAB:
// If already an empty last row, ignore forward tab
if (target.value == "" && !event.shiftKey) {
var row = Zotero.getAncestorByTagName(target, 'row');
if (row == row.parentNode.lastChild) {
return false;
return false;
case event.DOM_VK_TAB:
// If already an empty last row, ignore forward tab
if (target.value == "" && !event.shiftKey) {
var row = Zotero.getAncestorByTagName(target, 'row');
if (row == row.parentNode.lastChild) {
return false;
}
}
}
this._tabDirection = event.shiftKey ? -1 : 1;
focused.blur();
this._focusField();
return false;
}
return true;
]]>
</body>
this._tabDirection = event.shiftKey ? -1 : 1;
target.onblur = null;
yield this.blurHandler(target);
this._focusField();
return false;
}
return true;
}.bind(this));
]]></body>
</method>
<!--
@ -637,92 +640,94 @@
<method name="hideEditor">
<parameter name="textbox"/>
<body>
<![CDATA[
Zotero.debug('Hiding editor');
var fieldName = 'tag';
var tabindex = textbox.getAttribute('ztabindex');
var oldValue = textbox.getAttribute('value');
textbox.value = textbox.value.trim();
var value = textbox.value;
var tagsbox = Zotero.getAncestorByTagName(textbox, 'tagsbox');
if (!tagsbox)
{
Zotero.debug('Tagsbox not found', 1);
return;
}
var row = textbox.parentNode;
var rows = row.parentNode;
// Tag id encoded as 'tag-1234'
var oldTagID = row.getAttribute('id').split('-')[1];
// Remove empty row at end
if (!oldTagID && !value) {
row.parentNode.removeChild(row);
return;
}
// If row hasn't changed, change back to label
if (oldValue == value) {
this.textboxToLabel(textbox);
return;
}
var tagArray = value.split(/\r\n?|\n/);
// Modifying existing tag with a single new one
if (oldTagID && tagArray.length < 2) {
if (value) {
tagsbox.replace(oldTagID, value);
}
// Existing tag cleared
else {
this.item.removeTag(oldTagID);
}
}
// Multiple tags
else if (tagArray.length > 1) {
var lastTag = row == row.parentNode.lastChild;
<body><![CDATA[
return Zotero.spawn(function* () {
Zotero.debug('Hiding editor');
Zotero.DB.beginTransaction();
var fieldName = 'tag';
var tabindex = textbox.getAttribute('ztabindex');
if (oldTagID) {
var oldValue = Zotero.Tags.getName(oldTagID);
// If old tag isn't in array, remove it
if (tagArray.indexOf(oldValue) == -1) {
this.item.removeTag(oldTagID);
var oldValue = textbox.getAttribute('value');
var value = textbox.value = textbox.value.trim();
var tagsbox = Zotero.getAncestorByTagName(textbox, 'tagsbox');
if (!tagsbox)
{
Zotero.debug('Tagsbox not found', 1);
return;
}
var row = textbox.parentNode;
var rows = row.parentNode;
var isNew = row.getAttribute('isNew');
// Remove empty row at end
if (isNew && value === "") {
row.parentNode.removeChild(row);
return;
}
// If row hasn't changed, change back to label
if (oldValue == value) {
this.textboxToLabel(textbox);
return;
}
var tags = value.split(/\r\n?|\n/).map(function (val) val.trim());
// Modifying existing tag with a single new one
if (!isNew && tags.length < 2) {
if (value !== "") {
if (oldValue !== value) {
// The existing textbox will be removed in notify()
this.item.replaceTag(oldValue, value);
yield this.item.save();
}
}
// If old tag is staying, restore the textbox
// immediately. This isn't strictly necessary, but it
// makes the transition nicer.
// Existing tag cleared
else {
textbox.value = textbox.getAttribute('value');
this.textboxToLabel(textbox);
this.item.removeTag(oldValue);
yield this.item.save();
}
}
this.item.addTags(tagArray);
Zotero.DB.commitTransaction();
if (lastTag) {
this._lastTabIndex = this.item.getTags().length;
// Multiple tags
else if (tags.length > 1) {
var lastTag = row == row.parentNode.lastChild;
if (!isNew) {
// If old tag isn't in array, remove it
if (tags.indexOf(oldValue) == -1) {
this.item.removeTag(oldValue);
}
// If old tag is staying, restore the textbox
// immediately. This isn't strictly necessary, but it
// makes the transition nicer.
else {
textbox.value = textbox.getAttribute('value');
this.textboxToLabel(textbox);
}
}
this.item.addTags(tags);
yield this.item.save();
if (lastTag) {
this._lastTabIndex = this.item.getTags().length;
}
yield this.reload();
}
this.reload();
}
// Single tag at end
else {
row.parentNode.removeChild(row);
this.item.addTag(value);
}
]]>
</body>
// Single tag at end
else {
// Remove the textbox row. The new tag will be added in notify()
// if it doesn't already exist.
row.parentNode.removeChild(row);
this.item.addTag(value);
yield this.item.save();
}
}.bind(this));
]]></body>
</method>
@ -750,22 +755,31 @@
<method name="add">
<parameter name="tagID"/>
<parameter name="tagName"/>
<body><![CDATA[
Zotero.debug("ADDING ROW WITH " + tagName);
var rowsElement = this.id('tagRows');
var rows = rowsElement.childNodes;
// Get this tag's existing row, if there is one
var row = rowsElement.getElementsByAttribute('id', 'tag-' + tagID);
row = row.length ? row[0] : false;
var row = false;
for (let i=0; i<rows.length; i++) {
if (rows[i].getAttribute('tagName') === tagName) {
Zotero.debug("FOUND ROW with " + tagName);
return rows[i].getAttribute('ztabindex');
}
}
var tagObj = Zotero.Tags.get(tagID);
var name = tagObj.name;
var tagData = {
tag: tagName,
type: this.item.getTagType(tagName)
};
if (row) {
// Update row and label
this.updateRow(row, tagObj);
var elem = this.createValueElement(name);
this.updateRow(row, tagData);
var elem = this.createValueElement(tagName);
// Remove the old row, which we'll reinsert at the correct place
rowsElement.removeChild(row);
@ -779,7 +793,7 @@
}
else {
// Create new row, but don't insert it
row = this.addDynamicRow(tagObj, false, true);
row = this.addDynamicRow(tagData, false, true);
var elem = row.getElementsByAttribute('fieldname', 'tag')[0];
}
@ -797,7 +811,7 @@
continue;
}
if (collation.compareString(1, name, labels[i].textContent) > 0) {
if (collation.compareString(1, tagName, labels[i].textContent) > 0) {
labels[i].setAttribute('ztabindex', index);
continue;
}
@ -818,42 +832,22 @@
</method>
<method name="replace">
<parameter name="oldTagID"/>
<parameter name="newTag"/>
<body>
<![CDATA[
if(oldTagID && newTag)
{
var oldTag = Zotero.Tags.getName(oldTagID);
if (oldTag!=newTag)
{
return this.item.replaceTag(oldTagID, newTag);
}
}
return false;
]]>
</body>
</method>
<method name="remove">
<parameter name="id"/>
<parameter name="tagName"/>
<body><![CDATA[
Zotero.debug("REMOVING ROW WITH " + tagName);
var rowsElement = this.id('tagRows');
var row = rowsElement.getElementsByAttribute('id', 'tag-' + id);
row = row.length ? row[0] : false;
if (!row) {
return -1;
}
var rows = rowsElement.childNodes;
var removed = false;
var oldTabIndex = -1;
for (var i=0; i<rows.length; i++) {
let tagID = rows[i].getAttribute('id').split('-')[1];
if (tagID == id) {
let value = rows[i].getAttribute('tagName');
Zotero.debug("-=-=");
Zotero.debug(value);
Zotero.debug(tagName);
Zotero.debug(value === tagName);
if (value === tagName) {
oldTabIndex = i + 1;
removed = true;
rowsElement.removeChild(rows[i]);
@ -861,7 +855,7 @@
continue;
}
// After the removal, update tab indexes
if (removed && tagID) {
if (removed) {
var elem = rows[i].getElementsByAttribute('fieldname', 'tag')[0];
elem.setAttribute('ztabindex', i + 1);
}
@ -1043,14 +1037,16 @@
<method name="blurOpenField">
<body>
<![CDATA[
this._lastTabIndex = false;
var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
if (textboxes && textboxes.length) {
textboxes[0].inputField.blur();
}
<body><![CDATA[
return Zotero.spawn(function* () {
this._lastTabIndex = false;
var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
if (textboxes && textboxes.length) {
textboxes[0].inputField.onblur = null;
yield this.blurHandler(textboxes[0].inputField);
}
}.bind(this));
]]>
</body>
</method>

View file

@ -36,13 +36,17 @@
</resources>
<implementation>
<field name="collectionTreeRow"/>
<field name="updateScope"/>
<field name="selection"/>
<field name="onchange"/>
<field name="_initialized">false</field>
<field name="_notifierID">false</field>
<field name="_tags">null</field>
<field name="_dirty">null</field>
<field name="_emptyColored">null</field>
<field name="_emptyRegular">null</field>
<field name="selection"/>
<!-- Modes are predefined settings groups for particular tasks -->
<field name="_mode">"view"</field>
@ -118,9 +122,16 @@
<property name="scope" onget="return this._scope">
<setter>
<![CDATA[
if (val && !Zotero.Utilities.isEmpty(val)) {
if (val.length) {
this._hasScope = true;
this._scope = val;
this._scope = {};
for (let i=0; i<val.length; i++) {
let tag = val[i];
if (!this._scope[tag.tag]) {
this._scope[tag.tag] = [];
}
this._scope[tag.tag].push(tag.type);
}
}
else {
this._hasScope = false;
@ -161,7 +172,7 @@
<![CDATA[
this._initialized = true;
this.selection = {};
this._notifierID = Zotero.Notifier.registerObserver(this, ['collection-item', 'item-tag', 'tag', 'setting']);
this._notifierID = Zotero.Notifier.registerObserver(this, ['collection-item', 'item-tag', 'tag', 'setting'], 'tagSelector');
]]>
</body>
</method>
@ -177,7 +188,9 @@
this._initialized = false;
this.unregister();
this.selection = {};
this.doCommand();
if (this.onchange) {
this.onchange();
}
]]>
</body>
</method>
@ -198,33 +211,29 @@
<parameter name="fetch"/>
<body>
<![CDATA[
if (!this._initialized) {
this.init();
fetch = true;
}
Zotero.debug('Refreshing tags selector');
var emptyColored = true;
var emptyRegular = true;
var tagsToggleBox = this.id('tags-toggle');
var self = this;
Zotero.Tags.getColors(this.libraryID)
.then(function (tagColors) {
if (fetch || self._dirty) {
self._tags = Zotero.Tags.getAll(self._types, self.libraryID);
Zotero.spawn(function* () {
if (!this._initialized) {
this.init();
fetch = true;
}
Zotero.debug('Refreshing tags selector');
var emptyColored = true;
var emptyRegular = true;
var tagsToggleBox = this.id('tags-toggle');
var tagColors = yield Zotero.Tags.getColors(this.libraryID);
if (fetch || this._dirty) {
this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types);
// Remove children
tagsToggleBox.textContent = "";
// Sort by name
var orderedTags = [];
var collation = Zotero.getLocaleCollation();
for (let tagID in self._tags) {
orderedTags.push(self._tags[tagID])
}
var orderedTags = this._tags.concat();
orderedTags.sort(function(a, b) {
return collation.compareString(1, a.name, b.name);
return collation.compareString(1, a.tag, b.tag);
});
var tagColorsLowerCase = {};
@ -236,10 +245,8 @@
var positions = Object.keys(colorTags);
for (let i=positions.length-1; i>=0; i--) {
let name = colorTags[positions[i]];
let ids = Zotero.Tags.getIDs(name, self.libraryID);
orderedTags.unshift({
id: ids ? ids.join('-') : null,
name: name,
tag: name,
type: 0,
hasColor: true
});
@ -247,40 +254,41 @@
var lastTag;
for (let i=0; i<orderedTags.length; i++) {
let tagObj = orderedTags[i];
let tagData = orderedTags[i];
// Skip colored tags in the regular section,
// since we add them to the beginning above
if (!tagObj.hasColor && tagColorsLowerCase[tagObj.name.toLowerCase()]) {
if (!tagData.hasColor && tagColorsLowerCase[tagData.tag.toLowerCase()]) {
continue;
}
let tagButton = self._makeClickableTag(orderedTags[i], lastTag, self.editable);
// Only show tags of different types once
if (tagData.tag === lastTag) {
continue;
}
lastTag = tagData.tag;
let tagButton = this._makeClickableTag(tagData, this.editable);
if (tagButton) {
var self = this;
tagButton.addEventListener('click', function(event) {
self.handleTagClick(event, this);
});
if (self.editable) {
tagButton.addEventListener('dragover', self.dragObserver.onDragOver);
tagButton.addEventListener('dragexit', self.dragObserver.onDragExit);
tagButton.addEventListener('drop', self.dragObserver.onDrop, true);
if (this.editable) {
tagButton.addEventListener('dragover', this.dragObserver.onDragOver);
tagButton.addEventListener('dragexit', this.dragObserver.onDragExit);
tagButton.addEventListener('drop', this.dragObserver.onDrop, true);
}
lastTag = tagButton;
tagsToggleBox.appendChild(tagButton);
}
}
self._dirty = false;
this._dirty = false;
}
var searchTags = self._search ? Zotero.Tags.search(self._search) : {};
// Set attributes
var colorTags = {};
var labels = tagsToggleBox.getElementsByTagName('label');
for (let i=0; i<labels.length; i++) {
var tagIDs = labels[i].getAttribute('tagID');
tagIDs = tagIDs ? tagIDs.split('-') : [];
let name = labels[i].value;
let lcname = name.toLowerCase();
@ -295,7 +303,7 @@
}
// Restore selection
if (self.selection[name]){
if (this.selection[name]){
labels[i].setAttribute('selected', 'true');
}
else {
@ -303,39 +311,21 @@
}
// Check tags against search
if (self._search) {
var inSearch = false;
if (tagIDs.length) {
for (let i=0; i<tagIDs.length; i++) {
if (searchTags[tagIDs[i]]) {
inSearch = true;
break;
}
}
}
// For colored tags, compare by name
else if (lcname.indexOf(self._search) != -1) {
inSearch = true;
}
if (this._search) {
var inSearch = lcname.indexOf(this._search) != -1;
}
// Check tags against scope
if (self._hasScope) {
var inScope = false;
for (let i=0; i<tagIDs.length; i++) {
if (self._scope[tagIDs[i]]) {
inScope = true;
break;
}
}
if (this._hasScope) {
var inScope = !!this._scope[name];
}
// If not in search, hide
if (self._search && !inSearch) {
if (this._search && !inSearch) {
labels[i].setAttribute('hidden', true);
}
else if (self.filterToScope) {
if (self._hasScope && inScope) {
else if (this.filterToScope) {
if (this._hasScope && inScope) {
labels[i].className = 'zotero-clicky';
labels[i].setAttribute('inScope', true);
labels[i].setAttribute('hidden', false);
@ -349,7 +339,7 @@
}
// Display all
else {
if (self._hasScope && inScope) {
if (this._hasScope && inScope) {
labels[i].className = 'zotero-clicky';
labels[i].setAttribute('inScope', true);
}
@ -364,7 +354,7 @@
// Always show colored tags at top, unless they
// don't match an active tag search
if (colorData && (!self._search || inSearch)) {
if (colorData && (!this._search || inSearch)) {
labels[i].setAttribute('hidden', false);
labels[i].setAttribute('hasColor', true);
emptyColored = false;
@ -389,7 +379,7 @@
//replace getLinkedItems() with function that gets linked items within the current collection
var linked = self._tags[tagIDs[0]].getLinkedItems();
var linked = this._tags[tagIDs[0]].getLinkedItems();
numlinked.push(parseInt(linked.length));
}
@ -422,7 +412,7 @@
//replace getLinkedItems() with function that gets linked items within the current collection
var linked = self._tags[tagIDs[0]].getLinkedItems();
var linked = this._tags[tagIDs[0]].getLinkedItems();
numlink = linked.length;
@ -443,19 +433,21 @@
//end tag cloud code
self.updateNumSelected();
self._emptyColored = emptyColored;
self._emptyRegular = emptyRegular;
this.updateNumSelected();
this._emptyColored = emptyColored;
this._emptyRegular = emptyRegular;
var empty = emptyColored && emptyRegular;
self.id('tags-toggle').setAttribute('collapsed', empty);
self.id('no-tags-box').setAttribute('collapsed', !empty);
this.id('tags-toggle').setAttribute('collapsed', empty);
this.id('no-tags-box').setAttribute('collapsed', !empty);
if (self.onRefresh) {
self.onRefresh();
self.onRefresh = null;
if (this.onRefresh) {
this.onRefresh();
this.onRefresh = null;
}
})
.done();
// Clear "Loading tags…" after the first load
this.id('no-tags-deck').selectedIndex = 1;
}, this);
]]>
</body>
</method>
@ -501,80 +493,81 @@
<parameter name="event"/>
<parameter name="type"/>
<parameter name="ids"/>
<body>
<![CDATA[
if (type == 'setting') {
if (ids.some(function (val) val.split("/")[1] == 'tagColors')) {
this.refresh(true);
<body><![CDATA[
return Zotero.spawn(function* () {
if (type == 'setting') {
if (ids.some(function (val) val.split("/")[1] == 'tagColors')) {
this.refresh(true);
}
return;
}
return;
}
var itemGroup = ZoteroPane_Local.getItemGroup();
// Ignore anything other than deletes in duplicates view
if (itemGroup.isDuplicates()) {
switch (event) {
case 'delete':
case 'trash':
break;
default:
return;
}
}
// If a selected tag no longer exists, deselect it
if (event == 'delete') {
this._tags = Zotero.Tags.getAll(this._types, this.libraryID);
for (var tag in this.selection) {
for each(var tag2 in this._tags) {
if (tag == tag2) {
var found = true;
// Ignore anything other than deletes in duplicates view
if (this.collectionTreeRow.isDuplicates()) {
switch (event) {
case 'delete':
case 'trash':
break;
default:
return;
}
}
var selectionChanged = false;
// If a selected tag no longer exists, deselect it
if (event == 'delete' || event == 'modify') {
// TODO: necessary, or just use notifier value?
this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types);
for (var tag in this.selection) {
for each(var tag2 in this._tags) {
if (tag == tag2) {
var found = true;
break;
}
}
if (!found) {
delete this.selection[tag];
selectionChanged = true;
}
}
if (!found) {
delete this.selection[tag];
}
}
}
if(this._notified) return;
var me = this;
window.setTimeout(function() {
me._notified = false;
// This could be more optimized to insert new/changed tags at the appropriate
// spot if we cared, but we probably don't
var t = me.id('tags-search').inputField;
var t = this.id('tags-search').inputField;
if (t.value) {
me.setSearch(t.value, true);
this.setSearch(t.value, true);
}
else {
me.setSearch(false, true);
this.setSearch(false, true);
}
me._dirty = true;
this._dirty = true;
// This is a hack, but set this to run after the refresh,
// since _emptyRegular isn't set until then
me.onRefresh = function () {
this.onRefresh = function () {
// If no regular tags visible after a delete, deselect all.
// This is necessary so that a selected tag that's removed
// from its last item doesn't cause all regular tags to
// disappear without anything being visibly selected.
if ((event == 'remove' || event == 'delete') &&
me._emptyRegular && me.getNumSelected()) {
this._emptyRegular && this.getNumSelected()) {
Zotero.debug('No tags visible after delete -- deselecting all');
me.clearAll();
return this.clearAll();
}
};
}.bind(this);
me.doCommand();
}, 0);
this._notified = true;
// If the selection changed, update the items list
if (selectionChanged && this.onchange) {
return this.onchange();
}
// Otherwise, just update the tag selector
return this.updateScope();
}, this);
]]>
</body>
</method>
@ -600,8 +593,8 @@
<method name="clearVisible">
<body>
<![CDATA[
<body><![CDATA[
return Zotero.spawn(function* () {
var tagsToggleBox = this.id('tags-toggle');
var labels = Zotero.Utilities.xpath(tagsToggleBox, 'label[@selected="true"]');
@ -611,19 +604,19 @@
delete this.selection[label.value];
}
this.doCommand();
]]>
</body>
if (this.onchange) {
this.onchange();
}
}, this);
]]></body>
</method>
<method name="clearAll">
<body>
<![CDATA[
this.selection = {};
this.clearVisible();
]]>
</body>
<body><![CDATA[
this.selection = {};
return this.clearVisible();
]]></body>
</method>
@ -682,9 +675,11 @@
label.setAttribute('selected', 'true');
}
this.doCommand();
this.updateNumSelected();
if (this.onchange) {
this.onchange();
}
]]>
</body>
</method>
@ -692,76 +687,45 @@
<method name="rename">
<parameter name="oldName"/>
<body>
<![CDATA[
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var newName = { value: oldName };
var result = promptService.prompt(window,
Zotero.getString('pane.tagSelector.rename.title'),
Zotero.getString('pane.tagSelector.rename.message'),
newName, '', {});
if (!result || !newName.value || oldName == newName.value) {
return;
}
// Get current tagIDs with the old name
var tagIDs = Zotero.Tags.getIDs(oldName, this.libraryID) || [];
if (tagIDs.length) {
<body><![CDATA[
Zotero.spawn(function* () {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var newName = { value: oldName };
var result = promptService.prompt(window,
Zotero.getString('pane.tagSelector.rename.title'),
Zotero.getString('pane.tagSelector.rename.message'),
newName, '', {});
if (!result || !newName.value || oldName == newName.value) {
return;
}
if (this.selection[oldName]) {
var wasSelected = true;
delete this.selection[oldName];
}
// TODO: redo transaction for async DB
var promises = [];
Zotero.DB.beginTransaction();
for (var i=0; i<tagIDs.length; i++) {
promises.push(Zotero.Tags.rename(tagIDs[i], newName.value));
yield Zotero.Tags.load(this.libraryID);
if (Zotero.Tags.getID(this.libraryID, oldName)) {
yield Zotero.Tags.rename(this.libraryID, oldName, newName.value);
}
// Colored tags don't need to exist, so in that case
// just rename the color setting
else {
let color = yield Zotero.Tags.getColor(this.libraryID, oldName);
if (!color) {
throw new Error("Can't rename missing tag");
}
yield Zotero.Tags.setColor(this.libraryID, oldName, false);
yield Zotero.Tags.setColor(this.libraryID, newName, color);
}
if (wasSelected) {
this.selection[newName.value] = true;
}
Zotero.DB.commitTransaction();
Q.all(promises)
.done();
}
// Colored tags don't need to exist, so in that case
// just rename the color setting
else {
var self = this;
Zotero.Tags.getColor(this.libraryID, oldName)
.then(function (color) {
if (color) {
if (self.selection[oldName]) {
var wasSelected = true;
delete self.selection[oldName];
}
return Zotero.Tags.setColor(
self.libraryID, oldName, false
)
.then(function () {
return Zotero.Tags.setColor(
self.libraryID, newName, color
)
.then(function () {
if (wasSelected) {
self.selection[newName.value] = true;
}
});
});
}
else {
throw new Error("Can't rename missing tag");
}
})
.done();
}
}.bind(this));
]]>
</body>
</method>
@ -778,30 +742,21 @@
Zotero.getString('pane.tagSelector.delete.title'),
Zotero.getString('pane.tagSelector.delete.message'));
if (confirmed) {
Zotero.DB.beginTransaction();
// Add other ids with same tag
var ids = Zotero.Tags.getIDs(name, this.libraryID);
var tagIDs = [];
for each(var id in ids) {
if (tagIDs.indexOf(id) == -1) {
tagIDs.push(id);
}
if (!confirmed) {
return;
}
return Zotero.DB.executeTransaction(function* () {
yield Zotero.Tags.load(this.libraryID);
var tagID = Zotero.Tags.getID(this.libraryID, name);
if (tagID) {
yield Zotero.Tags.erase(this.libraryID, tagID);
}
if (tagIDs.length) {
Zotero.Tags.erase(tagIDs);
Zotero.Tags.purge(tagIDs);
}
Zotero.DB.commitTransaction()
// If only a tag color setting, remove that
if (!tagIDs.length) {
Zotero.Tags.setColor(this.libraryID, name, false);
}
}.bind(this));
// If only a tag color setting, remove that
if (!tagID) {
Zotero.Tags.setColor(this.libraryID, name, false);
}
]]>
</body>
@ -812,7 +767,7 @@
<body>
<![CDATA[
tagIDs = tagIDs.split('-');
var name = Zotero.Tags.getName(tagIDs[0]);
var name = Zotero.Tags.getName(this.libraryID, tagIDs[0]);
return Zotero.Tags.getColor(this.libraryID, name)
.then(function (colorData) {
return colorData ? colorData.color : '#000000';
@ -824,25 +779,15 @@
<method name="_makeClickableTag">
<parameter name="tagObj"/>
<parameter name="lastTag"/>
<parameter name="editable"/>
<body>
<![CDATA[
var tagID = tagObj.id, tagName = tagObj.name, tagType = tagObj.type;
// If the last tag was the same, add this tagID and tagType to it
if(lastTag && lastTag.value === tagName) {
lastTag.setAttribute('tagID', lastTag.getAttribute('tagID') + '-' + tagID);
lastTag.setAttribute('tagType', lastTag.getAttribute('tagType') + '-' + tagType);
return false;
}
var tagName = tagObj.tag;
var tagType = tagObj.type;
var label = document.createElement('label');
label.setAttribute('value', tagName);
// Not used for color tags
if (tagID) {
label.setAttribute('tagID', tagID);
}
label.setAttribute('tagType', tagType);
if (editable) {
label.setAttribute('context', 'tag-menu');
@ -872,20 +817,24 @@
return;
}
window.openDialog(
'chrome://zotero/content/tagColorChooser.xul',
"zotero-tagSelector-colorChooser",
"chrome,modal,centerscreen", io
);
// Dialog cancel
if (typeof io.color == 'undefined') {
return;
}
return Zotero.Tags.setColor(self.libraryID, io.name, io.color, io.position);
})
.done();
// Opening a modal window directly from within this promise handler causes
// the opened window to block on the first yielded promise until the window
// is closed.
setTimeout(function () {
window.openDialog(
'chrome://zotero/content/tagColorChooser.xul',
"zotero-tagSelector-colorChooser",
"chrome,modal,centerscreen", io
);
// Dialog cancel
if (typeof io.color == 'undefined') {
return;
}
Zotero.Tags.setColor(self.libraryID, io.name, io.color, io.position);
}, 0);
});
]]>
</body>
</method>
@ -931,7 +880,7 @@
}
this.onDrop = function (event) {
this.onDrop = Zotero.Promise.method(function (event) {
var node = event.target;
node.setAttribute('draggedOver', false);
@ -941,39 +890,18 @@
return;
}
Zotero.DB.beginTransaction();
ids = ids.split(',');
var items = Zotero.Items.get(ids);
// Find a manual tag if there is one
var tagID = null;
var tagIDs = node.getAttribute('tagID');
tagIDs = tagIDs ? node.getAttribute('tagID').split(/\-/) : [];
var tagTypes = node.getAttribute('tagType').split(/\-/);
for (var i=0; i<tagIDs.length; i++) {
if (tagTypes[i] == 0) {
tagID = Zotero.Tags.get(tagIDs[i]).id
break;
}
}
// Otherwise use value
if (!tagID) {
var value = node.getAttribute('value');
}
for each(var item in items) {
if (tagID) {
item.addTagByID(tagID);
}
else {
return Zotero.DB.executeTransaction(function* () {
ids = ids.split(',');
var items = Zotero.Items.get(ids);
var value = node.getAttribute('value')
for (let i=0; i<items.length; i++) {
let item = items[i];
item.addTag(value);
yield item.save();
}
}
Zotero.DB.commitTransaction();
}
}.bind(this));
});
]]>
</body>
</method>
@ -1001,7 +929,10 @@
</menupopup>
<vbox id="no-tags-box" align="center" pack="center" flex="1">
<label value="&zotero.tagSelector.noTagsToDisplay;"/>
<deck id="no-tags-deck">
<label value="&zotero.tagSelector.loadingTags;"/>
<label value="&zotero.tagSelector.noTagsToDisplay;"/>
</deck>
</vbox>
<vbox id="tags-toggle" flex="1"/>

View file

@ -177,8 +177,7 @@
this.onLibraryChange(libraryID);
}
// TODO: libraryIDInt
this.searchRef.libraryID = libraryID ? libraryID : null;
this.searchRef.libraryID = libraryID;
]]></body>
</method>

View file

@ -110,7 +110,7 @@ var Zotero_Browser = new function() {
if (!Zotero || Zotero.skipLoading) {
// Zotero either failed to load or is reloading in Connector mode
// In case of the latter, listen for the 'zotero-loaded' event (once) and retry
var zoteroInitDone_deferred = Q.defer();
var zoteroInitDone_deferred = Zotero.Promise.defer();
var obs = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
var observer = {
@ -123,14 +123,14 @@ var Zotero_Browser = new function() {
zoteroInitDone = zoteroInitDone_deferred.promise;
} else {
zoteroInitDone = Q();
zoteroInitDone = Zotero.Promise.resolve();
}
var chromeLoaded = Q.defer();
var chromeLoaded = Zotero.Promise.defer();
window.addEventListener("load", function(e) { chromeLoaded.resolve() }, false);
// Wait for Zotero to init and chrome to load before proceeding
Q.all([
Zotero.Promise.all([
zoteroInitDone.then(function() {
ZoteroPane_Local.addReloadListener(reload);
reload();
@ -139,8 +139,7 @@ var Zotero_Browser = new function() {
])
.then(function() {
Zotero_Browser.chromeLoad()
})
.done();
});
}
/**

View file

@ -63,7 +63,7 @@ var Zotero_DownloadOverlay = new function() {
.getMostRecentWindow("navigator:browser");
var libraryID, collection;
try {
if(win.ZoteroPane.getItemGroup().filesEditable) {
if(win.ZoteroPane.getCollectionTreeRow().filesEditable) {
libraryID = win.ZoteroPane.getSelectedLibraryID();
collection = win.ZoteroPane.getSelectedCollection();
}
@ -147,7 +147,7 @@ var Zotero_DownloadOverlay = new function() {
var zoteroSelected = document.getElementById('zotero-radio').selected;
var zp = Zotero.getActiveZoteroPane(), canSave = true;
try {
canSave = zp.getItemGroup().filesEditable;
canSave = zp.getCollectionTreeRow().filesEditable;
} catch(e) {
Zotero.logError(e);
};

View file

@ -145,7 +145,7 @@ var Zotero_Duplicates_Pane = new function () {
this.merge = function () {
var itembox = document.getElementById('zotero-duplicates-merge-item-box');
Zotero.ItemGroupCache.clear();
Zotero.CollectionTreeCache.clear();
Zotero.Items.merge(itembox.item, _otherItems);
}
}

View file

@ -210,7 +210,7 @@ var Zotero_File_Interface = new function() {
}
var translation = new Zotero.Translate.Import();
(file ? Q(file) : translation.getTranslators().then(function(translators) {
(file ? Zotero.Promise.resolve(file) : translation.getTranslators().then(function(translators) {
const nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);

View file

@ -141,9 +141,9 @@ var Zotero_Citation_Dialog = new function () {
// If we're in a different library, switch libraries
var id = io.citation.citationItems[0].id;
var itemGroup = collectionsView._getItemAtRow(collectionsView.selection.currentIndex);
var collectionTreeRow = collectionsView.selectedTreeRow;
var item = Zotero.Items.get(id);
if(item.libraryID != itemGroup.ref.libraryID) {
if(item.libraryID != collectionTreeRow.ref.libraryID) {
collectionsView.selectLibrary(item.libraryID);
}
var selected = itemsView.selectItem(id);

View file

@ -50,7 +50,7 @@ var ZoteroItemPane = new function() {
/*
* Load a top-level item
*/
this.viewItem = function (item, mode, index) {
this.viewItem = Zotero.Promise.coroutine(function* (item, mode, index) {
if (!index) {
index = 0;
}
@ -77,7 +77,7 @@ var ZoteroItemPane = new function() {
switch (index) {
case 0:
case 2:
box.blurOpenField();
yield box.blurOpenField();
// DEBUG: Currently broken
//box.scrollToTop();
break;
@ -94,10 +94,13 @@ var ZoteroItemPane = new function() {
_notesList.removeChild(_notesList.firstChild);
}
var notes = Zotero.Items.get(item.getNotes());
yield item.loadChildItems();
let notes = yield Zotero.Items.getAsync(item.getNotes());
if (notes.length) {
for(var i = 0; i < notes.length; i++) {
for (var i = 0; i < notes.length; i++) {
let note = notes[i];
let id = notes[i].id;
yield note.loadItemData();
var icon = document.createElement('image');
icon.className = "zotero-box-icon";
@ -105,7 +108,7 @@ var ZoteroItemPane = new function() {
var label = document.createElement('label');
label.className = "zotero-box-label";
var title = Zotero.Notes.noteToTitle(notes[i].getNote());
var title = note.getNoteTitle();
title = title ? title : Zotero.getString('pane.item.notes.untitled');
label.setAttribute('value', title);
label.setAttribute('flex','1'); //so that the long names will flex smaller
@ -144,12 +147,14 @@ var ZoteroItemPane = new function() {
else {
box.mode = 'edit';
}
yield [item.loadItemData(), item.loadCreators()];
box.item = item;
}
});
this.addNote = function (popup) {
ZoteroPane_Local.newNote(popup, _lastItem.id);
ZoteroPane_Local.newNote(popup, _lastItem.key);
}

View file

@ -143,14 +143,14 @@ var Zotero_LocateMenu = new function() {
* @param {Boolean} showIcons Whether menu items should have associated icons
* @param {Boolean} addExtraOptions Whether to add options that start with "_" below the separator
*/
function _addViewOptions(locateMenu, selectedItems, showIcons, addExtraOptions) {
var _addViewOptions = Zotero.Promise.coroutine(function* (locateMenu, selectedItems, showIcons, addExtraOptions) {
var optionsToShow = {};
// check which view options are available
for each(var item in selectedItems) {
for(var viewOption in ViewOptions) {
if(!optionsToShow[viewOption]) {
optionsToShow[viewOption] = ViewOptions[viewOption].canHandleItem(item);
optionsToShow[viewOption] = yield ViewOptions[viewOption].canHandleItem(item);
}
}
}
@ -178,7 +178,7 @@ var Zotero_LocateMenu = new function() {
ViewOptions[viewOption], showIcons), lastNode);
}
}
}
});
/**
* Get available locate engines that can handle a set of items
@ -343,26 +343,29 @@ var Zotero_LocateMenu = new function() {
ViewOptions.pdf = new function() {
this.icon = "chrome://zotero/skin/treeitem-attachment-pdf.png";
this._mimeTypes = ["application/pdf"];
this.canHandleItem = function(item) !!_getFirstAttachmentWithMIMEType(item, this._mimeTypes);
this.handleItems = function(items, event) {
this.canHandleItem = function (item) {
return _getFirstAttachmentWithMIMEType(item, this._mimeTypes).then((item) => !!item);
}
this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
var attachments = [];
for each(var item in items) {
var attachment = _getFirstAttachmentWithMIMEType(item, this._mimeTypes);
var attachment = yield _getFirstAttachmentWithMIMEType(item, this._mimeTypes);
if(attachment) attachments.push(attachment.id);
}
ZoteroPane_Local.viewAttachment(attachments, event);
}
});
function _getFirstAttachmentWithMIMEType(item, mimeTypes) {
var attachments = (item.isAttachment() ? [item] : Zotero.Items.get(item.getBestAttachments()));
var _getFirstAttachmentWithMIMEType = Zotero.Promise.coroutine(function* (item, mimeTypes) {
var attachments = (item.isAttachment() ? [item] : (yield item.getBestAttachments()));
for each(var attachment in attachments) {
if(mimeTypes.indexOf(attachment.attachmentMIMEType) !== -1
if (mimeTypes.indexOf(attachment.attachmentContentType) !== -1
&& attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) return attachment;
}
return false;
}
});
};
/**
@ -372,14 +375,16 @@ var Zotero_LocateMenu = new function() {
*/
ViewOptions.online = new function() {
this.icon = "chrome://zotero/skin/locate-view-online.png";
this.canHandleItem = function(item) _getURL(item) !== false;
this.handleItems = function(items, event) {
var urls = [_getURL(item) for each(item in items)];
ZoteroPane_Local.loadURI([url for each(url in urls) if(url)], event);
this.canHandleItem = function (item) {
return _getURL(item).then((val) => val !== false);
}
this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
var urls = yield [_getURL(item) for each(item in items)];
ZoteroPane_Local.loadURI([url for each(url in urls) if(url)], event);
});
function _getURL(item) {
var _getURL = Zotero.Promise.coroutine(function* (item) {
// try url field for item and for attachments
var urlField = item.getField('url');
if(urlField) {
@ -391,6 +396,7 @@ var Zotero_LocateMenu = new function() {
}
if(item.isRegularItem()) {
yield item.loadChildItems();
var attachments = item.getAttachments();
if(attachments) {
// look through url fields for non-file:/// attachments
@ -412,7 +418,7 @@ var Zotero_LocateMenu = new function() {
}
return false;
}
});
};
/**
@ -436,29 +442,32 @@ var Zotero_LocateMenu = new function() {
*/
ViewOptions.file = new function() {
this.icon = "chrome://zotero/skin/treeitem-attachment-file.png";
this.canHandleItem = function(item) !!_getFile(item);
this.handleItems = function(items, event) {
this.canHandleItem = function (item) {
return _getFile(item).then((item) => !!item);
}
this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
var attachments = [];
for each(var item in items) {
var attachment = _getFile(item);
var attachment = yield _getFile(item);
if(attachment) attachments.push(attachment.id);
}
ZoteroPane_Local.viewAttachment(attachments, event);
}
});
function _getFile(item) {
var attachments = (item.isAttachment() ? [item] : Zotero.Items.get(item.getBestAttachments()));
var _getFile = Zotero.Promise.coroutine(function* (item) {
var attachments = item.isAttachment() ? [item] : (yield item.getBestAttachments());
for each(var attachment in attachments) {
if(!ViewOptions.snapshot.canHandleItem(attachment)
&& !ViewOptions.pdf.canHandleItem(attachment)
if (!(yield ViewOptions.snapshot.canHandleItem(attachment))
&& !(yield ViewOptions.pdf.canHandleItem(attachment))
&& attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) {
return attachment;
}
}
return false;
}
});
};
/**
@ -471,28 +480,28 @@ var Zotero_LocateMenu = new function() {
this.icon = "chrome://zotero/skin/locate-external-viewer.png";
this.useExternalViewer = true;
this.canHandleItem = function(item) {
this.canHandleItem = Zotero.Promise.coroutine(function* (item) {
return (this.useExternalViewer ^ Zotero.Prefs.get('launchNonNativeFiles'))
&& _getBestNonNativeAttachment(item);
}
&& (yield _getBestNonNativeAttachment(item));
});
this.handleItems = function(items, event) {
this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
var attachments = [];
for each(var item in items) {
var attachment = _getBestNonNativeAttachment(item);
var attachment = yield _getBestNonNativeAttachment(item);
if(attachment) attachments.push(attachment.id);
}
ZoteroPane_Local.viewAttachment(attachments, event, false, this.useExternalViewer);
}
});
function _getBestNonNativeAttachment(item) {
var attachments = (item.isAttachment() ? [item] : Zotero.Items.get(item.getBestAttachments()));
var _getBestNonNativeAttachment = Zotero.Promise.coroutine(function* (item) {
var attachments = item.isAttachment() ? [item] : (yield item.getBestAttachments());
for each(var attachment in attachments) {
if(attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) {
var file = attachment.getFile();
if(file) {
var ext = Zotero.File.getExtension(file);
var path = yield attachment.getFilePath();
if (path) {
var ext = Zotero.File.getExtension(Zotero.File.pathToFile(path));
if(!attachment.attachmentMIMEType ||
Zotero.MIME.hasNativeHandler(attachment.attachmentMIMEType, ext) ||
!Zotero.MIME.hasInternalHandler(attachment.attachmentMIMEType, ext)) {
@ -503,7 +512,7 @@ var Zotero_LocateMenu = new function() {
}
}
return false;
}
});
};
/**
@ -529,27 +538,27 @@ var Zotero_LocateMenu = new function() {
this.icon = "chrome://zotero/skin/locate-show-file.png";
this.useExternalViewer = true;
this.canHandleItem = function(item) {
return !!_getBestFile(item);
this.canHandleItem = function (item) {
return _getBestFile(item).then(function (item) !!item);
}
this.handleItems = function(items, event) {
this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
for each(var item in items) {
var attachment = _getBestFile(item);
var attachment = yield _getBestFile(item);
if(attachment) {
ZoteroPane_Local.showAttachmentInFilesystem(attachment.id);
}
}
}
});
function _getBestFile(item) {
var _getBestFile = Zotero.Promise.coroutine(function* (item) {
if(item.isAttachment()) {
if(item.attachmentLinkMode === Zotero.Attachments.LINK_MODE_LINKED_URL) return false;
return item;
} else {
return Zotero.Items.get(item.getBestAttachment());
return yield item.getBestAttachment();
}
}
});
};
/**
@ -559,8 +568,8 @@ var Zotero_LocateMenu = new function() {
*/
ViewOptions._libraryLookup = new function() {
this.icon = "chrome://zotero/skin/locate-library-lookup.png";
this.canHandleItem = function(item) item.isRegularItem();
this.handleItems = function(items, event) {
this.canHandleItem = function (item) Zotero.Promise.resolve(item.isRegularItem());
this.handleItems = Zotero.Promise.method(function (items, event) {
var urls = [];
for each(var item in items) {
if(!item.isRegularItem()) continue;
@ -568,6 +577,6 @@ var Zotero_LocateMenu = new function() {
if(url) urls.push(url);
}
ZoteroPane_Local.loadURI(urls, event);
}
});
};
}

View file

@ -27,6 +27,13 @@ var noteEditor;
var notifierUnregisterID;
function onLoad() {
Zotero.spawn(function* () {
Zotero.debug('=-=-=');
var bar = yield Zotero.Promise.delay(1000).return('DONE');
Zotero.debug(bar);
Zotero.debug('-----');
});
noteEditor = document.getElementById('zotero-note-editor');
noteEditor.mode = 'edit';
noteEditor.focus();
@ -39,38 +46,40 @@ function onLoad() {
}
var itemID = io.itemID;
var collectionID = io.collectionID;
var parentItemID = io.parentItemID;
var parentItemKey = io.parentItemKey;
if (itemID) {
var ref = Zotero.Items.get(itemID);
var clearUndo = noteEditor.item ? noteEditor.item.id != ref.id : false;
noteEditor.item = ref;
// If loading new or different note, disable undo while we repopulate the text field
// so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
// undo content from another note into the current one.
if (clearUndo) {
noteEditor.clearUndo();
}
document.title = ref.getNoteTitle();
}
else {
if (parentItemID) {
var ref = Zotero.Items.get(parentItemID);
noteEditor.parent = ref;
return Zotero.spawn(function* () {
if (itemID) {
var ref = yield Zotero.Items.getAsync(itemID);
var clearUndo = noteEditor.item ? noteEditor.item.id != ref.id : false;
noteEditor.item = ref;
// If loading new or different note, disable undo while we repopulate the text field
// so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
// undo content from another note into the current one.
if (clearUndo) {
noteEditor.clearUndo();
}
document.title = ref.getNoteTitle();
}
else {
if (collectionID && collectionID != '' && collectionID != 'undefined') {
noteEditor.collection = Zotero.Collections.get(collectionID);
if (parentItemKey) {
var ref = Zotero.Items.getByLibraryAndKey(parentItemKey);
noteEditor.parentItem = ref;
}
else {
if (collectionID && collectionID != '' && collectionID != 'undefined') {
noteEditor.collection = Zotero.Collections.get(collectionID);
}
}
noteEditor.refresh();
}
noteEditor.refresh();
}
notifierUnregisterID = Zotero.Notifier.registerObserver(NotifyCallback, 'item');
notifierUnregisterID = Zotero.Notifier.registerObserver(NotifyCallback, 'item');
});
}
function onUnload()

View file

@ -60,7 +60,7 @@ var ZoteroOverlay = new function()
var self = this;
Q.fcall(function () {
Zotero.Promise.try(function () {
if (!Zotero || Zotero.skipLoading) {
throw true;
}
@ -227,12 +227,17 @@ var ZoteroOverlay = new function()
* the foreground.
*/
this.toggleDisplay = function(makeVisible, dontRefocus)
{
{
if (!Zotero || Zotero.skipLoading) {
ZoteroPane.displayStartupError();
return;
}
// Don't do anything if pane is already showing
if (makeVisible && ZoteroPane.isShowing()) {
return;
}
if(makeVisible || makeVisible === undefined) {
if(Zotero.isConnector) {
// If in connector mode, bring Zotero Standalone to foreground

View file

@ -181,7 +181,7 @@ Zotero_Preferences.Export = {
},
refreshQuickCopySiteList: function () {
refreshQuickCopySiteList: Zotero.Promise.coroutine(function* () {
var treechildren = document.getElementById('quickCopy-siteSettings-rows');
while (treechildren.hasChildNodes()) {
treechildren.removeChild(treechildren.firstChild);
@ -189,37 +189,31 @@ Zotero_Preferences.Export = {
var sql = "SELECT key AS domainPath, value AS format FROM settings "
+ "WHERE setting='quickCopySite' ORDER BY domainPath COLLATE NOCASE";
var siteData = Zotero.DB.query(sql);
var siteData = yield Zotero.DB.queryAsync(sql);
if (!siteData) {
return;
for (var i=0; i<siteData.length; i++) {
let treeitem = document.createElement('treeitem');
let treerow = document.createElement('treerow');
let domainCell = document.createElement('treecell');
let formatCell = document.createElement('treecell');
let HTMLCell = document.createElement('treecell');
domainCell.setAttribute('label', siteData[i].domainPath);
yield Zotero.QuickCopy.getFormattedNameFromSetting(siteData[i].format)
.then(function (formatted) {
formatCell.setAttribute('label', formatted);
var copyAsHTML = Zotero.QuickCopy.getContentType(siteData[i].format) == 'html';
HTMLCell.setAttribute('label', copyAsHTML ? ' ✓ ' : '');
treerow.appendChild(domainCell);
treerow.appendChild(formatCell);
treerow.appendChild(HTMLCell);
treeitem.appendChild(treerow);
treechildren.appendChild(treeitem);
});
}
Q.async(function () {
for (var i=0; i<siteData.length; i++) {
let treeitem = document.createElement('treeitem');
let treerow = document.createElement('treerow');
let domainCell = document.createElement('treecell');
let formatCell = document.createElement('treecell');
let HTMLCell = document.createElement('treecell');
domainCell.setAttribute('label', siteData[i].domainPath);
yield Zotero.QuickCopy.getFormattedNameFromSetting(siteData[i].format)
.then(function (formatted) {
formatCell.setAttribute('label', formatted);
var copyAsHTML = Zotero.QuickCopy.getContentType(siteData[i].format) == 'html';
HTMLCell.setAttribute('label', copyAsHTML ? ' ✓ ' : '');
treerow.appendChild(domainCell);
treerow.appendChild(formatCell);
treerow.appendChild(HTMLCell);
treeitem.appendChild(treerow);
treechildren.appendChild(treeitem);
});
}
})().done();
},
}),
deleteSelectedQuickCopySite: function () {

View file

@ -430,8 +430,8 @@ Zotero_Preferences.Search = {
},
updateIndexStats: function () {
var stats = Zotero.Fulltext.getIndexStats();
updateIndexStats: Zotero.Promise.coroutine(function* () {
var stats = yield Zotero.Fulltext.getIndexStats();
document.getElementById('fulltext-stats-indexed').
lastChild.setAttribute('value', stats.indexed);
document.getElementById('fulltext-stats-partial').
@ -440,10 +440,10 @@ Zotero_Preferences.Search = {
lastChild.setAttribute('value', stats.unindexed);
document.getElementById('fulltext-stats-words').
lastChild.setAttribute('value', stats.words);
},
}),
rebuildIndexPrompt: function () {
rebuildIndexPrompt: Zotero.Promise.coroutine(function* () {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
@ -462,17 +462,17 @@ Zotero_Preferences.Search = {
null, {});
if (index == 0) {
Zotero.Fulltext.rebuildIndex();
yield Zotero.Fulltext.rebuildIndex();
}
else if (index == 2) {
Zotero.Fulltext.rebuildIndex(true)
yield Zotero.Fulltext.rebuildIndex(true)
}
this.updateIndexStats();
},
yield this.updateIndexStats();
}),
clearIndexPrompt: function () {
clearIndexPrompt: Zotero.Promise.coroutine(function* () {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
@ -490,12 +490,12 @@ Zotero_Preferences.Search = {
Zotero.getString('zotero.preferences.search.clearNonLinkedURLs'), null, {});
if (index == 0) {
Zotero.Fulltext.clearIndex();
yield Zotero.Fulltext.clearIndex();
}
else if (index == 2) {
Zotero.Fulltext.clearIndex(true);
yield Zotero.Fulltext.clearIndex(true);
}
this.updateIndexStats();
}
yield this.updateIndexStats();
})
};

View file

@ -32,7 +32,6 @@
* @namespace
*/
var Zotero_RecognizePDF = new function() {
Components.utils.import("resource://zotero/q.js");
var _progressWindow, _progressIndicator;
/**
@ -40,8 +39,9 @@ var Zotero_RecognizePDF = new function() {
* @returns {Boolean} True if the PDF can be recognized, false if it cannot be
*/
this.canRecognize = function(/**Zotero.Item*/ item) {
return (item.attachmentMIMEType &&
item.attachmentMIMEType == "application/pdf" && !item.getSource());
return item.attachmentMIMEType
&& item.attachmentMIMEType == "application/pdf"
&& item.isTopLevelItem();
}
/**
@ -110,12 +110,12 @@ var Zotero_RecognizePDF = new function() {
translate.setSearch({"itemType":"book", "ISBN":isbns[0]});
promise = _promiseTranslate(translate, libraryID);
} else {
promise = Q.reject("No ISBN or DOI found");
promise = Zotero.Promise.reject("No ISBN or DOI found");
}
}
// If no DOI or ISBN, query Google Scholar
return promise.fail(function(error) {
return promise.catch(function(error) {
Zotero.debug("RecognizePDF: "+error);
return me.GSFullTextSearch.findItem(lines, libraryID, stopCheckCallback);
});
@ -183,7 +183,7 @@ var Zotero_RecognizePDF = new function() {
* @return {Promise}
*/
function _promiseTranslate(translate, libraryID) {
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
translate.setHandler("select", function(translate, items, callback) {
for(var i in items) {
var obj = {};
@ -337,8 +337,6 @@ var Zotero_RecognizePDF = new function() {
* @private
*/
"_recognizeItem": function() {
Components.utils.import("resource://zotero/q.js");
const SUCCESS_IMAGE = "chrome://zotero/skin/tick.png";
const FAILURE_IMAGE = "chrome://zotero/skin/cross.png";
const LOADING_IMAGE = "chrome://global/skin/icons/loading_16.png";
@ -383,7 +381,7 @@ var Zotero_RecognizePDF = new function() {
}
// put old item as a child of the new item
item.setSource(newItem.id);
item.parentID = newItem.id;
item.save();
itemTitle.setAttribute("label", newItem.getField("title"));

View file

@ -66,7 +66,7 @@ function onCollectionSelected()
if(collectionsView.selection.count == 1 && collectionsView.selection.currentIndex != -1)
{
var collection = collectionsView._getItemAtRow(collectionsView.selection.currentIndex);
var collection = collectionsView.getRow(collectionsView.selection.currentIndex);
collection.setSearch('');
try {

View file

@ -33,11 +33,11 @@ const ZoteroStandalone = new function() {
* Run when standalone window first opens
*/
this.onLoad = function() {
Q.fcall(function () {
Zotero.Promise.try(function () {
if(!Zotero) {
throw true;
}
if(Zotero.initializationPromise.isPending()) {
if(Zotero.initializationZotero.Promise.isPending()) {
Zotero.showZoteroPaneProgressMeter();
}
return Zotero.initializationPromise;

View file

@ -28,37 +28,37 @@ var _io;
var Zotero_Tag_Color_Chooser = new function() {
this.init = function () {
// Set font size from pref
Zotero.setFontSize(document.getElementById("tag-color-chooser-container"));
if (window.arguments && window.arguments.length) {
_io = window.arguments[0];
if (_io.wrappedJSObject) _io = _io.wrappedJSObject;
}
if (typeof _io.libraryID == 'undefined') throw new Error("libraryID not set");
if (typeof _io.name == 'undefined' || _io.name === "") throw new Error("name not set");
window.sizeToContent();
var dialog = document.getElementById('tag-color-chooser');
var colorPicker = document.getElementById('color-picker');
var tagPosition = document.getElementById('tag-position');
colorPicker.setAttribute('cols', 3);
colorPicker.setAttribute('tileWidth', 24);
colorPicker.setAttribute('tileHeight', 24);
colorPicker.colors = [
'#990000', '#CC9933', '#FF9900',
'#FFCC00', '#007439', '#1049A9',
'#9999FF', '#CC66CC', '#993399'
];
var maxTags = document.getElementById('max-tags');
maxTags.value = Zotero.getString('tagColorChooser.maxTags', Zotero.Tags.MAX_COLORED_TAGS);
var self = this;
Zotero.Tags.getColors(_io.libraryID)
.then(function (tagColors) {
return Zotero.spawn(function* () {
// Set font size from pref
Zotero.setFontSize(document.getElementById("tag-color-chooser-container"));
if (window.arguments && window.arguments.length) {
_io = window.arguments[0];
if (_io.wrappedJSObject) _io = _io.wrappedJSObject;
}
if (typeof _io.libraryID == 'undefined') throw new Error("libraryID not set");
if (typeof _io.name == 'undefined' || _io.name === "") throw new Error("name not set");
window.sizeToContent();
var colorPicker = document.getElementById('color-picker');
var tagPosition = document.getElementById('tag-position');
colorPicker.setAttribute('cols', 3);
colorPicker.setAttribute('tileWidth', 24);
colorPicker.setAttribute('tileHeight', 24);
colorPicker.colors = [
'#990000', '#CC9933', '#FF9900',
'#FFCC00', '#007439', '#1049A9',
'#9999FF', '#CC66CC', '#993399'
];
var maxTags = document.getElementById('max-tags');
maxTags.value = Zotero.getString('tagColorChooser.maxTags', Zotero.Tags.MAX_COLORED_TAGS);
var tagColors = yield Zotero.Tags.getColors(_io.libraryID);
var colorData = tagColors[_io.name];
// Color
@ -72,6 +72,7 @@ var Zotero_Tag_Color_Chooser = new function() {
for (var i in tagColors) {
usedColors.push(tagColors[i].color);
}
var unusedColors = Zotero.Utilities.arrayDiff(
colorPicker.colors, usedColors
);
@ -103,15 +104,16 @@ var Zotero_Tag_Color_Chooser = new function() {
tagPosition.selectedIndex = 0;
}
self.onPositionChange();
this.onPositionChange();
window.sizeToContent();
})
}.bind(this))
.catch(function (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e);
dialog.cancelDialog();
})
.done();
if (dialog.cancelDialog) {
dialog.cancelDialog();
}
});
};

View file

@ -41,7 +41,7 @@
height="140">
<script src="include.js"/>
<script src="tagColorChooser.js" type="text/javascript;version=1.8"/>
<script src="tagColorChooser.js"/>
<vbox id="tag-color-chooser-container">
<hbox align="center">

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -71,6 +71,7 @@ Zotero.CachedTypes = function() {
if (!_types['_' + idOrName]) {
Zotero.debug('Invalid ' + this._typeDesc + ' ' + idOrName, 1);
Zotero.debug((new Error()).stack, 1);
return '';
}
@ -90,6 +91,7 @@ Zotero.CachedTypes = function() {
if (!_types['_' + idOrName]) {
Zotero.debug('Invalid ' + this._typeDesc + ' ' + idOrName, 1);
Zotero.debug((new Error()).stack, 1);
return false;
}

File diff suppressed because it is too large Load diff

View file

@ -31,20 +31,22 @@ Zotero.Collections = new function() {
Zotero.DataObjects.apply(this, ['collection']);
this.constructor.prototype = new Zotero.DataObjects();
this.get = get;
this.add = add;
this.erase = erase;
/*
* Returns a Zotero.Collection object for a collectionID
*/
function get(id) {
if (this._reloadCache) {
this.reloadAll();
}
return this._objectCache[id] ? this._objectCache[id] : false;
}
this._primaryDataSQLParts = {
collectionID: "O.collectionID",
name: "O.collectionName AS name",
libraryID: "O.libraryID",
key: "O.key",
version: "O.version",
synced: "O.synced",
parentID: "O.parentCollectionID AS parentID",
parentKey: "CP.key AS parentKey",
hasChildCollections: "(SELECT COUNT(*) FROM collections WHERE "
+ "parentCollectionID=O.collectionID) != 0 AS hasChildCollections",
hasChildItems: "(SELECT COUNT(*) FROM collectionItems WHERE "
+ "collectionID=O.collectionID) != 0 AS hasChildItems "
};
/**
* Add new collection to DB and return Collection object
@ -54,19 +56,84 @@ Zotero.Collections = new function() {
*
* Returns true on success; false on error
**/
function add(name, parent) {
this.add = function (name, parent) {
var col = new Zotero.Collection;
col.name = name;
col.parent = parent;
var id = col.save();
return this.get(id);
return this.getAsync(id);
}
/*
* Zotero.getCollections(parent)
*
* Returns an array of all collections are children of a collection
* as Zotero.Collection instances
*
* Takes parent collectionID as optional parameter;
* by default, returns root collections
*/
this.getByParent = Zotero.Promise.coroutine(function* (libraryID, parent, recursive) {
var toReturn = [];
if (!parent) {
parent = null;
}
var sql = "SELECT collectionID AS id, collectionName AS name FROM collections C "
+ "WHERE libraryID=? AND parentCollectionID " + (parent ? '= ' + parent : 'IS NULL');
var children = yield Zotero.DB.queryAsync(sql, [libraryID]);
if (!children) {
Zotero.debug('No child collections of collection ' + parent, 5);
return toReturn;
}
// Do proper collation sort
var collation = Zotero.getLocaleCollation();
children.sort(function (a, b) {
return collation.compareString(1, a.name, b.name);
});
for (var i=0, len=children.length; i<len; i++) {
var obj = yield this.getAsync(children[i].id);
if (!obj) {
throw ('Collection ' + children[i].id + ' not found');
}
toReturn.push(obj);
// If recursive, get descendents
if (recursive) {
var desc = obj.getDescendents(false, 'collection');
for (var j in desc) {
var obj2 = yield this.getAsync(desc[j]['id']);
if (!obj2) {
throw new Error('Collection ' + desc[j] + ' not found');
}
// TODO: This is a quick hack so that we can indent subcollections
// in the search dialog -- ideally collections would have a
// getLevel() method, but there's no particularly quick way
// of calculating that without either storing it in the DB or
// changing the schema to Modified Preorder Tree Traversal,
// and I don't know if we'll actually need it anywhere else.
obj2.level = desc[j].level;
toReturn.push(obj2);
}
}
}
return toReturn;
});
this.getCollectionsContainingItems = function (itemIDs, asIDs) {
// If an unreasonable number of items, don't try
if (itemIDs.length > 100) {
return Q([]);
return Zotero.Promise.resolve([]);
}
var sql = "SELECT collectionID FROM collections WHERE ";
@ -86,28 +153,45 @@ Zotero.Collections = new function() {
/**
* Invalidate child collection cache in specified collections, skipping
* any that aren't loaded
* Invalidate child collection cache in specified collections, skipping any that aren't loaded
*
* @param {Integer|Integer[]} ids One or more collectionIDs
*/
this.refreshChildCollections = Zotero.Promise.coroutine(function* (ids) {
ids = Zotero.flattenArguments(ids);
for (let i=0; i<ids.length; i++) {
let id = ids[i];
if (this._objectCache[id]) {
yield this._objectCache[id]._refreshChildCollections();
}
}
});
/**
* Invalidate child item cache in specified collections, skipping any that aren't loaded
*
* @param {Integer|Integer[]} ids One or more itemIDs
*/
this.refreshChildCollections = function (ids) {
this.refreshChildItems = Zotero.Promise.coroutine(function* (ids) {
ids = Zotero.flattenArguments(ids);
for each(var id in ids) {
for (let i=0; i<ids.length; i++) {
let id = ids[i];
if (this._objectCache[id]) {
this._objectCache[id]._refreshChildCollections();
yield this._objectCache[id]._refreshChildItems();
}
}
}
});
function erase(ids) {
this.erase = function (ids) {
ids = Zotero.flattenArguments(ids);
Zotero.DB.beginTransaction();
for each(var id in ids) {
var collection = this.get(id);
var collection = this.getAsync(id);
if (collection) {
collection.erase();
}
@ -120,50 +204,14 @@ Zotero.Collections = new function() {
}
this._load = function () {
if (!arguments[0] && !this._reloadCache) {
return;
}
this._reloadCache = false;
this._getPrimaryDataSQL = function () {
// This should be the same as the query in Zotero.Collection.load(),
// just without a specific collectionID
var sql = "SELECT C.*, "
+ "(SELECT COUNT(*) FROM collections WHERE "
+ "parentCollectionID=C.collectionID)!=0 AS hasChildCollections, "
+ "(SELECT COUNT(*) FROM collectionItems WHERE "
+ "collectionID=C.collectionID)!=0 AS hasChildItems "
+ "FROM collections C WHERE 1";
if (arguments[0]) {
sql += " AND collectionID IN (" + Zotero.join(arguments[0], ",") + ")";
}
var rows = Zotero.DB.query(sql);
var ids = [];
for each(var row in rows) {
var id = row.collectionID;
ids.push(id);
// Collection doesn't exist -- create new object and stuff in array
if (!this._objectCache[id]) {
//this.get(id);
this._objectCache[id] = new Zotero.Collection;
this._objectCache[id].loadFromRow(row);
}
// Existing collection -- reload in place
else {
this._objectCache[id].loadFromRow(row);
}
}
// If loading all creators, remove old creators that no longer exist
if (!arguments[0]) {
for each(var c in this._objectCache) {
if (ids.indexOf(c.id) == -1) {
this.unload(c.id);
}
}
}
return "SELECT "
+ Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " "
+ "FROM collections O "
+ "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID) "
+ "WHERE 1";
}
}

View file

@ -1,560 +0,0 @@
/*
***** 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 *****
*/
Zotero.Creator = function () {
if (arguments[0]) {
throw ("Zotero.Creator constructor doesn't take any parameters");
}
this._init();
}
Zotero.Creator.prototype._init = function () {
this._id = null;
this._libraryID = null
this._key = null;
this._firstName = null;
this._lastName = null;
this._fieldMode = null;
this._birthYear = null;
this._dateAdded = null;
this._dateModified = null;
this._creatorDataID = null;
this._loaded = false;
this._changed = false;
this._previousData = false;
}
Zotero.Creator.prototype.__defineGetter__('objectType', function () { return 'creator'; });
Zotero.Creator.prototype.__defineGetter__('id', function () { return this._get('id'); });
Zotero.Creator.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
Zotero.Creator.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
Zotero.Creator.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
Zotero.Creator.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Creator.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
Zotero.Creator.prototype.__defineGetter__('creatorDataID', function () { return this._get('creatorDataID'); });
Zotero.Creator.prototype.__defineGetter__('firstName', function () { return this._get('firstName'); });
Zotero.Creator.prototype.__defineSetter__('firstName', function (val) { this._set('firstName', val); });
Zotero.Creator.prototype.__defineGetter__('lastName', function () { return this._get('lastName'); });
Zotero.Creator.prototype.__defineSetter__('lastName', function (val) { this._set('lastName', val); });
Zotero.Creator.prototype.__defineGetter__('fieldMode', function () { return this._get('fieldMode'); });
Zotero.Creator.prototype.__defineSetter__('fieldMode', function (val) { this._set('fieldMode', val); });
Zotero.Creator.prototype.__defineGetter__('birthYear', function () { return this._get('birthYear'); });
Zotero.Creator.prototype.__defineSetter__('birthYear', function (val) { this._set('birthYear', val); });
Zotero.Creator.prototype.__defineGetter__('dateAdded', function () { return this._get('dateAdded'); });
Zotero.Creator.prototype.__defineSetter__('dateAdded', function (val) { this._set('dateAdded', val); });
Zotero.Creator.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
Zotero.Creator.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
// Block properties that can't be set this way
//Zotero.Creator.prototype.__defineSetter__('creatorDataID', function () { this._set('creatorDataID', val); });
Zotero.Creator.prototype._get = function (field) {
if ((this._id || this._key) && !this._loaded) {
this.load(true);
}
return this['_' + field];
}
Zotero.Creator.prototype._set = function (field, val) {
switch (field) {
case 'id':
case 'libraryID':
case 'key':
if (field == 'libraryID') {
val = Zotero.DataObjectUtilities.checkLibraryID(val);
}
if (val == this['_' + field]) {
return;
}
if (this._loaded) {
throw ("Cannot set " + field + " after object is already loaded in Zotero.Creator._set()");
}
this._checkValue(field, val);
this['_' + field] = val;
return;
case 'firstName':
case 'lastName':
case 'shortName':
if (val) {
val = Zotero.Utilities.trim(val);
}
else {
val = '';
}
break;
case 'fieldMode':
val = val ? parseInt(val) : 0;
break;
case 'creatorDataID':
throw ("Invalid field '" + field + "' in Zotero.Creator.set()");
}
if (this.id || this.key) {
if (!this._loaded) {
this.load(true);
}
}
else {
this._loaded = true;
}
this._checkValue(field, val);
if (this['_' + field] != val) {
if (!this._changed) {
this._changed = {};
}
this._changed[field] = true;
if (this.id && this.exists() && !this._previousData) {
this._previousData = this.serialize();
}
this['_' + field] = val;
}
}
Zotero.Creator.prototype.setFields = function (fields) {
this.firstName = fields.firstName;
this.lastName = fields.lastName;
this.fieldMode = fields.fieldMode;
this.birthYear = fields.birthYear;
}
/**
* Check if creator exists in the database
*
* @return bool TRUE if the creator exists, FALSE if not
*/
Zotero.Creator.prototype.exists = function() {
if (!this.id) {
throw ('creatorID not set in Zotero.Creator.exists()');
}
var sql = "SELECT COUNT(*) FROM creators WHERE creatorID=?";
return !!Zotero.DB.valueQuery(sql, this.id);
}
Zotero.Creator.prototype.hasChanged = function () {
return this._changed;
}
Zotero.Creator.prototype.save = function () {
Zotero.Creators.editCheck(this);
Zotero.debug("Saving creator " + this.id);
if (!this.firstName && !this.lastName) {
throw ('First and last name are empty in Zotero.Creator.save()');
}
if (!this.hasChanged()) {
Zotero.debug("Creator " + this.id + " has not changed");
return false;
}
if (this.fieldMode == 1 && this.firstName) {
throw ("First name ('" + this.firstName + "') must be empty in single-field mode in Zotero.Creator.save()");
}
Zotero.DB.beginTransaction();
var isNew = !this.id || !this.exists();
try {
// how to know if date modified changed (in server code too?)
var creatorID = this.id ? this.id : Zotero.ID.get('creators');
var key = this.key ? this.key : this._generateKey();
// If this was the only creator with the previous data,
// see if we can reuse or remove the old data row
if (this.creatorDataID) {
var count = Zotero.Creators.countCreatorsWithData(this.creatorDataID);
if (count == 1) {
var newCreatorDataID = Zotero.Creators.getDataID(this);
// Data hasn't changed
if (this.creatorDataID == newCreatorDataID) {
var creatorDataID = this.creatorDataID;
}
// Existing data row with the new data -- switch to that
// and flag old row for deletion below
else if (newCreatorDataID) {
var deleteDataID = this.creatorDataID;
var creatorDataID = newCreatorDataID;
}
// Update current data row with new data
else {
Zotero.Creators.updateData(this.creatorDataID, this);
var creatorDataID = this.creatorDataID;
}
}
}
if (!creatorDataID) {
var creatorDataID = Zotero.Creators.getDataID(this, true);
if (creatorDataID != this.creatorDataID) {
this._creatorDataID = creatorDataID;
}
}
var columns = [
'creatorID',
'creatorDataID',
'dateAdded',
'dateModified',
'clientDateModified',
'libraryID',
'key'
];
var placeholders = columns.map(function () '?').join();
var sqlValues = [
creatorID ? { int: creatorID } : null,
{ int: creatorDataID },
// If date added isn't set, use current timestamp
this.dateAdded ? this.dateAdded : Zotero.DB.transactionDateTime,
// If date modified hasn't changed, use current timestamp
this._changed.dateModified ?
this.dateModified : Zotero.DB.transactionDateTime,
Zotero.DB.transactionDateTime,
this.libraryID ? this.libraryID : 0,
key
];
if (isNew) {
var sql = "INSERT INTO creators (" + columns.join(', ') + ") "
+ "VALUES (" + placeholders + ")";
var insertID = Zotero.DB.query(sql, sqlValues);
if (!creatorID) {
creatorID = insertID;
}
}
else {
// Remove tagID from beginning
columns.shift();
sqlValues.shift();
sqlValues.push(creatorID);
var sql = "UPDATE creators SET " + columns.join("=?, ") + "=?"
+ " WHERE creatorID=?";
Zotero.DB.query(sql, sqlValues);
}
if (deleteDataID) {
Zotero.Creators.deleteData(deleteDataID);
}
if (this.id) {
Zotero.debug("Updating linked items");
this.updateLinkedItems();
}
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.DB.rollbackTransaction();
throw (e);
}
// If successful, set values in object
if (!this.id) {
this._id = creatorID;
}
if (!this.key) {
this._key = key;
}
if (!this.creatorDataID) {
this._creatorDataID = creatorDataID;
}
Zotero.Creators.reload(this.id);
if (isNew) {
Zotero.Notifier.trigger('add', 'creator', this.id);
}
else {
Zotero.Notifier.trigger('modify', 'creator', this.id, this._previousData);
}
return this.id;
}
Zotero.Creator.prototype.countLinkedItems = function() {
var sql = "SELECT COUNT(*) FROM itemCreators WHERE creatorID=?";
return Zotero.DB.valueQuery(sql, this.id);
}
Zotero.Creator.prototype.getLinkedItems = function () {
var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
return Zotero.DB.columnQuery(sql, this.id);
}
Zotero.Creator.prototype.updateLinkedItems = function () {
Zotero.DB.beginTransaction();
var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
var changedItemIDs = Zotero.DB.columnQuery(sql, this.id);
if (!changedItemIDs) {
Zotero.DB.commitTransaction();
return;
}
var notifierData = {};
for each(var id in changedItemIDs) {
var item = Zotero.Items.get(id);
if (item) {
notifierData[item.id] = { old: item.serialize() };
}
}
sql = "UPDATE items SET dateModified=?, clientDateModified=? WHERE itemID IN "
+ "(SELECT itemID FROM itemCreators WHERE creatorID=?)";
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime, this.id]);
Zotero.Items.reload(changedItemIDs);
Zotero.DB.commitTransaction();
Zotero.Notifier.trigger('modify', 'item', changedItemIDs, notifierData);
}
Zotero.Creator.prototype.equals = function (creator) {
return (creator.firstName == this.firstName) &&
(creator.lastName == this.lastName) &&
(creator.fieldMode == this.fieldMode) &&
(creator.shortName == this.shortName) &&
(creator.birthYear == this.birthYear);
}
Zotero.Creator.prototype.serialize = function () {
var obj = {};
obj.primary = {};
obj.primary.creatorID = this.id;
obj.primary.libraryID = this.libraryID;
obj.primary.key = this.key;
obj.primary.dateAdded = this.dateAdded;
obj.primary.dateModified = this.dateModified;
obj.fields = {};
if (this.fieldMode == 1) {
obj.fields.name = this.lastName;
}
else {
obj.fields.firstName = this.firstName;
obj.fields.lastName = this.lastName;
}
obj.fields.fieldMode = this.fieldMode;
obj.fields.shortName = this.shortName;
obj.fields.birthYear = this.birthYear;
return obj;
}
/**
* Remove creator from all linked items
*
* Creators.erase() should be used instead of this
*
* Actual deletion of creator occurs in Zotero.Creators.purge(),
* which is called by Creators.erase()
*/
Zotero.Creator.prototype.erase = function () {
if (!this.id) {
return false;
}
Zotero.debug("Deleting creator " + this.id);
// TODO: notifier
var changedItems = [];
var changedItemsNotifierData = {};
Zotero.DB.beginTransaction();
var toSave = {};
var linkedItemIDs = this.getLinkedItems();
for each(var itemID in linkedItemIDs) {
var item = Zotero.Items.get(itemID)
if (!item) {
throw ('Linked item not found in Zotero.Creator.erase()');
}
var pos = item.getCreatorPosition(this.id);
if (!pos) {
throw ('Creator not found in linked item in Zotero.Creator.erase()');
}
item.removeCreator(pos);
if (!toSave[item.id]) {
toSave[item.id] = item;
}
}
for each(var item in toSave) {
item.save();
}
Zotero.DB.commitTransaction();
Zotero.Prefs.set('purge.creators', true);
}
Zotero.Creator.prototype.load = function (allowFail) {
var id = this._id;
var key = this._key;
var libraryID = this._libraryID;
var desc = id ? id : libraryID + "/" + key;
Zotero.debug("Loading data for creator " + desc + " in Zotero.Creator.load()");
if (!id && !key) {
throw ("ID or key not set in Zotero.Creator.load()");
}
var sql = "SELECT O.*, CD.* FROM creators O NATURAL JOIN creatorData CD WHERE ";
if (id) {
sql += "creatorID=?";
var params = id;
}
else {
sql += "key=? AND libraryID=?";
var params = [key, libraryID];
}
var row = Zotero.DB.rowQuery(sql, params);
if (!row) {
if (allowFail) {
this._loaded = true;
return false;
}
throw ("Creator " + desc + " not found in Zotero.Item.load()");
}
this.loadFromRow(row);
return true;
}
Zotero.Creator.prototype.loadFromRow = function (row) {
this._init();
for (var col in row) {
//Zotero.debug("Setting field '" + col + "' to '" + row[col] + "' for creator " + this.id);
switch (col) {
case 'clientDateModified':
continue;
case 'creatorID':
this._id = row[col];
continue;
case 'libraryID':
this['_' + col] = row[col];
continue;
}
this['_' + col] = row[col] ? row[col] : '';
}
this._loaded = true;
}
Zotero.Creator.prototype._checkValue = function (field, value) {
if (this['_' + field] === undefined) {
throw ("Invalid property " + field + " in Zotero.Creator._checkValue()");
}
// Data validation
switch (field) {
case 'id':
if (parseInt(value) != value) {
this._invalidValueError(field, value);
}
break;
case 'libraryID':
if (parseInt(value) != value) {
this._invalidValueError(field, value);
}
break;
case 'fieldMode':
if (value !== 0 && value !== 1) {
this._invalidValueError(field, value);
}
break;
case 'key':
if (!Zotero.ID.isValidKey(value)) {
this._invalidValueError(field, value);
}
break;
case 'dateAdded':
case 'dateModified':
if (value !== '' && !Zotero.Date.isSQLDateTime(value)) {
this._invalidValueError(field, value);
}
break;
}
}
Zotero.Creator.prototype._generateKey = function () {
return Zotero.Utilities.generateObjectKey();
}
Zotero.Creator.prototype._invalidValueError = function (field, value) {
throw ("Invalid '" + field + "' value '" + value + "' in Zotero.Creator._invalidValueError()");
}

View file

@ -25,327 +25,195 @@
Zotero.Creators = new function() {
Zotero.DataObjects.apply(this, ['creator']);
this.constructor.prototype = new Zotero.DataObjects();
this.fields = ['firstName', 'lastName', 'fieldMode'];
this.totes = 0;
this.get = get;
this.getDataID = getDataID;
this.getCreatorsWithData = getCreatorsWithData;
this.countCreatorsWithData = countCreatorsWithData;
this.updateData = updateData;
this.deleteData = deleteData;
this.erase = erase;
this.purge = purge;
this.__defineGetter__('fields', function () ['firstName', 'lastName', 'shortName', 'fieldMode', 'birthYear']);
var _creatorDataHash = {}; // creatorDataIDs indexed by md5 hash of data
var _creatorCache = {};
/*
* Returns a Zotero.Creator object for a given creatorID
* Returns creator data in internal format for a given creatorID
*/
function get(creatorID) {
this.getAsync = Zotero.Promise.coroutine(function* (creatorID) {
if (!creatorID) {
throw ("creatorID not provided in Zotero.Creators.get()");
throw new Error("creatorID not provided");
}
if (this._objectCache[creatorID]) {
return this._objectCache[creatorID];
if (_creatorCache[creatorID]) {
return this.cleanData(_creatorCache[creatorID]);
}
var sql = 'SELECT COUNT(*) FROM creators WHERE creatorID=?';
var result = Zotero.DB.valueQuery(sql, creatorID);
if (!result) {
return false;
var sql = "SELECT * FROM creators WHERE creatorID=?";
var row = yield Zotero.DB.rowQueryAsync(sql, creatorID);
if (!row) {
throw new Error("Creator " + creatorID + " not found");
}
var creator = new Zotero.Creator;
creator.id = creatorID;
this._objectCache[creatorID] = creator;
return this._objectCache[creatorID];
return _creatorCache[creatorID] = this.cleanData({
firstName: row.firstName, // avoid "DB column 'name' not found" warnings from the DB row Proxy
lastName: row.lastName,
fieldMode: row.fieldMode
});
});
this.getItemsWithCreator = function (creatorID) {
var sql = "SELECT DISTINCT itemID FROM itemCreators WHERE creatorID=?";
return Zotero.DB.columnQueryAsync(sql, creatorID);
}
this.countItemAssociations = function (creatorID) {
var sql = "SELECT COUNT(*) FROM itemCreators WHERE creatorID=?";
return Zotero.DB.valueQueryAsync(sql, creatorID);
}
/**
* Returns the creatorDataID matching given fields
* Returns the creatorID matching given fields, or creates a new creator and returns its id
*
* @param array fields
* @param bool create If no matching creatorDataID, create one
* @param {Object} data Creator data in API JSON format
* @param {Boolean} [create=false] If no matching creator, create one
* @return {Promise<Integer>} creatorID
*/
function getDataID(fields, create) {
fields = _cleanFields(fields);
if (!fields.firstName && !fields.lastName) {
throw ("First or last name must be provided in Zotero.Creators.getDataID()");
}
var hash = _getHash(fields);
if (_creatorDataHash[hash]) {
return _creatorDataHash[hash];
}
var params = [];
for each(var field in fields) {
params.push(field);
}
Zotero.DB.beginTransaction();
var sql = "SELECT creatorDataID FROM creatorData WHERE "
+ "firstName=? AND lastName=? AND shortName=? "
+ "AND fieldMode=? AND birthYear=?";
var id = Zotero.DB.valueQuery(sql, params);
if (!id && create) {
id = Zotero.ID.get('creatorData');
params.unshift(id);
sql = "INSERT INTO creatorData (creatorDataID, "
+ "firstName, lastName, shortName, fieldMode, birthYear) "
+ "VALUES (?, ?, ?, ?, ?, ?)";
var insertID = Zotero.DB.query(sql, params);
if (!id) {
id = insertID;
this.getIDFromData = Zotero.Promise.method(function (data, create) {
data = this.cleanData(data);
return Zotero.DB.executeTransaction(function* () {
var sql = "SELECT creatorID FROM creators WHERE "
+ "firstName=? AND lastName=? AND fieldMode=?";
var id = yield Zotero.DB.valueQueryAsync(
sql, [data.firstName, data.lastName, data.fieldMode]
);
if (!id && create) {
id = yield Zotero.ID.get('creators');
let sql = "INSERT INTO creators (creatorID, firstName, lastName, fieldMode) "
+ "VALUES (?, ?, ?, ?)";
let insertID = yield Zotero.DB.queryAsync(
sql, [id, data.firstName, data.lastName, data.fieldMode]
);
if (!id) {
id = insertID;
}
}
return id;
});
});
this.updateCreator = Zotero.Promise.coroutine(function* (creatorID, creatorData) {
var creator = yield this.get(creatorID);
if (!creator) {
throw new Error("Creator " + creatorID + " doesn't exist");
}
Zotero.DB.commitTransaction();
if (id) {
_creatorDataHash[hash] = id;
}
return id;
}
function getCreatorsWithData(creatorDataID, libraryID) {
var sql = "SELECT creatorID FROM creators WHERE creatorDataID=?";
if (libraryID !== undefined) {
sql += " AND libraryID=?";
return Zotero.DB.columnQuery(sql, [creatorDataID, libraryID])
}
return Zotero.DB.columnQuery(sql, [creatorDataID]);
}
function countCreatorsWithData(creatorDataID, libraryID) {
var sql = "SELECT COUNT(*) FROM creators WHERE creatorDataID=?";
return Zotero.DB.valueQuery(sql, [creatorDataID]);
}
function updateData(creatorDataID, fields) {
fields = _cleanFields(fields);
var sqlFields = [];
var sqlParams = [];
for (var field in fields) {
// Skip fields not specified as changeable creator fields
if (this.fields.indexOf(field) == -1) {
continue;
}
sqlFields.push(field + '=?');
sqlParams.push(fields[field]);
}
var sql = "UPDATE creatorData SET " + sqlFields.join(', ')
+ " WHERE creatorDataID=?";
sqlParams.push(creatorDataID);
Zotero.DB.query(sql, sqlParams);
_updateCachedData(creatorDataID);
}
function deleteData(creatorDataID) {
var sql = "DELETE FROM creatorData WHERE creatorDataID=?";
Zotero.DB.query(sql, creatorDataID);
_updateCachedData(creatorDataID);
}
creator.fieldMode = creatorData.fieldMode;
creator.firstName = creatorData.firstName;
creator.lastName = creatorData.lastName;
return creator.save();
});
/**
* Remove creator(s) from all linked items and call this.purge()
* to delete creator rows
* Delete obsolete creator rows from database and clear internal cache entries
*
* @return {Promise}
*/
function erase(ids) {
ids = Zotero.flattenArguments(ids);
var unlock = Zotero.Notifier.begin(true);
Zotero.UnresponsiveScriptIndicator.disable();
try {
Zotero.DB.beginTransaction();
for each(var id in ids) {
var creator = this.get(id);
if (!creator) {
Zotero.debug('Creator ' + id + ' does not exist in Creators.erase()!', 1);
Zotero.Notifier.trigger('delete', 'creator', id);
continue;
}
creator.erase();
creator = undefined;
}
this.purge();
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.DB.rollbackTransaction();
throw (e);
}
finally {
Zotero.Notifier.commit(unlock);
Zotero.UnresponsiveScriptIndicator.enable();
}
}
/*
* Delete obsolete creator/creatorData rows from database
* and clear internal array entries
*/
function purge() {
this.purge = Zotero.Promise.coroutine(function* () {
if (!Zotero.Prefs.get('purge.creators')) {
return;
}
Zotero.debug("Purging creator tables");
// Purge unused creators
var sql = 'SELECT creatorID FROM creators WHERE creatorID NOT IN '
+ '(SELECT creatorID FROM itemCreators)';
var toDelete = Zotero.DB.columnQuery(sql);
if (toDelete) {
var toDelete = yield Zotero.DB.columnQueryAsync(sql);
if (toDelete.length) {
// Clear creator entries in internal array
for each(var creatorID in toDelete) {
delete this._objectCache[creatorID];
for (let i=0; i<toDelete.length; i++) {
delete _creatorCache[toDelete[i]];
}
var sql = "DELETE FROM creators WHERE creatorID NOT IN "
+ "(SELECT creatorID FROM itemCreators)";
Zotero.DB.query(sql);
}
// Purge unused creatorData rows
var sql = 'SELECT creatorDataID FROM creatorData WHERE creatorDataID NOT IN '
+ '(SELECT creatorDataID FROM creators)';
var toDelete = Zotero.DB.columnQuery(sql);
if (toDelete) {
// Clear creator entries in internal array
for each(var creatorDataID in toDelete) {
_updateCachedData(creatorDataID);
}
var sql = "DELETE FROM creatorData WHERE creatorDataID NOT IN "
+ "(SELECT creatorDataID FROM creators)";
Zotero.DB.query(sql);
yield Zotero.DB.queryAsync(sql);
}
Zotero.Prefs.set('purge.creators', false);
}
});
this._load = function () {
if (!arguments[0] && !this._reloadCache) {
return;
this.cleanData = function (data) {
// Validate data
if (data.name === undefined && data.lastName === undefined) {
throw new Error("Creator data must contain either 'name' or 'firstName'/'lastName' properties");
}
if (data.name !== undefined && (data.firstName !== undefined || data.lastName !== undefined)) {
throw new Error("Creator data cannot contain both 'name' and 'firstName'/'lastName' properties");
}
if (data.name !== undefined && data.fieldMode === 0) {
throw new Error("'fieldMode' cannot be 0 with 'name' property");
}
if (data.fieldMode === 1 && !(data.firstName === undefined || data.firstName === "")) {
throw new Error("'fieldMode' cannot be 1 with 'firstName' property");
}
if (data.name !== undefined && typeof data.name != 'string') {
throw new Error("'name' must be a string");
}
if (data.firstName !== undefined && typeof data.firstName != 'string') {
throw new Error("'firstName' must be a string");
}
if (data.lastName !== undefined && typeof data.lastName != 'string') {
throw new Error("'lastName' must be a string");
}
if (this._reloadCache) {
Zotero.debug("Clearing creator data hash");
_creatorDataHash = {};
}
var sql = "SELECT C.*, CD.* FROM creators C NATURAL JOIN creatorData CD "
+ "WHERE 1";
if (arguments[0]) {
sql += " AND creatorID IN (" + Zotero.join(arguments[0], ",") + ")";
}
var rows = Zotero.DB.query(sql);
var ids = [];
for each(var row in rows) {
var id = row.creatorID;
ids.push(id);
// Creator doesn't exist -- create new object and stuff in array
if (!this._objectCache[id]) {
this.get(id);
}
// Existing creator -- reload in place
else {
this._objectCache[id].loadFromRow(row);
}
}
// If loading all creators, remove old creators that no longer exist
if (!arguments[0]) {
for each(var c in this._objectCache) {
if (ids.indexOf(c.id) == -1) {
this.unload(c.id);
}
}
this._reloadCache = false;
}
}
function _cleanFields(fields) {
var cleanedFields = {};
for each(var field in Zotero.Creators.fields) {
var cleanedData = {
fieldMode: 0,
firstName: '',
lastName: ''
};
for (i=0; i<this.fields.length; i++) {
let field = this.fields[i];
let val = data[field];
switch (field) {
// Strings
case 'firstName':
case 'lastName':
case 'shortName':
cleanedFields[field] = fields[field] ? fields[field] + '' : '';
break;
// Integer
case 'fieldMode':
cleanedFields[field] = fields[field] ? fields[field] : 0;
break;
// Null if empty
case 'birthYear':
cleanedFields[field] = fields[field] ? fields[field] : null;
}
}
return cleanedFields;
}
function _getHash(fields) {
var hashFields = [];
for each(var field in Zotero.Creators.fields) {
hashFields.push(fields[field]);
}
return Zotero.Utilities.Internal.md5(hashFields.join('_'));
}
function _getDataFromID(creatorDataID) {
var sql = "SELECT * FROM creatorData WHERE creatorDataID=?";
return Zotero.DB.rowQuery(sql, creatorDataID);
}
function _updateCachedData(creatorDataID) {
for (var hash in _creatorDataHash) {
if (_creatorDataHash[hash] == creatorDataID) {
delete _creatorDataHash[hash];
case 'firstName':
case 'lastName':
cleanedData[field] = val.trim();
break;
case 'fieldMode':
cleanedData[field] = val ? parseInt(val) : 0;
break;
}
}
var creators = getCreatorsWithData(creatorDataID);
for each(var creatorID in creators) {
if (Zotero.Creators._objectCache[creatorID]) {
Zotero.Creators._objectCache[creatorID].load();
// Handle API JSON format
if (data.name !== undefined) {
cleanedData.lastName = data.name.trim();
cleanedData.fieldMode = 1;
}
var creatorType = data.creatorType || data.creatorTypeID;
if (creatorType) {
cleanedData.creatorTypeID = Zotero.CreatorTypes.getID(creatorType);
if (!cleanedData.creatorTypeID) {
let msg = "'" + creatorType + "' isn't a valid creator type";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
}
}
return cleanedData;
}
this.internalToJSON = function (fields) {
var obj = {};
if (fields.fieldMode == 1) {
obj.name = fields.lastName;
}
else {
obj.firstName = fields.firstName;
obj.lastName = fields.lastName;
}
obj.creatorType = Zotero.CreatorTypes.getName(fields.creatorTypeID);
return obj;
}
}

View file

@ -0,0 +1,394 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2013 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 *****
*/
Zotero.DataObject = function (objectType, objectTypePlural, dataTypes) {
this._objectType = objectType;
this._ObjectType = objectType[0].toUpperCase() + objectType.substr(1);
this._objectTypePlural = objectTypePlural ? objectTypePlural : objectType + 's';
this._dataTypes = dataTypes;
// Public members for access by public methods -- do not access directly
if (this._id === undefined) {
this._id = null;
}
if (this._libraryID === undefined) {
this._libraryID = null;
}
if (this._key === undefined) {
this._key = null;
}
this._dateAdded = null;
this._dateModified = null;
this._version = null;
this._synced = null;
this._identified = false;
if (this._dataTypes === undefined) {
throw new Error("this._dataTypes is undefined");
}
this._loaded = {};
for (let i=0; i<this._dataTypes.length; i++) {
this._loaded[this._dataTypes[i]] = false;
}
this._clearChanged();
};
Zotero.DataObject.prototype.__defineGetter__('objectType', function () { return this._objectType; });
Zotero.DataObject.prototype.__defineGetter__('libraryKey', function () this.libraryID + "/" + this.key);
Zotero.DataObject.prototype.__defineGetter__('parentKey', function () this._parentKey );
Zotero.DataObject.prototype.__defineSetter__('parentKey', function (val) this._setParentKey(val) );
Zotero.DataObject.prototype.__defineGetter__('parentID', function () this._getParentID() );
Zotero.DataObject.prototype.__defineSetter__('parentID', function (val) this._setParentID(val) );
Zotero.DataObject.prototype._get = function (field) {
if (this._objectType == 'item') {
throw new Error("_get is not valid for items");
}
if (this['_' + field] !== null) {
return this['_' + field];
}
this._requireData('primaryData');
return null;
}
Zotero.DataObject.prototype._setIdentifier = function (field, value) {
// If primary data is loaded, the only allowed identifier change is libraryID
// (allowed mainly for the library switcher in the advanced search window),
// and then only for unsaved objects (i.e., those without an id or key)
if (this._loaded.primaryData) {
if (field != 'libraryID' || this._id || this._key) {
throw new Error("Cannot set " + field + " after object is already loaded");
}
}
switch (field) {
case 'id':
if (this._key) {
throw new Error("Cannot set id if key is already set");
}
value = parseInt(value);
this._identified = true;
break;
case 'libraryID':
value = Zotero.DataObjectUtilities.checkLibraryID(value);
break;
case 'key':
if (this._libraryID === undefined) {
throw new Error("libraryID must be set before key");
}
if (this._id) {
throw new Error("Cannot set key if id is already set");
}
this._identified = true;
}
if (value === this['_' + field]) {
return;
}
this['_' + field] = value;
}
/**
* Get the id of the parent object
*
* @return {Integer|false|undefined} The id of the parent object, false if none, or undefined
* on object types to which it doesn't apply (e.g., searches)
*/
Zotero.DataObject.prototype._getParentID = function () {
if (this._parentID !== null) {
return this._parentID;
}
if (!this._parentKey) {
return false;
}
return this._parentID = this._getClass().getIDFromLibraryAndKey(this._libraryID, this._parentKey);
}
/**
* Set the id of the parent object
*
* @param {Number|false} [id=false]
*/
Zotero.DataObject.prototype._setParentID = function (id) {
return this._setParentKey(id ? this._getClass().getLibraryAndKeyFromID(id)[1] : false);
}
Zotero.DataObject.prototype._setParentKey = function(key) {
if (this._objectType == 'item') {
if (!this.isNote() && !this.isAttachment()) {
throw new Error("_setParentKey() can only be called on items of type 'note' or 'attachment'");
}
}
if (this._parentKey == key) {
return false;
}
this._markFieldChange('parentKey', this._parentKey);
this._changed.parentKey = true;
this._parentKey = key ? key : null;
this._parentID = null;
return true;
}
/**
* Returns all relations of the object
*
* @return object Object with predicates as keys and URIs as values
*/
Zotero.DataObject.prototype.getRelations = function () {
this._requireData('relations');
var relations = {};
for (let i=0; i<this._relations.length; i++) {
let relation = this._relations[i];
// Relations are stored internally as predicate-object pairs
let predicate = relation[0];
if (relations[predicate]) {
// If object with predicate exists, convert to an array
if (typeof relations[predicate] == 'string') {
relations[predicate] = [relations[predicate]];
}
// Add new object to array
relations[predicate].push(relation[1]);
}
// Add first object as a string
else {
relations[predicate] = relation[1];
}
}
return relations;
}
/**
* Updates the object's relations
*
* @param {Object} newRelations Object with predicates as keys and URIs/arrays-of-URIs as values
*/
Zotero.DataObject.prototype.setRelations = function (newRelations) {
this._requireData('relations');
// There can be more than one object for a given predicate, so build
// flat arrays with individual predicate-object pairs converted to
// JSON strings so we can use array_diff to determine what changed
var oldRelations = this._relations;
var sortFunc = function (a, b) {
if (a[0] < b[0]) return -1;
if (a[0] > b[0]) return 1;
if (a[1] < b[1]) return -1;
if (a[1] > b[1]) return 1;
return 0;
};
var newRelationsFlat = [];
for (let predicate in newRelations) {
let object = newRelations[predicate];
if (Array.isArray(object)) {
for (let i=0; i<object.length; i++) {
newRelationsFlat.push([predicate, object[i]]);
}
}
else {
newRelationsFlat.push([predicate, object]);
}
}
var changed = false;
if (oldRelations.length != newRelationsFlat.length) {
changed = true;
}
else {
oldRelations.sort(sortFunc);
newRelationsFlat.sort(sortFunc);
for (let i=0; i<oldRelations.length; i++) {
if (!newRelationsFlat || oldRelations[i] != newRelationsFlat[i]) {
changed = true;
break;
}
}
}
if (!changed) {
Zotero.debug("Relations have not changed for " + this._objectType + " " + this.libraryKey, 4);
return false;
}
this._markFieldChange('relations', this._relations);
// Store relations internally as array of predicate-object pairs
this._relations = newRelationsFlat;
this._changed.relations = true;
}
/**
* Return an object in the specified library equivalent to this object
*/
Zotero.DataObject.prototype._getLinkedObject = Zotero.Promise.coroutine(function* (libraryID) {
if (libraryID == this.libraryID) {
throw new Error(this._ObjectType + " is already in library " + libraryID);
}
var predicate = Zotero.Relations.linkedObjectPredicate;
var uri = Zotero.URI['get' + this._ObjectType + 'URI'](this);
var links = yield [
Zotero.Relations.getSubject(false, predicate, uri),
Zotero.Relations.getObject(uri, predicate, false)
];
links = links[0].concat(links[1]);
if (!links.length) {
return false;
}
if (libraryID) {
var libraryObjectPrefix = Zotero.URI.getLibraryURI(libraryID) + "/" + this._objectTypePlural + "/";
}
else {
var libraryObjectPrefix = Zotero.URI.getCurrentUserURI() + "/" + this._objectTypePlural + "/";
}
for (let i=0; i<links.length; i++) {
let link = links[i];
if (link.indexOf(libraryObjectPrefix) == 0) {
var obj = yield Zotero.URI['getURI' + this._ObjectType](link);
if (!obj) {
Zotero.debug("Referenced linked " + this._objectType + " '" + link + "' not found "
+ "in Zotero." + this._ObjectType + ".getLinked" + this._ObjectType + "()", 2);
continue;
}
return obj;
}
}
return false;
});
/**
* Reloads loaded, changed data
*
* @param {Array} [dataTypes] - Data types to reload, or all loaded types if not provide
* @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally.
* This should be set to true for data that was
* changed externally (e.g., globally renamed tags).
*/
Zotero.DataObject.prototype.reload = Zotero.Promise.coroutine(function* (dataTypes, reloadUnchanged) {
if (!this._id) {
return;
}
if (!dataTypes) {
dataTypes = Object.keys(this._loaded).filter(
val => this._loaded[val]
);
}
if (dataTypes && dataTypes.length) {
for (let i=0; i<dataTypes.length; i++) {
let dataType = dataTypes[i];
if (!this._loaded[dataType] || (!reloadUnchanged && !this._changed[dataType])) {
continue;
}
yield this._loadDataType(dataType, true);
}
}
});
Zotero.DataObject.prototype._requireData = function (dataType) {
if (dataType != 'primaryData') {
this._requireData('primaryData');
}
if (!this._identified) {
this._loaded[dataType] = true;
}
else if (!this._loaded[dataType]) {
throw new Zotero.DataObjects.UnloadedDataException(
"'" + dataType + "' not loaded for " + this._objectType + " ("
+ this._id + "/" + this._libraryID + "/" + this._key + ")",
this._objectType + dataType[0].toUpperCase() + dataType.substr(1)
);
}
}
Zotero.DataObject.prototype._getClass = function () {
return Zotero.DataObjectUtilities.getClassForObjectType(this._objectType);
}
Zotero.DataObject.prototype._loadDataType = function (dataType, reload) {
return this["load" + dataType[0].toUpperCase() + dataType.substr(1)](reload);
}
/**
* Save old version of data that's being changed, to pass to the notifier
*/
Zotero.DataObject.prototype._markFieldChange = function (field, oldValue) {
// Only save if object already exists and field not already changed
if (!this.id || typeof this._previousData[field] != 'undefined') {
return;
}
this._previousData[field] = oldValue;
}
Zotero.DataObject.prototype._clearChanged = function (dataType) {
if (dataType) {
delete this._changed[dataType];
delete this._previousData[dataType];
}
else {
this._changed = {};
this._previousData = {};
}
}
Zotero.DataObject.prototype._clearFieldChange = function (field) {
delete this._previousData[field];
}
Zotero.DataObject.prototype._generateKey = function () {
return Zotero.Utilities.generateObjectKey();
}

View file

@ -51,7 +51,139 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
this._objectCache = {};
this._reloadCache = true;
this._objectKeys = {};
this._objectIDs = {};
this._loadedLibraries = {};
this._loadPromise = null;
// Public properties
this.table = this._ZDO_table;
this.init = function () {
this._loadIDsAndKeys();
}
this.__defineGetter__('primaryFields', function () {
var primaryFields = Object.keys(this._primaryDataSQLParts);
// Once primary fields have been cached, get rid of getter for speed purposes
delete this.primaryFields;
this.primaryFields = primaryFields;
return primaryFields;
});
this.isPrimaryField = function (field) {
return this.primaryFields.indexOf(field) != -1;
}
/**
* Retrieves one or more already-loaded items
*
* If an item hasn't been loaded, an error is thrown
*
* @param {Array|Integer} ids An individual object id or an array of object ids
* @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed;
* otherwise, an array of Zotero.[Object]
*/
this.get = function (ids) {
if (Array.isArray(ids)) {
var singleObject = false;
}
else {
var singleObject = true;
ids = [ids];
}
var toReturn = [];
for (let i=0; i<ids.length; i++) {
let id = ids[i];
// Check if already loaded
if (!this._objectCache[id]) {
throw new Error(this._ZDO_Object + " " + id + " not yet loaded");
}
toReturn.push(this._objectCache[id]);
}
// If single id, return the object directly
if (singleObject) {
return toReturn.length ? toReturn[0] : false;
}
return toReturn;
};
/**
* Retrieves (and loads, if necessary) one or more items
*
* @param {Array|Integer} ids An individual object id or an array of object ids
* @param {Object} options 'noCache': Don't cache loaded objects
* @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed;
* otherwise, an array of Zotero.[Object]
*/
this.getAsync = Zotero.Promise.coroutine(function* (ids, options) {
// Serialize loads
if (this._loadPromise && this._loadPromise.isPending()) {
yield this._loadPromise;
}
var deferred = Zotero.Promise.defer();
this._loadPromise = deferred.promise;
var toLoad = [];
var toReturn = [];
if (!ids) {
throw new Error("No arguments provided to " + this._ZDO_Objects + ".get()");
}
if (Array.isArray(ids)) {
var singleObject = false;
}
else {
var singleObject = true;
ids = [ids];
}
for (let i=0; i<ids.length; i++) {
let id = ids[i];
// Check if already loaded
if (this._objectCache[id]) {
toReturn.push(this._objectCache[id]);
}
else {
toLoad.push(id);
}
}
// New object to load
if (toLoad.length) {
let loaded = yield this._load(null, toLoad, options);
for (let i=0; i<toLoad.length; i++) {
let id = toLoad[i];
let obj = loaded[id];
if (!obj) {
Zotero.debug(this._ZDO_Object + " " + id + " doesn't exist", 2);
continue;
}
toReturn.push(obj);
}
}
deferred.resolve();
// If single id, return the object directly
if (singleObject) {
return toReturn.length ? toReturn[0] : false;
}
return toReturn;
});
this.makeLibraryKeyHash = function (libraryID, key) {
@ -83,7 +215,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* @param {String} key
* @return {Zotero.DataObject} Zotero data object, or FALSE if not found
*/
this.getByLibraryAndKey = function (libraryID, key) {
this.getByLibraryAndKey = Zotero.Promise.coroutine(function* (libraryID, key, options) {
var sql = "SELECT ROWID FROM " + this._ZDO_table + " WHERE ";
if (this._ZDO_idOnly) {
sql += "ROWID=?";
@ -93,130 +225,147 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
sql += "libraryID=? AND key=?";
var params = [libraryID, key];
}
var id = Zotero.DB.valueQuery(sql, params);
var id = yield Zotero.DB.valueQueryAsync(sql, params);
if (!id) {
return false;
}
return Zotero[this._ZDO_Objects].get(id);
return Zotero[this._ZDO_Objects].get(id, options);
});
this.exists = function (itemID) {
return !!this.getLibraryAndKeyFromID(itemID);
}
this.getOlder = function (date) {
/**
* @return {Array} Array with libraryID and key
*/
this.getLibraryAndKeyFromID = function (id) {
return this._objectKeys[id] ? this._objectKeys[id] : false;
}
this.getIDFromLibraryAndKey = function (libraryID, key) {
return this._objectIDs[libraryID][key] ? this._objectIDs[libraryID][key] : false;
}
this.getOlder = function (libraryID, date) {
if (!date || date.constructor.name != 'Date') {
throw ("date must be a JS Date in "
+ "Zotero." + this._ZDO_Objects + ".getOlder()")
}
var sql = "SELECT ROWID FROM " + this._ZDO_table + " WHERE ";
if (this._ZDO_object == 'relation') {
sql += "clientDateModified<?";
}
else {
sql += "dateModified<?";
}
return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
var sql = "SELECT ROWID FROM " + this._ZDO_table
+ " WHERE libraryID=? AND clientDateModified<?";
return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
}
this.getNewer = function (date, ignoreFutureDates) {
if (date && date.constructor.name != 'Date') {
this.getNewer = function (libraryID, date, ignoreFutureDates) {
if (!date || date.constructor.name != 'Date') {
throw ("date must be a JS Date in "
+ "Zotero." + this._ZDO_Objects + ".getNewer()")
}
var sql = "SELECT ROWID FROM " + this._ZDO_table;
if (date) {
sql += " WHERE clientDateModified>?";
if (ignoreFutureDates) {
sql += " AND clientDateModified<=CURRENT_TIMESTAMP";
}
return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
var sql = "SELECT ROWID FROM " + this._ZDO_table
+ " WHERE libraryID=? AND clientDateModified>?";
if (ignoreFutureDates) {
sql += " AND clientDateModified<=CURRENT_TIMESTAMP";
}
return Zotero.DB.columnQuery(sql);
return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
}
/*
* Reloads data for specified items into internal array
*
* Can be passed ids as individual parameters or as an array of ids, or both
/**
* @param {Integer} libraryID
* @return {Promise} A promise for an array of object ids
*/
this.reload = function () {
if (!arguments[0]) {
return false;
}
this.getUnsynced = function (libraryID) {
var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table
+ " WHERE libraryID=? AND synced=0";
return Zotero.DB.columnQueryAsync(sql, [libraryID]);
}
/**
* Get JSON from the sync cache that hasn't yet been written to the
* main object tables
*
* @param {Integer} libraryID
* @return {Promise} A promise for an array of JSON objects
*/
this.getUnwrittenData = function (libraryID) {
var sql = "SELECT data FROM syncCache SC "
+ "LEFT JOIN " + this._ZDO_table + " "
+ "USING (libraryID) "
+ "WHERE SC.libraryID=? AND "
+ "syncObjectTypeID IN (SELECT syncObjectTypeID FROM "
+ "syncObjectTypes WHERE name='" + this._ZDO_object + "') "
+ "AND IFNULL(O.version, 0) < SC.version";
return Zotero.DB.columnQueryAsync(sql, [libraryID]);
}
/**
* Reload loaded data of loaded objects
*
* @param {Array|Number} ids - An id or array of ids
* @param {Array} [dataTypes] - Data types to reload (e.g., 'primaryData'), or all loaded
* types if not provided
* @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally.
* This should be set to true for data that was
* changed externally (e.g., globally renamed tags).
*/
this.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) {
ids = Zotero.flattenArguments(ids);
var ids = Zotero.flattenArguments(arguments);
Zotero.debug('Reloading ' + this._ZDO_objects + ' ' + ids);
Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '')
+ this._ZDO_objects + ' ' + ids);
/*
// Reset cache keys to itemIDs stored in database using object keys
var sql = "SELECT " + this._ZDO_id + " AS id, key FROM " + this._ZDO_table
+ " WHERE " + this._ZDO_id + " IN ("
+ ids.map(function () '?').join() + ")";
var rows = Zotero.DB.query(sql, ids);
var keyIDs = {};
for each(var row in rows) {
keyIDs[row.key] = row.id
}
var store = {};
Zotero.debug('==================');
for (var id in this._objectCache) {
Zotero.debug('id is ' + id);
var obj = this._objectCache[id];
//Zotero.debug(obj);
var dbID = keyIDs[obj.key];
Zotero.debug("DBID: " + dbID);
if (!dbID || id == dbID) {
Zotero.debug('continuing');
continue;
for (let i=0; i<ids.length; i++) {
if (this._objectCache[ids[i]]) {
yield this._objectCache[ids[i]].reload(dataTypes, reloadUnchanged);
}
Zotero.debug('Assigning ' + dbID + ' to store');
store[dbID] = obj;
Zotero.debug('deleting ' + id);
delete this._objectCache[id];
}
Zotero.debug('------------------');
for (var id in store) {
Zotero.debug(id);
if (this._objectCache[id]) {
throw("Existing " + this._ZDO_object + " " + id
+ " exists in cache in Zotero.DataObjects.reload()");
}
this._objectCache[id] = store[id];
}
*/
// If there's an internal reload hook, call it
if (this._reload) {
this._reload(ids)
}
// Reload data
this._load(ids);
return true;
}
});
this.reloadAll = function () {
this.reloadAll = function (libraryID) {
Zotero.debug("Reloading all " + this._ZDO_objects);
// Remove objects not stored in database
var sql = "SELECT ROWID FROM " + this._ZDO_table;
var ids = Zotero.DB.columnQuery(sql);
for (var id in this._objectCache) {
if (!ids || ids.indexOf(parseInt(id)) == -1) {
delete this._objectCache[id];
}
var params = [];
if (libraryID !== undefined) {
sql += ' WHERE libraryID=?';
params.push(libraryID);
}
// Reload data
this._reloadCache = true;
this._load();
return Zotero.DB.columnQueryAsync(sql, params)
.then(function (ids) {
for (var id in this._objectCache) {
if (!ids || ids.indexOf(parseInt(id)) == -1) {
delete this._objectCache[id];
}
}
// Reload data
this._loadedLibraries[libraryID] = false;
return this._load(libraryID);
});
}
this.registerIdentifiers = function (id, libraryID, key) {
Zotero.debug("Registering " + this._ZDO_object + " " + id + " as " + libraryID + "/" + key);
if (!this._objectIDs[libraryID]) {
this._objectIDs[libraryID] = {};
}
this._objectIDs[libraryID][key] = id;
this._objectKeys[id] = [libraryID, key];
}
@ -228,7 +377,13 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
this.unload = function () {
var ids = Zotero.flattenArguments(arguments);
for (var i=0; i<ids.length; i++) {
delete this._objectCache[ids[i]];
let id = ids[i];
let [libraryID, key] = this.getLibraryAndKeyFromID(id);
if (key) {
delete this._objectIDs[libraryID][key];
delete this._objectKeys[id];
}
delete this._objectCache[id];
}
}
@ -342,5 +497,121 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library");
}
}
this.getPrimaryDataSQLPart = function (part) {
var sql = this._primaryDataSQLParts[part];
if (!sql) {
throw new Error("Invalid primary data SQL part '" + part + "'");
}
return sql;
}
this._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) {
var loaded = {};
// If library isn't an integer (presumably false or null), skip it
if (parseInt(libraryID) != libraryID) {
libraryID = false;
}
if (libraryID === false && !ids) {
throw new Error("Either libraryID or ids must be provided");
}
if (libraryID !== false && this._loadedLibraries[libraryID]) {
return loaded;
}
// _getPrimaryDataSQL() should use "O" for the primary table alias
var sql = this._getPrimaryDataSQL();
var params = [];
if (libraryID !== false) {
sql += ' AND O.libraryID=?';
params.push(libraryID);
}
if (ids) {
sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')';
}
var t = new Date();
yield Zotero.DB.queryAsync(
sql,
params,
{
onRow: function (row) {
var id = row.getResultByIndex(this._ZDO_id);
var columns = Object.keys(this._primaryDataSQLParts);
var rowObj = {};
for (let i=0; i<columns.length; i++) {
rowObj[columns[i]] = row.getResultByIndex(i);
}
var obj;
// Existing object -- reload in place
if (this._objectCache[id]) {
this._objectCache[id].loadFromRow(rowObj, true);
obj = this._objectCache[id];
}
// Object doesn't exist -- create new object and stuff in cache
else {
obj = new Zotero[this._ZDO_Object];
obj.loadFromRow(rowObj, true);
if (!options || !options.noCache) {
this._objectCache[id] = obj;
}
}
loaded[id] = obj;
}.bind(this)
}
);
Zotero.debug("Loaded " + this._ZDO_objects + " in " + ((new Date) - t) + "ms");
if (!ids) {
this._loadedLibraries[libraryID] = true;
// If loading all objects, remove cached objects that no longer exist
for (let i in this._objectCache) {
let obj = this._objectCache[i];
if (libraryID !== false && obj.libraryID !== libraryID) {
continue;
}
if (!loaded[obj.id]) {
this.unload(obj.id);
}
}
if (this._postLoad) {
this._postLoad(libraryID, ids);
}
}
return loaded;
});
this._loadIDsAndKeys = function () {
var sql = "SELECT ROWID AS id, libraryID, key FROM " + this._ZDO_table;
return Zotero.DB.queryAsync(sql)
.then(function (rows) {
for (let i=0; i<rows.length; i++) {
let row = rows[i];
this._objectKeys[row.id] = [row.libraryID, row.key];
if (!this._objectIDs[row.libraryID]) {
this._objectIDs[row.libraryID] = {};
}
this._objectIDs[row.libraryID][row.key] = row.id;
}
}.bind(this));
}
}
Zotero.DataObjects.UnloadedDataException = function (msg, dataType) {
this.message = msg;
this.dataType = dataType;
this.stack = (new Error).stack;
}
Zotero.DataObjects.UnloadedDataException.prototype = Object.create(Error.prototype);
Zotero.DataObjects.UnloadedDataException.prototype.name = "UnloadedDataException"

View file

@ -32,7 +32,6 @@ Zotero.Group = function () {
this._init();
}
Zotero.Group.prototype._init = function () {
this._id = null;
this._libraryID = null;
@ -40,6 +39,7 @@ Zotero.Group.prototype._init = function () {
this._description = null;
this._editable = null;
this._filesEditable = null;
this._etag = null;
this._loaded = false;
this._changed = false;
@ -61,13 +61,15 @@ Zotero.Group.prototype.__defineGetter__('editable', function () { return this._g
Zotero.Group.prototype.__defineSetter__('editable', function (val) { this._set('editable', val); });
Zotero.Group.prototype.__defineGetter__('filesEditable', function () { if (!this.editable) { return false; } return this._get('filesEditable'); });
Zotero.Group.prototype.__defineSetter__('filesEditable', function (val) { this._set('filesEditable', val); });
Zotero.Group.prototype.__defineGetter__('etag', function () { return this._get('etag'); });
Zotero.Group.prototype.__defineSetter__('etag', function (val) { this._set('etag', val); });
Zotero.Group.prototype._get = function (field) {
if (this._id && !this._loaded) {
this.load();
if (this['_' + field] !== null) {
return this['_' + field];
}
return this['_' + field];
this._requireLoad();
return null;
}
@ -80,23 +82,16 @@ Zotero.Group.prototype._set = function (field, val) {
}
if (this._loaded) {
throw ("Cannot set " + field + " after object is already loaded in Zotero.Group._set()");
throw new Error("Cannot set " + field + " after object is already loaded");
}
//this._checkValue(field, val);
this['_' + field] = val;
return;
}
if (this.id) {
if (!this._loaded) {
this.load();
}
}
else {
this._loaded = true;
}
this._requireLoad();
if (this['_' + field] != val) {
if (this['_' + field] !== val) {
this._prepFieldChange(field);
switch (field) {
@ -113,24 +108,24 @@ Zotero.Group.prototype._set = function (field, val) {
/*
* Build group from database
*/
Zotero.Group.prototype.load = function() {
Zotero.Group.prototype.load = Zotero.Promise.coroutine(function* () {
var id = this._id;
if (!id) {
throw ("ID not set in Zotero.Group.load()");
throw new Error("ID not set");
}
var sql = "SELECT G.* FROM groups G WHERE groupID=?";
var data = Zotero.DB.rowQuery(sql, id);
this._loaded = true;
var data = yield Zotero.DB.rowQueryAsync(sql, id);
if (!data) {
return;
this._loaded = true;
return false;
}
this.loadFromRow(data);
}
return true;
});
/*
@ -148,6 +143,7 @@ Zotero.Group.prototype.loadFromRow = function(row) {
this._description = row.description;
this._editable = !!row.editable;
this._filesEditable = !!row.filesEditable;
this._etag = row.etag;
}
@ -157,12 +153,7 @@ Zotero.Group.prototype.loadFromRow = function(row) {
* @return bool TRUE if the group exists, FALSE if not
*/
Zotero.Group.prototype.exists = function() {
if (!this.id) {
throw ('groupID not set in Zotero.Group.exists()');
}
var sql = "SELECT COUNT(*) FROM groups WHERE groupID=?";
return !!Zotero.DB.valueQuery(sql, this.id);
return Zotero.Groups.exists(this.id);
}
@ -170,9 +161,8 @@ Zotero.Group.prototype.hasCollections = function () {
if (this._hasCollections !== null) {
return this._hasCollections;
}
this._hasCollections = !!this.getCollections().length;
return this._hasCollections;
this._requireLoad();
return false;
}
@ -180,9 +170,8 @@ Zotero.Group.prototype.hasSearches = function () {
if (this._hasSearches !== null) {
return this._hasSearches;
}
this._hasSearches = !!Zotero.Searches.getAll(this.id).length;
return this._hasSearches;
this._requireLoad();
return false;
}
@ -200,15 +189,15 @@ Zotero.Group.prototype.clearSearchCache = function () {
* @param {Boolean} asIDs Return as collectionIDs
* @return {Zotero.Collection[]} Array of Zotero.Collection instances
*/
Zotero.Group.prototype.getCollections = function (parent) {
Zotero.Group.prototype.getCollections = Zotero.Promise.coroutine(function* (parent) {
var sql = "SELECT collectionID FROM collections WHERE libraryID=? AND "
+ "parentCollectionID " + (parent ? '=' + parent : 'IS NULL');
var ids = Zotero.DB.columnQuery(sql, this.libraryID);
var ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
// Return Zotero.Collection objects
var objs = [];
for each(var id in ids) {
var col = Zotero.Collections.get(id);
var col = yield Zotero.Collections.getAsync(id);
objs.push(col);
}
@ -219,11 +208,13 @@ Zotero.Group.prototype.getCollections = function (parent) {
});
return objs;
}
});
Zotero.Group.prototype.hasItem = function (itemID) {
var item = Zotero.Items.get(itemID);
Zotero.Group.prototype.hasItem = function (item) {
if (!(item instanceof Zotero.Item)) {
throw new Error("item must be a Zotero.Item");
}
return item.libraryID == this.libraryID;
}
@ -255,16 +246,18 @@ Zotero.Group.prototype.save = function () {
'name',
'description',
'editable',
'filesEditable'
'filesEditable',
'etag'
];
var placeholders = ['?', '?', '?', '?', '?', '?'];
var placeholders = columns.map(function () '?').join();
var sqlValues = [
this.id,
this.libraryID,
this.name,
this.description,
this.editable ? 1 : 0,
this.filesEditable ? 1 : 0
this.filesEditable ? 1 : 0,
this.etag
];
if (isNew) {
@ -303,83 +296,81 @@ Zotero.Group.prototype.save = function () {
/**
* Deletes group and all descendant objects
**/
Zotero.Group.prototype.erase = function() {
Zotero.Group.prototype.erase = Zotero.Promise.coroutine(function* () {
// Don't send notifications for items and other groups objects that are deleted,
// since we're really only removing the group from the client
var notifierDisabled = Zotero.Notifier.disable();
Zotero.DB.beginTransaction();
var sql, ids, obj;
// Delete items
sql = "SELECT itemID FROM items WHERE libraryID=?";
ids = Zotero.DB.columnQuery(sql, this.libraryID);
Zotero.Items.erase(ids);
// Delete collections
sql = "SELECT collectionID FROM collections WHERE libraryID=?";
ids = Zotero.DB.columnQuery(sql, this.libraryID);
for each(var id in ids) {
obj = Zotero.Collections.get(id);
// Subcollections might've already been deleted
if (obj) {
obj.erase();
yield Zotero.DB.executeTransaction(function* () {
var sql, ids, obj;
// Delete items
sql = "SELECT itemID FROM items WHERE libraryID=?";
ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
yield Zotero.Items.erase(ids);
// Delete collections
sql = "SELECT collectionID FROM collections WHERE libraryID=?";
ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
for each(var id in ids) {
obj = yield Zotero.Collections.getAsync(id);
// Subcollections might've already been deleted
if (obj) {
yield obj.erase();
}
}
}
// Delete creators
sql = "SELECT creatorID FROM creators WHERE libraryID=?";
ids = Zotero.DB.columnQuery(sql, this.libraryID);
for each(var id in ids) {
obj = Zotero.Creators.get(id);
obj.erase();
}
// Delete saved searches
sql = "SELECT savedSearchID FROM savedSearches WHERE libraryID=?";
ids = Zotero.DB.columnQuery(sql, this.libraryID);
if (ids) {
Zotero.Searches.erase(ids);
}
// Delete tags
sql = "SELECT tagID FROM tags WHERE libraryID=?";
ids = Zotero.DB.columnQuery(sql, this.libraryID);
Zotero.Tags.erase(ids);
// Delete delete log entries
sql = "DELETE FROM syncDeleteLog WHERE libraryID=?";
Zotero.DB.query(sql, this.libraryID);
var prefix = "groups/" + this.id;
Zotero.Relations.eraseByURIPrefix(Zotero.URI.defaultPrefix + prefix);
// Delete settings
sql = "DELETE FROM syncedSettings WHERE libraryID=?";
Zotero.DB.query(sql, this.libraryID ? parseInt(this.libraryID) : 0);
// Delete group
sql = "DELETE FROM groups WHERE groupID=?";
Zotero.DB.query(sql, this.id)
// Delete library
sql = "DELETE FROM libraries WHERE libraryID=?";
Zotero.DB.query(sql, this.libraryID)
Zotero.purgeDataObjects();
var notifierData = {};
notifierData[this.id] = this.serialize();
Zotero.DB.commitTransaction();
// Delete creators
sql = "SELECT creatorID FROM creators WHERE libraryID=?";
ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
for (let i=0; i<ids.length; i++) {
obj = yield Zotero.Creators.getAsync(ids[i]);
yield obj.erase();
}
// Delete saved searches
sql = "SELECT savedSearchID FROM savedSearches WHERE libraryID=?";
ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
if (ids) {
yield Zotero.Searches.erase(ids);
}
// Delete tags
sql = "SELECT tagID FROM tags WHERE libraryID=?";
ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
yield Zotero.Tags.erase(ids);
// Delete delete log entries
sql = "DELETE FROM syncDeleteLog WHERE libraryID=?";
yield Zotero.DB.queryAsync(sql, this.libraryID);
var prefix = "groups/" + this.id;
yield Zotero.Relations.eraseByURIPrefix(Zotero.URI.defaultPrefix + prefix);
// Delete settings
sql = "DELETE FROM syncedSettings WHERE libraryID=?";
yield Zotero.DB.queryAsync(sql, this.libraryID ? parseInt(this.libraryID) : 0);
// Delete group
sql = "DELETE FROM groups WHERE groupID=?";
yield Zotero.DB.queryAsync(sql, this.id)
// Delete library
sql = "DELETE FROM libraries WHERE libraryID=?";
yield Zotero.DB.queryAsync(sql, this.libraryID)
yield Zotero.purgeDataObjects();
var notifierData = {};
notifierData[this.id] = this.serialize();
}.bind(this));
if (notifierDisabled) {
Zotero.Notifier.enable();
}
Zotero.Notifier.trigger('delete', 'group', this.id, notifierData);
}
});
Zotero.Group.prototype.serialize = function() {
@ -399,6 +390,13 @@ Zotero.Group.prototype.serialize = function() {
}
Zotero.Group.prototype._requireLoad = function () {
if (!this._loaded) {
throw new Error("Group has not been loaded");
}
}
Zotero.Group.prototype._prepFieldChange = function (field) {
if (!this._changed) {
this._changed = {};

View file

@ -27,32 +27,39 @@
Zotero.Groups = new function () {
this.__defineGetter__('addGroupURL', function () ZOTERO_CONFIG.WWW_BASE_URL + 'groups/new/');
this.get = function (id) {
var _groupIDsByLibraryID = {};
var _libraryIDsByGroupID = {};
this.init = function () {
_loadIDs();
}
this.get = Zotero.Promise.coroutine(function* (id) {
if (!id) {
throw ("groupID not provided in Zotero.Groups.get()");
throw new Error("groupID not provided");
}
var group = new Zotero.Group;
group.id = id;
if (!group.exists()) {
if (!(yield group.load())) {
return false;
}
return group;
}
});
this.getAll = function () {
this.getAll = Zotero.Promise.coroutine(function* () {
var groups = [];
var sql = "SELECT groupID FROM groups ORDER BY name COLLATE locale";
var groupIDs = Zotero.DB.columnQuery(sql);
if (!groupIDs) {
var groupIDs = yield Zotero.DB.columnQueryAsync(sql);
if (!groupIDs.length) {
return groups;
}
for each(var groupID in groupIDs) {
var group = this.get(groupID);
groups.push(group);
groups.push(this.get(groupID));
}
return groups;
}
return Zotero.Promise.all(groups);
});
this.getByLibraryID = function (libraryID) {
@ -61,24 +68,38 @@ Zotero.Groups = new function () {
}
this.exists = function (groupID) {
return !!_libraryIDsByGroupID[groupID];
}
this.getGroupIDFromLibraryID = function (libraryID) {
var sql = "SELECT groupID FROM groups WHERE libraryID=?";
var groupID = Zotero.DB.valueQuery(sql, libraryID);
var groupID = _groupIDsByLibraryID[libraryID];
if (!groupID) {
throw ("Group with libraryID " + libraryID + " does not exist "
+ "in Zotero.Groups.getGroupIDFromLibraryID()");
throw new Error("Group with libraryID " + libraryID + " does not exist");
}
return groupID;
}
this.getLibraryIDFromGroupID = function (groupID) {
var sql = "SELECT libraryID FROM groups WHERE groupID=?";
var libraryID = Zotero.DB.valueQuery(sql, groupID);
var libraryID = _libraryIDsByGroupID[groupID];
if (!libraryID) {
throw ("Group with groupID " + groupID + " does not exist "
+ "in Zotero.Groups.getLibraryIDFromGroupID()");
throw new Error("Group with groupID " + groupID + " does not exist");
}
return libraryID;
}
function _loadIDs() {
var sql = "SELECT libraryID, groupID FROM groups";
return Zotero.DB.queryAsync(sql)
.then(function (rows) {
for (let i=0; i<rows.length; i++) {
let row = rows[i];
_groupIDsByLibraryID[row.libraryID] = row.groupID;
_libraryIDsByGroupID[row.groupID] = row.libraryID;
}
}.bind(this));
}
}

File diff suppressed because it is too large Load diff

View file

@ -201,7 +201,7 @@ Zotero.ItemFields = new function() {
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
throw ("Invalid field '" + baseField + '" for base field in ItemFields.getFieldIDFromTypeAndBase()');
throw new Error("Invalid field '" + baseField + '" for base field');
}
if (fieldID == baseFieldID) {
@ -233,12 +233,12 @@ Zotero.ItemFields = new function() {
function getFieldIDFromTypeAndBase(itemType, baseField) {
var itemTypeID = Zotero.ItemTypes.getID(itemType);
if (!itemTypeID) {
throw new Error("Invalid item type '" + itemType + "' in ItemFields.getFieldIDFromTypeAndBase()");
throw new Error("Invalid item type '" + itemType + "'");
}
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
throw new Error("Invalid field '" + baseField + '" for base field in ItemFields.getFieldIDFromTypeAndBase()');
throw new Error("Invalid field '" + baseField + '" for base field');
}
return _baseTypeFields[itemTypeID][baseFieldID];

File diff suppressed because it is too large Load diff

View file

@ -25,8 +25,17 @@
Zotero.Libraries = new function () {
this.exists = function (libraryID) {
var sql = "SELECT COUNT(*) FROM libraries WHERE libraryID=?";
return !!Zotero.DB.valueQuery(sql, [libraryID]);
// Until there are other library types, this can just check groups,
// which already preload ids at startup
try {
return !!Zotero.Groups.getGroupIDFromLibraryID(libraryID);
}
catch (e) {
if (e.getMessage().indexOf("does not exist") != -1) {
return false;
}
throw e;
}
}
@ -44,6 +53,11 @@ Zotero.Libraries = new function () {
}
this.dbLibraryID = function (libraryID) {
return (libraryID == Zotero.libraryID) ? 0 : libraryID;
}
this.getName = function (libraryID) {
if (!libraryID) {
return Zotero.getString('pane.collections.library');
@ -63,7 +77,7 @@ Zotero.Libraries = new function () {
this.getType = function (libraryID) {
if (libraryID === 0 || !Zotero.libraryID || libraryID == Zotero.libraryID) {
if (libraryID === 0 || !Zotero.libraryID || libraryID == Zotero.libraryID) {
return 'user';
}
var sql = "SELECT libraryType FROM libraries WHERE libraryID=?";
@ -74,6 +88,26 @@ Zotero.Libraries = new function () {
return libraryType;
}
/**
* @param {Integer} libraryID
* @return {Promise:Integer}
*/
this.getVersion = function (libraryID) {
var sql = "SELECT version FROM libraries WHERE libraryID=?";
return Zotero.DB.valueQueryAsync(sql, libraryID);
}
/**
* @param {Integer} libraryID
* @param {Integer} version
* @return {Promise}
*/
this.setVersion = function (libraryID, version) {
var sql = "UPDATE libraries SET version=? WHERE libraryID=?";
return Zotero.DB.queryAsync(sql, [version, libraryID]);
}
this.isEditable = function (libraryID) {
var type = this.getType(libraryID);

View file

@ -106,32 +106,32 @@ Zotero.Relation.prototype._set = function (field, val) {
*
* @return bool TRUE if the relation exists, FALSE if not
*/
Zotero.Relation.prototype.exists = function () {
Zotero.Relation.prototype.exists = Zotero.Promise.coroutine(function* () {
if (this.id) {
var sql = "SELECT COUNT(*) FROM relations WHERE relationID=?";
return !!Zotero.DB.valueQuery(sql, this.id);
return !!(yield Zotero.DB.valueQueryAsync(sql, this.id));
}
if (this.libraryID && this.subject && this.predicate && this.object) {
var sql = "SELECT COUNT(*) FROM relations WHERE libraryID=? AND "
+ "subject=? AND predicate=? AND object=?";
var params = [this.libraryID, this.subject, this.predicate, this.object];
return !!Zotero.DB.valueQuery(sql, params);
return !!(yield Zotero.DB.valueQueryAsync(sql, params));
}
throw ("ID or libraryID/subject/predicate/object not set in Zotero.Relation.exists()");
}
});
Zotero.Relation.prototype.load = function () {
Zotero.Relation.prototype.load = Zotero.Promise.coroutine(function* () {
var id = this._id;
if (!id) {
throw ("ID not set in Zotero.Relation.load()");
throw new Error("ID not set");
}
var sql = "SELECT * FROM relations WHERE ROWID=?";
var row = Zotero.DB.rowQuery(sql, id);
var row = yield Zotero.DB.rowQueryAsync(sql, id);
if (!row) {
return;
}
@ -144,10 +144,11 @@ Zotero.Relation.prototype.load = function () {
this._loaded = true;
return true;
}
});
Zotero.Relation.prototype.save = function () {
// TODO: async
Zotero.Relation.prototype.save = Zotero.Promise.coroutine(function* () {
if (this.id) {
throw ("Existing relations cannot currently be altered in Zotero.Relation.save()");
}
@ -168,7 +169,7 @@ Zotero.Relation.prototype.save = function () {
+ "(libraryID, subject, predicate, object, clientDateModified) "
+ "VALUES (?, ?, ?, ?, ?)";
try {
var insertID = Zotero.DB.query(
var insertID = yield Zotero.DB.queryAsync(
sql,
[
this.libraryID,
@ -181,52 +182,46 @@ Zotero.Relation.prototype.save = function () {
}
catch (e) {
// If above failed, try deleting existing row, in case libraryID has changed
Zotero.DB.beginTransaction();
var sql2 = "SELECT COUNT(*) FROM relations WHERE subject=? AND predicate=? AND object=?";
if (Zotero.DB.valueQuery(sql2, [this.subject, this.predicate, this.object])) {
// Delete
sql2 = "DELETE FROM relations WHERE subject=? AND predicate=? AND object=?";
Zotero.DB.query(sql2, [this.subject, this.predicate, this.object]);
// Insert with original query
var insertID = Zotero.DB.query(
sql,
[
this.libraryID,
this.subject,
this.predicate,
this.object,
Zotero.DB.transactionDateTime
]
);
}
Zotero.DB.commitTransaction();
yield Zotero.DB.executeTransaction(function* () {
var sql2 = "SELECT COUNT(*) FROM relations WHERE subject=? AND predicate=? AND object=?";
if (yield Zotero.DB.valueQueryAsync(sql2, [this.subject, this.predicate, this.object])) {
// Delete
sql2 = "DELETE FROM relations WHERE subject=? AND predicate=? AND object=?";
yield Zotero.DB.queryAsync(sql2, [this.subject, this.predicate, this.object]);
// Insert with original query
var insertID = yield Zotero.DB.queryAsync(
sql,
[
this.libraryID,
this.subject,
this.predicate,
this.object,
Zotero.DB.transactionDateTime
]
);
}
}.bind(this));
}
return insertID;
}
});
Zotero.Relation.prototype.erase = function () {
Zotero.Relation.prototype.erase = Zotero.Promise.coroutine(function* () {
if (!this.id) {
throw ("ID not set in Zotero.Relation.erase()");
}
Zotero.DB.beginTransaction();
var deleteData = {};
deleteData[this.id] = {
old: this.serialize()
}
var sql = "DELETE FROM relations WHERE ROWID=?";
Zotero.DB.query(sql, [this.id]);
Zotero.DB.commitTransaction();
yield Zotero.DB.queryAsync(sql, [this.id]);
Zotero.Notifier.trigger('delete', 'relation', [this.id], deleteData);
}
});
Zotero.Relation.prototype.toXML = function (doc) {

View file

@ -27,6 +27,7 @@ Zotero.Relations = new function () {
Zotero.DataObjects.apply(this, ['relation']);
this.constructor.prototype = new Zotero.DataObjects();
this.__defineGetter__('relatedItemPredicate', function () "dc:relation");
this.__defineGetter__('linkedObjectPredicate', function () "owl:sameAs");
this.__defineGetter__('deletedItemPredicate', function () 'dc:isReplacedBy');
@ -49,13 +50,13 @@ Zotero.Relations = new function () {
/**
* @return {Object[]}
*/
this.getByURIs = function (subject, predicate, object) {
this.getByURIs = Zotero.Promise.coroutine(function* (subject, predicate, object) {
if (predicate) {
predicate = _getPrefixAndValue(predicate).join(':');
}
if (!subject && !predicate && !object) {
throw ("No values provided in Zotero.Relations.get()");
throw new Error("No values provided");
}
var sql = "SELECT ROWID FROM relations WHERE 1";
@ -72,42 +73,42 @@ Zotero.Relations = new function () {
sql += " AND object=?";
params.push(object);
}
var rows = Zotero.DB.columnQuery(sql, params);
var rows = yield Zotero.DB.columnQueryAsync(sql, params);
if (!rows) {
return [];
}
var toReturn = [];
for each(var id in rows) {
for (let i=0; i<rows.length; i++) {
var relation = new Zotero.Relation;
relation.id = id;
relation.id = rows[i];
toReturn.push(relation);
}
return toReturn;
}
});
this.getSubject = function (subject, predicate, object) {
this.getSubject = Zotero.Promise.coroutine(function* (subject, predicate, object) {
var subjects = [];
var relations = this.getByURIs(subject, predicate, object);
var relations = yield this.getByURIs(subject, predicate, object);
for each(var relation in relations) {
subjects.push(relation.subject);
}
return subjects;
}
});
this.getObject = function (subject, predicate, object) {
this.getObject = Zotero.Promise.coroutine(function* (subject, predicate, object) {
var objects = [];
var relations = this.getByURIs(subject, predicate, object);
var relations = yield this.getByURIs(subject, predicate, object);
for each(var relation in relations) {
objects.push(relation.object);
}
return objects;
}
});
this.updateUser = function (fromUserID, fromLibraryID, toUserID, toLibraryID) {
this.updateUser = Zotero.Promise.coroutine(function* (fromUserID, fromLibraryID, toUserID, toLibraryID) {
if (!fromUserID) {
throw ("Invalid source userID " + fromUserID + " in Zotero.Relations.updateUserID");
}
@ -121,24 +122,22 @@ Zotero.Relations = new function () {
throw ("Invalid target libraryID " + toLibraryID + " in Zotero.Relations.updateUserID");
}
Zotero.DB.beginTransaction();
var sql = "UPDATE relations SET libraryID=? WHERE libraryID=?";
Zotero.DB.query(sql, [toLibraryID, fromLibraryID]);
sql = "UPDATE relations SET "
+ "subject=REPLACE(subject, 'zotero.org/users/" + fromUserID + "', "
+ "'zotero.org/users/" + toUserID + "'), "
+ "object=REPLACE(object, 'zotero.org/users/" + fromUserID + "', "
+ "'zotero.org/users/" + toUserID + "') "
+ "WHERE predicate IN (?, ?)";
Zotero.DB.query(sql, [this.linkedObjectPredicate, this.deletedItemPredicate]);
Zotero.DB.commitTransaction();
}
yield Zotero.DB.executeTransaction(function* () {
var sql = "UPDATE relations SET libraryID=? WHERE libraryID=?";
Zotero.DB.query(sql, [toLibraryID, fromLibraryID]);
sql = "UPDATE relations SET "
+ "subject=REPLACE(subject, 'zotero.org/users/" + fromUserID + "', "
+ "'zotero.org/users/" + toUserID + "'), "
+ "object=REPLACE(object, 'zotero.org/users/" + fromUserID + "', "
+ "'zotero.org/users/" + toUserID + "') "
+ "WHERE predicate IN (?, ?)";
Zotero.DB.query(sql, [this.linkedObjectPredicate, this.deletedItemPredicate]);
}.bind(this));
});
this.add = function (libraryID, subject, predicate, object) {
this.add = Zotero.Promise.coroutine(function* (libraryID, subject, predicate, object) {
predicate = _getPrefixAndValue(predicate).join(':');
var relation = new Zotero.Relation;
@ -154,24 +153,24 @@ Zotero.Relations = new function () {
relation.subject = subject;
relation.predicate = predicate;
relation.object = object;
relation.save();
}
yield relation.save();
});
/**
* Copy relations from one object to another within the same library
*/
this.copyURIs = function (libraryID, fromURI, toURI) {
var rels = this.getByURIs(fromURI);
this.copyURIs = Zotero.Promise.coroutine(function* (libraryID, fromURI, toURI) {
var rels = yield this.getByURIs(fromURI);
for each(var rel in rels) {
this.add(libraryID, toURI, rel.predicate, rel.object);
yield this.add(libraryID, toURI, rel.predicate, rel.object);
}
var rels = this.getByURIs(false, false, fromURI);
var rels = yield this.getByURIs(false, false, fromURI);
for each(var rel in rels) {
this.add(libraryID, rel.subject, rel.predicate, toURI);
yield this.add(libraryID, rel.subject, rel.predicate, toURI);
}
}
});
/**
@ -179,73 +178,72 @@ Zotero.Relations = new function () {
* @param {String[]} ignorePredicates
*/
this.eraseByURIPrefix = function (prefix, ignorePredicates) {
Zotero.DB.beginTransaction();
prefix = prefix + '%';
var sql = "SELECT ROWID FROM relations WHERE (subject LIKE ? OR object LIKE ?)";
var params = [prefix, prefix];
if (ignorePredicates) {
for each(var ignorePredicate in ignorePredicates) {
sql += " AND predicate != ?";
params.push(ignorePredicate);
return Zotero.DB.executeTransaction(function* () {
prefix = prefix + '%';
var sql = "SELECT ROWID FROM relations WHERE (subject LIKE ? OR object LIKE ?)";
var params = [prefix, prefix];
if (ignorePredicates) {
for each(var ignorePredicate in ignorePredicates) {
sql += " AND predicate != ?";
params.push(ignorePredicate);
}
}
}
var ids = Zotero.DB.columnQuery(sql, params);
for each(var id in ids) {
var relation = this.get(id);
relation.erase();
}
Zotero.DB.commitTransaction();
var ids = yield Zotero.DB.columnQueryAsync(sql, params);
for (let i=0; i<ids.length; i++) {
let relation = yield this.get(ids[i]);
yield relation.erase();
}
}.bind(this));
}
/**
* @return {Promise}
*/
this.eraseByURI = function (uri, ignorePredicates) {
Zotero.DB.beginTransaction();
var sql = "SELECT ROWID FROM relations WHERE (subject=? OR object=?)";
var params = [uri, uri];
if (ignorePredicates) {
for each(var ignorePredicate in ignorePredicates) {
sql += " AND predicate != ?";
params.push(ignorePredicate);
return Zotero.DB.executeTransaction(function* () {
var sql = "SELECT ROWID FROM relations WHERE (subject=? OR object=?)";
var params = [uri, uri];
if (ignorePredicates) {
for each(var ignorePredicate in ignorePredicates) {
sql += " AND predicate != ?";
params.push(ignorePredicate);
}
}
}
var ids = Zotero.DB.columnQuery(sql, params);
for each(var id in ids) {
var relation = this.get(id);
relation.erase();
}
Zotero.DB.commitTransaction();
var ids = yield Zotero.DB.columnQueryAsync(sql, params);
for (let i=0; i<ids.length; i++) {
let relation = yield this.get(ids[i]);
yield relation.erase();
}
}.bind(this));
}
this.purge = function () {
this.purge = Zotero.Promise.coroutine(function* () {
var sql = "SELECT subject FROM relations WHERE predicate != ? "
+ "UNION SELECT object FROM relations WHERE predicate != ?";
var uris = Zotero.DB.columnQuery(sql, [this.deletedItemPredicate, this.deletedItemPredicate]);
var uris = yield Zotero.DB.columnQueryAsync(sql, [this.deletedItemPredicate, this.deletedItemPredicate]);
if (uris) {
var prefix = Zotero.URI.defaultPrefix;
Zotero.DB.beginTransaction();
for each(var uri in uris) {
// Skip URIs that don't begin with the default prefix,
// since they don't correspond to local items
if (uri.indexOf(prefix) == -1) {
continue;
yield Zotero.DB.executeTransaction(function* () {
for each(var uri in uris) {
// Skip URIs that don't begin with the default prefix,
// since they don't correspond to local items
if (uri.indexOf(prefix) == -1) {
continue;
}
if (uri.indexOf(/\/items\//) != -1 && !Zotero.URI.getURIItem(uri)) {
this.eraseByURI(uri);
}
if (uri.indexOf(/\/collections\//) != -1 && !Zotero.URI.getURICollection(uri)) {
this.eraseByURI(uri);
}
}
if (uri.indexOf(/\/items\//) != -1 && !Zotero.URI.getURIItem(uri)) {
this.eraseByURI(uri);
}
if (uri.indexOf(/\/collections\//) != -1 && !Zotero.URI.getURICollection(uri)) {
this.eraseByURI(uri);
}
}
Zotero.DB.commitTransaction();
}.bind(this));
}
}
});
this.xmlToRelation = function (relationNode) {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -39,47 +39,35 @@ Zotero.Duplicates = function (libraryID) {
Zotero.Duplicates.prototype.__defineGetter__('name', function () Zotero.getString('pane.collections.duplicate'));
Zotero.Duplicates.prototype.__defineGetter__('libraryID', function () this._libraryID);
/**
* Get duplicates, populate a temporary table, and return a search based
* on that table
*
* @return {Zotero.Search}
*/
Zotero.Duplicates.prototype.getSearchObject = function () {
Zotero.DB.beginTransaction();
var sql = "DROP TABLE IF EXISTS tmpDuplicates";
Zotero.DB.query(sql);
var sql = "CREATE TEMPORARY TABLE tmpDuplicates "
+ "(id INTEGER PRIMARY KEY)";
Zotero.DB.query(sql);
this._findDuplicates();
var ids = this._sets.findAll(true);
sql = "INSERT INTO tmpDuplicates VALUES (?)";
var insertStatement = Zotero.DB.getStatement(sql);
for each(var id in ids) {
insertStatement.bindInt32Parameter(0, id);
Zotero.Duplicates.prototype.getSearchObject = Zotero.Promise.coroutine(function* () {
yield Zotero.DB.executeTransaction(function* () {
var sql = "DROP TABLE IF EXISTS tmpDuplicates";
yield Zotero.DB.queryAsync(sql);
try {
insertStatement.execute();
var sql = "CREATE TEMPORARY TABLE tmpDuplicates "
+ "(id INTEGER PRIMARY KEY)";
yield Zotero.DB.queryAsync(sql);
this._findDuplicates();
var ids = this._sets.findAll(true);
sql = "INSERT INTO tmpDuplicates VALUES (?)";
for (let i=0; i<ids.length; i++) {
yield Zotero.DB.queryAsync(sql, [ids[i]], { debug: false })
}
catch(e) {
throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
}
}
Zotero.DB.commitTransaction();
}.bind(this));
var s = new Zotero.Search;
s.libraryID = this._libraryID;
s.addCondition('tempTable', 'is', 'tmpDuplicates');
yield s.addCondition('tempTable', 'is', 'tmpDuplicates');
return s;
}
});
/**
@ -308,7 +296,6 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
else {
var sql = "SELECT lastName, firstName, fieldMode FROM itemCreators "
+ "JOIN creators USING (creatorID) "
+ "JOIN creatorData USING (creatorDataID) "
+ "WHERE itemID=? ORDER BY orderIndex LIMIT 10";
aCreatorRows = Zotero.DB.query(sql, a.itemID);
creatorRowsCache[a.itemID] = aCreatorRows;
@ -321,7 +308,6 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
else {
var sql = "SELECT lastName, firstName, fieldMode FROM itemCreators "
+ "JOIN creators USING (creatorID) "
+ "JOIN creatorData USING (creatorDataID) "
+ "WHERE itemID=? ORDER BY orderIndex LIMIT 10";
bCreatorRows = Zotero.DB.query(sql, b.itemID);
creatorRowsCache[b.itemID] = bCreatorRows;

View file

@ -28,13 +28,12 @@
* @namespace
*/
Zotero.File = new function(){
Components.utils.import("resource://zotero/q.js");
//Components.utils.import("resource://zotero/bluebird.js");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
this.getExtension = getExtension;
this.getClosestDirectory = getClosestDirectory;
this.getSample = getSample;
this.getContentsFromURL = getContentsFromURL;
this.putContents = putContents;
this.getValidFileName = getValidFileName;
@ -42,6 +41,21 @@ Zotero.File = new function(){
this.getCharsetFromFile = getCharsetFromFile;
this.addCharsetListener = addCharsetListener;
this.pathToFile = function (pathOrFile) {
if (typeof pathOrFile == 'string') {
let nsIFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
nsIFile.initWithPath(pathOrFile);
return nsIFile;
}
else if (pathOrFile instanceof Ci.nsIFile) {
return pathOrFile;
}
throw new Error('Unexpected value provided to Zotero.MIME.pathToFile() (' + pathOrFile + ')');
}
/**
* Encode special characters in file paths that might cause problems,
* like # (but preserve slashes or colons)
@ -82,11 +96,22 @@ Zotero.File = new function(){
}
/*
* Get the first 200 bytes of the file as a string (multibyte-safe)
/**
* Get the first 200 bytes of a source as a string (multibyte-safe)
*
* @param {nsIURI|nsIFile|string spec|nsIChannel|nsIInputStream} source - The source to read
* @return {Promise}
*/
function getSample(file) {
return this.getContents(file, null, 200);
this.getSample = function (file) {
var bytes = 200;
return this.getContentsAsync(file, null, bytes)
.catch(function (e) {
if (e.name == 'NS_ERROR_ILLEGAL_INPUT') {
Zotero.debug("Falling back to raw bytes");
return this.getBinaryContentsAsync(file, bytes);
}
throw e;
}.bind(this));
}
@ -114,7 +139,7 @@ Zotero.File = new function(){
* @return {String} The contents of the file
* @deprecated Use {@link Zotero.File.getContentsAsync} when possible
*/
this.getContents = function getContents(file, charset, maxLength){
this.getContents = function (file, charset, maxLength){
var fis;
if(file instanceof Components.interfaces.nsIInputStream) {
fis = file;
@ -163,21 +188,36 @@ Zotero.File = new function(){
*
* @param {nsIURI|nsIFile|string spec|nsIChannel|nsIInputStream} source The source to read
* @param {String} [charset] The character set; defaults to UTF-8
* @param {Integer} [maxLength] Maximum length to fetch, in bytes (unimplemented)
* @param {Integer} [maxLength] Maximum length to fetch, in bytes
* @return {Promise} A Q promise that is resolved with the contents of the file
*/
this.getContentsAsync = function getContentsAsync(source, charset, maxLength) {
this.getContentsAsync = function (source, charset, maxLength) {
Zotero.debug("Getting contents of " + source);
var options = {
charset: charset ? Zotero.CharacterSets.getName(charset) : "UTF-8"
charset: charset ? Zotero.CharacterSets.getName(charset) : "UTF-8",
// This doesn't seem to work -- reading an image file still throws NS_ERROR_ILLEGAL_INPUT
replacement: "\uFFFD"
};
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
NetUtil.asyncFetch(source, function(inputStream, status) {
if (!Components.isSuccessCode(status)) {
deferred.reject(new Components.Exception("File read operation failed", status));
return;
}
deferred.resolve(NetUtil.readInputStreamToString(inputStream, inputStream.available(), options));
try {
deferred.resolve(
NetUtil.readInputStreamToString(
inputStream,
Math.min(maxLength, inputStream.available()),
options
)
);
}
catch (e) {
deferred.reject(e);
}
});
return deferred.promise;
};
@ -187,17 +227,22 @@ Zotero.File = new function(){
* Get the contents of a binary source asynchronously
*
* @param {nsIURI|nsIFile|string spec|nsIChannel|nsIInputStream} source The source to read
* @param {Integer} [maxLength] Maximum length to fetch, in bytes (unimplemented)
* @return {Promise} A Q promise that is resolved with the contents of the source
*/
this.getBinaryContentsAsync = function (source) {
var deferred = Q.defer();
this.getBinaryContentsAsync = function (source, maxLength) {
var deferred = Zotero.Promise.defer();
NetUtil.asyncFetch(source, function(inputStream, status) {
if (!Components.isSuccessCode(status)) {
deferred.reject(new Components.Exception("Source read operation failed", status));
return;
}
deferred.resolve(NetUtil.readInputStreamToString(inputStream, inputStream.available()));
deferred.resolve(
NetUtil.readInputStreamToString(
inputStream,
Math.min(maxLength, inputStream.available())
)
);
});
return deferred.promise;
}
@ -251,20 +296,17 @@ Zotero.File = new function(){
/**
* Write data to a file asynchronously
* @param {nsIFile} The file to write to
* @param {String|nsIInputStream} data The string or nsIInputStream to write to the
* file
* @param {String} [charset] The character set; defaults to UTF-8
* @return {Promise} A Q promise that is resolved when the file has been written
*
* @param {nsIFile} - The file to write to
* @param {String|nsIInputStream} data - The string or nsIInputStream to write to the file
* @param {String} [charset] - The character set; defaults to UTF-8
* @return {Promise} - A promise that is resolved when the file has been written
*/
this.putContentsAsync = function putContentsAsync(file, data, charset) {
if (typeof data == 'string'
&& Zotero.platformMajorVersion >= 19
&& (!charset || charset.toLowerCase() == 'utf-8')) {
if (typeof data == 'string' && (!charset || charset.toLowerCase() == 'utf-8')) {
let encoder = new TextEncoder();
let array = encoder.encode(data);
Components.utils.import("resource://gre/modules/osfile.jsm");
return Q(OS.File.writeAtomic(
return Zotero.Promise.resolve(OS.File.writeAtomic(
file.path,
array,
{
@ -290,7 +332,7 @@ Zotero.File = new function(){
data = converter.convertToInputStream(data);
}
var deferred = Q.defer(),
var deferred = Zotero.Promise.defer(),
ostream = FileUtils.openSafeFileOutputStream(file);
NetUtil.asyncCopy(data, ostream, function(inputStream, status) {
if (!Components.isSuccessCode(status)) {
@ -311,7 +353,7 @@ Zotero.File = new function(){
* FALSE if missing
*/
this.deleteIfExists = function deleteIfExists(path) {
return Q(OS.File.remove(path))
return Zotero.Promise.resolve(OS.File.remove(path))
.thenResolve(true)
.catch(function (e) {
if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
@ -329,7 +371,7 @@ Zotero.File = new function(){
* The DirectoryInterator is passed as the first parameter to the generator.
* A StopIteration error will be caught automatically.
*
* Zotero.File.iterateDirectory(path, function (iterator) {
* Zotero.File.iterateDirectory(path, function* (iterator) {
* while (true) {
* var entry = yield iterator.next();
* [...]
@ -340,7 +382,7 @@ Zotero.File = new function(){
*/
this.iterateDirectory = function iterateDirectory(path, generator) {
var iterator = new OS.File.DirectoryIterator(path);
return Q.async(generator)(iterator)
return Zotero.Promise.coroutine(generator)(iterator)
.catch(function (e) {
if (e != StopIteration) {
throw e;

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,9 @@ Zotero.HTTP = new function() {
if (xmlhttp.channel.URI.password) {
xmlhttp.channel.URI.password = "********";
}
if (xmlhttp.channel.URI.spec) {
xmlhttp.channel.URI.spec = xmlhttp.channel.URI.spec.replace(/key=[^&]+&?/, "key=********");
}
}
catch (e) {
Zotero.debug(e, 1);
@ -76,6 +79,9 @@ Zotero.HTTP = new function() {
var dispURL = url;
}
// Don't display API key in console
dispURL = dispURL.replace(/key=[^&]+&?/, "").replace(/\?$/, "");
if(options && options.body) {
var bodyStart = options.body.substr(0, 1024);
// Don't display sync password or session id in console
@ -91,14 +97,14 @@ Zotero.HTTP = new function() {
}
if (this.browserIsOffline()) {
return Q.fcall(function() {
return Zotero.Promise.try(function() {
Zotero.debug("HTTP " + method + " " + dispURL + " failed: "
+ "Browser is offline");
throw new this.BrowserOfflineException();
});
}
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance();
@ -451,19 +457,19 @@ Zotero.HTTP = new function() {
if (!Zotero.isStandalone
|| !Zotero.Prefs.get("triggerProxyAuthentication")
|| Zotero.HTTP.browserIsOffline()) {
Zotero.proxyAuthComplete = Q();
Zotero.proxyAuthComplete = Zotero.Promise.resolve();
return false;
}
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
Zotero.proxyAuthComplete = deferred.promise;
Q.fcall(function () {
Zotero.Promise.try(function () {
var uris = Zotero.Prefs.get('proxyAuthenticationURLs').split(',');
uris = Zotero.Utilities.arrayShuffle(uris);
uris.unshift(ZOTERO_CONFIG.PROXY_AUTH_URL);
return Q.async(function () {
return Zotero.spawn(function* () {
let max = 3; // how many URIs to try after the general Zotero one
for (let i = 0; i <= max; i++) {
let uri = uris.shift();
@ -474,7 +480,7 @@ Zotero.HTTP = new function() {
// For non-Zotero URLs, wait for PAC initialization,
// in a rather ugly and inefficient manner
if (i == 1) {
let installed = yield Q.fcall(_pacInstalled)
let installed = yield Zotero.Promise.try(_pacInstalled)
.then(function (installed) {
if (installed) throw true;
})
@ -519,7 +525,7 @@ Zotero.HTTP = new function() {
}
}
deferred.resolve();
})();
});
})
.catch(function (e) {
Components.utils.reportError(e);
@ -544,7 +550,7 @@ Zotero.HTTP = new function() {
Components.utils.import("resource://gre/modules/NetUtil.jsm");
var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
.getService(Components.interfaces.nsIProtocolProxyService);
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
pps.asyncResolve(
NetUtil.newURI(uri),
0,

View file

@ -24,7 +24,6 @@
*/
Zotero.ID_Tracker = function () {
this.get = get;
this.getBigInt = getBigInt;
this.skip = skip;
this.getTableName = getTableName;
@ -46,7 +45,7 @@ Zotero.ID_Tracker = function () {
/*
* Gets an unused primary key id for a DB table
*/
function get(table, notNull) {
this.get = Zotero.Promise.coroutine(function* (table, notNull) {
table = this.getTableName(table);
switch (table) {
@ -62,7 +61,7 @@ Zotero.ID_Tracker = function () {
case 'tags':
case 'customItemTypes':
case 'customFields':
var id = _getNextAvailable(table);
var id = yield _getNextAvailable(table);
if (!id && notNull) {
return _getNext(table);
}
@ -72,7 +71,7 @@ Zotero.ID_Tracker = function () {
//
// TODO: use autoincrement instead where available in 1.5
case 'itemDataValues':
var id = _getNextAvailable(table);
var id = yield _getNextAvailable(table);
if (!id) {
// If we can't find an empty id quickly, just use MAX() + 1
return _getNext(table);
@ -82,7 +81,7 @@ Zotero.ID_Tracker = function () {
default:
throw ("Unsupported table '" + table + "' in Zotero.ID.get()");
}
}
});
this.isValidKey = function (value) {
@ -162,9 +161,9 @@ Zotero.ID_Tracker = function () {
* Returns the lowest available unused primary key id for table,
* or NULL if none could be loaded in _loadAvailable()
*/
function _getNextAvailable(table) {
var _getNextAvailable = Zotero.Promise.coroutine(function* (table) {
if (!_available[table]) {
_loadAvailable(table);
yield _loadAvailable(table);
}
var arr = _available[table];
@ -189,7 +188,7 @@ Zotero.ID_Tracker = function () {
if (_skip[table] && _skip[table][id]) {
Zotero.debug("Skipping " + table + " id " + id);
if (!_available[table]) {
_loadAvailable(table);
yield _loadAvailable(table);
}
continue;
}
@ -198,13 +197,13 @@ Zotero.ID_Tracker = function () {
return id;
}
return null;
}
});
/*
* Get MAX(id) + 1 from table
*/
function _getNext(table) {
var _getNext = Zotero.Promise.coroutine(function* (table) {
var column = _getTableColumn(table);
var sql = 'SELECT MAX(';
@ -216,7 +215,7 @@ Zotero.ID_Tracker = function () {
}
}
if (!max) {
throw ("_skip['" + table + "'] must contain positive values in Zotero.ID._getNext()");
throw new Error("_skip['" + table + "'] must contain positive values");
}
sql += 'MAX(' + column + ', ' + max + ')';
}
@ -224,14 +223,14 @@ Zotero.ID_Tracker = function () {
sql += column;
}
sql += ')+1 FROM ' + table;
return Zotero.DB.valueQuery(sql);
}
return Zotero.DB.valueQueryAsync(sql);
});
/*
* Loads available ids for table into memory
*/
function _loadAvailable(table) {
var _loadAvailable = Zotero.Promise.coroutine(function* (table) {
Zotero.debug("Loading available ids for table '" + table + "'");
var minID = _min[table] ? _min[table] + 1 : 1;
@ -263,7 +262,7 @@ Zotero.ID_Tracker = function () {
var maxID = minID + numIDs - 1;
var sql = "SELECT " + column + " FROM " + table
+ " WHERE " + column + " BETWEEN ? AND ? ORDER BY " + column;
var ids = Zotero.DB.columnQuery(sql, [minID, maxID]);
var ids = yield Zotero.DB.columnQueryAsync(sql, [minID, maxID]);
// If no ids found, we have numIDs unused ids
if (!ids) {
maxID = Math.min(maxID, minID + (maxToFind - 1));
@ -277,7 +276,7 @@ Zotero.ID_Tracker = function () {
Zotero.debug('No available ids found between ' + minID + ' and ' + maxID + '; trying next ' + numIDs);
minID = maxID + 1;
maxID = minID + numIDs - 1;
ids = Zotero.DB.columnQuery(sql, [minID, maxID]);
ids = yield Zotero.DB.columnQueryAsync(sql, [minID, maxID]);
maxTries--;
}
@ -329,37 +328,7 @@ Zotero.ID_Tracker = function () {
Zotero.debug("Found " + found + " available ids in table '" + table + "'");
_available[table] = available;
}
/**
* Find a unique random id for use in a DB table
*
* (No longer used)
**/
function _getRandomID(table, max){
var column = _getTableColumn(table);
var sql = 'SELECT COUNT(*) FROM ' + table + ' WHERE ' + column + '= ?';
if (!max){
max = 16383;
}
max--; // since we use ceil(), decrement max by 1
var tries = 3; // # of tries to find a unique id
for (var i=0; i<tries; i++) {
var rnd = Math.ceil(Math.random() * max);
var exists = Zotero.DB.valueQuery(sql, { int: rnd });
if (!exists) {
return rnd;
}
}
// If no luck after number of tries, try a larger range
var sql = 'SELECT MAX(' + column + ') + 1 FROM ' + table;
return Zotero.valueQuery(sql);
}
});
function _getTableColumn(table) {

View file

@ -127,7 +127,7 @@ Zotero.Integration = new function() {
Zotero.logError(e);
}
Q.delay(1000).then(_checkPluginVersions);
Zotero.Promise.delay(1000).then(_checkPluginVersions);
}
/**
@ -177,13 +177,13 @@ Zotero.Integration = new function() {
return function _checkPluginVersions() {
if(integrationVersionsOK) {
if(integrationVersionsOK === true) {
return Q.resolve(integrationVersionsOK);
return Zotero.Promise.resolve(integrationVersionsOK);
} else {
return Q.reject(integrationVersionsOK);
return Zotero.Promise.reject(integrationVersionsOK);
}
}
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
AddonManager.getAddonsByIDs(INTEGRATION_PLUGINS, function(addons) {
for(var i in addons) {
var addon = addons[i];
@ -237,8 +237,8 @@ Zotero.Integration = new function() {
// Try to execute the command; otherwise display an error in alert service or word processor
// (depending on what is possible)
document = (application.getDocument && docId ? application.getDocument(docId) : application.getActiveDocument());
return Q.resolve((new Zotero.Integration.Document(application, document))[command]());
}).fail(function(e) {
return Zotero.Promise.resolve((new Zotero.Integration.Document(application, document))[command]());
}).catch(function(e) {
if(!(e instanceof Zotero.Exception.UserCancelled)) {
try {
var displayError = null;
@ -295,7 +295,7 @@ Zotero.Integration = new function() {
if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
var oldWindow = Zotero.Integration.currentWindow;
Q.delay(100).then(function() {
Zotero.Promise.delay(100).then(function() {
oldWindow.close();
});
}
@ -689,7 +689,7 @@ Zotero.Integration = new function() {
Zotero.Integration.currentWindow = window;
Zotero.Integration.activate(window);
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
var listener = function() {
if(window.location.toString() === "about:blank") return;
@ -760,7 +760,7 @@ Zotero.Integration.MissingItemException.prototype = {
this.fieldGetter._doc.activate();
var result = this.fieldGetter._doc.displayAlert(msg, 1, 3);
if(result == 0) { // Cancel
return Q.reject(new Zotero.Exception.UserCancelled("document update"));
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document update"));
} else if(result == 1) { // No
for each(var reselectKey in this.reselectKeys) {
this.fieldGetter._removeCodeKeys[reselectKey] = true;
@ -817,7 +817,7 @@ Zotero.Integration.CorruptFieldException.prototype = {
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO_CANCEL);
if(result == 0) {
return Q.reject(new Zotero.Exception.UserCancelled("document update"));
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document update"));
} else if(result == 1) { // No
this.fieldGetter._removeCodeFields[this.fieldIndex] = true;
return this.fieldGetter._processFields(this.fieldIndex+1);
@ -873,12 +873,12 @@ Zotero.Integration.CorruptBibliographyException.prototype = {
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(result == 0) {
return Q.reject(new Zotero.Exception.UserCancelled("clearing corrupted bibliography"));
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("clearing corrupted bibliography"));
} else {
this.fieldGetter._bibliographyData = "";
this.fieldGetter._session.bibliographyHasChanged = true;
this.fieldGetter._session.bibliographyDataHasChanged = true;
return Q.resolve(true);
return Zotero.Promise.resolve(true);
}
}
};
@ -947,7 +947,7 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
// if no fields, throw an error
if(!haveFields) {
return Q.reject(new Zotero.Exception.Alert(
return Zotero.Promise.reject(new Zotero.Exception.Alert(
"integration.error.mustInsertCitation",
[], "integration.error.title"));
} else {
@ -958,7 +958,7 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
// Set doc prefs if no data string yet
this._session = this._createNewSession(data);
this._session.setData(data);
if(dontRunSetDocPrefs) return Q.resolve(false);
if(dontRunSetDocPrefs) return Zotero.Promise.resolve(false);
return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
this._app.secondaryFieldType).then(function(status) {
@ -984,16 +984,16 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(!warning) {
return Q.reject(new Zotero.Exception.UserCancelled("document upgrade"));
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document upgrade"));
}
} else if(data.dataVersion > DATA_VERSION) {
return Q.reject(new Zotero.Exception.Alert("integration.error.newerDocumentVersion",
return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.newerDocumentVersion",
[data.zoteroVersion, Zotero.version], "integration.error.title"));
}
if(data.prefs.fieldType !== this._app.primaryFieldType
&& data.prefs.fieldType !== this._app.secondaryFieldType) {
return Q.reject(new Zotero.Exception.Alert("integration.error.fieldTypeMismatch",
return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.fieldTypeMismatch",
[], "integration.error.title"));
}
@ -1013,14 +1013,14 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
return me._session;
});
} else {
return Q.reject(e);
return Zotero.Promise.reject(e);
}
}
this._doc.setDocumentData(this._session.data.serializeXML());
this._session.reload = true;
}
return Q.resolve(this._session);
return Zotero.Promise.resolve(this._session);
}
};
@ -1165,7 +1165,7 @@ Zotero.Integration.Document.prototype.setDocPrefs = function() {
return fieldGetter.updateSession().then(setDocPrefs);
} else {
// Can get fields while dialog is open
return Q.all([
return Zotero.Promise.all([
fieldGetter.get(),
setDocPrefs()
]).spread(function (fields, setDocPrefs) {
@ -1272,7 +1272,7 @@ Zotero.Integration.Fields = function(session, doc, fieldErrorHandler) {
Zotero.Integration.Fields.prototype.addField = function(note) {
// Get citation types if necessary
if(!this._doc.canInsertField(this._session.data.prefs['fieldType'])) {
return Q.reject(new Zotero.Exception.Alert("integration.error.cannotInsertHere",
return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.cannotInsertHere",
[], "integration.error.title"));
}
@ -1281,7 +1281,7 @@ Zotero.Integration.Fields.prototype.addField = function(note) {
if(!this._doc.displayAlert(Zotero.getString("integration.replace"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL)) {
return Q.reject(new Zotero.Exception.UserCancelled("inserting citation"));
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("inserting citation"));
}
}
@ -1290,7 +1290,7 @@ Zotero.Integration.Fields.prototype.addField = function(note) {
(note ? this._session.data.prefs["noteType"] : 0));
}
return Q.resolve(field);
return Zotero.Promise.resolve(field);
}
/**
@ -1321,11 +1321,11 @@ Zotero.Integration.Fields.prototype.getCodeTypeAndContent = function(rawCode) {
Zotero.Integration.Fields.prototype.get = function get() {
// If we already have fields, just return them
if(this._fields) {
return Q.resolve(this._fields);
return Zotero.Promise.resolve(this._fields);
}
// Create a new promise and add it to promise list
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
// If already getting fields, just return the promise
if(this._deferreds) {
@ -1737,7 +1737,7 @@ Zotero.Integration.Fields.prototype.addEditCitation = function(field) {
}
var me = this;
return Q(field).then(function(field) {
return Zotero.Promise.resolve(field).then(function(field) {
if(!citation) {
field.setCode("TEMP");
citation = {"citationItems":[], "properties":{}};
@ -1757,7 +1757,7 @@ Zotero.Integration.Fields.prototype.addEditCitation = function(field) {
}
if(newField) {
return io.promise.fail(function(e) {
return io.promise.catch(function(e) {
// Try to delete new field on failure
try {
field.delete();
@ -1792,7 +1792,7 @@ Zotero.Integration.CitationEditInterface = function(citation, field, fieldGetter
this.style = session.style;
// Start getting citation data
this._acceptDeferred = Q.defer();
this._acceptDeferred = Zotero.Promise.defer();
this._fieldIndexPromise = fieldGetter.get().then(function(fields) {
for(var i=0, n=fields.length; i<n; i++) {
if(fields[i].equals(field)) {
@ -1802,7 +1802,7 @@ Zotero.Integration.CitationEditInterface = function(citation, field, fieldGetter
});
var me = this;
this.promise = this._fieldIndexPromise.then(function(fieldIndex) {
this.promise = this._fieldIndexZotero.Promise.then(function(fieldIndex) {
me._fieldIndex = fieldIndex;
return me._acceptDeferred.promise;
}).then(function(progressCallback) {
@ -1843,7 +1843,7 @@ Zotero.Integration.CitationEditInterface.prototype = {
*/
"_updateSession":function _updateSession(resolveErrors) {
var me = this;
if(this._sessionUpdatePromise && this._sessionUpdatePromise.isFulfilled()) {
if(this._sessionUpdatePromise && this._sessionUpdateZotero.Promise.isFulfilled()) {
// Session has already been updated. If we were deferring resolving an error,
// and we are supposed to resolve it now, then do that
if(this._sessionUpdateError) {
@ -1852,13 +1852,13 @@ Zotero.Integration.CitationEditInterface.prototype = {
delete me._sessionUpdateError;
});
} else {
return Q.reject(this._sessionUpdateError);
return Zotero.Promise.reject(this._sessionUpdateError);
}
} else {
return Q.resolve(true);
return Zotero.Promise.resolve(true);
}
} else {
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
this._sessionUpdateResolveErrors = this._sessionUpdateResolveErrors || resolveErrors;
this._sessionUpdateDeferreds.push(deferred);
@ -1937,7 +1937,7 @@ Zotero.Integration.CitationEditInterface.prototype = {
* @return {Promise} A promise resolved by the items
*/
"getItems":function() {
if(this._fieldIndexPromise.isFulfilled()
if(this._fieldIndexZotero.Promise.isFulfilled()
|| Zotero.Utilities.isEmpty(this._session.citationsByItemID)) {
// Either we already have field data for this run or we have no item data at all.
// Update session before continuing.
@ -1950,7 +1950,7 @@ Zotero.Integration.CitationEditInterface.prototype = {
} else {
// We have item data left over from a previous run with this document, so we don't need
// to wait.
return Q.resolve(this._getItems());
return Zotero.Promise.resolve(this._getItems());
}
},

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,7 @@ Zotero.LibraryTreeView.prototype = {
* Called by HTML 5 Drag and Drop when dragging over the tree
*/
onDragEnter: function (event) {
Zotero.DragDrop.currentDragEvent = event;
Zotero.DragDrop.currentEvent = event;
return false;
},
@ -60,7 +60,7 @@ Zotero.LibraryTreeView.prototype = {
// Prevent modifier keys from doing their normal things
event.preventDefault();
Zotero.DragDrop.currentDragEvent = event;
Zotero.DragDrop.currentEvent = event;
var target = event.target;
if (target.tagName != 'treechildren') {
@ -83,28 +83,25 @@ Zotero.LibraryTreeView.prototype = {
return;
}
if (event.dataTransfer.getData("zotero/collection")) {
this._setDropEffect(event, "move");
}
else if (event.dataTransfer.getData("zotero/item")) {
var sourceItemGroup = Zotero.DragDrop.getDragSource();
if (sourceItemGroup) {
if (event.dataTransfer.getData("zotero/item")) {
var sourceCollectionTreeRow = Zotero.DragDrop.getDragSource();
if (sourceCollectionTreeRow) {
if (this.type == 'collection') {
var targetItemGroup = Zotero.DragDrop.getDragTarget();
var targetCollectionTreeRow = Zotero.DragDrop.getDragTarget();
}
else if (this.type == 'item') {
var targetItemGroup = this.itemGroup;
var targetCollectionTreeRow = this.collectionTreeRow;
}
else {
throw new Error("Invalid type '" + this.type + "'");
}
if (!targetItemGroup) {
if (!targetCollectionTreeRow) {
this._setDropEffect(event, "none");
return false;
}
if (sourceItemGroup.id == targetItemGroup.id) {
if (sourceCollectionTreeRow.id == targetCollectionTreeRow.id) {
// Ignore drag into the same collection
if (this.type == 'collection') {
this._setDropEffect(event, "none");
@ -116,12 +113,12 @@ Zotero.LibraryTreeView.prototype = {
return false;
}
// If the source isn't a collection, the action has to be a copy
if (!sourceItemGroup.isCollection()) {
if (!sourceCollectionTreeRow.isCollection()) {
this._setDropEffect(event, "copy");
return false;
}
// For now, all cross-library drags are copies
if (sourceItemGroup.ref.libraryID != targetItemGroup.ref.libraryID) {
if (sourceCollectionTreeRow.ref.libraryID != targetCollectionTreeRow.ref.libraryID) {
this._setDropEffect(event, "copy");
return false;
}
@ -172,7 +169,7 @@ Zotero.LibraryTreeView.prototype = {
// See note above
if (event.dataTransfer.types.contains("application/x-moz-file")) {
if (Zotero.isMac) {
Zotero.DragDrop.currentDragEvent = event;
Zotero.DragDrop.currentEvent = event;
if (event.metaKey) {
if (event.altKey) {
event.dataTransfer.dropEffect = 'link';
@ -192,7 +189,7 @@ Zotero.LibraryTreeView.prototype = {
onDragExit: function (event) {
//Zotero.debug("Clearing drag data");
Zotero.DragDrop.currentDragEvent = null;
Zotero.DragDrop.currentEvent = null;
},

View file

@ -311,16 +311,22 @@ Zotero.MIME = new function(){
* Try to determine the MIME type of the file, using a few different
* techniques
*/
this.getMIMETypeFromFile = function (file) {
var str = Zotero.File.getSample(file);
this.getMIMETypeFromFile = Zotero.Promise.coroutine(function* (file) {
var str = yield Zotero.File.getSample(file);
var ext = Zotero.File.getExtension(file);
return this.getMIMETypeFromData(str, ext);
}
});
this.getMIMETypeFromURL = function (url, callback, cookieSandbox) {
Zotero.HTTP.doHead(url, function(xmlhttp) {
/**
* @param {String} url
* @param {Zotero.CookieSandbox} [cookieSandbox]
* @return {Promise}
*/
this.getMIMETypeFromURL = function (url, cookieSandbox) {
return Zotero.HTTP.promise("HEAD", url, { cookieSandbox: cookieSandbox, successCodes: false })
.then(function (xmlhttp) {
if (xmlhttp.status != 200 && xmlhttp.status != 204) {
Zotero.debug("Attachment HEAD request returned with status code "
+ xmlhttp.status + " in Zotero.MIME.getMIMETypeFromURL()", 2);
@ -331,7 +337,7 @@ Zotero.MIME = new function(){
}
var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
.createInstance(Components.interfaces.nsIURL);
.createInstance(Components.interfaces.nsIURL);
nsIURL.spec = url;
// Override MIME type to application/pdf if extension is .pdf --
@ -344,10 +350,10 @@ Zotero.MIME = new function(){
}
var ext = nsIURL.fileExtension;
var hasNativeHandler = Zotero.MIME.hasNativeHandler(mimeType, ext)
var hasNativeHandler = Zotero.MIME.hasNativeHandler(mimeType, ext);
callback(mimeType, hasNativeHandler);
}, undefined, cookieSandbox);
return [mimeType, hasNativeHandler];
});
}
@ -407,11 +413,11 @@ Zotero.MIME = new function(){
}
this.fileHasInternalHandler = function (file){
var mimeType = this.getMIMETypeFromFile(file);
this.fileHasInternalHandler = Zotero.Promise.coroutine(function* (file){
var mimeType = yield this.getMIMETypeFromFile(file);
var ext = Zotero.File.getExtension(file);
return hasInternalHandler(mimeType, ext);
}
});
/*

View file

@ -302,8 +302,8 @@ Zotero.MIMETypeHandler = new function () {
inputStream.close();
var me = this;
Q(_typeHandlers[this._contentType](readString, (this._request.name ? this._request.name : null),
this._contentType, channel)).fail(function(e) {
Zotero.Promise.resolve(_typeHandlers[this._contentType](readString, (this._request.name ? this._request.name : null),
this._contentType, channel)).catch(function(e) {
// if there was an error, handle using nsIExternalHelperAppService
var externalHelperAppService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"].
getService(Components.interfaces.nsIExternalHelperAppService);

View file

@ -27,7 +27,7 @@ Zotero.Notifier = new function(){
var _observers = {};
var _disabled = false;
var _types = [
'collection', 'creator', 'search', 'share', 'share-items', 'item', 'file',
'collection', 'search', 'share', 'share-items', 'item', 'file',
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'bucket', 'relation'
];
var _inTransaction;
@ -36,17 +36,15 @@ Zotero.Notifier = new function(){
this.registerObserver = registerObserver;
this.unregisterObserver = unregisterObserver;
this.trigger = trigger;
this.untrigger = untrigger;
this.begin = begin;
this.commit = commit;
this.reset = reset;
this.disable = disable;
this.enable = enable;
this.isEnabled = isEnabled;
function registerObserver(ref, types){
function registerObserver(ref, types, id) {
if (types){
types = Zotero.flattenArguments(types);
@ -66,7 +64,7 @@ Zotero.Notifier = new function(){
tries = 10;
}
var hash = Zotero.randomString(len);
var hash = (id ? id + '_' : '') + Zotero.randomString(len);
tries--;
}
while (_observers[hash]);
@ -100,7 +98,7 @@ Zotero.Notifier = new function(){
*
* - New events and types should be added to the order arrays in commit()
**/
function trigger(event, type, ids, extraData, force){
this.trigger = Zotero.Promise.coroutine(function* (event, type, ids, extraData, force) {
if (_disabled){
Zotero.debug("Notifications are disabled");
return false;
@ -114,8 +112,12 @@ Zotero.Notifier = new function(){
var queue = _inTransaction && !force;
Zotero.debug("Notifier.trigger('" + event + "', '" + type + "', " + '[' + ids.join() + '])'
Zotero.debug("Notifier.trigger('" + event + "', '" + type + "', " + '[' + ids.join() + '], ' + extraData + ')'
+ (queue ? " queued" : " called " + "[observers: " + Object.keys(_observers).length + "]"));
if (extraData) {
Zotero.debug("EXTRA DATA:");
Zotero.debug(extraData);
}
// Merge with existing queue
if (queue) {
@ -135,18 +137,22 @@ Zotero.Notifier = new function(){
// Merge extraData keys
if (extraData) {
Zotero.debug("ADDING EXTRA DATA");
for (var dataID in extraData) {
Zotero.debug(dataID);
if (extraData[dataID]) {
Zotero.debug("YES");
_queue[type][event].data[dataID] = extraData[dataID];
}
}
}
Zotero.debug(_queue[type][event]);
return true;
}
for (var i in _observers){
Zotero.debug("Calling notify('" + event + "') on observer with hash '" + i + "'", 4);
Zotero.debug("Calling notify() with " + event + "/" + type + " on observer with hash '" + i + "'", 4);
if (!_observers[i]) {
Zotero.debug("Observer no longer exists");
@ -158,7 +164,7 @@ Zotero.Notifier = new function(){
// Catch exceptions so all observers get notified even if
// one throws an error
try {
_observers[i].ref.notify(event, type, ids, extraData);
yield Zotero.Promise.resolve(_observers[i].ref.notify(event, type, ids, extraData));
}
catch (e) {
Zotero.debug(e);
@ -168,7 +174,7 @@ Zotero.Notifier = new function(){
}
return true;
}
});
function untrigger(event, type, ids) {
@ -226,7 +232,7 @@ Zotero.Notifier = new function(){
*
* If the queue is locked, notifications will only run if _unlock_ is true
*/
function commit(unlock) {
this.commit = Zotero.Promise.coroutine(function* (unlock) {
// If there's a lock on the event queue and _unlock_ isn't given, don't commit
if ((unlock == undefined && _locked) || (unlock != undefined && !unlock)) {
//Zotero.debug("Keeping Notifier event queue open", 4);
@ -253,29 +259,25 @@ Zotero.Notifier = new function(){
for (var event in _queue[type]) {
runQueue[type][event] = {
ids: [],
data: {}
data: _queue[type][event].data
};
// Remove redundant ids
for (var i=0; i<_queue[type][event].ids.length; i++) {
var id = _queue[type][event].ids[i];
var data = _queue[type][event].data[id];
// Don't send modify on nonexistent items or tags
if (event == 'modify') {
if (type == 'item' && !Zotero.Items.get(id)) {
if (type == 'item' && !(yield Zotero.Items.getAsync(id))) {
continue;
}
else if (type == 'tag' && !Zotero.Tags.get(id)) {
else if (type == 'tag' && !(yield Zotero.Tags.getAsync(id))) {
continue;
}
}
if (runQueue[type][event].ids.indexOf(id) == -1) {
runQueue[type][event].ids.push(id);
if (data) {
runQueue[type][event].data[id] = data;
}
}
}
@ -293,13 +295,18 @@ Zotero.Notifier = new function(){
for (var type in runQueue) {
for (var event in runQueue[type]) {
if (runQueue[type][event].ids.length || event == 'refresh') {
trigger(event, type, runQueue[type][event].ids,
runQueue[type][event].data, true);
yield this.trigger(
event,
type,
runQueue[type][event].ids,
runQueue[type][event].data,
true
);
}
}
}
}
}
});
/*

View file

@ -27,7 +27,6 @@
Zotero.QuickCopy = new function() {
this.getContentType = getContentType;
this.stripContentType = stripContentType;
this.getFormatFromURL = getFormatFromURL;
this.getContentFromItems = getContentFromItems;
var _formattedNames = {};
@ -96,7 +95,7 @@ Zotero.QuickCopy = new function() {
}
function getFormatFromURL(url) {
this.getFormatFromURL = Zotero.Promise.coroutine(function* (url) {
if (!url) {
return Zotero.Prefs.get("export.quickCopy.setting");
}
@ -118,7 +117,7 @@ Zotero.QuickCopy = new function() {
var sql = "SELECT key AS domainPath, value AS format FROM settings "
+ "WHERE setting='quickCopySite' AND (key LIKE ? OR key LIKE ?)";
var urlDomain = urlHostPort.match(/[^\.]+\.[^\.]+$/);
var rows = Zotero.DB.query(sql, ['%' + urlDomain + '%', '/%']);
var rows = yield Zotero.DB.queryAsync(sql, ['%' + urlDomain + '%', '/%']);
for each(var row in rows) {
var [domain, path] = row.domainPath.split(/\//);
path = '/' + (path ? path : '');
@ -157,7 +156,7 @@ Zotero.QuickCopy = new function() {
}
return Zotero.Prefs.get("export.quickCopy.setting");
}
});
/*

View file

@ -220,7 +220,7 @@ Zotero.Report = new function() {
case 'key':
case 'itemType':
case 'itemID':
case 'sourceItemID':
case 'parentItemID':
case 'title':
case 'firstCreator':
case 'creators':

View file

@ -30,6 +30,7 @@ Zotero.Schema = new function(){
var _dbVersions = [];
var _schemaVersions = [];
var _maxCompatibility = 1;
var _repositoryTimer;
var _remoteUpdateInProgress = false, _localUpdateInProgress = false;
@ -40,11 +41,7 @@ Zotero.Schema = new function(){
*/
this.getDBVersion = function (schema) {
if (_dbVersions[schema]){
return Q(_dbVersions[schema]);
}
if (!Zotero.DB.tableExists('version')) {
return Q(false)
return Zotero.Promise.resolve(_dbVersions[schema]);
}
var sql = "SELECT version FROM version WHERE schema='" + schema + "'";
@ -55,6 +52,15 @@ Zotero.Schema = new function(){
_dbVersions[schema] = dbVersion;
}
return dbVersion;
})
.catch(function (e) {
return Zotero.DB.tableExistsAsync('version')
.then(function (exists) {
if (exists) {
throw e;
}
return false;
});
});
}
@ -63,42 +69,50 @@ Zotero.Schema = new function(){
* Checks if the DB schema exists and is up-to-date, updating if necessary
*/
this.updateSchema = function () {
return Q.all([this.getDBVersion('userdata'), this.getDBVersion('userdata3')])
.spread(function (oldDBVersion, dbVersion) {
if (!oldDBVersion) {
// 'userdata' is the last upgrade step run in _migrateUserDataSchema() based on the
// version in the schema file. Upgrade steps may or may not break DB compatibility.
//
// 'compatibility' is incremented manually by upgrade steps in order to break DB
// compatibility with older versions.
return Zotero.Promise.all([this.getDBVersion('userdata'), this.getDBVersion('compatibility')])
.spread(function (userdata, compatibility) {
if (!userdata) {
Zotero.debug('Database does not exist -- creating\n');
return _initializeSchema().thenResolve(true);
return _initializeSchema().return(true);
}
if (oldDBVersion < 76) {
// TODO: localize
let msg = "Zotero found a preZotero 2.1 database that cannot be upgraded to "
+ "work with this version of Zotero. To continue, either upgrade your "
+ "database using an earlier version of Zotero or delete your "
+ "Zotero data directory to start with a new database."
// We don't handle upgrades from pre-Zotero 2.1 databases
if (userdata < 76) {
let msg = Zotero.getString('upgrade.nonupgradeableDB1')
+ "\n\n" + Zotero.getString('upgrade.nonupgradeableDB2', "4.0");
throw new Error(msg);
}
return _getSchemaSQLVersion('userdata3')
if (compatibility > _maxCompatibility) {
throw new Error("Database is incompatible this Zotero version "
+ "(" + compatibility + " > " + _maxCompatibility + ")");
}
return _getSchemaSQLVersion('userdata')
// If upgrading userdata, make backup of database first
.then(function (schemaVersion) {
if (dbVersion < schemaVersion) {
return Zotero.DB.backupDatabase(dbVersion);
if (userdata < schemaVersion) {
return Zotero.DB.backupDatabase(userdata);
}
})
.then(function () {
return Zotero.DB.executeTransaction(function (conn) {
var up1 = yield _updateSchema('system');
var updated = yield _updateSchema('system');
// Update custom tables if they exist so that changes are in
// place before user data migration
if (Zotero.DB.tableExists('customItemTypes')) {
yield Zotero.Schema.updateCustomTables(up1);
yield Zotero.Schema.updateCustomTables(updated);
}
var up2 = yield _migrateUserDataSchema(dbVersion);
updated = yield _migrateUserDataSchema(userdata);
yield _updateSchema('triggers');
Zotero.DB.asyncResult(up2);
Zotero.DB.asyncResult(updated);
})
.then(function (updated) {
// Populate combined tables for custom types and fields
@ -110,7 +124,7 @@ Zotero.Schema = new function(){
.then(function () {
if (updated) {
// Upgrade seems to have been a success -- delete any previous backups
var maxPrevious = dbVersion - 1;
var maxPrevious = userdata - 1;
var file = Zotero.getZoteroDirectory();
var toDelete = [];
try {
@ -140,10 +154,15 @@ Zotero.Schema = new function(){
}
// After a delay, start update of bundled files and repo updates
Zotero.initializationPromise
//
// **************
// TEMP TEMP TEMP
// **************
//
/*Zotero.initializationPromise
.delay(5000)
.then(function () Zotero.Schema.updateBundledFiles(null, false, true))
.done();
.done();*/
return updated;
});
@ -155,7 +174,8 @@ Zotero.Schema = new function(){
// This is mostly temporary
// TEMP - NSF
this.importSchema = function (str, uri) {
// TODO: async
this.importSchema = Zotero.Promise.coroutine(function* (str, uri) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
@ -277,7 +297,7 @@ Zotero.Schema = new function(){
for each(var search in searches) {
if (search.name == 'Overdue NSF Reviewers') {
var id = search.id;
Zotero.Searches.erase(id);
yield Zotero.Searches.erase(id);
}
}
@ -285,7 +305,7 @@ Zotero.Schema = new function(){
ps.alert(null, "Zotero Item Type Removed", "The 'NSF Reviewer' item type has been uninstalled.");
}
}
});
function _reloadSchema() {
Zotero.Schema.updateCustomTables()
@ -314,10 +334,11 @@ Zotero.Schema = new function(){
if (!skipDelete) {
yield Zotero.DB.queryAsync("DELETE FROM itemTypesCombined");
yield Zotero.DB.queryAsync("DELETE FROM fieldsCombined");
yield Zotero.DB.queryAsync("DELETE FROM fieldsCombined WHERE fieldID NOT IN (SELECT fieldID FROM itemData)");
yield Zotero.DB.queryAsync("DELETE FROM itemTypeFieldsCombined");
yield Zotero.DB.queryAsync("DELETE FROM baseFieldMappingsCombined");
}
var offset = Zotero.ItemTypes.customIDOffset;
yield Zotero.DB.queryAsync(
"INSERT INTO itemTypesCombined "
@ -329,7 +350,7 @@ Zotero.Schema = new function(){
+ "SELECT customItemTypeID + " + offset + " AS itemTypeID, typeName, display, 1 AS custom FROM customItemTypes"
);
yield Zotero.DB.queryAsync(
"INSERT INTO fieldsCombined "
"INSERT OR IGNORE INTO fieldsCombined "
+ (
skipSystem
? ""
@ -372,9 +393,9 @@ Zotero.Schema = new function(){
* @return {Promise}
*/
this.updateBundledFiles = function(mode, skipDeleteUpdate, runRemoteUpdateWhenComplete) {
if (_localUpdateInProgress) return Q();
if (_localUpdateInProgress) return Zotero.Promise.resolve();
return Q.fcall(function () {
return Zotero.Promise.try(function () {
_localUpdateInProgress = true;
// Get path to addon and then call updateBundledFilesCallback
@ -389,7 +410,7 @@ Zotero.Schema = new function(){
}
// Asynchronous in Firefox
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID(
ZOTERO_CONFIG['GUID'],
@ -906,7 +927,7 @@ Zotero.Schema = new function(){
* long it's been since the last check
*/
this.updateFromRepository = function (force) {
return Q.fcall(function () {
return Zotero.Promise.try(function () {
if (force) return true;
if (_remoteUpdateInProgress) {
@ -1214,12 +1235,12 @@ Zotero.Schema = new function(){
],
// Note/child parents
[
"SELECT COUNT(*) FROM itemAttachments WHERE sourceItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
"UPDATE itemAttachments SET sourceItemID=NULL WHERE sourceItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
"SELECT COUNT(*) FROM itemAttachments WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
"UPDATE itemAttachments SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
],
[
"SELECT COUNT(*) FROM itemNotes WHERE sourceItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
"UPDATE itemNotes SET sourceItemID=NULL WHERE sourceItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
"SELECT COUNT(*) FROM itemNotes WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
"UPDATE itemNotes SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
],
// Wrong library tags
@ -1324,22 +1345,10 @@ Zotero.Schema = new function(){
* @return {Promise:String} A promise for the SQL file's version number
*/
function _getSchemaSQLVersion(schema){
// TEMP
if (schema == 'userdata3') {
schema = 'userdata';
var newUserdata = true;
}
return _getSchemaSQL(schema)
.then(function (sql) {
// Fetch the schema version from the first line of the file
var schemaVersion = parseInt(sql.match(/^-- ([0-9]+)/)[1]);
// TEMP: For 'userdata', cap the version at 76
// For 'userdata3', versions > 76 are allowed.
if (schema == 'userdata' && !newUserdata) {
schemaVersion = Math.min(76, schemaVersion);
}
_schemaVersions[schema] = schemaVersion;
return schemaVersion;
});
@ -1405,22 +1414,17 @@ Zotero.Schema = new function(){
yield _getSchemaSQLVersion('system').then(function (version) {
return _updateDBVersion('system', version);
});
// TEMP: 77 is for full-text syncing. New users don't need the
// prompt, so initialize new databases to 77.
//yield _getSchemaSQLVersion('userdata').then(function (version) {
// return _updateDBVersion('userdata', version);
//});
yield _updateDBVersion('userdata', 77);
yield _getSchemaSQLVersion('userdata3').then(function (version) {
return _updateDBVersion('userdata3', version);
yield _getSchemaSQLVersion('userdata').then(function (version) {
return _updateDBVersion('userdata', version);
});
yield _getSchemaSQLVersion('triggers').then(function (version) {
return _updateDBVersion('triggers', version);
});
yield _updateDBVersion('compatibility', _maxCompatibility);
if (!Zotero.Schema.skipDefaultData) {
// Quick Start Guide web page item
var sql = "INSERT INTO items VALUES(1, 13, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, 'ABCD2345')";
var sql = "INSERT INTO items VALUES(1, 13, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0, 'ABCD2345', 0, 0)";
yield Zotero.DB.queryAsync(sql);
var sql = "INSERT INTO itemDataValues VALUES (1, ?)";
yield Zotero.DB.queryAsync(sql, Zotero.getString('install.quickStartGuide'));
@ -1432,15 +1436,13 @@ Zotero.Schema = new function(){
yield Zotero.DB.queryAsync(sql);
// CHNM as creator
var sql = "INSERT INTO creatorData VALUES (1, '', 'Center for History and New Media', '', 1, NULL)";
yield Zotero.DB.queryAsync(sql);
var sql = "INSERT INTO creators VALUES (1, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, 'ABCD2345')";
var sql = "INSERT INTO creators VALUES (1, '', 'Center for History and New Media', 1)";
yield Zotero.DB.queryAsync(sql);
var sql = "INSERT INTO itemCreators VALUES (1, 1, 1, 0)";
yield Zotero.DB.queryAsync(sql);
// Welcome note
var sql = "INSERT INTO items VALUES(2, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, 'ABCD3456')";
var sql = "INSERT INTO items VALUES(2, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0, 'ABCD3456', 0, 0)";
yield Zotero.DB.queryAsync(sql);
var welcomeTitle = Zotero.getString('install.quickStartGuide.message.welcome');
var welcomeMsg = '<div class="zotero-note znv1"><p><strong>' + welcomeTitle + '</strong></p>'
@ -1480,7 +1482,7 @@ Zotero.Schema = new function(){
function _updateSchema(schema){
return Q.all([Zotero.Schema.getDBVersion(schema), _getSchemaSQLVersion(schema)])
return Zotero.Promise.all([Zotero.Schema.getDBVersion(schema), _getSchemaSQLVersion(schema)])
.spread(function (dbVersion, schemaVersion) {
if (dbVersion == schemaVersion) {
return false;
@ -1488,7 +1490,7 @@ Zotero.Schema = new function(){
else if (dbVersion < schemaVersion) {
return _getSchemaSQL(schema)
.then(function (sql) {
return Zotero.DB.queryAsync(sql);
return Zotero.DB.executeSQLFile(sql);
})
.then(function () {
return _updateDBVersion(schema, schemaVersion);
@ -1524,7 +1526,7 @@ Zotero.Schema = new function(){
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_RETRY_INTERVAL']);
}
return Q(false);
return Zotero.Promise.resolve(false);
}
var currentTime = xmlhttp.responseXML.
@ -1546,11 +1548,11 @@ Zotero.Schema = new function(){
if (!manual) {
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_CHECK_INTERVAL']);
}
return Q(true);
return Zotero.Promise.resolve(true);
});
}
return Q.async(function () {
return Zotero.spawn(function* () {
try {
for (var i=0, len=translatorUpdates.length; i<len; i++){
yield _translatorXMLToFile(translatorUpdates[i]);
@ -1569,11 +1571,11 @@ Zotero.Schema = new function(){
if (!manual){
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_RETRY_INTERVAL']);
}
Q.return(false);
return false;
}
Q.return(true);
})()
return true;
})
.then(function (update) {
if (!update) return false;
@ -1753,17 +1755,12 @@ Zotero.Schema = new function(){
// TODO
//
// Replace customBaseFieldMappings to fix FK fields/customField -> customFields->customFieldID
// If libraryID set, make sure no relations still use a local user key, and then remove on-error code in sync.js
function _migrateUserDataSchema(fromVersion) {
return _getSchemaSQLVersion('userdata3')
return _getSchemaSQLVersion('userdata')
.then(function (toVersion) {
if (!fromVersion) {
fromVersion = 76;
}
if (fromVersion > toVersion) {
if (fromVersion >= toVersion) {
return false;
}
@ -1774,24 +1771,337 @@ Zotero.Schema = new function(){
//
// Each block performs the changes necessary to move from the
// previous revision to that one.
for (var i=fromVersion + 1; i<=toVersion; i++) {
if (i == 77) {
for (let i = fromVersion + 1; i <= toVersion; i++) {
if (i == 80) {
yield _updateDBVersion('compatibility', 1);
yield Zotero.DB.queryAsync("CREATE TABLE IF NOT EXISTS syncedSettings (\n setting TEXT NOT NULL,\n libraryID INT NOT NULL,\n value NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n PRIMARY KEY (setting, libraryID)\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO syncObjectTypes VALUES (7, 'setting')");
}
if (i == 78) {
yield Zotero.DB.queryAsync("CREATE INDEX IF NOT EXISTS creatorData_name ON creatorData(lastName, firstName)");
}
if (i == 79) {
yield Zotero.DB.queryAsync("DELETE FROM version WHERE schema='userdata2'");
yield Zotero.DB.queryAsync("DELETE FROM version WHERE schema IN ('userdata2', 'userdata3')");
yield Zotero.DB.queryAsync("INSERT INTO libraries VALUES (0, 'user')");
yield Zotero.DB.queryAsync("ALTER TABLE libraries ADD COLUMN version INT NOT NULL DEFAULT 0");
yield Zotero.DB.queryAsync("CREATE TABLE syncCache (\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n syncObjectTypeID INT NOT NULL,\n version INT NOT NULL,\n data TEXT,\n PRIMARY KEY (libraryID, key, syncObjectTypeID),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE,\n FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)\n)");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_annotations_itemID_itemAttachments_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_annotations_itemID_itemAttachments_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_annotations_itemID_itemAttachments_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_annotations_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_collections_parentCollectionID_collections_collectionID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collections_parentCollectionID_collections_collectionID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_collections_parentCollectionID_collections_collectionID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collections_collectionID_collections_parentCollectionID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_collectionItems_collectionID_collections_collectionID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collectionItems_collectionID_collections_collectionID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_collectionItems_collectionID_collections_collectionID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collections_collectionID_collectionItems_collectionID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_collectionItems_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collectionItems_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_collectionItems_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_collectionItems_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_creators_creatorDataID_creatorData_creatorDataID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_creators_creatorDataID_creatorData_creatorDataID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_creators_creatorDataID_creatorData_creatorDataID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_creatorData_creatorDataID_creators_creatorDataID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypes_customItemTypeID_customBaseFieldMappings_customItemTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customBaseFieldMappings_baseFieldID_fields_fieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customBaseFieldMappings_baseFieldID_fields_fieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customBaseFieldMappings_customFieldID_customFields_customFieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customBaseFieldMappings_customFieldID_customFields_customFieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_customBaseFieldMappings_customFieldID_customFields_customFieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customFields_customFieldID_customBaseFieldMappings_customFieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypes_customItemTypeID_customItemTypeFields_customItemTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customItemTypeFields_fieldID_fields_fieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypeFields_fieldID_fields_fieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypeFields_customFieldID_customFields_customFieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_customItemTypeFields_customFieldID_customFields_customFieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customFields_customFieldID_customItemTypeFields_customFieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_fulltextItems_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_fulltextItems_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_fulltextItems_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_fulltextItems_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_fulltextItemWords_wordID_fulltextWords_wordID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_fulltextItemWords_wordID_fulltextWords_wordID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_fulltextItemWords_wordID_fulltextWords_wordID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_fulltextWords_wordID_fulltextItemWords_wordID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_fulltextItemWords_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_fulltextItemWords_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_fulltextItemWords_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_fulltextItemWords_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_groups_libraryID_libraries_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_groups_libraryID_libraries_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_groups_libraryID_libraries_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_libraries_libraryID_groups_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_groupItems_createdByUserID_users_userID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_groupItems_createdByUserID_users_userID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_groupItems_createdByUserID_users_userID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_users_userID_groupItems_createdByUserID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_groupItems_lastModifiedByUserID_users_userID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_groupItems_lastModifiedByUserID_users_userID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_groupItems_lastModifiedByUserID_users_userID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_users_userID_groupItems_lastModifiedByUserID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_highlights_itemID_itemAttachments_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_highlights_itemID_itemAttachments_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_highlights_itemID_itemAttachments_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_highlights_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemAttachments_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemAttachments_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemAttachments_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemAttachments_sourceItemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemAttachments_sourceItemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemAttachments_sourceItemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemAttachments_sourceItemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemCreators_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemCreators_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemCreators_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemCreators_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemCreators_creatorID_creators_creatorID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemCreators_creatorID_creators_creatorID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemCreators_creatorID_creators_creatorID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_creators_creatorID_itemCreators_creatorID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemCreators_creatorTypeID_creatorTypes_creatorTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemCreators_creatorTypeID_creatorTypes_creatorTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemCreators_creatorTypeID_creatorTypes_creatorTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_creatorTypes_creatorTypeID_itemCreators_creatorTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemCreators_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemCreators_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemData_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemData_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemData_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemData_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemData_fieldID_fields_fieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemData_fieldID_fields_fieldID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemData_valueID_itemDataValues_valueID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemData_valueID_itemDataValues_valueID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemData_valueID_itemDataValues_valueID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemDataValues_valueID_itemData_valueID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemNotes_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemNotes_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemNotes_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemNotes_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemNotes_sourceItemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemNotes_sourceItemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemNotes_sourceItemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemNotes_sourceItemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_items_libraryID_libraries_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_libraryID_libraries_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_items_libraryID_libraries_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_libraries_libraryID_items_libraryID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemSeeAlso_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemSeeAlso_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemSeeAlso_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemSeeAlso_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemSeeAlso_linkedItemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemSeeAlso_linkedItemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemSeeAlso_linkedItemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemSeeAlso_linkedItemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemTags_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemTags_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemTags_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_items_itemID_itemTags_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemTags_tagID_tags_tagID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemTags_tagID_tags_tagID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemTags_tagID_tags_tagID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_tags_tagID_itemTags_tagID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_savedSearchConditions_savedSearchID_savedSearches_savedSearchID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_savedSearchConditions_savedSearchID_savedSearches_savedSearchID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_savedSearches_savedSearchID_savedSearchConditions_savedSearchID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_deletedItems_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_deletedItems_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_deletedItems_itemID_items_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_deletedItems_itemID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_syncObjectTypes_syncObjectTypeID_syncDeleteLog_syncObjectTypeID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_proxyHosts_proxyID_proxies_proxyID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_proxyHosts_proxyID_proxies_proxyID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_proxyHosts_proxyID_proxies_proxyID");
yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_proxies_proxyID_proxyHosts_proxyID");
yield Zotero.DB.queryAsync("ALTER TABLE collections RENAME TO collectionsOld");
yield Zotero.DB.queryAsync("CREATE TABLE collections (\n collectionID INTEGER PRIMARY KEY,\n collectionName TEXT NOT NULL,\n parentCollectionID INT DEFAULT NULL,\n clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n UNIQUE (libraryID, key),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE,\n FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO collections SELECT collectionID, collectionName, parentCollectionID, clientDateModified, IFNULL(libraryID, 0), key, 0, 0 FROM collectionsOld ORDER BY collectionID DESC");
yield Zotero.DB.queryAsync("CREATE INDEX collections_synced ON collections(synced)");
yield Zotero.DB.queryAsync("ALTER TABLE items RENAME TO itemsOld");
yield Zotero.DB.queryAsync("CREATE TABLE items (\n itemID INTEGER PRIMARY KEY,\n itemTypeID INT NOT NULL,\n dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n UNIQUE (libraryID, key),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO items SELECT itemID, itemTypeID, dateAdded, dateModified, clientDateModified, IFNULL(libraryID, 0), key, 0, 0 FROM itemsOld ORDER BY dateAdded DESC");
yield Zotero.DB.queryAsync("CREATE INDEX items_synced ON items(synced)");
yield Zotero.DB.queryAsync("ALTER TABLE creators RENAME TO creatorsOld");
yield Zotero.DB.queryAsync("CREATE TABLE creators (\n creatorID INTEGER PRIMARY KEY,\n firstName TEXT,\n lastName TEXT,\n fieldMode INT,\n UNIQUE (lastName, firstName, fieldMode)\n)");
yield Zotero.DB.queryAsync("INSERT INTO creators SELECT creatorDataID, firstName, lastName, fieldMode FROM creatorData");
yield Zotero.DB.queryAsync("ALTER TABLE itemCreators RENAME TO itemCreatorsOld");
yield Zotero.DB.queryAsync("CREATE TABLE itemCreators (\n itemID INT NOT NULL,\n creatorID INT NOT NULL,\n creatorTypeID INT NOT NULL DEFAULT 1,\n orderIndex INT NOT NULL DEFAULT 0,\n PRIMARY KEY (itemID, creatorID, creatorTypeID, orderIndex),\n UNIQUE (itemID, orderIndex),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (creatorID) REFERENCES creators(creatorID) ON DELETE CASCADE,\n FOREIGN KEY (creatorTypeID) REFERENCES creatorTypes(creatorTypeID)\n)");
yield Zotero.DB.queryAsync("CREATE INDEX itemCreators_creatorTypeID ON itemCreators(creatorTypeID)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemCreators SELECT itemID, C.creatorID, creatorTypeID, orderIndex FROM itemCreatorsOld ICO JOIN creatorsOld CO USING (creatorID) JOIN creators C ON (CO.creatorDataID=C.creatorID)");
yield Zotero.DB.queryAsync("ALTER TABLE savedSearches RENAME TO savedSearchesOld");
yield Zotero.DB.queryAsync("CREATE TABLE savedSearches (\n savedSearchID INTEGER PRIMARY KEY,\n savedSearchName TEXT NOT NULL,\n clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n UNIQUE (libraryID, key),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO savedSearches SELECT savedSearchID, savedSearchName, clientDateModified, IFNULL(libraryID, 0), key, 0, 0 FROM savedSearchesOld ORDER BY savedSearchID DESC");
yield Zotero.DB.queryAsync("CREATE INDEX savedSearches_synced ON savedSearches(synced)");
yield Zotero.DB.queryAsync("ALTER TABLE tags RENAME TO tagsOld");
yield Zotero.DB.queryAsync("CREATE TABLE tags (\n tagID INTEGER PRIMARY KEY,\n libraryID INT NOT NULL,\n name TEXT NOT NULL,\n UNIQUE (libraryID, name)\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO tags SELECT tagID, IFNULL(libraryID, 0), name FROM tagsOld");
yield Zotero.DB.queryAsync("ALTER TABLE itemTags RENAME TO itemTagsOld");
yield Zotero.DB.queryAsync("CREATE TABLE itemTags (\n itemID INT NOT NULL,\n tagID INT NOT NULL,\n type INT NOT NULL,\n PRIMARY KEY (itemID, tagID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (tagID) REFERENCES tags(tagID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemTags SELECT itemID, T.tagID, TOld.type FROM itemTagsOld ITO JOIN tagsOld TOld USING (tagID) JOIN tags T ON (IFNULL(TOld.libraryID, 0)=T.libraryID AND TOld.name=T.name COLLATE BINARY)");
yield Zotero.DB.queryAsync("DROP INDEX itemTags_tagID");
yield Zotero.DB.queryAsync("CREATE INDEX itemTags_tagID ON itemTags(tagID)");
yield Zotero.DB.queryAsync("ALTER TABLE syncedSettings RENAME TO syncedSettingsOld");
yield Zotero.DB.queryAsync("CREATE TABLE syncedSettings (\n setting TEXT NOT NULL,\n libraryID INT NOT NULL,\n value NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n PRIMARY KEY (setting, libraryID),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO syncedSettings SELECT * FROM syncedSettingsOld");
yield Zotero.DB.queryAsync("ALTER TABLE itemData RENAME TO itemDataOld");
yield Zotero.DB.queryAsync("CREATE TABLE itemData (\n itemID INT,\n fieldID INT,\n valueID,\n PRIMARY KEY (itemID, fieldID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (fieldID) REFERENCES fieldsCombined(fieldID),\n FOREIGN KEY (valueID) REFERENCES itemDataValues(valueID)\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemData SELECT * FROM itemDataOld");
yield Zotero.DB.queryAsync("DROP INDEX itemData_fieldID");
yield Zotero.DB.queryAsync("CREATE INDEX itemData_fieldID ON itemData(fieldID)");
yield Zotero.DB.queryAsync("ALTER TABLE itemNotes RENAME TO itemNotesOld");
yield Zotero.DB.queryAsync("CREATE TABLE itemNotes (\n itemID INTEGER PRIMARY KEY,\n parentItemID INT,\n note TEXT,\n title TEXT,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (parentItemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemNotes SELECT * FROM itemNotesOld");
yield Zotero.DB.queryAsync("CREATE INDEX itemNotes_parentItemID ON itemNotes(parentItemID)");
yield Zotero.DB.queryAsync("ALTER TABLE itemAttachments RENAME TO itemAttachmentsOld");
yield Zotero.DB.queryAsync("CREATE TABLE itemAttachments (\n itemID INTEGER PRIMARY KEY,\n parentItemID INT,\n linkMode INT,\n contentType TEXT,\n charsetID INT,\n path TEXT,\n originalPath TEXT,\n syncState INT DEFAULT 0,\n storageModTime INT,\n storageHash TEXT,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (parentItemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemAttachments SELECT * FROM itemAttachmentsOld");
yield Zotero.DB.queryAsync("CREATE INDEX itemAttachments_parentItemID ON itemAttachments(parentItemID)");
yield Zotero.DB.queryAsync("CREATE INDEX itemAttachments_contentType ON itemAttachments(contentType)");
yield Zotero.DB.queryAsync("DROP INDEX itemAttachments_syncState");
yield Zotero.DB.queryAsync("CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState)");
yield Zotero.DB.queryAsync("ALTER TABLE itemSeeAlso RENAME TO itemSeeAlsoOld");
yield Zotero.DB.queryAsync("CREATE TABLE itemSeeAlso (\n itemID INT NOT NULL,\n linkedItemID INT NOT NULL,\n PRIMARY KEY (itemID, linkedItemID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (linkedItemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemSeeAlso SELECT * FROM itemSeeAlsoOld");
yield Zotero.DB.queryAsync("DROP INDEX itemSeeAlso_linkedItemID");
yield Zotero.DB.queryAsync("CREATE INDEX itemSeeAlso_linkedItemID ON itemSeeAlso(linkedItemID)");
yield Zotero.DB.queryAsync("ALTER TABLE collectionItems RENAME TO collectionItemsOld");
yield Zotero.DB.queryAsync("CREATE TABLE collectionItems (\n collectionID INT NOT NULL,\n itemID INT NOT NULL,\n orderIndex INT NOT NULL DEFAULT 0,\n PRIMARY KEY (collectionID, itemID),\n FOREIGN KEY (collectionID) REFERENCES collections(collectionID) ON DELETE CASCADE,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO collectionItems SELECT * FROM collectionItemsOld");
yield Zotero.DB.queryAsync("DROP INDEX IF EXISTS itemID"); // incorrect old name
yield Zotero.DB.queryAsync("CREATE INDEX collectionItems_itemID ON collectionItems(itemID)");
yield Zotero.DB.queryAsync("ALTER TABLE savedSearchConditions RENAME TO savedSearchConditionsOld");
yield Zotero.DB.queryAsync("CREATE TABLE savedSearchConditions (\n savedSearchID INT NOT NULL,\n searchConditionID INT NOT NULL,\n condition TEXT NOT NULL,\n operator TEXT,\n value TEXT,\n required NONE,\n PRIMARY KEY (savedSearchID, searchConditionID),\n FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO savedSearchConditions SELECT * FROM savedSearchConditionsOld");
yield Zotero.DB.queryAsync("DROP TABLE savedSearchConditionsOld");
yield Zotero.DB.queryAsync("ALTER TABLE deletedItems RENAME TO deletedItemsOld");
yield Zotero.DB.queryAsync("CREATE TABLE deletedItems (\n itemID INTEGER PRIMARY KEY,\n dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO deletedItems SELECT * FROM deletedItemsOld");
yield Zotero.DB.queryAsync("DROP INDEX deletedItems_dateDeleted");
yield Zotero.DB.queryAsync("CREATE INDEX deletedItems_dateDeleted ON deletedItems(dateDeleted)");
yield Zotero.DB.queryAsync("UPDATE relations SET libraryID=0 WHERE libraryID=(SELECT value FROM settings WHERE setting='account' AND key='libraryID')");
yield Zotero.DB.queryAsync("ALTER TABLE relations RENAME TO relationsOld");
yield Zotero.DB.queryAsync("CREATE TABLE relations (\n libraryID INT NOT NULL,\n subject TEXT NOT NULL,\n predicate TEXT NOT NULL,\n object TEXT NOT NULL,\n clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (subject, predicate, object),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO relations SELECT * FROM relationsOld");
yield Zotero.DB.queryAsync("DROP INDEX relations_object");
yield Zotero.DB.queryAsync("CREATE INDEX relations_object ON relations(object)");
yield Zotero.DB.queryAsync("ALTER TABLE groups RENAME TO groupsOld");
yield Zotero.DB.queryAsync("CREATE TABLE groups (\n groupID INTEGER PRIMARY KEY,\n libraryID INT NOT NULL UNIQUE,\n name TEXT NOT NULL,\n description TEXT NOT NULL,\n editable INT NOT NULL,\n filesEditable INT NOT NULL,\n etag TEXT NOT NULL DEFAULT '',\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO groups SELECT groupID, libraryID, name, description, editable, filesEditable, '' FROM groupsOld");
yield Zotero.DB.queryAsync("ALTER TABLE groupItems RENAME TO groupItemsOld");
yield Zotero.DB.queryAsync("CREATE TABLE groupItems (\n itemID INTEGER PRIMARY KEY,\n createdByUserID INT,\n lastModifiedByUserID INT,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (createdByUserID) REFERENCES users(userID) ON DELETE SET NULL,\n FOREIGN KEY (lastModifiedByUserID) REFERENCES users(userID) ON DELETE SET NULL\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO groupItems SELECT * FROM groupItemsOld");
let cols = yield Zotero.DB.getColumnsAsync('fulltextItems');
if (cols.indexOf("synced") == -1) {
Zotero.DB.queryAsync("ALTER TABLE fulltextItems ADD COLUMN synced INT DEFAULT 0");
Zotero.DB.queryAsync("REPLACE INTO settings (setting, key, value) VALUES ('fulltext', 'downloadAll', 1)");
}
yield Zotero.DB.queryAsync("ALTER TABLE fulltextItems RENAME TO fulltextItemsOld");
yield Zotero.DB.queryAsync("CREATE TABLE fulltextItems (\n itemID INTEGER PRIMARY KEY,\n version INT,\n indexedPages INT,\n totalPages INT,\n indexedChars INT,\n totalChars INT,\n synced INT DEFAULT 0,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO fulltextItems SELECT * FROM fulltextItemsOld");
yield Zotero.DB.queryAsync("ALTER TABLE fulltextItemWords RENAME TO fulltextItemWordsOld");
yield Zotero.DB.queryAsync("CREATE TABLE fulltextItemWords (\n wordID INT,\n itemID INT,\n PRIMARY KEY (wordID, itemID),\n FOREIGN KEY (wordID) REFERENCES fulltextWords(wordID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO fulltextItemWords SELECT * FROM fulltextItemWordsOld");
yield Zotero.DB.queryAsync("DROP INDEX fulltextItems_version");
yield Zotero.DB.queryAsync("DROP INDEX fulltextItemWords_itemID");
yield Zotero.DB.queryAsync("CREATE INDEX fulltextItems_version ON fulltextItems(version)");
yield Zotero.DB.queryAsync("CREATE INDEX fulltextItemWords_itemID ON fulltextItemWords(itemID)");
yield Zotero.DB.queryAsync("UPDATE syncDeleteLog SET libraryID=0 WHERE libraryID=(SELECT value FROM settings WHERE setting='account' AND key='libraryID')");
yield Zotero.DB.queryAsync("ALTER TABLE syncDeleteLog RENAME TO syncDeleteLogOld");
yield Zotero.DB.queryAsync("CREATE TABLE syncDeleteLog (\n syncObjectTypeID INT NOT NULL,\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n timestamp INT NOT NULL,\n UNIQUE (syncObjectTypeID, libraryID, key),\n FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO syncDeleteLog SELECT * FROM syncDeleteLogOld");
yield Zotero.DB.queryAsync("DROP INDEX syncDeleteLog_timestamp");
yield Zotero.DB.queryAsync("CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp)");
yield Zotero.DB.queryAsync("ALTER TABLE storageDeleteLog RENAME TO storageDeleteLogOld");
yield Zotero.DB.queryAsync("CREATE TABLE storageDeleteLog (\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n timestamp INT NOT NULL,\n PRIMARY KEY (libraryID, key),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO storageDeleteLog SELECT * FROM storageDeleteLogOld");
yield Zotero.DB.queryAsync("DROP INDEX storageDeleteLog_timestamp");
yield Zotero.DB.queryAsync("CREATE INDEX storageDeleteLog_timestamp ON storageDeleteLog(timestamp)");
yield Zotero.DB.queryAsync("ALTER TABLE annotations RENAME TO annotationsOld");
yield Zotero.DB.queryAsync("CREATE TABLE annotations (\n annotationID INTEGER PRIMARY KEY,\n itemID INT NOT NULL,\n parent TEXT,\n textNode INT,\n offset INT,\n x INT,\n y INT,\n cols INT,\n rows INT,\n text TEXT,\n collapsed BOOL,\n dateModified DATE,\n FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO annotations SELECT * FROM annotationsOld");
yield Zotero.DB.queryAsync("DROP INDEX annotations_itemID");
yield Zotero.DB.queryAsync("CREATE INDEX annotations_itemID ON annotations(itemID)");
yield Zotero.DB.queryAsync("ALTER TABLE highlights RENAME TO highlightsOld");
yield Zotero.DB.queryAsync("CREATE TABLE highlights (\n highlightID INTEGER PRIMARY KEY,\n itemID INT NOT NULL,\n startParent TEXT,\n startTextNode INT,\n startOffset INT,\n endParent TEXT,\n endTextNode INT,\n endOffset INT,\n dateModified DATE,\n FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO highlights SELECT * FROM highlightsOld");
yield Zotero.DB.queryAsync("DROP INDEX highlights_itemID");
yield Zotero.DB.queryAsync("CREATE INDEX highlights_itemID ON highlights(itemID)");
yield Zotero.DB.queryAsync("ALTER TABLE customBaseFieldMappings RENAME TO customBaseFieldMappingsOld");
yield Zotero.DB.queryAsync("CREATE TABLE customBaseFieldMappings (\n customItemTypeID INT,\n baseFieldID INT,\n customFieldID INT,\n PRIMARY KEY (customItemTypeID, baseFieldID, customFieldID),\n FOREIGN KEY (customItemTypeID) REFERENCES customItemTypes(customItemTypeID),\n FOREIGN KEY (baseFieldID) REFERENCES fields(fieldID),\n FOREIGN KEY (customFieldID) REFERENCES customFields(customFieldID)\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO customBaseFieldMappings SELECT * FROM customBaseFieldMappingsOld");
yield Zotero.DB.queryAsync("DROP INDEX customBaseFieldMappings_baseFieldID");
yield Zotero.DB.queryAsync("DROP INDEX customBaseFieldMappings_customFieldID");
yield Zotero.DB.queryAsync("CREATE INDEX customBaseFieldMappings_baseFieldID ON customBaseFieldMappings(baseFieldID)");
yield Zotero.DB.queryAsync("CREATE INDEX customBaseFieldMappings_customFieldID ON customBaseFieldMappings(customFieldID)");
yield Zotero.DB.queryAsync("DROP TABLE annotationsOld");
yield Zotero.DB.queryAsync("DROP TABLE collectionItemsOld");
yield Zotero.DB.queryAsync("DROP TABLE customBaseFieldMappingsOld");
yield Zotero.DB.queryAsync("DROP TABLE deletedItemsOld");
yield Zotero.DB.queryAsync("DROP TABLE fulltextItemWordsOld");
yield Zotero.DB.queryAsync("DROP TABLE fulltextItemsOld");
yield Zotero.DB.queryAsync("DROP TABLE groupItemsOld");
yield Zotero.DB.queryAsync("DROP TABLE groupsOld");
yield Zotero.DB.queryAsync("DROP TABLE highlightsOld");
yield Zotero.DB.queryAsync("DROP TABLE itemAttachmentsOld");
yield Zotero.DB.queryAsync("DROP TABLE itemCreatorsOld");
yield Zotero.DB.queryAsync("DROP TABLE itemDataOld");
yield Zotero.DB.queryAsync("DROP TABLE itemNotesOld");
yield Zotero.DB.queryAsync("DROP TABLE itemSeeAlsoOld");
yield Zotero.DB.queryAsync("DROP TABLE itemTagsOld");
yield Zotero.DB.queryAsync("DROP TABLE relationsOld");
yield Zotero.DB.queryAsync("DROP TABLE savedSearchesOld");
yield Zotero.DB.queryAsync("DROP TABLE storageDeleteLogOld");
yield Zotero.DB.queryAsync("DROP TABLE syncDeleteLogOld");
yield Zotero.DB.queryAsync("DROP TABLE syncedSettingsOld");
yield Zotero.DB.queryAsync("DROP TABLE collectionsOld");
yield Zotero.DB.queryAsync("DROP TABLE creatorsOld");
yield Zotero.DB.queryAsync("DROP TABLE creatorData");
yield Zotero.DB.queryAsync("DROP TABLE itemsOld");
yield Zotero.DB.queryAsync("DROP TABLE tagsOld");
}
}
yield _updateDBVersion('userdata3', toVersion);
yield _updateDBVersion('userdata', toVersion);
})
.then(function () true);
.return(true);
})
}
}

File diff suppressed because it is too large Load diff

View file

@ -97,7 +97,7 @@ Zotero.Server.Connector.GetTranslators.prototype = {
Zotero.Translators.getAll().then(function(translators) {
var responseData = me._serializeTranslators(translators);
sendResponseCallback(200, "application/json", JSON.stringify(responseData));
}).fail(function(e) {
}).catch(function(e) {
sendResponseCallback(500);
throw e;
}).done();
@ -440,7 +440,11 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
// save snapshot
if(filesEditable) {
Zotero.Attachments.importFromDocument(doc, itemID);
// TODO: async
Zotero.Attachments.importFromDocument({
document: doc,
parentItemID: itemID
});
}
sendResponseCallback(201);

View file

@ -103,7 +103,7 @@ Zotero.Sync.Storage = new function () {
var librarySyncTimes = {};
// Get personal library file sync mode
return Q.fcall(function () {
return Zotero.Promise.try(function () {
// TODO: Make sure modes are active
if (options.libraries && options.libraries.indexOf(0) == -1) {
@ -149,11 +149,11 @@ Zotero.Sync.Storage = new function () {
else {
var promise = mode.cacheCredentials();
}
promises.push(Q.allSettled([mode, promise]));
promises.push(Zotero.Promise.allSettled([mode, promise]));
}
}
return Q.all(promises)
return Zotero.Promise.all(promises)
// Get library last-sync times
.then(function (cacheCredentialsPromises) {
var promises = [];
@ -165,8 +165,8 @@ Zotero.Sync.Storage = new function () {
let mode = results[0].value;
if (mode == Zotero.Sync.Storage.WebDAV) {
if (results[1].state == "rejected") {
promises.push(Q.allSettled(
[0, Q.reject(results[1].reason)]
promises.push(Zotero.Promise.allSettled(
[0, Zotero.Promise.reject(results[1].reason)]
));
// Skip further syncing of user library
delete libraryModes[0];
@ -179,16 +179,16 @@ Zotero.Sync.Storage = new function () {
// Get the last sync time for each library
if (self.downloadOnSync(libraryID)) {
promises.push(Q.allSettled(
promises.push(Zotero.Promise.allSettled(
[libraryID, libraryModes[libraryID].getLastSyncTime(libraryID)]
));
}
// If download-as-needed, we don't need the last sync time
else {
promises.push(Q.allSettled([libraryID, null]));
promises.push(Zotero.Promise.allSettled([libraryID, null]));
}
}
return Q.all(promises);
return Zotero.Promise.all(promises);
});
})
.then(function (promises) {
@ -245,7 +245,7 @@ Zotero.Sync.Storage = new function () {
}
promises.push(promise);
}
return Q.all(promises)
return Zotero.Promise.all(promises)
.then(function () {
// Queue files to download and upload from each library
for (let libraryID in librarySyncTimes) {
@ -308,13 +308,13 @@ Zotero.Sync.Storage = new function () {
// Start queues for each library
for (let libraryID in librarySyncTimes) {
libraryID = parseInt(libraryID);
libraryQueues.push(Q.allSettled(
libraryQueues.push(Zotero.Promise.allSettled(
[libraryID, Zotero.Sync.Storage.QueueManager.start(libraryID)]
));
}
// The promise is done when all libraries are done
return Q.all(libraryQueues);
return Zotero.Promise.all(libraryQueues);
});
})
.then(function (promises) {
@ -329,14 +329,14 @@ Zotero.Sync.Storage = new function () {
if (results[1].state == "fulfilled") {
libraryQueues.forEach(function (queuePromise) {
if (queuePromise.isFulfilled()) {
let result = queuePromise.inspect().value;
if (queueZotero.Promise.isFulfilled()) {
let result = queueZotero.Promise.value();
Zotero.debug("File " + result.type + " sync finished "
+ "for library " + libraryID);
if (result.localChanges) {
changedLibraries.push(libraryID);
}
finalPromises.push(Q.allSettled([
finalPromises.push(Zotero.Promise.allSettled([
libraryID,
libraryModes[libraryID].setLastSyncTime(
libraryID,
@ -345,11 +345,11 @@ Zotero.Sync.Storage = new function () {
]));
}
else {
let e = queuePromise.inspect().reason;
let e = queueZotero.Promise.reason();
Zotero.debug("File " + e.type + " sync failed "
+ "for library " + libraryID);
finalPromises.push(Q.allSettled(
[libraryID, Q.reject(e)]
finalPromises.push(Zotero.Promise.allSettled(
[libraryID, Zotero.Promise.reject(e)]
));
}
});
@ -382,7 +382,7 @@ Zotero.Sync.Storage = new function () {
Zotero.debug("No local changes made during file sync");
}
return Q.all(finalPromises)
return Zotero.Promise.all(finalPromises)
.then(function (promises) {
var results = {
changesMade: !!changedLibraries.length,
@ -695,7 +695,7 @@ Zotero.Sync.Storage = new function () {
* FALSE otherwise
*/
this.checkForUpdatedFiles = function (libraryID, itemIDs, itemModTimes) {
return Q.fcall(function () {
return Zotero.Promise.try(function () {
libraryID = parseInt(libraryID);
if (isNaN(libraryID)) {
libraryID = false;
@ -813,7 +813,7 @@ Zotero.Sync.Storage = new function () {
var updatedStates = {};
let checkItems = function () {
if (!items.length) return Q();
if (!items.length) return Zotero.Promise.resolve();
//Zotero.debug("Memory usage: " + memmgr.resident);
@ -829,7 +829,7 @@ Zotero.Sync.Storage = new function () {
return checkItems();
}
let file = null;
return Q(OS.File.open(nsIFile.path))
return Zotero.Promise.resolve(OS.File.open(nsIFile.path))
.then(function (promisedFile) {
file = promisedFile;
return file.stat()
@ -905,7 +905,7 @@ Zotero.Sync.Storage = new function () {
// We have to close the file before modifying it from the main
// thread (at least on Windows, where assigning lastModifiedTime
// throws an NS_ERROR_FILE_IS_LOCKED otherwise)
return Q(file.close())
return Zotero.Promise.resolve(file.close())
.then(function () {
Zotero.debug("Mod time didn't match (" + fmtime + "!=" + mtime + ") "
+ "but hash did for " + nsIFile.leafName + " for item " + lk
@ -1008,7 +1008,7 @@ Zotero.Sync.Storage = new function () {
}
// TODO: start sync icon in cacheCredentials
return Q.fcall(function () {
return Zotero.Promise.try(function () {
return mode.cacheCredentials();
})
.then(function () {
@ -1045,9 +1045,10 @@ Zotero.Sync.Storage = new function () {
*
* This is called from Zotero.Sync.Server.StreamListener.onStopRequest()
*
* @return {Object} data Properties 'request', 'item', 'compressed', 'syncModTime', 'syncHash'
* @return {Promise<Object>} data - Promise for object with properties 'request', 'item',
* 'compressed', 'syncModTime', 'syncHash'
*/
this.processDownload = function (data) {
this.processDownload = Zotero.Promise.coroutine(function* (data) {
var funcName = "Zotero.Sync.Storage.processDownload()";
if (!data) {
@ -1073,10 +1074,10 @@ Zotero.Sync.Storage = new function () {
// TODO: Test file hash
if (data.compressed) {
var newFile = _processZipDownload(item);
var newFile = yield _processZipDownload(item);
}
else {
var newFile = _processDownload(item);
var newFile = yield _processDownload(item);
}
// If |newFile| is set, the file was renamed, so set item filename to that
@ -1153,7 +1154,7 @@ Zotero.Sync.Storage = new function () {
Zotero.DB.commitTransaction();
return true;
}
});
this.checkServerPromise = function (mode) {
@ -1220,9 +1221,9 @@ Zotero.Sync.Storage = new function () {
var item = Zotero.Items.getByLibraryAndKey(libraryID, key);
Zotero.Notifier.trigger('redraw', 'item', item.id, { column: "hasAttachment" });
var parent = item.getSource();
var parent = item.parentItemKey;
if (parent) {
var parentItem = Zotero.Items.get(parent);
var parentItem = Zotero.Items.getByLibraryAndKey(libraryID, parent);
var parentLibraryKey = libraryID + "/" + parentItem.key;
if (percentage !== false) {
_itemDownloadPercentages[parentLibraryKey] = percentage;
@ -1320,7 +1321,7 @@ Zotero.Sync.Storage = new function () {
}
function _processDownload(item) {
var _processDownload = Zotero.Promise.coroutine(function* (item) {
var funcName = "Zotero.Sync.Storage._processDownload()";
var tempFile = Zotero.getTempDirectory();
@ -1331,9 +1332,9 @@ Zotero.Sync.Storage = new function () {
throw ("Downloaded file not found in " + funcName);
}
var parentDir = Zotero.Attachments.getStorageDirectory(item.id);
var parentDir = Zotero.Attachments.getStorageDirectory(item);
if (!parentDir.exists()) {
Zotero.Attachments.createDirectoryForItem(item.id);
yield Zotero.Attachments.createDirectoryForItem(item);
}
_deleteExistingAttachmentFiles(item);
@ -1433,10 +1434,10 @@ Zotero.Sync.Storage = new function () {
}
return returnFile;
}
});
function _processZipDownload(item) {
var _processZipDownload = Zotero.Promise.coroutine(function* (item) {
var funcName = "Zotero.Sync.Storage._processDownloadedZip()";
var zipFile = Zotero.getTempDirectory();
@ -1470,9 +1471,9 @@ Zotero.Sync.Storage = new function () {
return false;
}
var parentDir = Zotero.Attachments.getStorageDirectory(item.id);
var parentDir = Zotero.Attachments.getStorageDirectory(item);
if (!parentDir.exists()) {
Zotero.Attachments.createDirectoryForItem(item.id);
yield Zotero.Attachments.createDirectoryForItem(item);
}
try {
@ -1717,13 +1718,13 @@ Zotero.Sync.Storage = new function () {
zipFile.remove(false);
return returnFile;
}
});
function _deleteExistingAttachmentFiles(item) {
var funcName = "Zotero.Sync.Storage._deleteExistingAttachmentFiles()";
var parentDir = Zotero.Attachments.getStorageDirectory(item.id);
var parentDir = Zotero.Attachments.getStorageDirectory(item);
// Delete existing files
var otherFiles = parentDir.directoryEntries;
@ -1799,7 +1800,7 @@ Zotero.Sync.Storage = new function () {
));
}
var dir = Zotero.Attachments.getStorageDirectoryByKey(item.key);
var dir = Zotero.Attachments.getStorageDirectory(item);
var tmpFile = Zotero.getTempDirectory();
tmpFile.append(item.key + '.zip');

View file

@ -225,7 +225,7 @@ Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority
Zotero.Sync.Storage.Queue.prototype.start = function () {
if (!this._deferred || this._deferred.promise.isFulfilled()) {
Zotero.debug("Creating deferred for queue " + this.name);
this._deferred = Q.defer();
this._deferred = Zotero.Promise.defer();
}
// The queue manager needs to know what queues were running in the
// current session
@ -284,7 +284,7 @@ Zotero.Sync.Storage.Queue.prototype.advance = function () {
let requestName = name;
Q.fcall(function () {
Zotero.Promise.try(function () {
var promise = request.start();
self.advance();
return promise;
@ -319,7 +319,7 @@ Zotero.Sync.Storage.Queue.prototype.advance = function () {
let requestName = request.name;
// This isn't in an fcall() because the request needs to get marked
// This isn't in a Zotero.Promise.try() because the request needs to get marked
// as running immediately so that it doesn't get run again by a
// subsequent advance() call.
try {
@ -330,8 +330,7 @@ Zotero.Sync.Storage.Queue.prototype.advance = function () {
self.error(e);
}
Q.when(promise)
.then(function (result) {
promise.then(function (result) {
if (result.localChanges) {
self._localChanges = true;
}

View file

@ -53,7 +53,7 @@ Zotero.Sync.Storage.QueueManager = new function () {
Zotero.debug("No files to sync" + suffix);
}
return Q.allSettled(promises)
return Zotero.Promise.allSettled(promises)
.then(function (results) {
Zotero.debug("All storage queues are finished" + suffix);

View file

@ -41,7 +41,7 @@ Zotero.Sync.Storage.Request = function (name, callbacks) {
this.progress = 0;
this.progressMax = 0;
this._deferred = Q.defer();
this._deferred = Zotero.Promise.defer();
this._running = false;
this._stopping = false;
this._percentage = 0;
@ -199,7 +199,7 @@ Zotero.Sync.Storage.Request.prototype.start = function () {
//
// The main sync logic is triggered here.
Q.all([f(this) for each(f in this._onStart)])
Zotero.Promise.all([f(this) for each(f in this._onStart)])
.then(function (results) {
return {
localChanges: results.some(function (val) val && val.localChanges == true),

View file

@ -265,7 +265,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
channel.setRequestHeader('Keep-Alive', '', false);
channel.setRequestHeader('Connection', '', false);
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
@ -276,7 +276,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
data.request.setChannel(false);
deferred.resolve(
Q.fcall(function () {
Zotero.Promise.try(function () {
return onUploadComplete(httpRequest, status, response, data);
})
);
@ -479,13 +479,13 @@ Zotero.Sync.Storage.WebDAV = (function () {
};
if (files.length == 0) {
return Q(results);
return Zotero.Promise.resolve(results);
}
let deleteURI = _rootURI.clone();
// This should never happen, but let's be safe
if (!deleteURI.spec.match(/\/$/)) {
return Q.reject("Root URI does not end in slash in "
return Zotero.Promise.reject("Root URI does not end in slash in "
+ "Zotero.Sync.Storage.WebDAV.deleteStorageFiles()");
}
@ -559,12 +559,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
Components.utils.import("resource://zotero/concurrent-caller.js");
var caller = new ConcurrentCaller(4);
caller.stopOnError = true;
caller.setLogger(function (msg) {
Zotero.debug("[ConcurrentCaller] " + msg);
});
caller.setErrorLogger(function (msg) {
Components.utils.reportError(msg);
});
caller.setLogger(function (msg) Zotero.debug(msg));
caller.onError(function (e) Components.utils.reportError(e));
return caller.fcall(funcs)
.then(function () {
return results;
@ -883,7 +879,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
destFile.remove(false);
}
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
@ -977,7 +973,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
obj._uploadFile = function (request) {
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
var created = Zotero.Sync.Storage.createUploadFile(
request,
function (data) {
@ -986,14 +982,14 @@ Zotero.Sync.Storage.WebDAV = (function () {
return;
}
deferred.resolve(
Q.fcall(function () {
Zotero.Promise.try(function () {
return processUploadFile(data);
})
);
}
);
if (!created) {
return Q(false);
return Zotero.Promise.resolve(false);
}
return deferred.promise;
};
@ -1005,7 +1001,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
// Cache the credentials at the root URI
var self = this;
return Q.fcall(function () {
return Zotero.Promise.try(function () {
return self._cacheCredentials();
})
.then(function () {
@ -1066,7 +1062,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
+ "for GET request");
}
return Q.reject(e);
return Zotero.Promise.reject(e);
}
// TODO: handle browser offline exception
else {
@ -1128,7 +1124,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
Zotero.debug("Credentials are cached");
_cachedCredentials = true;
})
.fail(function (e) {
.catch(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
var msg = "HTTP " + e.status + " error from WebDAV server "
+ "for OPTIONS request";
@ -1142,7 +1138,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
obj._checkServer = function () {
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
try {
// Clear URIs
@ -1564,7 +1560,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
* @param {Function} callback Passed number of files deleted
*/
obj._purgeDeletedStorageFiles = function () {
return Q.fcall(function () {
return Zotero.Promise.try(function () {
if (!this.includeUserFiles) {
return false;
}
@ -1614,7 +1610,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
* Delete orphaned storage files older than a day before last sync time
*/
obj._purgeOrphanedStorageFiles = function () {
return Q.fcall(function () {
return Zotero.Promise.try(function () {
const daysBeforeSyncTime = 1;
if (!this.includeUserFiles) {
@ -1639,10 +1635,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
var lastSyncDate = new Date(Zotero.Sync.Server.lastLocalSyncTime * 1000);
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
Zotero.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (xmlhttp) {
Q.fcall(function () {
Zotero.Promise.try(function () {
Zotero.debug(xmlhttp.responseText);
var funcName = "Zotero.Sync.Storage.purgeOrphanedStorageFiles()";

View file

@ -278,7 +278,7 @@ Zotero.Sync.Storage.ZFS = (function () {
}
return uploadCallback(item, url, uploadKey, params);
})
.fail(function (e) {
.catch(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
if (e.status == 413) {
var retry = e.xmlhttp.getResponseHeader('Retry-After');
@ -458,7 +458,7 @@ Zotero.Sync.Storage.ZFS = (function () {
request.setChannel(channel);
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
@ -571,7 +571,7 @@ Zotero.Sync.Storage.ZFS = (function () {
remoteChanges: true
};
})
.fail(function (e) {
.catch(function (e) {
var msg = "Unexpected file registration status " + e.status
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
Zotero.debug(msg, 1);
@ -835,7 +835,7 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.File.checkFileAccessError(e, destFile, 'create');
}
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
@ -960,7 +960,7 @@ Zotero.Sync.Storage.ZFS = (function () {
obj._uploadFile = function (request) {
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
if (Zotero.Attachments.getNumFiles(item) > 1) {
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
var created = Zotero.Sync.Storage.createUploadFile(
request,
function (data) {
@ -972,7 +972,7 @@ Zotero.Sync.Storage.ZFS = (function () {
}
);
if (!created) {
return Q(false);
return Zotero.Promise.resolve(false);
}
return deferred.promise;
}
@ -989,7 +989,7 @@ Zotero.Sync.Storage.ZFS = (function () {
var lastSyncURI = this._getLastSyncURI(libraryID);
var self = this;
return Q.fcall(function () {
return Zotero.Promise.try(function () {
// Cache the credentials at the root
return self._cacheCredentials();
})
@ -1010,14 +1010,14 @@ Zotero.Sync.Storage.ZFS = (function () {
+ libraryID + " was " + date);
return ts;
})
.fail(function (e) {
.catch(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
if (e.status == 401 || e.status == 403) {
Zotero.debug("Clearing ZFS authentication credentials", 2);
_cachedCredentials = false;
}
return Q.reject(e);
return Zotero.Promise.reject(e);
}
// TODO: handle browser offline exception
else {
@ -1054,7 +1054,7 @@ Zotero.Sync.Storage.ZFS = (function () {
sql, ['storage_zfs_' + libraryID, { int: ts }]
);
})
.fail(function (e) {
.catch(function (e) {
var msg = "Unexpected status code " + e.xmlhttp.status
+ " setting last file sync time";
Zotero.debug(msg, 1);
@ -1089,7 +1089,7 @@ Zotero.Sync.Storage.ZFS = (function () {
obj._cacheCredentials = function () {
if (_cachedCredentials) {
Zotero.debug("ZFS credentials are already cached");
return Q();
return Zotero.Promise.resolve();
}
var uri = this.rootURI;
@ -1126,7 +1126,7 @@ Zotero.Sync.Storage.ZFS = (function () {
* Remove all synced files from the server
*/
obj._purgeDeletedStorageFiles = function () {
return Q.fcall(function () {
return Zotero.Promise.try(function () {
// Cache the credentials at the root
return this._cacheCredentials();
}.bind(this))

View file

@ -34,7 +34,7 @@ Zotero.Styles = new function() {
var _renamedStyles = null;
Components.utils.import("resource://zotero/q.js");
//Components.utils.import("resource://zotero/bluebird.js");
Components.utils.import("resource://gre/modules/Services.jsm");
this.xsltProcessor = null;
@ -179,7 +179,7 @@ Zotero.Styles = new function() {
* with the validation error if validation fails, or resolved if it is not.
*/
this.validate = function(style) {
var deferred = Q.defer(),
var deferred = Zotero.Promise.defer(),
worker = new Worker("resource://zotero/csl-validator.js");
worker.onmessage = function(event) {
if(event.data) {
@ -212,7 +212,7 @@ Zotero.Styles = new function() {
styleInstalled = _install(style, origin);
}
styleInstalled.fail(function(error) {
styleInstalled.catch(function(error) {
// Unless user cancelled, show an alert with the error
if(typeof error === "object" && error instanceof Zotero.Exception.UserCancelled) return;
if(typeof error === "object" && error instanceof Zotero.Exception.Alert) {
@ -238,7 +238,7 @@ Zotero.Styles = new function() {
if(!_initialized || !_cacheTranslatorData) Zotero.Styles.init();
var existingFile, destFile, source, styleID
return Q.fcall(function() {
return Zotero.Promise.try(function() {
// First, parse style and make sure it's valid XML
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser),
@ -334,7 +334,7 @@ Zotero.Styles = new function() {
}
}
return Zotero.Styles.validate(style).fail(function(validationErrors) {
return Zotero.Styles.validate(style).catch(function(validationErrors) {
Zotero.logError("Style from "+origin+" failed to validate:\n\n"+validationErrors);
// If validation fails on the parent of a dependent style, ignore it (for now)
@ -362,7 +362,7 @@ Zotero.Styles = new function() {
if(source.substr(0, 7) === "http://" || source.substr(0, 8) === "https://") {
return Zotero.HTTP.promise("GET", source).then(function(xmlhttp) {
return _install(xmlhttp.responseText, origin, true);
}).fail(function(error) {
}).catch(function(error) {
if(typeof error === "object" && error instanceof Zotero.Exception.Alert) {
throw new Zotero.Exception.Alert("styles.installSourceError", [origin, source],
"styles.install.title", error);

View file

@ -31,26 +31,20 @@ Zotero.SyncedSettings = (function () {
// Public methods
//
var module = {
get: function (libraryID, setting) {
return Q.fcall(function () {
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
return JSON.parse(Zotero.DB.valueQuery(sql, [setting, libraryID]));
});
},
get: Zotero.Promise.coroutine(function* (libraryID, setting) {
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
var json = yield Zotero.DB.valueQueryAsync(sql, [setting, libraryID]);
if (!json) {
return false;
}
return JSON.parse(json);
}),
set: function (libraryID, setting, value, version, synced) {
var self = this;
return Q.fcall(function () {
return self.setSynchronous(libraryID, setting, value, version, synced);
});
},
setSynchronous: function (libraryID, setting, value, version, synced) {
set: Zotero.Promise.coroutine(function* (libraryID, setting, value, version, synced) {
// TODO: get rid of this once we have proper affected rows handling
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
var currentValue = Zotero.DB.valueQuery(sql, [setting, libraryID]);
var currentValue = yield Zotero.DB.valueQueryAsync(sql, [setting, libraryID]);
// Make sure we can tell the difference between a
// missing setting (FALSE as returned by valueQuery())
@ -79,7 +73,7 @@ Zotero.SyncedSettings = (function () {
// Clear
if (typeof value == 'undefined') {
var sql = "DELETE FROM syncedSettings WHERE setting=? AND libraryID=?";
Zotero.DB.query(sql, [setting, libraryID]);
yield Zotero.DB.queryAsync(sql, [setting, libraryID]);
Zotero.Notifier.trigger('delete', 'setting', [id], extraData);
return true;
@ -99,16 +93,16 @@ Zotero.SyncedSettings = (function () {
if (hasCurrentValue) {
var sql = "UPDATE syncedSettings SET value=?, synced=? WHERE setting=? AND libraryID=?";
Zotero.DB.query(sql, [JSON.stringify(value), synced, setting, libraryID]);
yield Zotero.DB.queryAsync(sql, [JSON.stringify(value), synced, setting, libraryID]);
}
else {
var sql = "INSERT INTO syncedSettings "
+ "(setting, libraryID, value, synced) VALUES (?, ?, ?, ?)";
Zotero.DB.query(sql, [setting, libraryID, JSON.stringify(value), synced]);
yield Zotero.DB.queryAsync(sql, [setting, libraryID, JSON.stringify(value), synced]);
}
Zotero.Notifier.trigger(event, 'setting', [id], extraData);
return true;
}
})
};
return module;

View file

@ -313,7 +313,7 @@ Zotero.Translate.Sandbox = {
}
var translator = translation.translator[0];
(typeof translator === "object" ? Q(translator) : Zotero.Translators.get(translator)).
(typeof translator === "object" ? Zotero.Promise.resolve(translator) : Zotero.Translators.get(translator)).
then(function(translator) {
return translation._loadTranslator(translator);
}).then(function() {
@ -354,7 +354,7 @@ Zotero.Translate.Sandbox = {
callback(sandbox);
translate.decrementAsyncProcesses("safeTranslator#getTranslatorObject()");
}).fail(function(e) {
}).catch(function(e) {
translate.complete(false, e);
return;
});
@ -977,7 +977,7 @@ Zotero.Translate.Base.prototype = {
if(checkSetTranslator) {
// setTranslator must be called beforehand if checkSetTranslator is set
if( !this.translator || !this.translator[0] ) {
return Q.reject(new Error("getTranslators: translator must be set via setTranslator before calling" +
return Zotero.Promise.reject(new Error("getTranslators: translator must be set via setTranslator before calling" +
" getTranslators with the checkSetTranslator flag"));
}
var promises = new Array();
@ -992,8 +992,8 @@ Zotero.Translate.Base.prototype = {
/**TODO: check that the translator is of appropriate type?*/
if(t) promises.push(t);
}
if(!promises.length) return Q.reject(new Error("getTranslators: no valid translators were set"));
potentialTranslators = Q.all(promises);
if(!promises.length) return Zotero.Promise.reject(new Error("getTranslators: no valid translators were set"));
potentialTranslators = Zotero.Promise.all(promises);
} else {
potentialTranslators = this._getTranslatorsGetPotentialTranslators();
}
@ -1022,7 +1022,7 @@ Zotero.Translate.Base.prototype = {
// Attach handler for translators, so that we can return a
// promise that provides them.
// TODO make me._detect() return a promise
var deferred = Q.defer(),
var deferred = Zotero.Promise.defer(),
translatorsHandler = function(obj, translators) {
me.removeHandler("translators", translatorsHandler);
deferred.resolve(translators);
@ -1054,7 +1054,7 @@ Zotero.Translate.Base.prototype = {
}
return deferred.promise;
}).fail(function(e) {
}).catch(function(e) {
Zotero.logError(e);
me.complete(false, e);
});
@ -1441,7 +1441,7 @@ Zotero.Translate.Base.prototype = {
"ZOTERO_TRANSLATOR_INFO"],
(translator.file ? translator.file.path : translator.label));
me._translatorInfo = me._sandboxManager.sandbox.ZOTERO_TRANSLATOR_INFO;
}).fail(function(e) {
}).catch(function(e) {
me.complete(false, e);
});
},
@ -2060,7 +2060,7 @@ Zotero.Translate.Export.prototype.complete = function(returnValue, error) {
*/
Zotero.Translate.Export.prototype.getTranslators = function() {
if(this._currentState === "detect") {
return Q.reject(new Error("getTranslators: detection is already running"));
return Zotero.Promise.reject(new Error("getTranslators: detection is already running"));
}
var me = this;
return Zotero.Translators.getAllForType(this.type).then(function(translators) {

View file

@ -216,7 +216,7 @@ Zotero.Translate.ItemSaver.prototype = {
return topLevelCollection;
},
"_saveAttachmentFile":function(attachment, parentID, attachmentCallback) {
"_saveAttachmentFile": Zotero.Promise.coroutine(function* (attachment, parentID, attachmentCallback) {
const urlRe = /(([a-z][-+\.a-z0-9]*):\/\/[^\s]*)/i; //according to RFC3986
Zotero.debug("Translate: Adding attachment", 4);
@ -241,37 +241,46 @@ Zotero.Translate.ItemSaver.prototype = {
if(!attachment.path) {
// create from URL
attachment.linkMode = "linked_file";
try {
var myID = Zotero.Attachments.linkFromURL(attachment.url, parentID,
(attachment.mimeType ? attachment.mimeType : undefined),
(attachment.title ? attachment.title : undefined));
} catch(e) {
var newItem = yield Zotero.Attachments.linkFromURL({
url: attachment.url,
parentItemID: parentID,
contentType: attachment.mimeType ? attachment.mimeType : undefined,
title: attachment.title ? attachment.title : undefined
})
.catch(function (e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
attachmentCallback(attachment, false, e);
});
if (!newItem) {
return false;
}
Zotero.debug("Translate: Created attachment; id is "+myID, 4);
Zotero.debug("Translate: Created attachment; id is " + newItem.id, 4);
attachmentCallback(attachment, 100);
var newItem = Zotero.Items.get(myID);
} else {
var file = this._parsePath(attachment.path);
if(!file) return;
if (attachment.url) {
attachment.linkMode = "imported_url";
var myID = Zotero.Attachments.importSnapshotFromFile(file,
attachment.url, attachment.title, attachment.mimeType, attachment.charset,
parentID);
var newItem = yield Zotero.Attachments.importSnapshotFromFile({
file: file,
url: attachment.url,
title: attachment.title,
contentType: attachment.mimeType,
charset: attachment.charset,
parentItemID: parentID
});
}
else {
attachment.linkMode = "imported_file";
var myID = Zotero.Attachments.importFromFile(file, parentID);
var newItem = yield Zotero.Attachments.importFromFile({
file: file,
parentItemID: parentID
});
}
attachmentCallback(attachment, 100);
}
var newItem = Zotero.Items.get(myID);
// save fields
attachment.itemType = "attachment";
this._saveFields(attachment, newItem);
@ -281,10 +290,10 @@ Zotero.Translate.ItemSaver.prototype = {
newItem.setNote(attachment.note);
}
newItem.save();
yield newItem.save();
return newItem;
},
}),
"_parsePathURI":function(path) {
try {
@ -381,7 +390,7 @@ Zotero.Translate.ItemSaver.prototype = {
return false;
},
"_saveAttachmentDownload":function(attachment, parentID, attachmentCallback) {
"_saveAttachmentDownload": Zotero.Promise.coroutine(function* (attachment, parentID, attachmentCallback) {
Zotero.debug("Translate: Adding attachment", 4);
if(!attachment.url && !attachment.document) {
@ -413,80 +422,99 @@ Zotero.Translate.ItemSaver.prototype = {
// if snapshot is explicitly set to false, attach as link
attachment.linkMode = "linked_url";
if(attachment.document) {
try {
Zotero.Attachments.linkFromURL(attachment.document.location.href, parentID,
(attachment.mimeType ? attachment.mimeType : attachment.document.contentType),
title);
attachmentCallback(attachment, 100);
} catch(e) {
yield Zotero.Attachments.linkFromURL({
url: attachment.document.location.href,
parentItemID: parentID,
contentType: attachment.mimeType ? attachment.mimeType : attachment.document.contentType,
title: title
})
.catch(function (e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
attachmentCallback(attachment, false, e);
}
});
attachmentCallback(attachment, 100);
return true;
} else {
if(!attachment.mimeType || !title) {
Zotero.debug("Translate: Either mimeType or title is missing; attaching file will be slower", 3);
}
try {
Zotero.Attachments.linkFromURL(attachment.url, parentID,
(attachment.mimeType ? attachment.mimeType : undefined),
title);
attachmentCallback(attachment, 100);
} catch(e) {
yield Zotero.Attachments.linkFromURL({
url: attachment.url,
parentItemID: parentID,
contentType: attachment.mimeType ? attachment.mimeType : undefined,
title: title
})
.catch(function (e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
attachmentCallback(attachment, false, e);
}
});
attachmentCallback(attachment, 100);
return true;
}
} else {
// if snapshot is not explicitly set to false, retrieve snapshot
if(attachment.document) {
try {
attachment.linkMode = "imported_url";
Zotero.Attachments.importFromDocument(attachment.document,
parentID, title, null, function(status, err) {
if(status) {
attachmentCallback(attachment, 100);
} else {
attachmentCallback(attachment, false, err);
}
}, this._libraryID);
attachmentCallback(attachment, 0);
} catch(e) {
attachment.linkMode = "imported_url";
attachmentCallback(attachment, 0);
yield Zotero.Attachments.importFromDocument({
libraryID: this._libraryID,
document: attachment.document,
parentItemID: parentID,
title: title
})
.then(function (attachmentItem) {
attachmentCallback(attachment, 100);
})
.catch(function (e) {
Zotero.debug("Translate: Error attaching document", 2);
attachmentCallback(attachment, false, e);
}
});
return true;
// Save attachment if snapshot pref enabled or not HTML
// (in which case downloadAssociatedFiles applies)
} else {
var mimeType = (attachment.mimeType ? attachment.mimeType : null);
var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentID);
try {
Zotero.debug('Importing attachment from URL');
attachment.linkMode = "imported_url";
Zotero.Attachments.importFromURL(attachment.url, parentID, title,
fileBaseName, null, mimeType, this._libraryID, function(status, err) {
// TODO: actually indicate progress during download
if(status) {
attachmentCallback(attachment, 100);
} else {
attachmentCallback(attachment, false, err);
}
}, this._cookieSandbox);
attachmentCallback(attachment, 0);
} catch(e) {
let parentItem = yield Zotero.Items.getAsync(parentID);
var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
Zotero.debug('Importing attachment from URL');
attachment.linkMode = "imported_url";
attachmentCallback(attachment, 0);
yield Zotero.Attachments.importFromURL({
libraryID: this._libraryID,
url: attachment.url,
parentItemID: parentID,
title: title,
fileBaseName: fileBaseName,
contentType: mimeType,
cookieSandbox: this._cookieSandbox
})
.then(function (attachmentItem) {
// TODO: actually indicate progress during download
attachmentCallback(attachment, 100);
})
.catch(function (e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
attachmentCallback(attachment, false, e);
}
});
return true;
}
}
}
return false;
},
}),
"_saveFields":function(item, newItem) {
// fields that should be handled differently
@ -587,7 +615,7 @@ Zotero.Translate.ItemSaver.prototype = {
myNote.libraryID = this._libraryID;
myNote.setNote(typeof note == "object" ? note.note : note);
if(parentID) {
myNote.setSource(parentID);
myNote.parentID = parentID;
}
var noteID = myNote.save();
@ -706,7 +734,7 @@ Zotero.Translate.ItemGetter.prototype = {
},
"setAll":function(getChildCollections) {
this._itemsLeft = Zotero.Items.getAll(true);
this._itemsLeft = Zotero.Items.getAll(0, true);
if(getChildCollections) {
this._collectionsLeft = Zotero.getCollections();

View file

@ -131,7 +131,7 @@ Zotero.Translator.prototype.init = function(info) {
* Load code for a translator
*/
Zotero.Translator.prototype.getCode = function() {
if(this.code) return Q(this.code);
if(this.code) return Zotero.Promise.resolve(this.code);
var me = this;
if(Zotero.isConnector) {

View file

@ -37,7 +37,7 @@ Zotero.Translators = new function() {
/**
* Initializes translator cache, loading all relevant translators into memory
*/
this.reinit = Q.async(function() {
this.reinit = Zotero.Promise.coroutine(function* () {
var start = (new Date()).getTime();
var transactionStarted = false;
@ -156,7 +156,7 @@ Zotero.Translators = new function() {
.then(function(source) {
return Zotero.Translators.load(file, infoRe.exec(source)[0], source);
})
.fail(function() {
.catch(function() {
throw "Invalid or missing translator metadata JSON object in " + file.leafName;
});
}
@ -388,7 +388,7 @@ Zotero.Translators = new function() {
var sameFile = translator && destFile.equals(translator.file);
if (sameFile) return;
return Q(OS.File.exists(destFile.path))
return Zotero.Promise.resolve(OS.File.exists(destFile.path))
.then(function (exists) {
if (exists) {
var msg = "Overwriting translator with same filename '"
@ -402,7 +402,7 @@ Zotero.Translators = new function() {
.then(function () {
if (!translator) return;
return Q(OS.File.exists(translator.file.path))
return Zotero.Promise.resolve(OS.File.exists(translator.file.path))
.then(function (exists) {
translator.file.remove(false);
});

View file

@ -561,10 +561,10 @@ Zotero.Utilities = {
*/
"arrayDiff":function(array1, array2, useIndex) {
if (!Array.isArray(array1)) {
throw ("array1 is not an array (" + array1 + ")");
throw new Error("array1 is not an array (" + array1 + ")");
}
if (!Array.isArray(array2)) {
throw ("array2 is not an array (" + array2 + ")");
throw new Error("array2 is not an array (" + array2 + ")");
}
var val, pos, vals = [];
@ -578,6 +578,39 @@ Zotero.Utilities = {
return vals;
},
/**
* Determine whether two arrays are identical
*
* Modified from http://stackoverflow.com/a/14853974
*
* @return {Boolean}
*/
"arrayEquals": function (array1, array2) {
// If either array is a falsy value, return
if (!array1 || !array2)
return false;
// Compare lengths - can save a lot of time
if (array1.length != array2.length)
return false;
for (var i = 0, l=array1.length; i < l; i++) {
// Check if we have nested arrays
if (array1[i] instanceof Array && array2[i] instanceof Array) {
// Recurse into the nested arrays
if (!array1[i].compare(array2[i]))
return false;
}
else if (array1[i] != array2[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
},
/**
* Return new array with values shuffled
*
@ -648,6 +681,7 @@ Zotero.Utilities = {
return retValues;
},
/**
* Generate a random integer between min and max inclusive
*
@ -1178,7 +1212,7 @@ Zotero.Utilities = {
}
if (maxLevel === undefined) {
maxLevel = 4;
maxLevel = 5;
}
// The padding given at the beginning of the line.

View file

@ -30,6 +30,31 @@
* @class Utility functions not made available to translators
*/
Zotero.Utilities.Internal = {
/**
* Run a function on chunks of a given size of an array's elements.
*
* @param {Array} arr
* @param {Integer} chunkSize
* @param {Function} func
* @return {Array} The return values from the successive runs
*/
"forEachChunkAsync": Zotero.Promise.coroutine(function* (arr, chunkSize, func) {
var retValues = [];
var tmpArray = arr.concat();
var num = arr.length;
var done = 0;
do {
var chunk = tmpArray.splice(0, chunkSize);
done += chunk.length;
retValues.push(yield func(chunk));
}
while (done < num);
return retValues;
}),
/*
* Adapted from http://developer.mozilla.org/en/docs/nsICryptoHash
*
@ -98,7 +123,7 @@ Zotero.Utilities.Internal = {
"md5Async": function (file, base64) {
const CHUNK_SIZE = 16384;
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
function toHexString(charCode) {
return ("0" + charCode.toString(16)).slice(-2);
@ -256,14 +281,14 @@ Zotero.Utilities.Internal = {
*/
"exec":function(cmd, args) {
if(!cmd.isExecutable()) {
return Q.reject(cmd.path+" is not an executable");
return Zotero.Promise.reject(cmd.path+" is not an executable");
}
var proc = Components.classes["@mozilla.org/process/util;1"].
createInstance(Components.interfaces.nsIProcess);
proc.init(cmd);
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
proc.runwAsync(args, args.length, {"observe":function(subject, topic) {
if(topic !== "process-finished") {
deferred.reject(new Error(cmd.path+" failed"));
@ -313,6 +338,30 @@ Zotero.Utilities.Internal = {
childWindow = childWindow.parent;
if(childWindow === parentWindow) return true;
}
},
/**
* A generator that yields promises that delay for the given intervals
*
* @param {Array<Integer>} intervals An array of intervals in milliseconds
* @param {Boolean} [finite=FALSE] If TRUE, repeat the last interval forever
*/
"delayGenerator": function (intervals, finite) {
var lastInterval = intervals[intervals.length - 1];
while (true) {
let interval = intervals.shift();
if (interval) {
lastInterval = interval;
yield Zotero.Promise.delay(interval);
}
else if (finite) {
yield Zotero.Promise.delay(lastInterval);
}
else {
break;
}
}
}
}

View file

@ -41,7 +41,6 @@ const ZOTERO_CONFIG = {
};
// Commonly used imports accessible anywhere
Components.utils.import("resource://zotero/q.js");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm");
@ -85,6 +84,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
this.isWin;
this.initialURL; // used by Schema to show the changelog on upgrades
Components.utils.import("resource://zotero/bluebird.js", this);
this.__defineGetter__('userID', function () {
if (_userID !== undefined) return _userID;
@ -162,7 +162,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
_locked = lock;
if (!wasLocked && lock) {
this.unlockDeferred = Q.defer();
this.unlockDeferred = Zotero.Promise.defer();
this.unlockPromise = this.unlockDeferred.promise;
}
else if (wasLocked && !lock) {
@ -237,7 +237,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
return false;
}
this.initializationDeferred = Q.defer();
this.initializationDeferred = Zotero.Promise.defer();
this.initializationPromise = this.initializationDeferred.promise;
this.locked = true;
@ -257,11 +257,11 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
this.platformMajorVersion = parseInt(appInfo.platformVersion.match(/^[0-9]+/)[0]);
this.isFx = true;
this.isStandalone = Services.appinfo.ID == ZOTERO_CONFIG['GUID'];
return Q.fcall(function () {
return Zotero.Promise.try(function () {
if(Zotero.isStandalone) {
return Services.appinfo.version;
} else {
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID(
ZOTERO_CONFIG.GUID,
@ -494,7 +494,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
}
} else {
Zotero.debug("Loading in full mode");
return Q.fcall(_initFull)
return Zotero.Promise.try(_initFull)
.then(function (success) {
if(!success) return false;
@ -537,7 +537,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
*
* @return {Promise:Boolean}
*/
function _initFull() {
var _initFull = Zotero.Promise.coroutine(function* () {
Zotero.VersionHeader.init();
// Check for DB restore
@ -573,17 +573,14 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
Zotero.HTTP.triggerProxyAuth();
// Add notifier queue callbacks to the DB layer
Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
Zotero.DB.addCallback('commit', Zotero.Notifier.commit);
Zotero.DB.addCallback('rollback', Zotero.Notifier.reset);
Zotero.DB.addCallback('begin', function () { return Zotero.Notifier.begin(); });
Zotero.DB.addCallback('commit', function () { return Zotero.Notifier.commit(); });
Zotero.DB.addCallback('rollback', function () { return Zotero.Notifier.reset(); });
return Q.fcall(function () {
try {
// Require >=2.1b3 database to ensure proper locking
if (!Zotero.isStandalone) {
return;
}
return Zotero.Schema.getDBVersion('system')
.then(function (dbSystemVersion) {
if (Zotero.isStandalone) {
let dbSystemVersion = yield Zotero.Schema.getDBVersion('system');
if (dbSystemVersion > 0 && dbSystemVersion < 31) {
var dir = Zotero.getProfileDirectory();
dir.append('zotero');
@ -636,11 +633,11 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
throw true;
}
});
})
.then(function () {
return Zotero.Schema.updateSchema()
.then(function (updated) {
}
try {
var updated = yield Zotero.Schema.updateSchema();
Zotero.locked = false;
// Initialize various services
@ -650,7 +647,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
Zotero.Server.init();
}
Zotero.Fulltext.init();
yield Zotero.Fulltext.init();
Zotero.Notifier.registerObserver(Zotero.Tags, 'setting');
@ -666,18 +663,22 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
// Initialize Locate Manager
Zotero.LocateManager.init();
Zotero.Collections.init();
Zotero.Items.init();
Zotero.Searches.init();
Zotero.Groups.init();
Zotero.Items.startEmptyTrashTimer();
})
.catch(function (e) {
}
catch (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e); // DEBUG: doesn't always work
if (typeof e == 'string' && e.match('newer than SQL file')) {
var kbURL = "http://zotero.org/support/kb/newer_db_version";
var msg = Zotero.localeJoin([
Zotero.getString('startupError.zoteroVersionIsOlder'),
Zotero.getString('startupError.zoteroVersionIsOlder.upgrade')
]) + "\n\n"
if (typeof e == 'string' && (e.indexOf('newer than SQL file') != -1
|| e.indexOf('Database is incompatible') != -1)) {
var kbURL = "https://www.zotero.org/support/kb/newer_db_version";
var msg = Zotero.getString('startupError.zoteroVersionIsOlder')
+ " " + Zotero.getString('startupError.zoteroVersionIsOlder.upgrade') + "\n\n"
+ Zotero.getString('startupError.zoteroVersionIsOlder.current', Zotero.version) + "\n\n"
+ Zotero.getString('general.seeForMoreInformation', kbURL);
Zotero.startupError = msg;
@ -747,16 +748,15 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
Zotero.startupError = Zotero.getString('startupError.databaseUpgradeError') + "\n\n" + e;
throw true;
});
})
.then(function () {
};
return true;
})
.catch(function (e) {
}
catch (e) {
Zotero.skipLoading = true;
return false;
});
}
}
});
/**
* Initializes the DB connection
@ -907,10 +907,10 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
});
}
return Q();
return Zotero.Promise.resolve();
} catch(e) {
Zotero.debug(e);
return Q.reject(e);
return Zotero.Promise.reject(e);
}
}
@ -1320,7 +1320,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
}
/**
* Log a JS error to the Mozilla JS error console.
* Log a JS error to the Mozilla JS error console and the text console
* @param {Exception} err
*/
function logError(err) {
@ -1688,6 +1688,20 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
};
};
this.serial = function (fn) {
Components.utils.import("resource://zotero/concurrent-caller.js");
var caller = new ConcurrentCaller(1);
caller.setLogger(Zotero.debug);
return function () {
var args = arguments;
return caller.fcall(function () {
return fn.apply(this, args);
}.bind(this));
};
}
/**
* Pumps a generator until it yields false. See itemTreeView.js for an example.
*
@ -1747,13 +1761,22 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
* Pumps a generator until it yields false. Unlike the above, this returns a promise.
*/
this.promiseGenerator = function(generator, ms) {
var deferred = Q.defer();
var deferred = Zotero.Promise.defer();
this.pumpGenerator(generator, ms,
function(e) { deferred.reject(e); },
function(data) { deferred.resolve(data) });
return deferred.promise;
};
this.spawn = function (generator, thisObject) {
if (thisObject) {
return Zotero.Promise.coroutine(generator.bind(thisObject))();
}
return Zotero.Promise.coroutine(generator)();
}
/**
* Emulates the behavior of window.setTimeout, but ensures that callbacks do not get called
* during Zotero.wait()
@ -2027,22 +2050,24 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
/*
* Clear entries that no longer exist from various tables
*/
this.purgeDataObjects = function (skipStoragePurge) {
Zotero.Creators.purge();
Zotero.Tags.purge();
this.purgeDataObjects = Zotero.Promise.coroutine(function* (skipStoragePurge) {
yield Zotero.Creators.purge();
yield Zotero.Tags.purge();
// TEMP: Disabled until we have async DB (and maybe SQLite FTS)
//Zotero.Fulltext.purgeUnusedWords();
Zotero.Items.purge();
yield Zotero.Items.purge();
// DEBUG: this might not need to be permanent
Zotero.Relations.purge();
}
});
this.reloadDataObjects = function () {
Zotero.Tags.reloadAll();
Zotero.Collections.reloadAll();
Zotero.Creators.reloadAll();
Zotero.Items.reloadAll();
return Zotero.Promise.all([
Zotero.Tags.reloadAll(),
Zotero.Collections.reloadAll(),
Zotero.Creators.reloadAll(),
Zotero.Items.reloadAll()
]);
}
@ -2577,8 +2602,7 @@ Zotero.VersionHeader = {
}
Zotero.DragDrop = {
currentDragEvent: null,
currentTarget: null,
currentEvent: null,
currentOrientation: 0,
getDataFromDataTransfer: function (dataTransfer, firstOnly) {
@ -2630,23 +2654,22 @@ Zotero.DragDrop = {
},
getDragSource: function () {
var dt = this.currentDragEvent.dataTransfer;
if (!dt) {
getDragSource: function (dataTransfer) {
if (!dataTransfer) {
Zotero.debug("Drag data not available", 2);
return false;
}
// For items, the drag source is the ItemGroup of the parent window
// For items, the drag source is the CollectionTreeRow of the parent window
// of the source tree
if (dt.types.contains("zotero/item")) {
var sourceNode = dt.mozSourceNode;
if (dataTransfer.types.contains("zotero/item")) {
var sourceNode = dataTransfer.mozSourceNode;
if (!sourceNode || sourceNode.tagName != 'treechildren'
|| sourceNode.parentElement.id != 'zotero-items-tree') {
return false;
}
var win = sourceNode.ownerDocument.defaultView;
return win.ZoteroPane.collectionsView.itemGroup;
return win.ZoteroPane.collectionsView.selectedTreeRow;
}
else {
return false;
@ -2654,8 +2677,7 @@ Zotero.DragDrop = {
},
getDragTarget: function () {
var event = this.currentDragEvent;
getDragTarget: function (event) {
var target = event.target;
if (target.tagName == 'treechildren') {
var tree = target.parentNode;
@ -2663,7 +2685,7 @@ Zotero.DragDrop = {
let row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
let win = tree.ownerDocument.defaultView;
return win.ZoteroPane.collectionsView.getItemGroupAtRow(row.value);
return win.ZoteroPane.collectionsView.getRow(row.value);
}
}
return false;

File diff suppressed because it is too large Load diff

View file

@ -60,6 +60,7 @@
<command id="cmd_zotero_newCollection" oncommand="ZoteroPane_Local.newCollection()"/>
<command id="cmd_zotero_newSavedSearch" oncommand="ZoteroPane_Local.newSearch()"/>
<command id="cmd_zotero_newStandaloneNote" oncommand="ZoteroPane_Local.newNote(event.shiftKey);"/>
<command id="cmd_zotero_newChildNote" oncommand="ZoteroPane_Local.newChildNote(event.shiftKey);"/>
<command id="cmd_zotero_newItemFromCurrentPage" oncommand="ZoteroPane.addItemFromPage('temporaryPDFHack', event.shiftKey ? !Zotero.Prefs.get('automaticSnapshots') : null)"/>
</commandset>
@ -71,10 +72,10 @@
command="cmd_zotero_newItemFromCurrentPage"/>
<menuitem id="zotero-context-add-to-current-note" class="menu-iconic"
label="&zotero.contextMenu.addTextToCurrentNote;" hidden="true"
oncommand="var str = event.currentTarget.ownerDocument.popupNode.ownerDocument.defaultView.getSelection().toString(); var uri = event.currentTarget.ownerDocument.popupNode.ownerDocument.location.href; ZoteroPane.addTextToNote(str, uri)"/>
oncommand="ZoteroPane.addSelectedTextToCurrentNote()"/>
<menuitem id="zotero-context-add-to-new-note" class="menu-iconic"
label="&zotero.contextMenu.addTextToNewNote;" hidden="true"
oncommand="var str = event.currentTarget.ownerDocument.popupNode.ownerDocument.defaultView.getSelection().toString(); var uri = event.currentTarget.ownerDocument.popupNode.ownerDocument.location.href; var itemID = ZoteroPane.addItemFromPage(); ZoteroPane.newNote(false, itemID, str, uri)"/>
oncommand="ZoteroPane.createItemAndNoteFromSelectedText()"/>
<menuitem id="zotero-context-save-link-as-item" class="menu-iconic"
label="&zotero.contextMenu.saveLinkAsItem;" hidden="true"
oncommand="ZoteroPane.addItemFromURL(window.gContextMenu.linkURL, 'temporaryPDFHack')"/>
@ -165,7 +166,7 @@
<toolbarbutton id="zotero-tb-note-add" class="zotero-tb-button" tooltiptext="&zotero.toolbar.newNote;" type="menu">
<menupopup onpopupshowing="ZoteroPane_Local.updateNoteButtonMenu()">
<menuitem label="&zotero.toolbar.note.standalone;" command="cmd_zotero_newStandaloneNote"/>
<menuitem id="zotero-tb-add-child-note" label="&zotero.toolbar.note.child;" oncommand="var selected = ZoteroPane_Local.getSelectedItems()[0]; var parent = selected.getSource(); parent = parent ? parent : selected.id; ZoteroPane_Local.newNote(event.shiftKey, parent);"/>
<menuitem label="&zotero.toolbar.note.child;" command="cmd_zotero_newChildNote"/>
</menupopup>
</toolbarbutton>
<toolbarbutton id="zotero-tb-attachment-add" class="zotero-tb-button" tooltiptext="&zotero.items.menu.attach;" type="menu">
@ -240,7 +241,7 @@
<!-- Keep order in sync with buildCollectionContextMenu -->
<menuitem class="menuitem-iconic zotero-menuitem-new-collection" label="&zotero.toolbar.newCollection.label;" command="cmd_zotero_newCollection"/>
<menuitem class="menuitem-iconic zotero-menuitem-new-saved-search" label="&zotero.toolbar.newSavedSearch.label;" command="cmd_zotero_newSavedSearch"/>
<menuitem class="menuitem-iconic zotero-menuitem-new-collection" label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane_Local.newCollection(ZoteroPane_Local.getSelectedCollection().id)"/>
<menuitem class="menuitem-iconic zotero-menuitem-new-collection" label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane_Local.newCollection(ZoteroPane_Local.getSelectedCollection().key)"/>
<menuseparator/>
<menuitem class="menuitem-iconic zotero-menuitem-show-duplicates" label="&zotero.toolbar.duplicate.label;" oncommand="ZoteroPane_Local.setVirtual(ZoteroPane_Local.getSelectedLibraryID(), 'duplicates', true)"/>
<menuitem class="menuitem-iconic zotero-menuitem-show-unfiled" label="&zotero.collections.showUnfiledItems;" oncommand="ZoteroPane_Local.setVirtual(ZoteroPane_Local.getSelectedLibraryID(), 'unfiled', true)"/>
@ -261,7 +262,7 @@
<menuitem class="menuitem-iconic zotero-menuitem-show-in-library" label="&zotero.items.menu.showInLibrary;" oncommand="ZoteroPane_Local.selectItem(this.parentNode.getAttribute('itemID'), true)"/>
<menuseparator/>
<!-- with icon: <menuitem class="menuitem-iconic" id="zotero-menuitem-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemID'))"/>-->
<menuitem class="menuitem-iconic zotero-menuitem-attach-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemID'))"/>
<menuitem class="menuitem-iconic zotero-menuitem-attach-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemKey'))"/>
<menu class="menu-iconic zotero-menuitem-attach" label="&zotero.items.menu.attach;">
<menupopup id="zotero-add-attachment-popup">
<menuitem class="menuitem-iconic zotero-menuitem-attachments-snapshot standalone-no-display" label="&zotero.items.menu.attach.snapshot;" oncommand="var itemID = parseInt(this.parentNode.parentNode.parentNode.getAttribute('itemID')); ZoteroPane_Local.addAttachmentFromPage(false, itemID)"/>
@ -272,7 +273,7 @@
</menupopup>
</menu>
<menuseparator/>
<menuitem class="menuitem-iconic zotero-menuitem-duplicate-item" label="&zotero.items.menu.duplicateItem;" oncommand="ZoteroPane_Local.duplicateSelectedItem();"/>
<menuitem class="menuitem-iconic zotero-menuitem-duplicate-item" label="&zotero.items.menu.duplicateItem;" oncommand="ZoteroPane_Local.duplicateSelectedItem().done();"/>
<menuitem class="menuitem-iconic zotero-menuitem-delete-collection" oncommand="ZoteroPane_Local.deleteSelectedItems();"/>
<menuitem class="menuitem-iconic zotero-menuitem-move-to-trash" oncommand="ZoteroPane_Local.deleteSelectedItems(true, true);"/>
<menuitem class="menuitem-iconic zotero-menuitem-restore-to-library" label="&zotero.items.menu.restoreToLibrary;" oncommand="ZoteroPane_Local.restoreSelectedItems();"/>
@ -324,8 +325,7 @@
TODO: deal with this some other way?
-->
<zoterotagselector id="zotero-tag-selector" zotero-persist="height,collapsed,showAutomatic,filterToScope"
oncommand="ZoteroPane_Local.updateTagFilter()"/>
<zoterotagselector id="zotero-tag-selector" zotero-persist="height,collapsed,showAutomatic,filterToScope"/>
</vbox>
<splitter id="zotero-collections-splitter" resizebefore="closest" resizeafter="closest" collapse="before"

View file

@ -135,6 +135,7 @@
<!ENTITY zotero.toolbar.attachment.snapshot "Take Snapshot of Current Page">
<!ENTITY zotero.tagSelector.noTagsToDisplay "No tags to display">
<!ENTITY zotero.tagSelector.loadingTags "Loading tags…">
<!ENTITY zotero.tagSelector.filter "Filter:">
<!ENTITY zotero.tagSelector.showAutomatic "Show Automatic">
<!ENTITY zotero.tagSelector.displayAllInLibrary "Display All Tags in This Library">

View file

@ -77,6 +77,8 @@ upgrade.integrityCheckFailed = Your Zotero database must be repaired before the
upgrade.loadDBRepairTool = Load Database Repair Tool
upgrade.couldNotMigrate = Zotero could not migrate all necessary files.\nPlease close any open attachment files and restart %S to try the upgrade again.
upgrade.couldNotMigrate.restart = If you continue to receive this message, restart your computer.
upgrade.nonupgradeableDB1 = Zotero found an old database that cannot be upgraded to work with this version of Zotero.
upgrade.nonupgradeableDB2 = To continue, upgrade your database using Zotero %S first or delete your Zotero data directory to start with a new database.
errorReport.reportError = Report Error…
errorReport.reportErrors = Report Errors…

View file

@ -106,12 +106,16 @@ ZoteroAutoComplete.prototype.startSearch = function(searchString, searchParams,
if (searchParams.fieldMode == 2) {
var sql = "SELECT DISTINCT CASE fieldMode WHEN 1 THEN lastName "
+ "WHEN 0 THEN firstName || ' ' || lastName END AS val, NULL AS comment "
+ "FROM creators NATURAL JOIN creatorData WHERE CASE fieldMode "
+ "FROM creators ";
if (searchParams.libraryID !== undefined) {
sql += "JOIN itemCreators USING (creatorID) JOIN items USING (itemID) ";
}
sql += "WHERE CASE fieldMode "
+ "WHEN 1 THEN lastName "
+ "WHEN 0 THEN firstName || ' ' || lastName END "
+ "LIKE ? ";
var sqlParams = [searchString + '%'];
if (typeof searchParams.libraryID != 'undefined') {
if (searchParams.libraryID !== undefined) {
sql += " AND libraryID=?";
sqlParams.push(searchParams.libraryID);
}
@ -139,8 +143,11 @@ ZoteroAutoComplete.prototype.startSearch = function(searchString, searchParams,
+ "ELSE 2 END AS comment";
}
var fromSQL = " FROM creators NATURAL JOIN creatorData "
+ "WHERE " + subField + " LIKE ? " + "AND fieldMode=?";
var fromSQL = " FROM creators "
if (searchParams.libraryID !== undefined) {
fromSQL += "JOIN itemCreators USING (creatorID) JOIN items USING (itemID) ";
}
fromSQL += "WHERE " + subField + " LIKE ? " + "AND fieldMode=?";
var sqlParams = [
searchString + '%',
searchParams.fieldMode ? searchParams.fieldMode : 0
@ -155,7 +162,7 @@ ZoteroAutoComplete.prototype.startSearch = function(searchString, searchParams,
}
fromSQL += ")";
}
if (typeof searchParams.libraryID != 'undefined') {
if (searchParams.libraryID !== undefined) {
fromSQL += " AND libraryID=?";
sqlParams.push(searchParams.libraryID);
}

View file

@ -216,9 +216,9 @@ function ChromeExtensionHandler() {
for (var i=0; i<results.length; i++) {
// Don't add child items directly
// (instead mark their parents for inclusion below)
var sourceItemID = results[i].getSource();
if (sourceItemID) {
searchParentIDs[sourceItemID] = true;
var parentItemID = results[i].getSource();
if (parentItemID) {
searchParentIDs[parentItemID] = true;
searchChildIDs[results[i].getID()] = true;
// Don't include all child items if any child

View file

@ -60,23 +60,23 @@ const xpcomFilesLocal = [
'cite',
'cookieSandbox',
'data_access',
'data/dataObject',
'data/dataObjects',
'data/dataObjectLoader',
'data/dataObjectUtilities',
'data/cachedTypes',
'data/notes',
'data/item',
'data/items',
'data/collection',
'data/collections',
'data/creator',
'data/creators',
'data/group',
'data/groups',
'data/itemFields',
'data/notes',
'data/libraries',
'data/relation',
'data/relations',
'data/tag',
'data/tags',
'db',
'duplicates',
@ -95,6 +95,9 @@ const xpcomFilesLocal = [
'server',
'style',
'sync',
'sync/syncEngine',
'sync/syncAPI',
'sync/syncLocal',
'storage',
'storage/streamListener',
'storage/queueManager',
@ -295,9 +298,7 @@ function ZoteroService() {
if(isFirstLoadThisSession) {
makeZoteroContext(false);
Q.fcall(function () {
return zContext.Zotero.init(zInitOptions);
})
zContext.Zotero.init(zInitOptions)
.catch(function (e) {
dump(e + "\n\n");
Components.utils.reportError(e);

View file

@ -79,6 +79,7 @@ pref("extensions.zotero.keys.quicksearch", 'K');
pref("extensions.zotero.keys.copySelectedItemCitationsToClipboard", 'A');
pref("extensions.zotero.keys.copySelectedItemsToClipboard", 'C');
pref("extensions.zotero.keys.toggleTagSelector", 'T');
pref("extensions.zotero.keys.sync", 'Y');
// Fulltext indexing
pref("extensions.zotero.fulltext.textMaxLength", 500000);

5261
resource/bluebird.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -24,19 +24,19 @@
*/
EXPORTED_SYMBOLS = ["ConcurrentCaller"];
Components.utils.import("resource://zotero/q.js");
Components.utils.import("resource://zotero/bluebird.js");
/**
* Call a fixed number of functions at once, queueing the rest until slots
* open and returning a promise for the final completion. The functions do
* not need to return promises, but they should if they have asynchronous
* work to perform..
* work to perform.
*
* Example:
*
* var caller = new ConcurrentCaller(2);
* caller.stopOnError = true;
* caller.fcall([foo, bar, baz, qux).done();
* caller.fcall([foo, bar, baz, qux);
*
* In this example, foo and bar would run immediately, and baz and qux would
* be queued for later. When foo or bar finished, baz would be run, followed
@ -93,16 +93,16 @@ ConcurrentCaller.prototype.fcall = function (func) {
//this._log("Running fcall on function");
promises.push(this.fcall(func[i]));
}
return Q.allSettled(promises);
return Promise.settle(promises);
}
// If we're at the maximum number of concurrent functions,
// queue this function for later
if (this._numRunning == this._numConcurrent) {
this._log("Already at " + this._numConcurrent + " -- queueing for later");
var deferred = Q.defer();
var deferred = Promise.defer();
this._queue.push({
func: Q.fbind(func),
func: Promise.method(func),
deferred: deferred
});
return deferred.promise;
@ -112,7 +112,7 @@ ConcurrentCaller.prototype.fcall = function (func) {
// Otherwise run it now
this._numRunning++;
return this._onFunctionDone(Q.fcall(func));
return this._onFunctionDone(Promise.try(func));
}
@ -124,58 +124,55 @@ ConcurrentCaller.prototype.stop = function () {
ConcurrentCaller.prototype._onFunctionDone = function (promise) {
var self = this;
return Q.when(
promise,
function (promise) {
self._numRunning--;
self._log("Done with function ("
+ self._numRunning + "/" + self._numConcurrent + " running, "
+ self._queue.length + " queued)");
// If there's a function to call and we're under the concurrent limit,
// run it now
let f = self._queue.shift();
if (f && self._numRunning < self._numConcurrent) {
// Wait until the specified interval has elapsed or the current
// pause (if there is one) is over, whichever is longer
let interval = self._interval;
let now = Date.now();
if (self._pauseUntil > now && (self._pauseUntil - now > interval)) {
interval = self._pauseUntil - now;
}
Q.delay(interval)
.then(function () {
self._log("Running new function ("
+ self._numRunning + "/" + self._numConcurrent + " running, "
+ self._queue.length + " queued)");
self._numRunning++;
var p = self._onFunctionDone(f.func());
f.deferred.resolve(p);
});
return promise.then(function (promise) {
self._numRunning--;
self._log("Done with function ("
+ self._numRunning + "/" + self._numConcurrent + " running, "
+ self._queue.length + " queued)");
// If there's a function to call and we're under the concurrent limit,
// run it now
let f = self._queue.shift();
if (f && self._numRunning < self._numConcurrent) {
// Wait until the specified interval has elapsed or the current
// pause (if there is one) is over, whichever is longer
let interval = self._interval;
let now = Date.now();
if (self._pauseUntil > now && (self._pauseUntil - now > interval)) {
interval = self._pauseUntil - now;
}
return promise;
},
function (e) {
self._numRunning--;
self._log("Done with function (" + self._numRunning + "/" + self._numConcurrent + ", "
+ self._queue.length + " in queue)");
if (self.onError) {
self.onError(e);
}
if (self.stopOnError && self._queue.length) {
self._log("Stopping on error: " + e);
self._queue = [];
}
throw e;
Promise.delay(interval)
.then(function () {
self._log("Running new function ("
+ self._numRunning + "/" + self._numConcurrent + " running, "
+ self._queue.length + " queued)");
self._numRunning++;
var p = self._onFunctionDone(f.func());
f.deferred.resolve(p);
});
}
);
return promise;
})
.catch(function (e) {
self._numRunning--;
self._log("Done with function (" + self._numRunning + "/" + self._numConcurrent + ", "
+ self._queue.length + " in queue)");
if (self.onError) {
self.onError(e);
}
if (self.stopOnError && self._queue.length) {
self._log("Stopping on error: " + e);
self._queue = [];
}
throw e;
});
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
-- 79
-- 80
-- Copyright (c) 2009 Center for History and New Media
-- George Mason University, Fairfax, Virginia, USA
@ -45,22 +45,25 @@ CREATE TABLE syncedSettings (
value NOT NULL,
version INT NOT NULL DEFAULT 0,
synced INT NOT NULL DEFAULT 0,
PRIMARY KEY (setting, libraryID)
PRIMARY KEY (setting, libraryID),
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
-- The foundational table; every item collected has a unique record here
-- 'items' view and triggers created in triggers.sql
CREATE TABLE items (
itemID INTEGER PRIMARY KEY,
itemTypeID INT NOT NULL,
dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
libraryID INT,
libraryID INT NOT NULL,
key TEXT NOT NULL,
version INT NOT NULL DEFAULT 0,
synced INT NOT NULL DEFAULT 0,
UNIQUE (libraryID, key),
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID)
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE INDEX items_synced ON items(synced);
CREATE TABLE itemDataValues (
valueID INTEGER PRIMARY KEY,
@ -73,8 +76,8 @@ CREATE TABLE itemData (
fieldID INT,
valueID,
PRIMARY KEY (itemID, fieldID),
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (fieldID) REFERENCES fields(fieldID),
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
FOREIGN KEY (fieldID) REFERENCES fieldsCombined(fieldID),
FOREIGN KEY (valueID) REFERENCES itemDataValues(valueID)
);
CREATE INDEX itemData_fieldID ON itemData(fieldID);
@ -82,147 +85,133 @@ CREATE INDEX itemData_fieldID ON itemData(fieldID);
-- Note data for note and attachment items
CREATE TABLE itemNotes (
itemID INTEGER PRIMARY KEY,
sourceItemID INT,
parentItemID INT,
note TEXT,
title TEXT,
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (sourceItemID) REFERENCES items(itemID)
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
FOREIGN KEY (parentItemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX itemNotes_sourceItemID ON itemNotes(sourceItemID);
CREATE INDEX itemNotes_parentItemID ON itemNotes(parentItemID);
-- Metadata for attachment items
CREATE TABLE itemAttachments (
itemID INTEGER PRIMARY KEY,
sourceItemID INT,
parentItemID INT,
linkMode INT,
mimeType TEXT,
contentType TEXT,
charsetID INT,
path TEXT,
originalPath TEXT,
syncState INT DEFAULT 0,
storageModTime INT,
storageHash TEXT,
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (sourceItemID) REFERENCES items(itemID)
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
FOREIGN KEY (parentItemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX itemAttachments_sourceItemID ON itemAttachments(sourceItemID);
CREATE INDEX itemAttachments_mimeType ON itemAttachments(mimeType);
CREATE INDEX itemAttachments_parentItemID ON itemAttachments(parentItemID);
CREATE INDEX itemAttachments_contentType ON itemAttachments(contentType);
CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState);
CREATE TABLE tags (
tagID INTEGER PRIMARY KEY,
name TEXT NOT NULL COLLATE NOCASE,
type INT NOT NULL,
dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
libraryID INT,
key TEXT NOT NULL,
UNIQUE (libraryID, name, type),
UNIQUE (libraryID, key)
libraryID INT NOT NULL,
name TEXT NOT NULL,
UNIQUE (libraryID, name)
);
CREATE TABLE itemTags (
itemID INT,
tagID INT,
itemID INT NOT NULL,
tagID INT NOT NULL,
type INT NOT NULL,
PRIMARY KEY (itemID, tagID),
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (tagID) REFERENCES tags(tagID)
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
FOREIGN KEY (tagID) REFERENCES tags(tagID) ON DELETE CASCADE
);
CREATE INDEX itemTags_tagID ON itemTags(tagID);
CREATE TABLE itemSeeAlso (
itemID INT,
linkedItemID INT,
itemID INT NOT NULL,
linkedItemID INT NOT NULL,
PRIMARY KEY (itemID, linkedItemID),
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (linkedItemID) REFERENCES items(itemID)
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
FOREIGN KEY (linkedItemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX itemSeeAlso_linkedItemID ON itemSeeAlso(linkedItemID);
CREATE TABLE creators (
creatorID INTEGER PRIMARY KEY,
creatorDataID INT NOT NULL,
dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
libraryID INT,
key TEXT NOT NULL,
UNIQUE (libraryID, key),
FOREIGN KEY (creatorDataID) REFERENCES creatorData(creatorDataID)
);
CREATE INDEX creators_creatorDataID ON creators(creatorDataID);
-- Unique creator data, which can be associated with more than one creator
CREATE TABLE creatorData (
creatorDataID INTEGER PRIMARY KEY,
firstName TEXT,
lastName TEXT,
shortName TEXT,
fieldMode INT,
birthYear INT
UNIQUE (lastName, firstName, fieldMode)
);
CREATE INDEX creatorData_name ON creatorData(lastName, firstName);
CREATE TABLE itemCreators (
itemID INT,
creatorID INT,
creatorTypeID INT DEFAULT 1,
orderIndex INT DEFAULT 0,
itemID INT NOT NULL,
creatorID INT NOT NULL,
creatorTypeID INT NOT NULL DEFAULT 1,
orderIndex INT NOT NULL DEFAULT 0,
PRIMARY KEY (itemID, creatorID, creatorTypeID, orderIndex),
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (creatorID) REFERENCES creators(creatorID)
UNIQUE (itemID, orderIndex),
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
FOREIGN KEY (creatorID) REFERENCES creators(creatorID) ON DELETE CASCADE,
FOREIGN KEY (creatorTypeID) REFERENCES creatorTypes(creatorTypeID)
);
CREATE INDEX itemCreators_creatorTypeID ON itemCreators(creatorTypeID);
CREATE TABLE collections (
collectionID INTEGER PRIMARY KEY,
collectionName TEXT NOT NULL,
parentCollectionID INT DEFAULT NULL,
dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
libraryID INT,
libraryID INT NOT NULL,
key TEXT NOT NULL,
version INT NOT NULL DEFAULT 0,
synced INT NOT NULL DEFAULT 0,
UNIQUE (libraryID, key),
FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID)
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE,
FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID) ON DELETE CASCADE
);
CREATE INDEX collections_synced ON collections(synced);
CREATE TABLE collectionItems (
collectionID INT,
itemID INT,
orderIndex INT DEFAULT 0,
collectionID INT NOT NULL,
itemID INT NOT NULL,
orderIndex INT NOT NULL DEFAULT 0,
PRIMARY KEY (collectionID, itemID),
FOREIGN KEY (collectionID) REFERENCES collections(collectionID),
FOREIGN KEY (itemID) REFERENCES items(itemID)
FOREIGN KEY (collectionID) REFERENCES collections(collectionID) ON DELETE CASCADE,
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX itemID ON collectionItems(itemID);
CREATE INDEX collectionItems_itemID ON collectionItems(itemID);
CREATE TABLE savedSearches (
savedSearchID INTEGER PRIMARY KEY,
savedSearchName TEXT NOT NULL,
dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
libraryID INT,
libraryID INT NOT NULL,
key TEXT NOT NULL,
UNIQUE (libraryID, key)
version INT NOT NULL DEFAULT 0,
synced INT NOT NULL DEFAULT 0,
UNIQUE (libraryID, key),
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE INDEX savedSearches_synced ON savedSearches(synced);
CREATE TABLE savedSearchConditions (
savedSearchID INT,
searchConditionID INT,
condition TEXT,
savedSearchID INT NOT NULL,
searchConditionID INT NOT NULL,
condition TEXT NOT NULL,
operator TEXT,
value TEXT,
required NONE,
PRIMARY KEY (savedSearchID, searchConditionID),
FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID)
FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID) ON DELETE CASCADE
);
CREATE TABLE deletedItems (
itemID INTEGER PRIMARY KEY,
dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL
dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX deletedItems_dateDeleted ON deletedItems(dateDeleted);
@ -232,13 +221,15 @@ CREATE TABLE relations (
predicate TEXT NOT NULL,
object TEXT NOT NULL,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (subject, predicate, object)
PRIMARY KEY (subject, predicate, object),
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE INDEX relations_object ON relations(object);
CREATE TABLE libraries (
libraryID INTEGER PRIMARY KEY,
libraryType TEXT NOT NULL
libraryType TEXT NOT NULL,
version INT NOT NULL DEFAULT 0
);
CREATE TABLE users (
@ -253,15 +244,17 @@ CREATE TABLE groups (
description TEXT NOT NULL,
editable INT NOT NULL,
filesEditable INT NOT NULL,
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID)
etag TEXT NOT NULL DEFAULT '',
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE TABLE groupItems (
itemID INTEGER PRIMARY KEY,
createdByUserID INT NOT NULL,
lastModifiedByUserID INT NOT NULL,
FOREIGN KEY (createdByUserID) REFERENCES users(userID),
FOREIGN KEY (lastModifiedByUserID) REFERENCES users(userID)
createdByUserID INT,
lastModifiedByUserID INT,
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
FOREIGN KEY (createdByUserID) REFERENCES users(userID) ON DELETE SET NULL,
FOREIGN KEY (lastModifiedByUserID) REFERENCES users(userID) ON DELETE SET NULL
);
CREATE TABLE fulltextItems (
@ -272,7 +265,7 @@ CREATE TABLE fulltextItems (
indexedChars INT,
totalChars INT,
synced INT DEFAULT 0,
FOREIGN KEY (itemID) REFERENCES items(itemID)
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX fulltextItems_version ON fulltextItems(version);
@ -286,31 +279,44 @@ CREATE TABLE fulltextItemWords (
itemID INT,
PRIMARY KEY (wordID, itemID),
FOREIGN KEY (wordID) REFERENCES fulltextWords(wordID),
FOREIGN KEY (itemID) REFERENCES items(itemID)
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX fulltextItemWords_itemID ON fulltextItemWords(itemID);
CREATE TABLE syncCache (
libraryID INT NOT NULL,
key TEXT NOT NULL,
syncObjectTypeID INT NOT NULL,
version INT NOT NULL,
data TEXT,
PRIMARY KEY (libraryID, key, syncObjectTypeID),
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE,
FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)
);
CREATE TABLE syncDeleteLog (
syncObjectTypeID INT NOT NULL,
libraryID INT NOT NULL,
key TEXT NOT NULL,
timestamp INT NOT NULL,
UNIQUE (syncObjectTypeID, libraryID, key),
FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)
FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID),
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp);
CREATE TABLE storageDeleteLog (
libraryID INT,
libraryID INT NOT NULL,
key TEXT NOT NULL,
timestamp INT NOT NULL,
PRIMARY KEY (libraryID, key)
PRIMARY KEY (libraryID, key),
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE INDEX storageDeleteLog_timestamp ON storageDeleteLog(timestamp);
CREATE TABLE annotations (
annotationID INTEGER PRIMARY KEY,
itemID INT,
itemID INT NOT NULL,
parent TEXT,
textNode INT,
offset INT,
@ -321,13 +327,13 @@ CREATE TABLE annotations (
text TEXT,
collapsed BOOL,
dateModified DATE,
FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)
FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE
);
CREATE INDEX annotations_itemID ON annotations(itemID);
CREATE TABLE highlights (
highlightID INTEGER PRIMARY KEY,
itemID INTEGER,
itemID INT NOT NULL,
startParent TEXT,
startTextNode INT,
startOffset INT,
@ -335,7 +341,7 @@ CREATE TABLE highlights (
endTextNode INT,
endOffset INT,
dateModified DATE,
FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)
FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE
);
CREATE INDEX highlights_itemID ON highlights(itemID);