2.0b3 megacommit

- Support for group libraries
- General support for multiple libraries of different types
- Streamlined sync support
  - Using solely libraryID and key rather than itemID, and removed all itemID-changing code
  - Combined two requests for increased performance and decreased server load
  - Added warning on user account change
  - Provide explicit error message on SSL failure
- Removed snapshot and link toolbar buttons and changed browser context menu options and drags to create parent items + snapshots
- Closes #786, Add numPages field
- Fixes #1063, Duplicate item with tags broken in Sync Preview
- Added better purging of deleted tags
- Added local user key before first sync
- Add clientDateModified to all objects for more flexibility in syncing
- Added new triples-based Relation object type, currently used to store links between items copied between local and group libraries
- Updated zotero.org translator for groups
- Additional trigger-based consistency checks
- Fixed broken URL drag in Firefox 3.5
- Disabled zeroconf menu option (no longer functional)

Developer-specific changes:

- Overhauled data layer
  - Data object constructors no longer take arguments (return to 1.0-like API)
  - Existing objects can be retrieved by setting id or library/key properties
  - id/library/key must be set for new objects before other fields
- New methods:
  - ZoteroPane.getSelectedLibraryID()
  - ZoteroPane.getSelectedGroup(asID)
  - ZoteroPane.addItemFromDocument(doc, itemType, saveSnapshot)
  - ZoteroPane.addItemFromURL(url, itemType)
  - ZoteroPane.canEdit()
  - Zotero.CollectionTreeView.selectLibrary(libraryID)
  - New Zotero.URI methods
- Changed methods
  - Many data object methods now take a libraryID
  - ZoteroPane.addAttachmentFromPage(link, itemID)
- Removed saveItem and saveAttachments parameters from Zotero.Translate constructor
- translate() now takes a libraryID, null for local library, or false to not save items (previously on constructor)
- saveAttachments is now a translate() parameter
- Zotero.flattenArguments() better handles passed objects
- Zotero.File.getFileHash() (not currently used)
This commit is contained in:
Dan Stillman 2009-05-14 18:23:40 +00:00
parent 1db1de2257
commit 91459f95f7
55 changed files with 5370 additions and 1816 deletions

View file

@ -29,7 +29,14 @@ var ZoteroAdvancedSearch = new function() {
var itemGroup = {
isSearchMode: function() { return true; },
getChildItems: function () {
var ids = _searchBox.search.search();
var search = _searchBox.search.clone();
// FIXME: Hack to exclude group libraries for now
var groups = Zotero.Groups.getAll();
for each(var group in groups) {
search.addCondition('libraryID', 'isNot', group.libraryID);
}
//var search = _searchBox.search;
var ids = search.search();
return Zotero.Items.get(ids);
},
isLibrary: function () { return false; },

View file

@ -407,7 +407,7 @@
this._tabIndexMaxInfoFields = Math.max(this._tabIndexMaxInfoFields, tabindex);
if (fieldIsClickable &&
!this.item.isPrimaryField(fieldName) &&
!Zotero.Items.isPrimaryField(fieldName) &&
Zotero.ItemFields.isFieldOfBase(Zotero.ItemFields.getID(fieldName), 'date')) {
this.addDateRow(fieldNames[i], this.item.getField(fieldName, true), tabindex);
continue;
@ -1688,7 +1688,7 @@
<body>
<![CDATA[
return !this.clickByRow &&
((this.clickable && !this.item.isPrimaryField(fieldName))
((this.clickable && !Zotero.Items.isPrimaryField(fieldName))
|| this._clickableFields.indexOf(fieldName) != -1);
]]>
</body>
@ -1802,6 +1802,7 @@
<parameter name="changeGlobally"/>
<body>
<![CDATA[
var libraryID = this.item.libraryID;
var firstName = fields.firstName;
var lastName = fields.lastName;
//var shortName = fields.shortName;
@ -1823,16 +1824,17 @@
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);
newLinkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID, libraryID);
}
if (oldCreator) {
if (oldCreator.ref.equals(newCreator)) {
if (oldCreator.ref.equals(newCreator) || (oldCreator.ref.libraryID != newCreator.libraryID)) {
if (oldCreator.creatorTypeID == creatorTypeID) {
Zotero.debug("Creator " + oldCreator.ref.id + " hasn't changed");
}
@ -1913,7 +1915,15 @@
this.item.setCreator(index, creator, creatorTypeID);
if (this.saveOnEdit) {
this.item.save();
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();

View file

@ -193,7 +193,7 @@
this._leftpane.objectbox.clickableFields = diffFields;
this._rightpane.objectbox.clickableFields = diffFields;
var mergeItem = new Zotero.Item(false, this._leftpane.ref.itemTypeID);
var mergeItem = new Zotero.Item(this._leftpane.ref.itemTypeID);
this._mergepane.ref = mergeItem;
this._mergepane.objectbox.visibleFields = fields;
}
@ -428,7 +428,7 @@
value = row.lastChild.getAttribute('itemTypeID');
if (!mergepane.ref) {
mergepane.ref = new Zotero.Item(false, value);
mergepane.ref = new Zotero.Item(value);
}
else {
mergepane.objectbox.changeTypeTo(value, true);

View file

@ -222,7 +222,10 @@
}
// Create new note
var item = new Zotero.Item(false, '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);
@ -230,7 +233,7 @@
if (this.saveOnEdit) {
var id = item.save();
if (this.parent && this.collection) {
if (!this.parent && this.collection) {
this.collection.addItem(id);
}
}

View file

@ -137,10 +137,18 @@
window.openDialog('chrome://zotero/content/selectItemsDialog.xul', '',
'chrome,dialog=no,modal,centerscreen,resizable=yes', io);
if(io.dataOut && this.item)
{
for(var i = 0; i < io.dataOut.length; i++)
{
if(io.dataOut) {
if (io.dataOut.length) {
var relItem = Zotero.Items.get(io.dataOut[0]);
if (relItem.libraryID != this.item.libraryID) {
// FIXME
var prompt = Components.classes["@mozilla.org/network/default-prompt;1"]
.getService(Components.interfaces.nsIPrompt);
prompt.alert("", "You cannot relate items in different libraries in this Zotero release.");
return;
}
}
for(var i = 0; i < io.dataOut.length; i++) {
this.item.addRelatedItem(io.dataOut[i]);
}
this.item.save();

View file

@ -39,6 +39,19 @@
<field name="_dirty">null</field>
<field name="_empty">null</field>
<field name="selection"/>
<field name="_libraryID"/>
<property name="libraryID" onget="return this._libraryID">
<setter>
<![CDATA[
if (this._libraryID != val) {
this._dirty = true;
}
this._libraryID = val;
]]>
</setter>
</property>
<property name="showAutomatic" onget="return this.getAttribute('showAutomatic') != 'false'"/>
<property name="_types">
<getter>
@ -170,7 +183,7 @@
var tagsToggleBox = this.id('tags-toggle');
if (fetch || this._dirty) {
this._tags = Zotero.Tags.getAll(this._types);
this._tags = Zotero.Tags.getAll(this._types, this.libraryID);
// Remove children
while (tagsToggleBox.hasChildNodes()){
@ -414,7 +427,7 @@
<![CDATA[
// If a selected tag no longer exists, deselect it
if (event == 'delete') {
this._tags = Zotero.Tags.getAll(this._types);
this._tags = Zotero.Tags.getAll(this._types, this.libraryID);
for (var tag in this.selection) {
for each(var tag2 in this._tags) {
@ -658,6 +671,17 @@
function onDragOver(event, flavour, session) {
/*
// TODO: get drop data
var ids = dropData.data.split(',');
var items = Zotero.Items.get(ids);
for (var i=0; i<items.length; i++) {
if (!Zotero.Items.isEditable(items[i])) {
return false;
}
}
*/
event.target.setAttribute('draggedOver', true);
return true;
}
@ -716,7 +740,9 @@
<xul:hbox>
<xul:hbox pack="start">
<xul:checkbox id="display-all-tags" label="&zotero.tagSelector.displayAll;"
<!-- TODO: localize or change -->
<!-- <xul:checkbox id="display-all-tags" label="&zotero.tagSelector.displayAll;"-->
<xul:checkbox id="display-all-tags" label="Display all tags in this library"
oncommand="var ts = document.getBindingParent(this); ts.filterToScope = !this.checked; event.stopPropagation();">
</xul:checkbox>
</xul:hbox>

View file

@ -112,11 +112,14 @@ var Zotero_Browser = new function() {
function(e) { Zotero_Browser.chromeUnload(e) }, false);
}
/*
* Scrapes a page (called when the capture icon is clicked); takes a collection
* ID as the argument
/**
* Scrapes a page (called when the capture icon is clicked)
*
* @param {Integer} libraryID
* @param {Integer} collectionID
* @return void
*/
function scrapeThisPage(saveLocation) {
function scrapeThisPage(libraryID, collectionID) {
if (!Zotero.stateCheck()) {
Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scrapeError"));
var desc = Zotero.getString("ingester.scrapeError.transactionInProgress.previousError")
@ -126,7 +129,7 @@ var Zotero_Browser = new function() {
Zotero_Browser.progress.startCloseTimer(8000);
return;
}
_getTabObject(this.tabbrowser.selectedBrowser).translate(saveLocation);
_getTabObject(this.tabbrowser.selectedBrowser).translate(libraryID, collectionID);
}
/*
@ -192,6 +195,8 @@ var Zotero_Browser = new function() {
/*
* called to show the collection selection popup
*
* not currently used
*/
function showPopup(collectionID, parentElement) {
if(_scrapePopupShowing && parentElement.hasChildNodes()) {
@ -676,19 +681,18 @@ Zotero_Browser.Tab.prototype._attemptLocalFileImport = function(doc) {
}
/*
* translate a page, saving in saveLocation
* translate a page
*
* @param {Integer} libraryID
* @param {Integer} collectionID
*/
Zotero_Browser.Tab.prototype.translate = function(saveLocation) {
Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID) {
if(this.page.translators && this.page.translators.length) {
Zotero_Browser.progress.show();
Zotero_Browser.isScraping = true;
if(saveLocation) {
saveLocation = Zotero.Collections.get(saveLocation);
} else { // save to currently selected collection, if a collection is selected
try {
saveLocation = ZoteroPane.getSelectedCollection();
} catch(e) {}
if(collectionID) {
collection = Zotero.Collections.get(collectionID);
}
var me = this;
@ -701,9 +705,9 @@ Zotero_Browser.Tab.prototype.translate = function(saveLocation) {
this.page.hasBeenTranslated = true;
}
this.page.translate.clearHandlers("itemDone");
this.page.translate.setHandler("itemDone", function(obj, item) { Zotero_Browser.itemDone(obj, item, saveLocation) });
this.page.translate.setHandler("itemDone", function(obj, item) { Zotero_Browser.itemDone(obj, item, collection) });
this.page.translate.translate();
this.page.translate.translate(libraryID);
}
}

View file

@ -330,7 +330,8 @@ var Zotero_File_Interface = new function() {
} else {
var searchRef = ZoteroPane.getSelectedSavedSearch();
if(searchRef) {
var search = new Zotero.Search(searchRef.id);
var search = new Zotero.Search();
search.id = searchRef.id;
name = search.name;
}
}

View file

@ -18,7 +18,7 @@ const Zotero_Lookup = new function () {
}
}
var translate = new Zotero.Translate("search", true, false);
var translate = new Zotero.Translate("search");
translate.setSearch(item);
// be lenient about translators
@ -38,15 +38,17 @@ const Zotero_Lookup = new function () {
}
});
var saveLocation = false;
var libraryID = null;
var collection = false;
try {
saveLocation = window.opener.ZoteroPane.getSelectedCollection();
libraryID = window.opener.ZoteroPane.getSelectedLibraryID();
collection = window.opener.ZoteroPane.getSelectedCollection();
} catch(e) {}
translate.setHandler("itemDone", function(obj, item) {
if(saveLocation) saveLocation.addItem(item.getID());
if(collection) collection.addItem(item.id);
});
translate.translate();
translate.translate(libraryID);
return false;
}
}

View file

@ -66,7 +66,6 @@ var ZoteroPane = new function()
this.getSortedItems = getSortedItems;
this.getSortField = getSortField;
this.getSortDirection = getSortDirection;
this.buildCollectionContextMenu = buildCollectionContextMenu;
this.buildItemContextMenu = buildItemContextMenu;
this.onDoubleClick = onDoubleClick;
this.loadURI = loadURI;
@ -74,11 +73,8 @@ var ZoteroPane = new function()
this.clearItemsPaneMessage = clearItemsPaneMessage;
this.contextPopupShowing = contextPopupShowing;
this.openNoteWindow = openNoteWindow;
this.newNote = newNote;
this.addTextToNote = addTextToNote;
this.addItemFromPage = addItemFromPage;
this.addAttachmentFromDialog = addAttachmentFromDialog;
this.addAttachmentFromPage = addAttachmentFromPage;
this.viewAttachment = viewAttachment;
this.viewSelectedAttachment = viewSelectedAttachment;
this.showAttachmentNotFoundDialog = showAttachmentNotFoundDialog;
@ -92,6 +88,9 @@ var ZoteroPane = new function()
var self = this;
var titlebarcolorState, toolbarCollapseState, titleState;
// Also needs to be changed in collectionTreeView.js
var _lastViewedFolderRE = /^(?:(C|S|G)([0-9]+)|L)$/;
/*
* Called when the window is open
*/
@ -136,7 +135,7 @@ var ZoteroPane = new function()
Zotero.setFontSize(document.getElementById('zotero-pane'))
if (Zotero.isMac) {
document.getElementById('zotero-tb-actions-zeroconf-update').setAttribute('hidden', false);
//document.getElementById('zotero-tb-actions-zeroconf-update').setAttribute('hidden', false);
document.getElementById('zotero-pane').setAttribute('platform', 'mac');
} else if(Zotero.isWin) {
document.getElementById('zotero-pane').setAttribute('platform', 'win');
@ -180,11 +179,6 @@ var ZoteroPane = new function()
Zotero.Keys.windowInit(document);
// If the database was initialized and Zotero hasn't been run before
// in this profile, display the Quick Start Guide -- this way the guide
// won't be displayed when they sync their DB to another profile or if
// they the DB is initialized erroneously (e.g. while switching data
// directory locations)
if (Zotero.restoreFromServer) {
Zotero.restoreFromServer = false;
@ -213,6 +207,11 @@ var ZoteroPane = new function()
}
}, 1000);
}
// If the database was initialized and Zotero hasn't been run before
// in this profile, display the Quick Start Guide -- this way the guide
// won't be displayed when they sync their DB to another profile or if
// they the DB is initialized erroneously (e.g. while switching data
// directory locations)
else if (Zotero.Schema.dbInitialized && Zotero.Prefs.get('firstRun')) {
setTimeout(function () {
gBrowser.selectedTab = gBrowser.addTab(ZOTERO_CONFIG.FIRST_RUN_URL);
@ -629,29 +628,42 @@ var ZoteroPane = new function()
return false;
}
var item = new Zotero.Item(false, typeID);
for (var i in data)
{
item.setField(i, data[i]);
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
item.save();
var item = new Zotero.Item(typeID);
item.libraryID = this.getSelectedLibraryID();
for (var i in data) {
item.setField(i, data[i]);
}
var itemID = item.save();
if (this.itemsView && this.itemsView._itemGroup.isCollection()) {
this.itemsView._itemGroup.ref.addItem(item.id);
this.itemsView._itemGroup.ref.addItem(itemID);
}
//set to Info tab
document.getElementById('zotero-view-item').selectedIndex = 0;
this.selectItem(item.id);
this.selectItem(itemID);
return item;
return Zotero.Items.get(itemID);
}
function newCollection(parent)
{
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return false;
}
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
@ -674,13 +686,29 @@ var ZoteroPane = new function()
}
var collection = new Zotero.Collection;
collection.libraryID = this.getSelectedLibraryID();
collection.name = newName.value;
collection.parent = parent;
collection.save();
}
this.newGroup = function () {
if (this.isFullScreen()) {
this.toggleDisplay();
}
window.loadURI(Zotero.Groups.addGroupURL);
}
function newSearch()
{
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return false;
}
var s = new Zotero.Search();
s.addCondition('title', 'contains', '');
@ -692,6 +720,21 @@ var ZoteroPane = new function()
}
this.openLookupWindow = function () {
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return false;
}
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
window.openDialog('chrome://zotero/content/lookup.xul', 'zotero-lookup', 'chrome,modal');
}
function openAdvancedSearchWindow() {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
@ -825,13 +868,13 @@ var ZoteroPane = new function()
* Passed to the items tree to trigger on changes
*/
function _setTagScope() {
var itemgroup = self.collectionsView.
_getItemAtRow(self.collectionsView.selection.currentIndex);
var itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex);
var tagSelector = document.getElementById('zotero-tag-selector');
if (!tagSelector.getAttribute('collapsed') ||
tagSelector.getAttribute('collapsed') == 'false') {
Zotero.debug('Updating tag selector with current tags');
tagSelector.scope = itemgroup.getChildTags();
tagSelector.libraryID = itemGroup.ref.libraryID;
tagSelector.scope = itemGroup.getChildTags();
}
}
@ -841,45 +884,59 @@ var ZoteroPane = new function()
if (this.itemsView)
{
this.itemsView.unregister();
document.getElementById('zotero-items-tree').removeEventListener(
'keypress', this.itemsView.wrappedJSObject.listener, false
);
if (this.itemsView.wrappedJSObject.listener) {
document.getElementById('zotero-items-tree').removeEventListener(
'keypress', this.itemsView.wrappedJSObject.listener, false
);
}
this.itemsView.wrappedJSObject.listener = null;
document.getElementById('zotero-items-tree').view = this.itemsView = null;
}
document.getElementById('zotero-tb-search').value = "";
if (this.collectionsView.selection.count == 1 && this.collectionsView.selection.currentIndex != -1) {
var itemgroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
itemgroup.setSearch('');
itemgroup.setTags(getTagSelection());
itemgroup.showDuplicates = false;
try {
Zotero.UnresponsiveScriptIndicator.disable();
this.itemsView = new Zotero.ItemTreeView(itemgroup);
this.itemsView.addCallback(_setTagScope);
document.getElementById('zotero-items-tree').view = this.itemsView;
this.itemsView.selection.clearSelection();
}
finally {
Zotero.UnresponsiveScriptIndicator.enable();
}
if (itemgroup.isLibrary()) {
Zotero.Prefs.set('lastViewedFolder', 'L');
}
if (itemgroup.isCollection()) {
Zotero.Prefs.set('lastViewedFolder', 'C' + itemgroup.ref.id);
}
else if (itemgroup.isSearch()) {
Zotero.Prefs.set('lastViewedFolder', 'S' + itemgroup.ref.id);
}
}
else
{
if (this.collectionsView.selection.count != 1) {
document.getElementById('zotero-items-tree').view = this.itemsView = null;
return;
}
// this.collectionsView.selection.currentIndex != -1
var itemgroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
/*
if (itemgroup.isSeparator()) {
document.getElementById('zotero-items-tree').view = this.itemsView = null;
return;
}
*/
itemgroup.setSearch('');
itemgroup.setTags(getTagSelection());
itemgroup.showDuplicates = false;
try {
Zotero.UnresponsiveScriptIndicator.disable();
this.itemsView = new Zotero.ItemTreeView(itemgroup);
this.itemsView.addCallback(_setTagScope);
document.getElementById('zotero-items-tree').view = this.itemsView;
this.itemsView.selection.clearSelection();
}
finally {
Zotero.UnresponsiveScriptIndicator.enable();
}
if (itemgroup.isLibrary()) {
Zotero.Prefs.set('lastViewedFolder', 'L');
}
if (itemgroup.isCollection()) {
Zotero.Prefs.set('lastViewedFolder', 'C' + itemgroup.ref.id);
}
else if (itemgroup.isSearch()) {
Zotero.Prefs.set('lastViewedFolder', 'S' + itemgroup.ref.id);
}
else if (itemgroup.isGroup()) {
Zotero.Prefs.set('lastViewedFolder', 'G' + itemgroup.ref.id);
}
}
@ -928,7 +985,7 @@ var ZoteroPane = new function()
tabs.hidden = true;
var noteEditor = document.getElementById('zotero-note-editor');
noteEditor.mode = this.itemsView.readOnly ? 'view' : 'edit';
noteEditor.mode = this.collectionsView.editable ? 'edit' : 'view';
// 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
@ -942,10 +999,7 @@ var ZoteroPane = new function()
noteEditor.enableUndo();
var viewButton = document.getElementById('zotero-view-note-button');
if (this.itemsView.readOnly) {
viewButton.hidden = true;
}
else {
if (this.collectionsView.editable) {
viewButton.hidden = false;
viewButton.setAttribute('noteID', item.ref.id);
if (item.ref.getSource()) {
@ -955,6 +1009,9 @@ var ZoteroPane = new function()
viewButton.removeAttribute('sourceID');
}
}
else {
viewButton.hidden = true;
}
document.getElementById('zotero-item-pane-content').selectedIndex = 2;
}
@ -963,7 +1020,7 @@ var ZoteroPane = new function()
tabs.hidden = true;
var attachmentBox = document.getElementById('zotero-attachment-box');
attachmentBox.mode = this.itemsView.readOnly ? 'view' : 'edit';
attachmentBox.mode = this.collectionsView.editable ? 'edit' : 'view';
attachmentBox.item = item.ref;
document.getElementById('zotero-item-pane-content').selectedIndex = 3;
@ -973,16 +1030,16 @@ var ZoteroPane = new function()
else
{
document.getElementById('zotero-item-pane-content').selectedIndex = 1;
if (this.itemsView.readOnly) {
document.getElementById('zotero-view-item').selectedIndex = 0;
ZoteroItemPane.viewItem(item.ref, 'view');
tabs.hidden = true;
}
else {
if (this.collectionsView.editable) {
ZoteroItemPane.viewItem(item.ref);
tabs.selectedIndex = document.getElementById('zotero-view-item').selectedIndex;
tabs.hidden = false;
}
else {
document.getElementById('zotero-view-item').selectedIndex = 0;
ZoteroItemPane.viewItem(item.ref, 'view');
tabs.hidden = true;
}
}
}
else
@ -1041,21 +1098,26 @@ var ZoteroPane = new function()
function duplicateSelectedItem() {
var item = this.getSelectedItems()[0];
if (item.getTags()) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
ps.alert(null, "Error", "Duplication of tagged items is not available in this Zotero release.");
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
var newItem = this.getSelectedItems()[0].clone();
var newItemID = newItem.save()
var newItem = Zotero.Items.get(newItemID);
var item = this.getSelectedItems()[0];
// Create new unsaved clone item in target library
var newItem = new Zotero.Item(item.itemTypeID);
newItem.libraryID = item.libraryID;
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
var id = newItem.save();
var newItem = Zotero.Items.get(id);
item.clone(false, newItem);
newItem.save();
if (this.itemsView._itemGroup.isCollection()) {
this.itemsView._itemGroup.ref.addItem(newItem.id);
this.selectItem(newItemID);
this.selectItem(newItem.id);
}
}
@ -1070,17 +1132,23 @@ var ZoteroPane = new function()
*/
this.deleteSelectedItems = function (force) {
if (this.itemsView && this.itemsView.selection.count > 0) {
var itemGroup = this.itemsView._itemGroup;
if (!itemGroup.isTrash() && !this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
if (!force){
if (this.itemsView._itemGroup.isCollection()) {
if (itemGroup.isCollection()) {
var noPrompt = true;
}
// Do nothing in search and share views
else if (this.itemsView._itemGroup.isSearch() ||
this.itemsView._itemGroup.isShare()) {
else if (itemGroup.isSearch() || itemGroup.isShare()) {
return;
}
// Do nothing in trash view if any non-deleted items are selected
else if (this.itemsView._itemGroup.isTrash()) {
else if (itemGroup.isTrash()) {
var start = {};
var end = {};
for (var i=0, len=this.itemsView.selection.getRangeCount(); i<len; i++) {
@ -1127,6 +1195,11 @@ var ZoteroPane = new function()
function deleteSelectedCollection()
{
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
if (this.collectionsView.selection.count == 1) {
var row =
this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
@ -1179,6 +1252,11 @@ var ZoteroPane = new function()
function editSelectedCollection()
{
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
if (this.collectionsView.selection.count > 0) {
var row = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
@ -1196,7 +1274,8 @@ var ZoteroPane = new function()
}
}
else {
var s = new Zotero.Search(row.ref.id);
var s = new Zotero.Search();
s.id = row.ref.id;
var io = {dataIn: {search: s, name: row.getName()}, dataOut: null};
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
if (io.dataOut) {
@ -1310,24 +1389,55 @@ var ZoteroPane = new function()
function selectItem(itemID, inLibrary, expand)
{
if (!itemID) {
return;
return false;
}
if (this.itemsView) {
if (!this.itemsView._itemGroup.isLibrary() && inLibrary) {
this.collectionsView.selection.select(0);
}
var selected = this.itemsView.selectItem(itemID, expand);
if (!selected) {
this.collectionsView.selection.select(0);
this.itemsView.selectItem(itemID, expand);
}
var item = Zotero.Items.get(itemID);
if (!item) {
return false;
}
if (!this.itemsView) {
Components.utils.reportError("Items view not set in ZoteroPane.selectItem()");
return false;
}
var currentLibraryID = this.getSelectedLibraryID();
// If in a different library
if (item.libraryID != currentLibraryID) {
this.collectionsView.selectLibrary(item.libraryID);
}
// Force switch to library view
else if (!this.itemsView._itemGroup.isLibrary() && inLibrary) {
this.collectionsView.selectLibrary(item.libraryID);
}
var selected = this.itemsView.selectItem(itemID, expand);
if (!selected) {
this.collectionsView.selectLibrary(item.libraryID);
this.itemsView.selectItem(itemID, expand);
}
return true;
}
this.getSelectedLibraryID = function () {
var group = this.getSelectedGroup();
if (group) {
return group.libraryID;
}
var collection = this.getSelectedCollection();
if (collection) {
return collection.libraryID;
}
return null;
}
function getSelectedCollection(asID) {
if (this.collectionsView.selection
if (this.collectionsView
&& this.collectionsView.selection
&& this.collectionsView.selection.count > 0
&& this.collectionsView.selection.currentIndex != -1) {
var collection = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
@ -1335,20 +1445,10 @@ var ZoteroPane = new function()
return asID ? collection.ref.id : collection.ref;
}
}
// If the Zotero pane hasn't yet been opened, use the lastViewedFolder pref
else {
var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder');
var matches = lastViewedFolder.match(/^(?:(C|S)([0-9]+)|L)$/);
if (matches && matches[1] == 'C') {
var col = Zotero.Collections.get(matches[2]);
if (col) {
return asID ? col.id : col;
}
}
}
return false;
}
function getSelectedSavedSearch(asID)
{
if (this.collectionsView.selection.count > 0 && this.collectionsView.selection.currentIndex != -1) {
@ -1357,20 +1457,10 @@ var ZoteroPane = new function()
return asID ? collection.ref.id : collection.ref;
}
}
// If the Zotero pane hasn't yet been opened, use the lastViewedFolder pref
else {
var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder');
var matches = lastViewedFolder.match(/^(?:(C|S)([0-9]+)|L)$/);
if (matches && matches[1] == 'S') {
var search = Zotero.Search.get(matches[2]);
if (search) {
return asID ? search.id : search;
}
}
}
return false;
}
/*
* Return an array of Item objects for selected items
*
@ -1386,6 +1476,19 @@ var ZoteroPane = new function()
}
this.getSelectedGroup = function (asID) {
if (this.collectionsView.selection
&& this.collectionsView.selection.count > 0
&& this.collectionsView.selection.currentIndex != -1) {
var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
if (itemGroup && itemGroup.isGroup()) {
return asID ? itemGroup.ref.id : itemGroup.ref;
}
}
return false;
}
/*
* Returns an array of Zotero.Item objects of visible items in current sort order
*
@ -1418,10 +1521,9 @@ var ZoteroPane = new function()
}
function buildCollectionContextMenu()
this.buildCollectionContextMenu = function buildCollectionContextMenu()
{
var menu = document.getElementById('zotero-collectionmenu');
var m = {
newCollection: 0,
newSavedSearch: 1,
@ -1437,22 +1539,32 @@ var ZoteroPane = new function()
emptyTrash: 11
};
var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
var enable = [], disable = [], show = [];
// Collection
if (this.collectionsView.selection.count == 1 &&
this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isCollection())
{
var show = [m.newSubcollection, m.sep1, m.editSelectedCollection, m.removeCollection,
m.sep2, m.exportCollection, m.createBibCollection, m.loadReport];
if (itemGroup.isCollection()) {
show = [
m.newSubcollection,
m.sep1,
m.editSelectedCollection,
m.removeCollection,
m.sep2,
m.exportCollection,
m.createBibCollection,
m.loadReport
];
var s = [m.exportCollection, m.createBibCollection, m.loadReport];
if (this.itemsView.rowCount>0) {
var enable = [m.exportCollection, m.createBibCollection, m.loadReport];
enable = s;
}
else if (!this.collectionsView.isContainerEmpty(this.collectionsView.selection.currentIndex)) {
var enable = [m.exportCollection];
var disable = [m.createBibCollection, m.loadReport];
enable = [m.exportCollection];
disable = [m.createBibCollection, m.loadReport];
}
else
{
var disable = [m.exportCollection, m.createBibCollection, m.loadReport];
else {
disable = s;
}
// Adjust labels
@ -1463,17 +1575,22 @@ var ZoteroPane = new function()
menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.collection'));
}
// Saved Search
else if (this.collectionsView.selection.count == 1 &&
this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isSearch()) {
var show = [m.editSelectedCollection, m.removeCollection, m.sep2, m.exportCollection,
m.createBibCollection, m.loadReport];
else if (itemGroup.isSearch()) {
show = [
m.editSelectedCollection,
m.removeCollection,
m.sep2,
m.exportCollection,
m.createBibCollection,
m.loadReport
];
var s = [m.exportCollection, m.createBibCollection, m.loadReport];
if (this.itemsView.rowCount>0) {
var enable = [m.exportCollection, m.createBibCollection, m.loadReport];
enable = s;
}
else
{
var disable = [m.exportCollection, m.createBibCollection, m.loadReport];
else {
disable = s;
}
// Adjust labels
@ -1484,14 +1601,30 @@ var ZoteroPane = new function()
menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.savedSearch'));
}
// Trash
else if (this.collectionsView.selection.count == 1 &&
this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isTrash()) {
var show = [m.emptyTrash];
else if (itemGroup.isTrash()) {
show = [m.emptyTrash];
}
// Header
else if (itemGroup.isHeader()) {
}
// Group
else if (itemGroup.isGroup()) {
show = [m.newCollection];
}
// Library
else
{
var show = [m.newCollection, m.newSavedSearch, m.sep1, m.exportFile];
show = [m.newCollection, m.newSavedSearch, m.sep1, m.exportFile];
}
// Disable some actions if user doesn't have write access
var s = [m.editSelectedCollection, m.removeCollection, m.newCollection, m.newSavedSearch];
if (itemGroup.isGroup() && !itemGroup.ref.editable) {
disable = disable.concat(s);
}
else {
enable = enable.concat(s);
}
for (var i in disable)
@ -1541,7 +1674,7 @@ var ZoteroPane = new function()
var enable = [], disable = [], show = [], hide = [], multiple = '';
// TODO: implement menu for remote items
if (this.itemsView.readOnly) {
if (!this.collectionsView.editable) {
for each(var pos in m) {
disable.push(pos);
}
@ -1725,13 +1858,23 @@ var ZoteroPane = new function()
}
if (tree.id == 'zotero-collections-tree') {
var s = this.getSelectedSavedSearch();
if (s) {
var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
if (itemGroup.isSearch()) {
this.editSelectedCollection();
}
else if (itemGroup.isGroup()) {
var uri = Zotero.URI.getGroupURI(itemGroup.ref, true);
window.loadURI(uri);
}
else if (itemGroup.isHeader()) {
if (itemGroup.ref.id == 'group-libraries-header') {
var uri = Zotero.URI.getGroupsURL();
window.loadURI(uri);
}
}
}
else if (tree.id == 'zotero-items-tree') {
if (this.itemsView.readOnly) {
if (!this.collectionsView.editable) {
return;
}
@ -1893,7 +2036,7 @@ var ZoteroPane = new function()
}
}
var menuitem = document.getElementById("zotero-context-save-link-as-snapshot");
var menuitem = document.getElementById("zotero-context-save-link-as-item");
if (menuitem) {
if (window.gContextMenu.onLink) {
menuitem.hidden = false;
@ -1904,7 +2047,7 @@ var ZoteroPane = new function()
}
}
var menuitem = document.getElementById("zotero-context-save-image-as-snapshot");
var menuitem = document.getElementById("zotero-context-save-image-as-item");
if (menuitem) {
// Not using window.gContextMenu.hasBGImage -- if the user wants it,
// they can use the Firefox option to view and then import from there
@ -1922,26 +2065,32 @@ var ZoteroPane = new function()
}
function newNote(popup, parent, text) {
this.newNote = function (popup, parent, text) {
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return;
}
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
if (!popup) {
if (!text) {
text = '';
}
text = Zotero.Utilities.prototype.trim(text);
var item = new Zotero.Item(false, 'note');
var item = new Zotero.Item('note');
item.libraryID = this.getSelectedLibraryID();
item.setNote(text);
if (parent) {
item.setSource(parent);
}
var itemID = item.save();
if (this.itemsView && this.itemsView._itemGroup.isCollection()) {
if (!parent && this.itemsView && this.itemsView._itemGroup.isCollection()) {
this.itemsView._itemGroup.ref.addItem(itemID);
}
@ -1964,6 +2113,11 @@ var ZoteroPane = new function()
function addTextToNote(text)
{
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
try {
// trim
text = text.replace(/^[\xA0\r\n\s]*(.*)[\xA0\r\n\s]*$/m, "$1");
@ -1996,6 +2150,11 @@ var ZoteroPane = new function()
function openNoteWindow(itemID, col, parentItemID)
{
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
var name = null;
if (itemID) {
@ -2026,6 +2185,20 @@ var ZoteroPane = new function()
function addAttachmentFromDialog(link, id)
{
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
// FIXME: temporarily disable file attachment options for groups
var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
if (itemGroup.isWithinGroup()) {
var pr = Components.classes["@mozilla.org/network/default-prompt;1"]
.getService(Components.interfaces.nsIPrompt);
pr.alert("", "Files cannot currently be added to group libraries.");
return;
}
var nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
@ -2055,80 +2228,120 @@ var ZoteroPane = new function()
}
function addItemFromPage() {
this.addItemFromPage = function (itemType) {
return this.addItemFromDocument(window.content.document, itemType);
}
/**
* @param {Document} doc
* @param {String|Integer} [itemType='webpage'] Item type id or name
* @param {Boolean} [saveSnapshot] Force saving of a snapshot,
* regardless of automaticSnapshots pref
*/
this.addItemFromDocument = function (doc, itemType, saveSnapshot) {
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return false;
}
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
var progressWin = new Zotero.ProgressWindow();
progressWin.changeHeadline(Zotero.getString('ingester.scraping'));
var icon = 'chrome://zotero/skin/treeitem-webpage.png';
progressWin.addLines(window.content.document.title, icon)
progressWin.addLines(doc.title, icon)
progressWin.show();
progressWin.startCloseTimer();
var data = {
title: window.content.document.title,
url: window.content.document.location.href,
title: doc.title,
url: doc.location.href,
accessDate: "CURRENT_TIMESTAMP"
}
var item = this.newItem(Zotero.ItemTypes.getID('webpage'), data);
// Save web page item by default
if (!itemType) {
itemType = 'webpage';
}
itemType = Zotero.ItemTypes.getID(itemType);
var item = this.newItem(itemType, data);
// Automatically save snapshot if pref set
if (item.id && Zotero.Prefs.get('automaticSnapshots'))
{
var f = function() {
// We set |noParent|, since child items don't belong to collections
ZoteroPane.addAttachmentFromPage(false, item.id, true);
var filesEditable = false;
if (item.libraryID) {
var group = Zotero.Groups.getByLibraryID(item.libraryID);
filesEditable = group.filesEditable;
}
// Save snapshot if explicitly enabled or automatically pref is set and not explicitly disabled
if (saveSnapshot || (saveSnapshot !== false && Zotero.Prefs.get('automaticSnapshots'))) {
var link = false;
if (link) {
Zotero.Attachments.linkFromDocument(doc, item.id);
}
else if (filesEditable) {
Zotero.Attachments.importFromDocument(doc, item.id);
}
// Give progress window time to appear
setTimeout(f, 300);
}
return item.id;
}
this.addItemFromURL = function (url, itemType) {
if (url == window.content.document.location.href) {
return this.addItemFromPage(itemType);
}
var processor = function (doc) {
ZoteroPane.addItemFromDocument(doc, itemType);
};
var done = function () {}
var exception = function (e) {
Zotero.debug(e);
}
Zotero.Utilities.HTTP.processDocuments([url], processor, done, exception);
}
/*
* Create an attachment from the current page
*
* |link| -- create web link instead of snapshot
* |itemID| -- itemID of parent item
* |noParent| -- don't add to current collection
* |link| -- create web link instead of snapshot
*/
function addAttachmentFromPage(link, itemID, noParent)
this.addAttachmentFromPage = function (link, itemID)
{
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return;
}
if (!noParent) {
var progressWin = new Zotero.ProgressWindow();
progressWin.changeHeadline(Zotero.getString('save.' + (link ? 'link' : 'attachment')));
var type = link ? 'web-link' : 'snapshot';
var icon = 'chrome://zotero/skin/treeitem-attachment-' + type + '.png';
progressWin.addLines(window.content.document.title, icon)
progressWin.show();
progressWin.startCloseTimer();
if (this.itemsView && this.itemsView._itemGroup.isCollection()) {
var parentCollectionID = this.itemsView._itemGroup.ref.id;
}
if (typeof itemID != 'number') {
throw ("itemID must be an integer in ZoteroPane.addAttachmentFromPage()");
}
var f = function() {
if (link) {
Zotero.Attachments.linkFromDocument(window.content.document, itemID, parentCollectionID);
}
else {
Zotero.Attachments.importFromDocument(window.content.document, itemID, false, parentCollectionID);
}
var progressWin = new Zotero.ProgressWindow();
progressWin.changeHeadline(Zotero.getString('save.' + (link ? 'link' : 'attachment')));
var type = link ? 'web-link' : 'snapshot';
var icon = 'chrome://zotero/skin/treeitem-attachment-' + type + '.png';
progressWin.addLines(window.content.document.title, icon)
progressWin.show();
progressWin.startCloseTimer();
if (link) {
Zotero.Attachments.linkFromDocument(window.content.document, itemID);
}
else {
Zotero.Attachments.importFromDocument(window.content.document, itemID);
}
// Give progress window time to appear
setTimeout(f, 100);
}
@ -2225,6 +2438,28 @@ var ZoteroPane = new function()
}
/**
* Test if the user can edit the currently selected library/collection,
* and display an error if not
*
* @return {Boolean} TRUE if user can edit, FALSE if not
*/
this.canEdit = function () {
var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
return itemGroup.isEditable();
}
this.displayCannotEditLibraryMessage = function () {
var pr = Components.classes["@mozilla.org/network/default-prompt;1"]
.getService(Components.interfaces.nsIPrompt);
pr.alert(
Zotero.getString('general.accessDenied'),
"You cannot make changes to the currently selected library."
);
}
function showAttachmentNotFoundDialog(itemID, noLocate) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
@ -2254,6 +2489,11 @@ var ZoteroPane = new function()
function relinkAttachment(itemID) {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
var item = Zotero.Items.get(itemID);
if (!item) {
throw('Item ' + itemID + ' not found in ZoteroPane.relinkAttachment()');

View file

@ -60,12 +60,13 @@
<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 itemID = ZoteroPane.addItemFromPage(); ZoteroPane.newNote(false, itemID, str)"/>
<menuitem id="zotero-context-save-link-as-snapshot" class="menu-iconic"
label="&zotero.contextMenu.saveLinkAsSnapshot;" hidden="true"
oncommand="Zotero.Attachments.importFromURL(window.gContextMenu.linkURL, false, false, false, ZoteroPane.getSelectedCollection(true))"/>
<menuitem id="zotero-context-save-image-as-snapshot" class="menu-iconic"
label="&zotero.contextMenu.saveImageAsSnapshot;" hidden="true"
oncommand="Zotero.Attachments.importFromURL(window.gContextMenu.onImage ? window.gContextMenu.imageURL : window.gContextMenu.bgImageURL, false, false, false, ZoteroPane.getSelectedCollection(true))"/>
<!-- TODO: localize and remove zotero.contextMenu.saveLinkAsItem/saveImageAsSnapshot -->
<menuitem id="zotero-context-save-link-as-item" class="menu-iconic"
label="Save Link as Zotero Item" hidden="true"
oncommand="ZoteroPane.addItemFromURL(window.gContextMenu.linkURL)"/>
<menuitem id="zotero-context-save-image-as-item" class="menu-iconic"
label="Save Image as Zotero Item" hidden="true"
oncommand="ZoteroPane.addItemFromURL(window.gContextMenu.onImage ? (window.gContextMenu.mediaURL ? window.gContextMenu.mediaURL : window.gContextMenu.imageURL) : window.gContextMenu.bgImageURL, 'artwork')"/>
</popup>
<vbox id="appcontent">
@ -96,8 +97,8 @@
<menuitem label="&zotero.items.menu.showInLibrary;" oncommand="ZoteroPane.selectItem(this.parentNode.getAttribute('itemID'), true)"/>
<menuseparator/>
<menuitem label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane.newNote(false, this.parentNode.getAttribute('itemID'))"/>
<menuitem label="&zotero.items.menu.attach.snapshot;" oncommand="ZoteroPane.addAttachmentFromPage(false, this.parentNode.getAttribute('itemID'));"/>
<menuitem label="&zotero.items.menu.attach.link;" oncommand="ZoteroPane.addAttachmentFromPage(true, this.parentNode.getAttribute('itemID'));"/>
<menuitem label="&zotero.items.menu.attach.snapshot;" oncommand="ZoteroPane.addAttachmentFromPage(false, parseInt(this.parentNode.getAttribute('itemID')))"/>
<menuitem label="&zotero.items.menu.attach.link;" oncommand="ZoteroPane.addAttachmentFromPage(true, parseInt(this.parentNode.getAttribute('itemID')))"/>
<menuseparator/>
<menuitem label="&zotero.items.menu.duplicateItem;" oncommand="ZoteroPane.duplicateSelectedItem();"/>
<menuitem oncommand="ZoteroPane.deleteSelectedItems();"/>
@ -118,6 +119,7 @@
<vbox flex="1">
<hbox class="toolbar">
<toolbarbutton id="zotero-tb-collection-add" tooltiptext="&zotero.toolbar.newCollection.label;" oncommand="ZoteroPane.newCollection()"/>
<toolbarbutton id="zotero-tb-group-add" tooltiptext="&zotero.toolbar.newGroup;" oncommand="ZoteroPane.newGroup()"/>
<spacer flex="1"/>
<toolbarbutton id="zotero-tb-tag-selector" tooltiptext="&zotero.toolbar.tagSelector.label;" oncommand="ZoteroPane.toggleTagSelector()"/>
<toolbarbutton id="zotero-tb-actions-menu" tooltiptext="&zotero.toolbar.actions.label;" type="menu">
@ -154,7 +156,7 @@
onmouseover="ZoteroPane.collectionsView.setHighlightedRows();"
ondblclick="ZoteroPane.onDoubleClick(event, this);"
onkeypress="ZoteroPane.handleKeyPress(event, this.id)"
onselect="ZoteroPane.onCollectionSelected();" seltype="single"
onselect="ZoteroPane.onCollectionSelected();" seltype="cell"
ondraggesture="if (event.target.localName == 'treechildren') { ZoteroPane.startDrag(event, ZoteroPane.collectionsView); }"
ondragenter="return ZoteroPane.dragEnter(event, ZoteroPane.collectionsView)"
ondragover="return ZoteroPane.dragOver(event, ZoteroPane.collectionsView)"
@ -192,10 +194,12 @@
</menupopup>
</toolbarbutton>
<toolbarbutton id="zotero-tb-item-from-page" tooltiptext="&zotero.toolbar.newItemFromPage.label;" oncommand="ZoteroPane.addItemFromPage()"/>
<toolbarbutton id="zotero-tb-lookup" tooltiptext="&zotero.toolbar.lookup.label;" oncommand="window.openDialog('chrome://zotero/content/lookup.xul', 'lookup', 'chrome,modal')"/>
<toolbarbutton id="zotero-tb-lookup" tooltiptext="&zotero.toolbar.lookup.label;" oncommand="ZoteroPane.openLookupWindow()"/>
<!--
<toolbarseparator/>
<toolbarbutton id="zotero-tb-link-page" tooltiptext="&zotero.toolbar.attachment.weblink;" oncommand="ZoteroPane.addAttachmentFromPage(true)"/>
<toolbarbutton id="zotero-tb-snapshot-page" tooltiptext="&zotero.toolbar.attachment.snapshot;" oncommand="ZoteroPane.addAttachmentFromPage()"/>
-->
<toolbarbutton id="zotero-tb-note-add" tooltiptext="&zotero.toolbar.note.standalone;" oncommand="ZoteroPane.newNote(event.shiftKey);"/>
<toolbarseparator/>
<toolbarbutton id="zotero-tb-advanced-search" tooltiptext="&zotero.toolbar.advancedSearch;" oncommand="ZoteroPane.openAdvancedSearchWindow()"/>
@ -406,7 +410,7 @@
<!-- Scrape Code -->
<hbox id="urlbar-icons">
<image src="chrome://zotero/skin/treeitem-book.png" id="zotero-status-image" onclick="Zotero_Browser.scrapeThisPage()" position="1" hidden="true"/>
<image src="chrome://zotero/skin/treeitem-book.png" id="zotero-status-image" onclick="Zotero_Browser.scrapeThisPage(ZoteroPane.getSelectedLibraryID(), ZoteroPane.getSelectedCollection(true))" position="1" hidden="true"/>
</hbox>
<statusbar id="status-bar">

View file

@ -329,8 +329,7 @@ function handleSyncReset(action) {
var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
.getService(Components.interfaces.nsIAppStartup);
appStartup.quit(Components.interfaces.nsIAppStartup.eRestart);
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
appStartup.quit(Components.interfaces.nsIAppStartup.eRestart | Components.interfaces.nsIAppStartup.eAttemptQuit);
};
// TODO: better way of checking for an active session?

View file

@ -244,11 +244,10 @@ To add a new preference:
<groupbox>
<caption label="Storage Server"/>
<hbox>
<checkbox label="Enable file syncing" preference="pref-storage-enabled"/>
</hbox>
<separator class="thin"/>
<label value="Please note: Attachment files in group libraries are not currently synced."/>
<separator/>
<grid id="storage-settings">
<columns>
@ -714,8 +713,8 @@ To add a new preference:
<!-- These mess up the prefwindow (more) if they come before the prefpanes
https://bugzilla.mozilla.org/show_bug.cgi?id=296418 -->
<script src="chrome://zotero/content/include.js"/>
<script src="chrome://zotero/content/charsetMenu.js"/>
<script src="chrome://zotero/content/include.js"></script>
<script src="chrome://zotero/content/charsetMenu.js"></script>
<script type="application/javascript">
<![CDATA[
var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);

View file

@ -53,7 +53,7 @@
<hbox flex="1">
<tree id="zotero-collections-tree"
style="width: 200px;" hidecolumnpicker="true" seltype="single"
style="width: 200px;" hidecolumnpicker="true" seltype="cell"
onselect="onCollectionSelected();">
<treecols>
<treecol

View file

@ -56,7 +56,11 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item(false, 'attachment');
var attachmentItem = new Zotero.Item('attachment');
if (sourceItemID) {
var parentItem = Zotero.Items.get(sourceItemID);
attachmentItem.libraryID = parentItem.libraryID;
}
attachmentItem.setField('title', title);
attachmentItem.setSource(sourceItemID);
attachmentItem.attachmentLinkMode = this.LINK_MODE_IMPORTED_FILE;
@ -128,7 +132,11 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item(false, 'attachment');
var attachmentItem = new Zotero.Item('attachment');
if (sourceItemID) {
var parentItem = Zotero.Items.get(sourceItemID);
attachmentItem.libraryID = parentItem.libraryID;
}
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
attachmentItem.setSource(sourceItemID);
@ -182,6 +190,13 @@ Zotero.Attachments = new function(){
function importFromURL(url, sourceItemID, forceTitle, forceFileBaseName, parentCollectionIDs){
Zotero.debug('Importing attachment from URL');
if (sourceItemID && parentCollectionIDs) {
var msg = "parentCollectionIDs is ignored when sourceItemID is set in Zotero.Attachments.importFromURL()";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
parentCollectionIDs = undefined;
}
// Throw error on invalid URLs
//
// TODO: allow other schemes
@ -264,7 +279,11 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item(false, 'attachment');
var attachmentItem = new Zotero.Item('attachment');
if (sourceItemID) {
var parentItem = Zotero.Items.get(sourceItemID);
attachmentItem.libraryID = parentItem.libraryID;
}
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
@ -292,6 +311,7 @@ Zotero.Attachments = new function(){
wbp.progressListener = new Zotero.WebProgressFinishListener(function(){
try {
var str = Zotero.File.getSample(file);
if (mimeType == 'application/pdf' &&
Zotero.MIME.sniffForMIMEType(str) != 'application/pdf') {
Zotero.debug("Downloaded PDF did not have MIME type "
@ -432,6 +452,13 @@ Zotero.Attachments = new function(){
function linkFromDocument(document, sourceItemID, parentCollectionIDs){
Zotero.debug('Linking attachment from document');
if (sourceItemID && parentCollectionIDs) {
var msg = "parentCollectionIDs is ignored when sourceItemID is set in Zotero.Attachments.linkFromDocument()";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
parentCollectionIDs = undefined;
}
var url = document.location.href;
var title = document.title; // TODO: don't use Mozilla-generated title for images, etc.
var mimeType = document.contentType;
@ -475,6 +502,13 @@ Zotero.Attachments = new function(){
function importFromDocument(document, sourceItemID, forceTitle, parentCollectionIDs, callback) {
Zotero.debug('Importing attachment from document');
if (sourceItemID && parentCollectionIDs) {
var msg = "parentCollectionIDs is ignored when sourceItemID is set in Zotero.Attachments.importFromDocument()";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
parentCollectionIDs = undefined;
}
var url = document.location.href;
var title = forceTitle ? forceTitle : document.title;
var mimeType = document.contentType;
@ -496,7 +530,11 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item(false, 'attachment');
var attachmentItem = new Zotero.Item('attachment');
if (sourceItemID) {
var parentItem = Zotero.Items.get(sourceItemID);
attachmentItem.libraryID = parentItem.libraryID;
}
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
@ -679,7 +717,7 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item(false, 'attachment');
var attachmentItem = new Zotero.Item('attachment');
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
@ -1118,7 +1156,14 @@ Zotero.Attachments = new function(){
function _addToDB(file, url, title, linkMode, mimeType, charsetID, sourceItemID) {
Zotero.DB.beginTransaction();
var attachmentItem = new Zotero.Item(false, 'attachment');
var attachmentItem = new Zotero.Item('attachment');
if (sourceItemID) {
var parentItem = Zotero.Items.get(sourceItemID);
if (parentItem.libraryID && linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
throw ("Cannot save linked file in non-local library");
}
attachmentItem.libraryID = parentItem.libraryID;
}
attachmentItem.setField('title', title);
if (linkMode == self.LINK_MODE_IMPORTED_URL
|| linkMode == self.LINK_MODE_LINKED_URL) {

View file

@ -36,7 +36,7 @@ Zotero.CollectionTreeView = function()
this._treebox = null;
this.itemToSelect = null;
this._highlightedRows = {};
this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share']);
this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group']);
this.showDuplicates = false;
}
@ -72,7 +72,7 @@ Zotero.CollectionTreeView.prototype.setTree = function(treebox)
// Select the last-viewed collection
var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder');
var matches = lastViewedFolder.match(/^(?:(C|S)([0-9]+)|L)$/);
var matches = lastViewedFolder.match(/^(?:(C|S|G)([0-9]+)|L)$/);
var select = 0;
if (matches) {
if (matches[1] == 'C') {
@ -131,7 +131,12 @@ Zotero.CollectionTreeView.prototype.setTree = function(treebox)
else if (matches[1] == 'S' && this._searchRowMap[matches[2]]) {
select = this._searchRowMap[matches[2]];
}
else if (matches[1] == 'G' && this._groupRowMap[matches[2]]) {
select = this._groupRowMap[matches[2]];
}
}
this.selection.currentColumn = this._treebox.columns.getFirstColumn();
this.selection.select(select);
}
@ -145,32 +150,67 @@ Zotero.CollectionTreeView.prototype.refresh = function()
var oldCount = this.rowCount;
this._dataItems = [];
this.rowCount = 0;
this._showItem(new Zotero.ItemGroup('library',null),0,1);
this._showItem(new Zotero.ItemGroup('library', { id: null, libraryID: null }), 0, 1, 1); // itemgroup ref, level, beforeRow, startOpen
var newRows = Zotero.getCollections();
for(var i = 0; i < newRows.length; i++)
this._showItem(new Zotero.ItemGroup('collection',newRows[i]), 0, this._dataItems.length); //itemgroup ref, level, beforeRow
var collections = Zotero.getCollections();
for (var i=0; i<collections.length; i++) {
// Skip group collections
if (collections[i].libraryID) {
continue;
}
this._showItem(new Zotero.ItemGroup('collection', collections[i]), 1);
}
var savedSearches = Zotero.Searches.getAll();
if (savedSearches) {
for (var i=0; i<savedSearches.length; i++) {
this._showItem(new Zotero.ItemGroup('search',savedSearches[i]), 0, this._dataItems.length); //itemgroup ref, level, beforeRow
}
}
var shares = Zotero.Zeroconf.instances;
if (shares) {
for each(var share in shares) {
this._showItem(new Zotero.ItemGroup('share', share), 0, this._dataItems.length); //itemgroup ref, level, beforeRow
this._showItem(new Zotero.ItemGroup('search', savedSearches[i]), 1);
}
}
var deletedItems = Zotero.Items.getDeleted();
if (deletedItems || Zotero.Prefs.get("showTrashWhenEmpty")) {
this._showItem(new Zotero.ItemGroup('trash', null), 0, this._dataItems.length);
this._showItem(new Zotero.ItemGroup('trash', false), 1);
}
this.trashNotEmpty = !!deletedItems;
var groups = Zotero.Groups.getAll();
if (groups.length) {
this._showItem(new Zotero.ItemGroup('separator', false));
var self = this;
var header = {
id: "group-libraries-header",
label: "Group Libraries", // TODO: localize
expand: function (groups) {
if (!groups) {
var groups = Zotero.Groups.getAll();
}
for (var i=0; i<groups.length; i++) {
var startOpen = groups[i].hasCollections();
self._showItem(new Zotero.ItemGroup('group', groups[i]), 1, null, startOpen);
// Add group collections
var collections = groups[i].getCollections();
for (var j=0; j<collections.length; j++) {
self._showItem(new Zotero.ItemGroup('collection', collections[j]), 2);
}
}
}
};
this._showItem(new Zotero.ItemGroup('header', header), null, null, true);
header.expand(groups);
}
var shares = Zotero.Zeroconf.instances;
if (shares.length) {
this._showItem(new Zotero.ItemGroup('separator', false));
for each(var share in shares) {
this._showItem(new Zotero.ItemGroup('share', share));
}
}
this._refreshHashMap();
// Update the treebox's row count
@ -248,6 +288,12 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
rows.push(this._searchRowMap[ids[i]]);
}
break;
case 'group':
if (this._groupRowMap[ids[i]] != null) {
rows.push(this._groupRowMap[ids[i]]);
}
break;
}
}
@ -303,13 +349,27 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
}
this.reload();
if (Zotero.Sync.Server.syncInProgress) {
this.rememberSelection(savedSelection);
break;
}
this.selection.select(this._collectionRowMap[collectionID]);
break;
case 'search':
this.reload();
if (Zotero.Sync.Server.syncInProgress) {
this.rememberSelection(savedSelection);
break;
}
this.selection.select(this._searchRowMap[ids]);
break;
case 'group':
this.reload();
// Groups can only be created during sync
this.rememberSelection(savedSelection);
break;
}
}
@ -318,7 +378,7 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
/*
* Set the rows that should be highlighted -- actually highlighting is done
* Set the rows that should be highlighted -- actual highlighting is done
* by getRowProperties based on the array set here
*/
Zotero.CollectionTreeView.prototype.setHighlightedRows = function (ids) {
@ -376,16 +436,36 @@ Zotero.CollectionTreeView.prototype.getCellText = function(row, column)
Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
{
var collectionType = this._getItemAtRow(row).type;
if (collectionType == 'trash' && this.trashNotEmpty) {
collectionType += "-full";
var source = this._getItemAtRow(row);
var collectionType = source.type;
switch (collectionType) {
case 'trash':
if (this.trashNotEmpty) {
collectionType += '-full';
}
break;
case 'collection':
// TODO: group collection
break;
case 'header':
if (source.ref.id == 'group-libraries-header') {
collectionType = 'groups';
}
break;
case 'group':
collectionType = 'library';
break;
}
return "chrome://zotero/skin/treesource-" + collectionType + ".png";
}
Zotero.CollectionTreeView.prototype.isContainer = function(row)
{
return this._getItemAtRow(row).isCollection();
var itemGroup = this._getItemAtRow(row);
return itemGroup.isLibrary(true) || itemGroup.isCollection() || itemGroup.isHeader();
}
Zotero.CollectionTreeView.prototype.isContainerOpen = function(row)
@ -399,6 +479,15 @@ Zotero.CollectionTreeView.prototype.isContainerOpen = function(row)
Zotero.CollectionTreeView.prototype.isContainerEmpty = function(row)
{
var itemGroup = this._getItemAtRow(row);
if (itemGroup.isLibrary()) {
return false;
}
if (itemGroup.isHeader()) {
return false;
}
if (itemGroup.isGroup()) {
return !itemGroup.ref.hasCollections();
}
if (itemGroup.isCollection()) {
return !itemGroup.ref.hasChildCollections();
}
@ -440,22 +529,30 @@ Zotero.CollectionTreeView.prototype.toggleOpenState = function(row)
var thisLevel = this.getLevel(row);
this._treebox.beginUpdateBatch();
if(this.isContainerOpen(row))
{
if (this.isContainerOpen(row)) {
while((row + 1 < this._dataItems.length) && (this.getLevel(row + 1) > thisLevel))
{
this._hideItem(row+1);
count--; //count is negative when closing a container because we are removing rows
}
}
else
{
var newRows = Zotero.getCollections(this._getItemAtRow(row).ref.id); //Get children
else {
var itemGroup = this._getItemAtRow(row);
for(var i = 0; i < newRows.length; i++)
{
count++;
this._showItem(new Zotero.ItemGroup('collection',newRows[i]), thisLevel+1, row+i+1); //insert new row
if (itemGroup.type == 'header') {
itemGroup.ref.expand();
}
else {
if (itemGroup.isGroup()) {
var collections = itemGroup.ref.getCollections(); // Get child collections
}
else {
var collections = Zotero.getCollections(itemGroup.ref.id); // Get child collections
}
for (var i=0; i<collections.length; i++) {
count++;
this._showItem(new Zotero.ItemGroup('collection', collections[i]), thisLevel+1, row+i+1); //insert new row
}
}
}
this._dataItems[row][1] = !this._dataItems[row][1]; //toggle container open value
@ -467,6 +564,21 @@ Zotero.CollectionTreeView.prototype.toggleOpenState = function(row)
}
Zotero.CollectionTreeView.prototype.isSelectable = function (row, col) {
var itemGroup = this._getItemAtRow(row);
switch (itemGroup.type) {
case 'separator':
return false;
}
return true;
}
Zotero.CollectionTreeView.prototype.__defineGetter__('editable', function () {
return this._getItemAtRow(this.selection.currentIndex).isEditable();
});
Zotero.CollectionTreeView.prototype.expandAllRows = function(treebox) {
var view = treebox.view;
treebox.beginUpdateBatch();
@ -522,6 +634,39 @@ Zotero.CollectionTreeView.prototype.collapseAllRows = function(treebox) {
/// Additional functions for managing data in the tree
///
////////////////////////////////////////////////////////////////////////////////
/**
* @param {Integer|null} libraryID Library to select, or null for local library
*/
Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
if (Zotero.Sync.Server.syncInProgress) {
Zotero.debug("Sync in progress -- not changing library selection");
return false;
}
// Select local library
if (!libraryID) {
this.selection.select(0);
return true;
}
// Already selected
var itemGroup = this._getItemAtRow(this.selection.currentIndex);
if (itemGroup.ref.libraryID == libraryID) {
return true;
}
// Find library
for (var i=0, rows=this.rowCount; i<rows.length; i++) {
var itemGroup = this._getItemAtRow(this.selection.currentIndex);
if (itemGroup.ref && itemGroup.ref.libraryID == libraryID) {
this.selection.select(i);
return true;
}
}
return false;
}
/*
* Delete the selection
@ -579,9 +724,21 @@ Zotero.CollectionTreeView.prototype.deleteSelection = function()
* level: the indent level of the row
* beforeRow: row index to insert new row before
*/
Zotero.CollectionTreeView.prototype._showItem = function(itemGroup, level, beforeRow)
Zotero.CollectionTreeView.prototype._showItem = function(itemGroup, level, beforeRow, startOpen)
{
this._dataItems.splice(beforeRow, 0, [itemGroup, false, level]);
if (!level) {
level = 0;
}
if (!beforeRow) {
beforeRow = this._dataItems.length;
}
if (!startOpen) {
startOpen = false;
}
this._dataItems.splice(beforeRow, 0, [itemGroup, startOpen, level]);
this.rowCount++;
}
@ -609,18 +766,22 @@ Zotero.CollectionTreeView.prototype.saveSelection = function()
{
for (var i=0, len=this.rowCount; i<len; i++) {
if (this.selection.isSelected(i)) {
if (this._getItemAtRow(i).isLibrary()) {
var itemGroup = this._getItemAtRow(i);
if (itemGroup.isLibrary()) {
return 'L';
}
else if (this._getItemAtRow(i).isCollection()) {
return 'C' + this._getItemAtRow(i).ref.id;
else if (itemGroup.isCollection()) {
return 'C' + itemGroup.ref.id;
}
else if (this._getItemAtRow(i).isSearch()) {
return 'S' + this._getItemAtRow(i).ref.id;
else if (itemGroup.isSearch()) {
return 'S' + itemGroup.ref.id;
}
else if (this._getItemAtRow(i).isTrash()) {
else if (itemGroup.isTrash()) {
return 'T';
}
else if (itemGroup.isGroup()) {
return 'G' + itemGroup.ref.id;
}
}
}
return false;
@ -658,6 +819,7 @@ Zotero.CollectionTreeView.prototype.rememberSelection = function(selection)
}
break;
// Trash
case 'T':
if (this._getItemAtRow(this.rowCount-1).isTrash()){
this.selection.select(this.rowCount-1);
@ -666,6 +828,13 @@ Zotero.CollectionTreeView.prototype.rememberSelection = function(selection)
this.selection.select(0);
}
break;
// Group
case 'G':
if (this._groupRowMap[id] != undefined) {
this.selection.select(this._groupRowMap[i]);
}
break;
}
}
@ -679,12 +848,17 @@ Zotero.CollectionTreeView.prototype._refreshHashMap = function()
{
this._collectionRowMap = [];
this._searchRowMap = [];
this._groupRowMap = [];
for(var i=0; i < this.rowCount; i++){
if (this.isCollection(i)){
this._collectionRowMap[this._getItemAtRow(i).ref.id] = i;
var itemGroup = this._getItemAtRow(i);
if (itemGroup.isCollection(i)) {
this._collectionRowMap[itemGroup.ref.id] = i;
}
else if (this.isSearch(i)){
this._searchRowMap[this._getItemAtRow(i).ref.id] = i;
else if (itemGroup.isSearch(i)) {
this._searchRowMap[itemGroup.ref.id] = i;
}
else if (itemGroup.isGroup(i)) {
this._groupRowMap[itemGroup.ref.id] = i;
}
}
}
@ -730,7 +904,11 @@ Zotero.CollectionTreeCommandController.prototype.onEvent = function(evt)
* Start a drag using nsDragAndDrop.js or HTML 5 Drag and Drop
*/
Zotero.CollectionTreeView.prototype.onDragStart = function(event, transferData, action) {
var collectionID = this._getItemAtRow(this.selection.currentIndex).ref.id;
var itemGroup = this._getItemAtRow(this.selection.currentIndex);
if (!itemGroup.isCollection()) {
return false;
}
var collectionID = itemGroup.ref.id;
// Use nsDragAndDrop.js interface for Firefox 2 and Firefox 3.0
var oldMethod = Zotero.isFx2 || Zotero.isFx30;
@ -789,23 +967,57 @@ Zotero.CollectionTreeView.prototype.canDrop = function(row, orient, dragData)
}
else if(orient == 0) //directly on a row...
{
var rowCollection = this._getItemAtRow(row).ref; //the collection we are dragging over
var itemGroup = this._getItemAtRow(row); //the collection we are dragging over
if (!itemGroup.isEditable()) {
return false;
}
if (dataType == 'zotero/item') {
var ids = data;
for each(var id in ids)
{
var item = Zotero.Items.get(id);
// Can only drag top-level items into collections
if (item.isRegularItem() || !item.getSource())
{
// Make sure there's at least one item that's not already
// in this collection
if (!rowCollection.hasItem(id))
{
return true;
var items = Zotero.Items.get(ids);
for each(var item in items) {
// Can only drag top-level items
if (!(item.isRegularItem() || !item.getSource())) {
continue;
}
// TODO: for now, only allow regular items to be dragged to groups
if (itemGroup.isWithinGroup() && itemGroup.ref.libraryID != item.libraryID
&& !item.isRegularItem()) {
return false;
}
// TODO: for now, skip items that are already linked
if (itemGroup.isWithinGroup() && itemGroup.ref.libraryID != item.libraryID) {
if (item.getLinkedItem(itemGroup.ref.libraryID)) {
continue;
}
}
if (itemGroup.isGroup()) {
// Don't allow drag onto library of same group
if (itemGroup.ref.libraryID == item.libraryID) {
continue;
}
return true;
}
// Allow drag of group items to library
if (item.libraryID && (itemGroup.isLibrary()
|| itemGroup.isCollection() && !itemGroup.isWithinGroup())) {
// TODO: for now, skip items that are already linked
if (item.getLinkedItem()) {
continue;
}
return true;
}
// Make sure there's at least one item that's not already
// in this collection
if (itemGroup.isCollection() && !itemGroup.ref.hasItem(item.id)) {
return true;
}
}
return false;
}
@ -819,9 +1031,8 @@ Zotero.CollectionTreeView.prototype.canDrop = function(row, orient, dragData)
}
return false;
}
else if (dataType == 'text/x-moz-url'
|| dataType == 'application/x-moz-file') {
if (this._getItemAtRow(row).isSearch()) {
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
if (itemGroup.isSearch()) {
return false;
}
// Don't allow folder drag
@ -830,10 +1041,30 @@ Zotero.CollectionTreeView.prototype.canDrop = function(row, orient, dragData)
}
return true;
}
else if (dataType == 'zotero/collection'
// Collections cannot be dropped on themselves, nor in their children
&& data[0] != rowCollection.id
&& !Zotero.Collections.get(data[0]).hasDescendent('collection', rowCollection.id)) {
else if (dataType == 'zotero/collection') {
// Collections cannot be dropped on themselves
if (data[0] == itemGroup.ref.id) {
return false;
}
// Nor in their children
if (Zotero.Collections.get(data[0]).hasDescendent('collection', itemGroup.ref.id)) {
return false;
}
var col = Zotero.Collections.get(data[0]);
// Nor, at least for now, on another group
if (itemGroup.isWithinGroup()) {
if (itemGroup.ref.libraryID != col.libraryID) {
return false;
}
}
// Nor from a group library to the local library
else if (col.libraryID) {
return false;
}
return true;
}
}
@ -853,12 +1084,14 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
var dataType = dragData.dataType;
var data = dragData.data;
var itemGroup = this._getItemAtRow(row);
if(dataType == 'zotero/collection')
{
var targetCollectionID;
if(this._getItemAtRow(row).isCollection())
targetCollectionID = this._getItemAtRow(row).ref.id;
if (itemGroup.isCollection()) {
targetCollectionID = itemGroup.ref.id;
}
var droppedCollection = Zotero.Collections.get(data[0]);
droppedCollection.parent = targetCollectionID;
droppedCollection.save();
@ -869,18 +1102,150 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
return;
}
var toAdd = [];
for (var i=0; i<ids.length; i++) {
var item = Zotero.Items.get(ids[i]);
// Only accept top-level items
if (item.isRegularItem() || !item.getSource()) {
toAdd.push(ids[i]);
if (itemGroup.isWithinGroup()) {
var targetLibraryID = itemGroup.ref.libraryID;
}
else {
var targetLibraryID = null;
}
Zotero.DB.beginTransaction();
var items = Zotero.Items.get(ids);
if (!items) {
return;
}
var newItems = [];
var newIDs = [];
// DEBUG: support items coming from different sources?
if (items[0].libraryID == targetLibraryID) {
var sameLibrary = true;
}
else {
var sameLibrary = false;
}
for each(var item in items) {
if (!(item.isRegularItem() || !item.getSource())) {
continue;
}
if (sameLibrary) {
newIDs.push(item.id);
}
else {
newItems.push(item);
}
}
if (toAdd.length > 0) {
this._getItemAtRow(row).ref.addItems(toAdd);
if (!sameLibrary) {
var toReconcile = [];
for each(var item in newItems) {
// Check if there's already a copy of this item in the library
var linkedItem = item.getLinkedItem(targetLibraryID);
if (linkedItem) {
Zotero.debug("Linked item already exists -- skipping");
continue;
/*
// TODO: support tags, related, attachments, etc.
// Overlay source item fields on unsaved clone of linked item
var newItem = item.clone(false, linkedItem.clone(true));
newItem.setField('dateAdded', item.dateAdded);
newItem.setField('dateModified', item.dateModified);
var diff = newItem.diff(linkedItem, false, ["dateAdded", "dateModified"]);
if (!diff) {
// Check if creators changed
var creatorsChanged = false;
var creators = item.getCreators();
var linkedCreators = linkedItem.getCreators();
if (creators.length != linkedCreators.length) {
Zotero.debug('Creators have changed');
creatorsChanged = true;
}
else {
for (var i=0; i<creators.length; i++) {
if (!creators[i].ref.equals(linkedCreators[i].ref)) {
Zotero.debug('changed');
creatorsChanged = true;
break;
}
}
}
if (!creatorsChanged) {
Zotero.debug("Linked item hasn't changed -- skipping conflict resolution");
continue;
}
}
toReconcile.push([newItem, linkedItem]);
continue;
*/
}
// Create new unsaved clone item in target library
var newItem = new Zotero.Item(item.itemTypeID);
newItem.libraryID = targetLibraryID;
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
var id = newItem.save();
var newItem = Zotero.Items.get(id);
item.clone(false, newItem);
newItem.save();
//var id = newItem.save();
//var newItem = Zotero.Items.get(id);
// Record link
item.addLinkedItem(newItem);
newIDs.push(id);
}
if (toReconcile.length) {
var sourceName = items[0].libraryID ? Zotero.Libraries.getName(items[0].libraryID)
: Zotero.getString('pane.collections.library');
var targetName = targetLibraryID ? Zotero.Libraries.getName(libraryID)
: Zotero.getString('pane.collections.library');
var io = {
dataIn: {
type: "item",
captions: [
// TODO: localize
sourceName,
targetName,
"Merged Item"
],
objects: toReconcile
}
};
/*
if (type == 'item') {
if (!Zotero.Utilities.prototype.isEmpty(changedCreators)) {
io.dataIn.changedCreators = changedCreators;
}
}
*/
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var lastWin = wm.getMostRecentWindow("navigator:browser");
lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io);
for each(var obj in io.dataOut) {
obj.ref.save();
}
}
}
if (newIDs.length && itemGroup.isCollection()) {
itemGroup.ref.addItems(newIDs);
}
Zotero.DB.commitTransaction();
}
else if (dataType == 'zotero/item-xml') {
Zotero.DB.beginTransaction();
@ -901,8 +1266,16 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
return;
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
if (this._getItemAtRow(row).isCollection()) {
var parentCollectionID = this._getItemAtRow(row).ref.id;
// FIXME: temporarily disable dragging in of files
if (dataType == 'application/x-moz-file' && itemGroup.isWithinGroup()) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
ps.alert(null, "", "Files cannot currently be added to group libraries.");
return;
}
if (itemGroup.isCollection()) {
var parentCollectionID = itemGroup.ref.id;
}
else {
var parentCollectionID = false;
@ -972,7 +1345,7 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
* Called by HTML 5 Drag and Drop when dragging over the tree
*/
Zotero.CollectionTreeView.prototype.onDragEnter = function (event) {
Zotero.debug("Storing current drag data");
//Zotero.debug("Storing current drag data");
Zotero.DragDrop.currentDataTransfer = event.dataTransfer;
}
@ -993,7 +1366,7 @@ Zotero.CollectionTreeView.prototype.onDrop = function (event, dropdata, session)
}
Zotero.CollectionTreeView.prototype.onDragExit = function (event) {
Zotero.debug("Clearing drag data");
//Zotero.debug("Clearing drag data");
Zotero.DragDrop.currentDataTransfer = null;
}
@ -1007,7 +1380,6 @@ Zotero.CollectionTreeView.prototype.onDragExit = function (event) {
////////////////////////////////////////////////////////////////////////////////
Zotero.CollectionTreeView.prototype.isSorted = function() { return false; }
Zotero.CollectionTreeView.prototype.isSeparator = function(row) { return false; }
Zotero.CollectionTreeView.prototype.isEditable = function(row, idx) { return false; }
/* Set 'highlighted' property on rows set by setHighlightedRows */
@ -1021,6 +1393,10 @@ Zotero.CollectionTreeView.prototype.getRowProperties = function(row, props) {
Zotero.CollectionTreeView.prototype.getColumnProperties = function(col, prop) { }
Zotero.CollectionTreeView.prototype.getCellProperties = function(row, col, prop) { }
Zotero.CollectionTreeView.prototype.isSeparator = function(index) {
var source = this._getItemAtRow(index);
return source.type == 'separator';
}
Zotero.CollectionTreeView.prototype.performAction = function(action) { }
Zotero.CollectionTreeView.prototype.performActionOnCell = function(action, row, col) { }
Zotero.CollectionTreeView.prototype.getProgressMode = function(row, col) { }
@ -1039,8 +1415,11 @@ Zotero.ItemGroup = function(type, ref)
this.ref = ref;
}
Zotero.ItemGroup.prototype.isLibrary = function()
Zotero.ItemGroup.prototype.isLibrary = function(includeGlobal)
{
if (includeGlobal) {
return this.type == 'library' || this.type == 'group';
}
return this.type == 'library';
}
@ -1064,37 +1443,100 @@ Zotero.ItemGroup.prototype.isTrash = function()
return this.type == 'trash';
}
Zotero.ItemGroup.prototype.isGroup = function() {
return this.type == 'group';
}
Zotero.ItemGroup.prototype.isHeader = function () {
return this.type == 'header';
}
Zotero.ItemGroup.prototype.isSeparator = function () {
return this.type == 'separator';
}
// Special
Zotero.ItemGroup.prototype.isWithinGroup = function () {
return this.ref && !!this.ref.libraryID;
}
Zotero.ItemGroup.prototype.isEditable = function () {
if (this.isTrash() || this.isShare()) {
return false;
}
if (!this.isWithinGroup()) {
return true;
}
var libraryID = this.ref.libraryID;
if (this.isGroup()) {
return this.ref.editable;
}
if (this.isCollection()) {
var type = Zotero.Libraries.getType(libraryID);
if (type == 'group') {
var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
var group = Zotero.Groups.get(groupID);
return group.editable;
}
else {
throw ("Unknown library type '" + type + "' in Zotero.ItemGroup.isEditable()");
}
}
}
Zotero.ItemGroup.prototype.getName = function()
{
if (this.isCollection()) {
return this.ref.name;
}
else if (this.isLibrary()) {
return Zotero.getString('pane.collections.library');
}
else if (this.isSearch()) {
return this.ref.name;
}
else if (this.isShare()) {
return this.ref.name;
}
else if (this.isTrash()) {
return Zotero.getString('pane.collections.trash');
}
else {
return "";
switch (this.type) {
case 'collection':
return this.ref.name;
case 'library':
return Zotero.getString('pane.collections.library');
case 'search':
return this.ref.name;
case 'share':
return this.ref.name;
case 'trash':
return Zotero.getString('pane.collections.trash');
case 'group':
return this.ref.name;
case 'header':
return this.ref.label;
default:
return "";
}
}
Zotero.ItemGroup.prototype.getChildItems = function()
{
// Fake results if this is a shared library
if (this.isShare()) {
return this.ref.getAll();
switch (this.type) {
// Fake results if this is a shared library
case 'share':
return this.ref.getAll();
case 'header':
return [];
}
var s = this.getSearchObject();
// FIXME: Hack to exclude group libraries for now
if (this.isSearch()) {
var groups = Zotero.Groups.getAll();
for each(var group in groups) {
s.addCondition('libraryID', 'isNot', group.libraryID);
}
}
try {
var ids;
if (this.showDuplicates) {
@ -1125,8 +1567,14 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
var includeScopeChildren = false;
// Create/load the inner search
var s = new Zotero.Search(this.isSearch() ? this.ref.id : null);
var s = new Zotero.Search();
if (this.isLibrary()) {
s.addCondition('libraryID', 'is', null);
s.addCondition('noChildren', 'true');
includeScopeChildren = true;
}
else if (this.isGroup()) {
s.addCondition('libraryID', 'is', this.ref.libraryID);
s.addCondition('noChildren', 'true');
includeScopeChildren = true;
}
@ -1141,7 +1589,10 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
else if (this.isTrash()) {
s.addCondition('deleted', 'true');
}
else if (!this.isSearch()) {
else if (this.isSearch()) {
s.id = this.ref.id;
}
else {
throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');
}
@ -1172,10 +1623,15 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
* Returns all the tags used by items in the current view
*/
Zotero.ItemGroup.prototype.getChildTags = function() {
// TODO: implement?
if (this.isShare()) {
return false;
switch (this.type) {
// TODO: implement?
case 'share':
return false;
case 'header':
return false;
}
var s = this.getSearchObject();
return Zotero.Tags.getAllWithinSearch(s);
@ -1196,8 +1652,10 @@ Zotero.ItemGroup.prototype.setTags = function(tags)
* Returns TRUE if saved search, quicksearch or tag filter
*/
Zotero.ItemGroup.prototype.isSearchMode = function() {
if (this.isSearch() || this.isTrash()) {
return true;
switch (this.type) {
case 'search':
case 'trash':
return true;
}
// Quicksearch

View file

@ -21,18 +21,23 @@
*/
Zotero.Collection = function(collectionID) {
this._collectionID = collectionID ? collectionID : null;
Zotero.Collection = function() {
if (arguments[0]) {
throw ("Zotero.Collection constructor doesn't take any parameters");
}
this._init();
}
Zotero.Collection.prototype._init = function () {
// Public members for access by public methods -- do not access directly
this._id = null;
this._libraryID = null
this._key = null;
this._name = null;
this._parent = null;
this._parent = false;
this._dateAdded = null;
this._dateModified = null;
this._key = null;
this._loaded = false;
this._changed = false;
@ -50,44 +55,66 @@ Zotero.Collection.prototype._init = function () {
}
Zotero.Collection.prototype.__defineGetter__('id', function () { return this._collectionID; });
Zotero.Collection.prototype.__defineSetter__('collectionID', function (val) { this._set('collectionID', val); });
Zotero.Collection.prototype.__defineGetter__('objectType', function () { return 'collection'; });
Zotero.Collection.prototype.__defineGetter__('id', function () { return this._get('id'); });
Zotero.Collection.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
Zotero.Collection.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
Zotero.Collection.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
Zotero.Collection.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Collection.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
Zotero.Collection.prototype.__defineGetter__('name', function () { return this._get('name'); });
Zotero.Collection.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
Zotero.Collection.prototype.__defineGetter__('parent', function () { return this._get('parent'); });
Zotero.Collection.prototype.__defineSetter__('parent', function (val) { this._set('parent', val); });
Zotero.Collection.prototype.__defineGetter__('parentKey', function () { return this._get('parentKey'); });
Zotero.Collection.prototype.__defineSetter__('parentKey', function (val) { this._set('parentKey', val); });
Zotero.Collection.prototype.__defineGetter__('dateAdded', function () { return this._get('dateAdded'); });
Zotero.Collection.prototype.__defineSetter__('dateAdded', function (val) { this._set('dateAdded', val); });
Zotero.Collection.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
Zotero.Collection.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
Zotero.Collection.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Collection.prototype.__defineSetter__('key', function (val) { this._set('key', val); });
//Zotero.Collection.prototype.__defineSetter__('childCollections', function (arr) { this._setChildCollections(arr); });
Zotero.Collection.prototype.__defineSetter__('childItems', function (arr) { this._setChildItems(arr); });
Zotero.Collection.prototype._get = function (field) {
if (this.id && !this._loaded) {
if ((this._id || this._key) && !this._loaded) {
this.load();
}
switch (field) {
case 'parent':
return this._getParent();
case 'parentKey':
return this._getParentKey();
}
return this['_' + field];
}
Zotero.Collection.prototype._set = function (field, val) {
if (field == 'name') {
val = Zotero.Utilities.prototype.trim(val);
}
switch (field) {
case 'id': // set using constructor
//case 'collectionID': // set using constructor
throw ("Invalid field '" + field + "' in Zotero.Collection.set()");
case 'id':
case 'libraryID':
case 'key':
if (val == this['_' + field]) {
return;
}
if (this._loaded) {
throw ("Cannot set " + field + " after object is already loaded in Zotero.Collection._set()");
}
//this._checkValue(field, val);
this['_' + field] = val;
return;
case 'name':
val = Zotero.Utilities.prototype.trim(val);
break;
}
if (this.id) {
if (this.id || this.key) {
if (!this._loaded) {
this.load();
}
@ -96,6 +123,16 @@ Zotero.Collection.prototype._set = function (field, val) {
this._loaded = true;
}
switch (field) {
case 'parent':
this._setParent(val);
return;
case 'parentKey':
this._setParentKey(val);
return;
}
if (this['_' + field] != val) {
this._prepFieldChange(field);
@ -126,16 +163,35 @@ Zotero.Collection.prototype.getParent = function() {
* Build collection from database
*/
Zotero.Collection.prototype.load = function() {
var id = this._id;
var key = this._key;
var libraryID = this._libraryID;
//var desc = id ? id : libraryID + "/" + key;
// Should be same as query in Zotero.Collections, just with 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 collectionID=?";
var data = Zotero.DB.rowQuery(sql, this.id);
+ "FROM collections C WHERE ";
if (id) {
sql += "collectionID=?";
var params = id;
}
else {
sql += "key=?";
var params = [key];
if (libraryID) {
sql += " AND libraryID=?";
params.push(libraryID);
}
else {
sql += " AND libraryID IS NULL";
}
}
var data = Zotero.DB.rowQuery(sql, params);
this._init();
this._loaded = true;
if (!data) {
@ -154,13 +210,16 @@ Zotero.Collection.prototype.loadFromRow = function(row) {
this._changed = false;
this._previousData = false;
this._collectionID = row.collectionID;
this._id = row.collectionID;
this._libraryID = row.libraryID;
this._key = row.key;
this._name = row.collectionName;
this._parent = row.parentCollectionID;
this._dateAdded = row.dateAdded;
this._dateModified = row.dateModified;
this._key = row.key;
this._childCollectionsLoaded = false;
this._hasChildCollections = !!row.hasChildCollections;
this._childItemsLoaded = false;
this._hasChildItems = !!row.hasChildItems;
this._loadChildItems();
}
@ -288,6 +347,8 @@ Zotero.Collection.prototype.unlockDateModified = function () {
Zotero.Collection.prototype.save = function () {
Zotero.Collections.editCheck(this);
if (!this.name) {
throw ('Collection name is empty in Zotero.Collection.save()');
}
@ -297,54 +358,8 @@ Zotero.Collection.prototype.save = function () {
return false;
}
if (this._changed.parent && this.parent) {
if (!Zotero.Collections.get(this.parent)) {
throw ('Cannot set parent of collection ' + this.id
+ ' to invalid parent ' + this.parent);
}
if (this.parent == this.id) {
throw ('Cannot move collection into itself!');
}
if (this.id && this.hasDescendent('collection', this.parent)) {
throw ('Cannot move collection into one of its own descendents!', 2);
}
}
Zotero.DB.beginTransaction();
// ID change
if (this._changed['collectionID']) {
var oldID = this._previousData.primary.collectionID;
var params = [this.id, oldID];
Zotero.debug("Changing collectionID " + oldID + " to " + this.id);
var row = Zotero.DB.rowQuery("SELECT * FROM collections WHERE collectionID=?", oldID);
// Add a new row so we can update the old rows despite FK checks
// Use temp key due to UNIQUE constraint on key column
Zotero.DB.query("INSERT INTO collections VALUES (?, ?, ?, ?, ?, ?)",
[this.id, row.collectionName, row.parentCollectionID,
row.dateAdded, row.dateModified, 'TEMPKEY']);
Zotero.DB.query("UPDATE collectionItems SET collectionID=? WHERE collectionID=?", params);
Zotero.DB.query("UPDATE collections SET parentCollectionID=? WHERE parentCollectionID=?", params);
Zotero.DB.query("DELETE FROM collections WHERE collectionID=?", oldID);
Zotero.DB.query("UPDATE collections SET key=? WHERE collectionID=?", [row.key, this.id]);
Zotero.Notifier.trigger('id-change', 'collection', oldID + '-' + this.id);
// Update child collections that have cached the previous id
var sql = "SELECT collectionID FROM collections WHERE parentCollectionID=?";
var children = Zotero.DB.columnQuery(sql, this.id);
if (children) {
Zotero.Collections.refreshParents(children);
}
}
var isNew = !this.id || !this.exists();
try {
@ -356,20 +371,55 @@ Zotero.Collection.prototype.save = function () {
var key = this.key ? this.key : this._generateKey();
// Verify parent
if (this._parent) {
if (typeof this._parent == 'number') {
var newParent = Zotero.Collections.get(this._parent);
}
else {
var newParent = Zotero.Collections.getByLibraryAndKey(this.libraryID, this._parent);
}
if (!newParent) {
throw("Cannot set parent to invalid collection " + this._parent + " in Zotero.Collection.save()");
}
if (newParent.id == this.id) {
throw ('Cannot move collection into itself!');
}
if (this.id && this.hasDescendent('collection', newParent.id)) {
throw ('Cannot move collection into one of its own descendents!', 2);
}
var parent = newParent.id;
}
else {
var parent = null;
}
var columns = [
'collectionID', 'collectionName', 'parentCollectionID',
'dateAdded', 'dateModified', 'key'
'collectionID',
'collectionName',
'parentCollectionID',
'dateAdded',
'dateModified',
'clientDateModified',
'libraryID',
'key'
];
var placeholders = ['?', '?', '?', '?', '?', '?'];
var placeholders = ['?', '?', '?', '?', '?', '?', '?', '?'];
var sqlValues = [
collectionID ? { int: collectionID } : null,
{ string: this.name },
this.parent ? { int: this.parent } : null,
parent ? parent : null,
// 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 : null,
key
];
@ -380,20 +430,19 @@ Zotero.Collection.prototype.save = function () {
collectionID = insertID;
}
if (this._changed.parent) {
var parentIDs = [];
if (this._previousData.parent) {
if (this.id && this._previousData.parent) {
parentIDs.push(this._previousData.parent);
}
if (this.parent) {
parentIDs.push(this.parent);
}
Zotero.Notifier.trigger('move', 'collection', this.id);
if (this.id) {
Zotero.Notifier.trigger('move', 'collection', this.id);
}
}
/*
// Subcollections
if (this._changed.childCollections) {
@ -502,6 +551,12 @@ Zotero.Collection.prototype.save = function () {
insertStatement.execute();
}
catch (e) {
Zotero.debug('=======');
Zotero.debug(collectionID);
Zotero.debug(itemID);
Zotero.debug(orderIndex);
Zotero.debug(Zotero.DB.query("SELECT * FROM collections"));
Zotero.debug(Zotero.DB.query("SELECT * FROM collectionItems"));
throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
}
}
@ -510,6 +565,11 @@ Zotero.Collection.prototype.save = function () {
//Zotero.Notifier.trigger('add', 'collection-item', this.id + '-' + itemID);
}
if (this._changed.libraryID) {
var groupID = Zotero.Libraries.getGroupIDFromLibraryID(this.libraryID);
var group = Zotero.Groups.get(groupID);
group.clearCollectionsCache();
}
Zotero.DB.commitTransaction();
}
catch (e) {
@ -519,7 +579,7 @@ Zotero.Collection.prototype.save = function () {
// If successful, set values in object
if (!this.id) {
this._collectionID = collectionID;
this._id = collectionID;
}
if (!this.key) {
@ -537,7 +597,7 @@ Zotero.Collection.prototype.save = function () {
// Invalidate cached child collections
if (parentIDs) {
Zotero.Collections.refreshChildCollections(parentIDs)
Zotero.Collections.refreshChildCollections(parentIDs);
}
return this.id;
@ -569,8 +629,8 @@ Zotero.Collection.prototype.addItem = function(itemID) {
sql = "INSERT OR IGNORE INTO collectionItems VALUES (?,?,?)";
Zotero.DB.query(sql, [this.id, itemID, nextOrderIndex]);
sql = "UPDATE collections SET dateModified=? WHERE collectionID=?";
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, this.id]);
sql = "UPDATE collections SET dateModified=?, clientDateModified=? WHERE collectionID=?";
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime, this.id]);
Zotero.DB.commitTransaction();
@ -618,8 +678,8 @@ Zotero.Collection.prototype.removeItem = function(itemID) {
Zotero.DB.query(sql, [this.id, itemID]);
if (!this._dateModifiedLocked) {
sql = "UPDATE collections SET dateModified=? WHERE collectionID=?";
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, this.id])
sql = "UPDATE collections SET dateModified=?, clientDateModified=? WHERE collectionID=?";
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime, this.id])
}
Zotero.DB.commitTransaction();
@ -812,9 +872,10 @@ Zotero.Collection.prototype.serialize = function(nested) {
var obj = {
primary: {
collectionID: this.id,
libraryID: this.libraryID,
key: this.key,
dateAdded: this.dateAdded,
dateModified: this.dateModified,
key: this.key
dateModified: this.dateModified
},
fields: {
name: this.name,
@ -835,7 +896,7 @@ Zotero.Collection.prototype.serialize = function(nested) {
* @param bool nested Return multidimensional array with 'children'
* nodes instead of flat array
* @param string type 'item', 'collection', or FALSE for both
* @return {Object[]} Array of objects with 'id',
* @return {Object[]} Array of objects with 'id', 'key',
* 'type' ('item' or 'collection'), 'parent',
* and, if collection, 'name' and the nesting 'level'
*/
@ -853,10 +914,10 @@ Zotero.Collection.prototype.getChildren = function(recursive, nested, type, leve
// 0 == collection
// 1 == item
var children = Zotero.DB.query('SELECT collectionID AS id, '
+ "0 AS type, collectionName AS collectionName "
+ "0 AS type, collectionName AS collectionName, key "
+ 'FROM collections WHERE parentCollectionID=?1'
+ ' UNION SELECT itemID AS id, 1 AS type, NULL AS collectionName '
+ 'FROM collectionItems WHERE collectionID=?1', this.id);
+ ' UNION SELECT itemID AS id, 1 AS type, NULL AS collectionName, key '
+ 'FROM collectionItems JOIN items USING (itemID) WHERE collectionID=?1', this.id);
if (type) {
switch (type) {
@ -879,6 +940,7 @@ Zotero.Collection.prototype.getChildren = function(recursive, nested, type, leve
toReturn.push({
id: children[i].id,
name: children[i].collectionName,
key: children[i].key,
type: 'collection',
level: level,
parent: this.id
@ -905,6 +967,7 @@ Zotero.Collection.prototype.getChildren = function(recursive, nested, type, leve
if (!type || type=='item') {
toReturn.push({
id: children[i].id,
key: children[i].key,
type: 'item',
parent: this.id
});
@ -939,6 +1002,139 @@ Zotero.Collection.prototype._prepFieldChange = function (field) {
}
/**
* Get the collectionID of the parent collection
* @return {Integer}
*/
Zotero.Collection.prototype._getParent = function() {
if (this._parent !== false) {
if (!this._parent) {
return null;
}
if (typeof this._parent == 'number') {
return this._parent;
}
var parentCollection = Zotero.Collections.getByLibraryAndKey(this.libraryID, this._parent);
if (!parentCollection) {
throw ("Parent collection for keyed parent doesn't exist in Zotero.Collection._getParent()");
}
// Replace stored key with id
this._parent = parentCollection.id;
return parentCollection.id;
}
if (!this.id) {
return false;
}
var sql = "SELECT parentCollectionID FROM collections WHERE collectionID=?";
var parentCollectionID = Zotero.DB.valueQuery(sql, this.id);
if (!parentCollectionID) {
parentCollectionID = null;
}
this._parent = parentCollectionID;
return parentCollectionID;
}
/**
* Get the key of the parent collection
* @return {String}
*/
Zotero.Collection.prototype._getParentKey = function() {
if (this._parent !== false) {
if (!this._parent) {
return null;
}
if (typeof this._parent == 'string') {
return this._parent;
}
var parentCollection = Zotero.Collections.get(this._parent);
return parentCollection.key;
}
if (!this.id) {
return false;
}
var sql = "SELECT B.key FROM collections A JOIN collections B "
+ "ON (A.parentCollectionID=B.collectionID) WHERE A.collectionID=?";
var key = Zotero.DB.valueQuery(sql, this.id);
if (!key) {
key = null;
}
this._parent = key;
return key;
}
Zotero.Collection.prototype._setParent = function(parentCollectionID) {
if (this.id || this.key) {
if (!this.loaded) {
this.load(true);
}
}
else {
this.loaded = true;
}
var oldParentCollectionID = this._getParent();
if (oldParentCollectionID == parentCollectionID) {
Zotero.debug("Parent collection has not changed for collection " + this.id);
return false;
}
if (this.id && this.exists() && !this._previousData) {
this._previousData = this.serialize();
}
this._parent = parentCollectionID ? parseInt(parentCollectionID) : null;
if (!this._changed) {
this._changed = {};
}
this._changed.parent = true;
return true;
}
Zotero.Collection.prototype._setParentKey = function(parentCollectionKey) {
if (this.id || this.key) {
if (!this.loaded) {
this.load(true);
}
}
else {
this.loaded = true;
}
var oldParentCollectionID = this._getParent();
if (oldParentCollectionID) {
var parentCollection = Zotero.Collections.get(oldParentCollectionID)
var oldParentCollectionKey = parentCollection.key;
}
else {
var oldParentCollectionKey = null;
}
if (oldParentCollectionKey == parentCollectionKey) {
Zotero.debug("Parent collection has not changed in Zotero.Collection._setParentKey()");
return false;
}
if (this.id && this.exists() && !this._previousData) {
this._previousData = this.serialize();
}
this._parent = parentCollectionKey ? parentCollectionKey : null;
if (!this._changed) {
this._changed = {};
}
this._changed.parent = true;
return true;
}
/*
Zotero.Collection.prototype._setChildCollections = function (collectionIDs) {
this._setChildren('collection', collectionIDs);
@ -1053,7 +1249,13 @@ Zotero.Collection.prototype._loadChildCollections = function () {
}
Zotero.Collection.prototype._loadChildItems = function() {
var sql = "SELECT itemID FROM collectionItems WHERE collectionID=? ";
if (!this.id) {
//throw ("Collection id not set in Zotero.Collection._loadChildItems()");
this._childItemsLoaded = true;
return;
}
var sql = "SELECT itemID FROM collectionItems WHERE collectionID=? "
// DEBUG: Fix for child items created via context menu on parent within
// a collection being added to the current collection
+ "AND itemID NOT IN "
@ -1074,22 +1276,6 @@ Zotero.Collection.prototype._loadChildItems = function() {
}
/**
* Note: This is called by Zotero.Collections.refreshParent()
*
* @private
*/
Zotero.Collection.prototype._refreshParent = function () {
if (!this.id) {
throw ("Cannot call Zotero.Collection._refreshParent() on unsaved collection");
}
var sql = "SELECT parentCollectionID FROM collections "
+ "WHERE collectionID=?";
this._parent = Zotero.DB.valueQuery(sql, this.id);
}
/**
* Invalid child collection cache
*

View file

@ -80,23 +80,6 @@ Zotero.Collections = new function() {
}
/**
* Refresh cached parents in specified collections, skipping
* any that aren't loaded
*
* @param {Integer|Integer[]} ids One or more itemIDs
*/
this.refreshParents = function (ids) {
ids = Zotero.flattenArguments(ids);
for each(var id in ids) {
if (this._objectCache[id]) {
this._objectCache[id]._refreshParent();
}
}
}
/**
* Invalidate child collection cache in specified collections, skipping
* any that aren't loaded

View file

@ -21,18 +21,23 @@
*/
Zotero.Creator = function (creatorID) {
this._creatorID = creatorID ? creatorID : null;
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._key = null;
this._dateAdded = null;
this._dateModified = null;
@ -43,10 +48,14 @@ Zotero.Creator.prototype._init = function () {
}
Zotero.Creator.prototype.__defineGetter__('id', function () { return this._creatorID; });
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.__defineSetter__('creatorID', function (val) { this._set('creatorID', val); });
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'); });
@ -59,16 +68,13 @@ Zotero.Creator.prototype.__defineGetter__('dateAdded', function () { return this
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); });
Zotero.Creator.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Creator.prototype.__defineSetter__('key', function (val) { this._set('key', val); });
// Block properties that can't be set this way
Zotero.Creator.prototype.__defineSetter__('id', function () { this._set('id', val); });
Zotero.Creator.prototype.__defineSetter__('creatorDataID', function () { this._set('creatorDataID', val); });
//Zotero.Creator.prototype.__defineSetter__('creatorDataID', function () { this._set('creatorDataID', val); });
Zotero.Creator.prototype._get = function (field) {
if (this.id && !this._loaded) {
if ((this._id || this._key) && !this._loaded) {
this.load(true);
}
return this['_' + field];
@ -77,26 +83,40 @@ Zotero.Creator.prototype._get = function (field) {
Zotero.Creator.prototype._set = function (field, val) {
switch (field) {
case 'id':
case 'libraryID':
case 'key':
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 = value = Zotero.Utilities.prototype.trim(val);
val = Zotero.Utilities.prototype.trim(val);
}
else {
val = value = '';
val = '';
}
break;
}
switch (field) {
case 'id': // set using constructor
//case 'creatorID': // set using constructor
case 'fieldMode':
val = val ? parseInt(val) : 0;
break;
case 'creatorDataID':
throw ("Invalid field '" + field + "' in Zotero.Creator.set()");
}
if (this.id) {
if (this.id || this.key) {
if (!this._loaded) {
this.load(true);
}
@ -121,10 +141,11 @@ Zotero.Creator.prototype._set = function (field, val) {
}
Zotero.Creator.prototype.setFields = function(fields) {
for (var field in fields) {
this[field] = fields[field];
}
Zotero.Creator.prototype.setFields = function (fields) {
this.firstName = fields.firstName;
this.lastName = fields.lastName;
this.fieldMode = fields.fieldMode;
this.birthYear = fields.birthYear;
}
@ -149,6 +170,10 @@ Zotero.Creator.prototype.hasChanged = function () {
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()');
}
@ -164,32 +189,6 @@ Zotero.Creator.prototype.save = function () {
Zotero.DB.beginTransaction();
// ID change
if (this._changed['creatorID']) {
var oldID = this._previousData.primary.creatorID;
var params = [this.id, oldID];
Zotero.debug("Changing creatorID " + oldID + " to " + this.id);
var row = Zotero.DB.rowQuery("SELECT * FROM creators WHERE creatorID=?", oldID);
// Add a new row so we can update the old rows despite FK checks
// Use temp key due to UNIQUE constraint on key column
Zotero.DB.query("INSERT INTO creators VALUES (?, ?, ?, ?, ?)",
[this.id, row.creatorDataID, row.dateAdded, row.dateModified, 'TEMPKEY']);
Zotero.DB.query("UPDATE itemCreators SET creatorID=? WHERE creatorID=?", params);
Zotero.DB.query("DELETE FROM creators WHERE creatorID=?", oldID);
Zotero.DB.query("UPDATE creators SET key=? WHERE creatorID=?", [row.key, this.id]);
Zotero.Notifier.trigger('id-change', 'creator', oldID + '-' + this.id);
// Do this here because otherwise updateLinkedItems() below would
// load a duplicate copy in the new position
Zotero.Creators.reload(this.id);
// update caches
}
var isNew = !this.id || !this.exists();
try {
@ -197,8 +196,6 @@ Zotero.Creator.prototype.save = function () {
var creatorID = this.id ? this.id : Zotero.ID.get('creators');
Zotero.debug("Saving creator " + this.id);
var key = this.key ? this.key : this._generateKey();
// If this was the only creator with the previous data,
@ -229,8 +226,16 @@ Zotero.Creator.prototype.save = function () {
var creatorDataID = Zotero.Creators.getDataID(this, true);
}
var columns = ['creatorID', 'creatorDataID', 'dateAdded', 'dateModified', 'key'];
var placeholders = ['?', '?', '?', '?', '?'];
var columns = [
'creatorID',
'creatorDataID',
'dateAdded',
'dateModified',
'clientDateModified',
'libraryID',
'key'
];
var placeholders = ['?', '?', '?', '?', '?', '?', '?'];
var sqlValues = [
creatorID ? { int: creatorID } : null,
{ int: creatorDataID },
@ -239,6 +244,8 @@ Zotero.Creator.prototype.save = function () {
// If date modified hasn't changed, use current timestamp
this._changed.dateModified ?
this.dateModified : Zotero.DB.transactionDateTime,
Zotero.DB.transactionDateTime,
this.libraryID ? this.libraryID : null,
key
];
@ -267,7 +274,7 @@ Zotero.Creator.prototype.save = function () {
// If successful, set values in object
if (!this.id) {
this._creatorID = creatorID;
this._id = creatorID;
}
if (!this.key) {
this._key = key;
@ -320,9 +327,9 @@ Zotero.Creator.prototype.updateLinkedItems = function () {
}
}
sql = "UPDATE items SET dateModified=? WHERE itemID IN "
sql = "UPDATE items SET dateModified=?, clientDateModified=? WHERE itemID IN "
+ "(SELECT itemID FROM itemCreators WHERE creatorID=?)";
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, this.id]);
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime, this.id]);
Zotero.Items.reload(changedItemIDs);
@ -346,9 +353,10 @@ Zotero.Creator.prototype.serialize = function () {
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.primary.key = this.key;
obj.fields = {};
if (this.fieldMode == 1) {
@ -419,22 +427,41 @@ Zotero.Creator.prototype.erase = function () {
Zotero.Creator.prototype.load = function (allowFail) {
Zotero.debug("Loading data for creator " + this.id + " in Zotero.Creator.load()");
var id = this._id;
var key = this._key;
var libraryID = this._libraryID;
var desc = id ? id : libraryID + "/" + key;
if (!this.id) {
throw ("creatorID not set in Zotero.Creator.load()");
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 C.*, CD.* FROM creators C NATURAL JOIN creatorData CD "
+ "WHERE creatorID=?";
var row = Zotero.DB.rowQuery(sql, this.id);
var sql = "SELECT C.*, CD.* FROM creators C NATURAL JOIN creatorData CD WHERE ";
if (id) {
sql += "creatorID=?";
var params = id;
}
else {
sql += "key=?";
var params = [key];
if (libraryID) {
sql += " AND libraryID=?";
params.push(libraryID);
}
else {
sql += " AND libraryID IS NULL";
}
}
var row = Zotero.DB.rowQuery(sql, params);
if (!row) {
if (allowFail) {
this._loaded = true;
return false;
}
throw ("Creator " + this.id + " not found in Zotero.Item.load()");
throw ("Creator " + desc + " not found in Zotero.Item.load()");
}
this.loadFromRow(row);
@ -444,12 +471,22 @@ Zotero.Creator.prototype.load = function (allowFail) {
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] ? row[col] : null;
continue;
}
this['_' + col] = row[col] ? row[col] : '';
}
this._loaded = true;
}
@ -462,6 +499,18 @@ Zotero.Creator.prototype._checkValue = function (field, value) {
// Data validation
switch (field) {
case 'id':
if (parseInt(value) != value) {
this._invalidValueError(field, value);
}
break;
case 'libraryID':
if (value && parseInt(value) != value) {
this._invalidValueError(field, value);
}
break;
case 'fieldMode':
if (value !== 0 && value !== 1) {
this._invalidValueError(field, value);

View file

@ -42,6 +42,10 @@ Zotero.Creators = new function() {
* Returns a Zotero.Creator object for a given creatorID
*/
function get(creatorID) {
if (!creatorID) {
throw ("creatorID not provided in Zotero.Creators.get()");
}
if (this._objectCache[creatorID]) {
return this._objectCache[creatorID];
}
@ -53,7 +57,9 @@ Zotero.Creators = new function() {
return false;
}
this._objectCache[creatorID] = new Zotero.Creator(creatorID);
var creator = new Zotero.Creator;
creator.id = creatorID;
this._objectCache[creatorID] = creator;
return this._objectCache[creatorID];
}
@ -114,15 +120,28 @@ Zotero.Creators = new function() {
}
function getCreatorsWithData(creatorDataID) {
function getCreatorsWithData(creatorDataID, libraryID) {
var sql = "SELECT creatorID FROM creators WHERE creatorDataID=?";
return Zotero.DB.columnQuery(sql, creatorDataID);
var params = [creatorDataID];
if (libraryID) {
sql += " AND libraryID=?";
params.push(libraryID);
}
else {
sql += " AND libraryID IS NULL";
}
return Zotero.DB.columnQuery(sql, params);
}
function countCreatorsWithData(creatorDataID) {
function countCreatorsWithData(creatorDataID, libraryID) {
var sql = "SELECT COUNT(*) FROM creators WHERE creatorDataID=?";
return Zotero.DB.valueQuery(sql, creatorDataID);
var params = [creatorDataID];
if (libraryID) {
sql += " AND libraryID=?";
params.push(libraryID);
}
return Zotero.DB.valueQuery(sql, params);
}

View file

@ -14,20 +14,86 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
this._ZDO_id = (id ? id : object) + 'ID';
this._ZDO_table = table ? table : this._ZDO_objects;
// Certain object types don't have a libary and key and only use an id
switch (object) {
case 'relation':
this._ZDO_idOnly = true;
break;
default:
this._ZDO_idOnly = false;
}
this._objectCache = {};
this._reloadCache = true;
this.makeLibraryKeyHash = function (libraryID, key) {
var libraryID = libraryID ? libraryID : 0;
return libraryID + '_' + key;
}
this.getLibraryKeyHash = function (obj) {
return this.makeLibraryKeyHash(obj.libraryID, obj.key);
}
this.parseLibraryKeyHash = function (libraryKey) {
var [libraryID, key] = libraryKey.split('_');
libraryID = parseInt(libraryID);
return {
libraryID: libraryID ? libraryID : null,
key: key
};
}
/**
* Retrieves an object by its secondary lookup key
* Retrieves an object of the current by its key
*
* @param string key Secondary lookup key
* @return object Zotero data object, or FALSE if not found
* @param {String} key
* @return {Zotero.DataObject} Zotero data object, or FALSE if not found
*/
this.getByKey = function (key) {
var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table
+ " WHERE key=?";
var id = Zotero.DB.valueQuery(sql, key);
if (arguments.length > 1) {
throw ("getByKey() takes only one argument");
}
Components.utils.reportError("Zotero." + this._ZDO_Objects
+ ".getByKey() is deprecated -- use getByLibraryAndKey()");
return this.getByLibraryAndKey(null, key);
}
/**
* Retrieves an object by its libraryID and key
*
* @param {Integer|NULL} libraryID
* @param {String} key
* @return {Zotero.DataObject} Zotero data object, or FALSE if not found
*/
this.getByLibraryAndKey = function (libraryID, key) {
var sql = "SELECT ROWID FROM " + this._ZDO_table + " WHERE ";
var params = [];
if (this._ZDO_idOnly) {
sql += "ROWID=?";
params.push(key);
}
else {
sql += "libraryID";
if (libraryID) {
sql += "=? ";
params.push(libraryID);
}
else {
sql += " IS NULL ";
}
sql += "AND key=?";
params.push(key);
}
var id = Zotero.DB.valueQuery(sql, params);
if (!id) {
return false;
}
@ -41,8 +107,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
+ "Zotero." + this._ZDO_Objects + ".getOlder()")
}
var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table
+ " WHERE dateModified<?";
var sql = "SELECT ROWID FROM " + this._ZDO_table + " WHERE clientDateModified<?";
return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
}
@ -53,9 +118,9 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
+ "Zotero." + this._ZDO_Objects + ".getNewer()")
}
var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table;
var sql = "SELECT ROWID FROM " + this._ZDO_table;
if (date) {
sql += " WHERE dateModified>?";
sql += " WHERE clientDateModified>?";
return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
}
return Zotero.DB.columnQuery(sql);
@ -75,6 +140,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
var ids = Zotero.flattenArguments(arguments);
Zotero.debug('Reloading ' + 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 ("
@ -87,22 +153,32 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
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;
}
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) {
@ -119,22 +195,16 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
this.reloadAll = function () {
Zotero.debug("Reloading all " + this._ZDO_objects);
// Reset cache keys to itemIDs stored in database using object keys
var sql = "SELECT " + this._ZDO_id + " AS id, key FROM " + this._ZDO_table;
var rows = Zotero.DB.query(sql);
// Remove objects not stored in database
var sql = "SELECT ROWID FROM " + this._ZDO_table;
var ids = Zotero.DB.columnQuery(sql);
var keyIDs = {};
for each(var row in rows) {
keyIDs[row.key] = row.id;
for (var id in this._objectCache) {
if (!ids || ids.indexOf(id) == -1) {
delete this._objectCache[id];
}
}
var store = {};
for each(var obj in this._objectCache) {
store[keyIDs[obj.key]] = obj;
}
this._objectCache = store;
// Reload data
this._reloadCache = true;
this._load();
@ -166,11 +236,16 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
var numDiffs = 0;
var subs = ['primary', 'fields'];
var skipFields = ['collectionID', 'creatorID', 'itemID', 'searchID', 'tagID', 'libraryID', 'key'];
for each(var sub in subs) {
diff[0][sub] = {};
diff[1][sub] = {};
for (var field in data1[sub]) {
if (skipFields.indexOf(field) != -1) {
continue;
}
if (!data1[sub][field] && !data2[sub][field]) {
continue;
}
@ -192,6 +267,10 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
// DEBUG: some of this is probably redundant
for (var field in data2[sub]) {
if (skipFields.indexOf(field) != -1) {
continue;
}
if (diff[0][sub][field] != undefined) {
continue;
}
@ -218,5 +297,39 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
return numDiffs;
}
this.isEditable = function (obj) {
var libraryID = obj.libraryID;
if (!libraryID) {
return true;
}
var type = Zotero.Libraries.getType(libraryID);
switch (type) {
case 'group':
var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
var group = Zotero.Groups.get(groupID);
if (!group.editable) {
return false;
}
if (obj.objectType == 'item' && obj.isAttachment()
&& (obj.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
obj.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE)) {
return group.filesEditable;
}
return true;
default:
throw ("Unsupported library type '" + type + "' in Zotero.DataObjects.isEditable()");
}
}
this.editCheck = function (obj) {
if (!Zotero.Sync.Server.syncInProgress && !this.isEditable(obj)) {
throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library");
}
}
}

View file

@ -0,0 +1,470 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright (c) 2006 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://chnm.gmu.edu
Licensed under the Educational Community License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.opensource.org/licenses/ecl1.php
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***** END LICENSE BLOCK *****
*/
Zotero.Group = function () {
if (arguments[0]) {
throw ("Zotero.Group constructor doesn't take any parameters");
}
this._init();
}
Zotero.Group.prototype._init = function () {
this._id = null;
this._libraryID = null;
this._name = null;
this._description = null;
this._editable = null;
this._filesEditable = null;
this._loaded = false;
this._changed = false;
this._hasCollections = null;
}
Zotero.Group.prototype.__defineGetter__('objectType', function () { return 'group'; });
Zotero.Group.prototype.__defineGetter__('id', function () { return this._get('id'); });
Zotero.Group.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
Zotero.Group.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
Zotero.Group.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
Zotero.Group.prototype.__defineGetter__('name', function () { return this._get('name'); });
Zotero.Group.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
Zotero.Group.prototype.__defineGetter__('description', function () { return this._get('description'); });
Zotero.Group.prototype.__defineSetter__('description', function (val) { this._set('description', val); });
Zotero.Group.prototype.__defineGetter__('editable', function () { return this._get('editable'); });
Zotero.Group.prototype.__defineSetter__('editable', function (val) { this._set('editable', val); });
Zotero.Group.prototype.__defineGetter__('filesEditable', function () { return this._get('filesEditable'); });
Zotero.Group.prototype.__defineSetter__('filesEditable', function (val) { this._set('filesEditable', val); });
Zotero.Group.prototype._get = function (field) {
if (this._id && !this._loaded) {
this.load();
}
return this['_' + field];
}
Zotero.Group.prototype._set = function (field, val) {
switch (field) {
case 'id':
case 'libraryID':
if (val == this['_' + field]) {
return;
}
if (this._loaded) {
throw ("Cannot set " + field + " after object is already loaded in Zotero.Group._set()");
}
//this._checkValue(field, val);
this['_' + field] = val;
return;
}
if (this.id) {
if (!this._loaded) {
this.load();
}
}
else {
this._loaded = true;
}
if (this['_' + field] != val) {
this._prepFieldChange(field);
switch (field) {
default:
this['_' + field] = val;
}
}
}
/*
* Build group from database
*/
Zotero.Group.prototype.load = function() {
var id = this._id;
if (!id) {
throw ("ID not set in Zotero.Group.load()");
}
var sql = "SELECT G.* FROM groups G WHERE groupID=?";
var data = Zotero.DB.rowQuery(sql, id);
this._loaded = true;
if (!data) {
return;
}
this.loadFromRow(data);
}
/*
* Populate group data from a database row
*/
Zotero.Group.prototype.loadFromRow = function(row) {
this._loaded = true;
this._changed = false;
this._hasCollections = null;
this._id = row.groupID;
this._libraryID = row.libraryID;
this._name = row.name;
this._description = row.description;
this._editable = row.editable;
this._filesEditable = row.filesEditable;
}
/**
* Check if group exists in the database
*
* @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);
}
Zotero.Group.prototype.hasCollections = function () {
if (this._hasCollections !== null) {
return this._hasCollections;
}
this._hasCollections = !!this.getCollections().length;
return this._hasCollections;
}
Zotero.Group.prototype.clearCollectionsCache = function () {
this._hasCollections = null;
}
/**
* Returns collections of this group
*
* @param {Boolean} asIDs Return as collectionIDs
* @return {Zotero.Collection[]} Array of Zotero.Collection instances
*/
Zotero.Group.prototype.getCollections = function (parent) {
var sql = "SELECT collectionID FROM collections WHERE libraryID=? AND "
+ "parentCollectionID " + (parent ? '=' + parent : 'IS NULL');
var ids = Zotero.DB.columnQuery(sql, this.libraryID);
Zotero.debug(ids);
// Return Zotero.Collection objects
var objs = [];
for each(var id in ids) {
var col = Zotero.Collections.get(id);
objs.push(col);
}
return objs;
}
Zotero.Group.prototype.hasItem = function (itemID) {
var item = Zotero.Items.get(itemID);
return item.libraryID == this.libraryID;
}
Zotero.Group.prototype.save = function () {
if (!this.id) {
throw ("ID not set in Zotero.Group.save()");
}
if (!this.libraryID) {
throw ("libraryID not set in Zotero.Group.save()");
}
if (!this._changed) {
Zotero.debug("Group " + this.id + " has not changed");
return false;
}
Zotero.DB.beginTransaction();
var isNew = !this.exists();
try {
Zotero.debug("Saving group " + this.id);
var columns = [
'groupID',
'libraryID',
'name',
'description',
'editable',
'filesEditable'
];
var placeholders = ['?', '?', '?', '?', '?', '?'];
var sqlValues = [
this.id,
this.libraryID,
this.name,
this.description,
this.editable ? 1 : 0,
this.filesEditable ? 1 : 0
];
if (isNew) {
if (!Zotero.Libraries.exists(this.libraryID)) {
Zotero.Libraries.add(this.libraryID, 'group');
}
var sql = "INSERT INTO groups (" + columns.join(', ') + ") "
+ "VALUES (" + placeholders.join(', ') + ")";
Zotero.DB.query(sql, sqlValues);
}
else {
columns.shift();
sqlValues.shift();
var sql = "UPDATE groups SET "
+ columns.map(function (val) val + '=?').join(', ')
+ " WHERE groupID=?";
sqlValues.push(this.id);
Zotero.DB.query(sql, sqlValues);
}
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.DB.rollbackTransaction();
throw (e);
}
//Zotero.Groups.reload(this.id);
Zotero.Notifier.trigger('add', 'group', this.id);
}
/**
* Deletes group and all descendant objects
**/
Zotero.Group.prototype.erase = function(deleteItems) {
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();
}
}
// 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);
for each(var id in ids) {
obj = Zotero.Searches.get(id);
obj.erase();
}
// 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);
// Delete group
sql = "DELETE FROM groups WHERE groupID=?";
ids = Zotero.DB.query(sql, this.id)
Zotero.purgeDataObjects();
var notifierData = {};
notifierData[this.id] = this.serialize();
Zotero.DB.commitTransaction();
Zotero.Notifier.trigger('delete', 'group', this.id, notifierData);
}
Zotero.Group.prototype.serialize = function() {
var obj = {
primary: {
groupID: this.id,
libraryID: this.libraryID
},
fields: {
name: this.name,
description: this.description,
editable: this.editable,
filesEditable: this.filesEditable
}
};
return obj;
}
/**
* Returns an array of descendent groups and items
*
* @param bool recursive Descend into subgroups
* @param bool nested Return multidimensional array with 'children'
* nodes instead of flat array
* @param string type 'item', 'group', or FALSE for both
* @return {Object[]} Array of objects with 'id', 'key',
* 'type' ('item' or 'group'), 'parent',
* and, if group, 'name' and the nesting 'level'
*/
Zotero.Group.prototype.getChildren = function(recursive, nested, type, level) {
if (!this.id) {
throw ('Zotero.Group.getChildren() cannot be called on an unsaved item');
}
var toReturn = [];
if (!level) {
level = 1;
}
// 0 == group
// 1 == item
var children = Zotero.DB.query('SELECT groupID AS id, '
+ "0 AS type, groupName AS groupName, key "
+ 'FROM groups WHERE parentGroupID=?1'
+ ' UNION SELECT itemID AS id, 1 AS type, NULL AS groupName, key '
+ 'FROM groupItems JOIN items USING (itemID) WHERE groupID=?1', this.id);
if (type) {
switch (type) {
case 'item':
case 'group':
break;
default:
throw ("Invalid type '" + type + "' in Group.getChildren()");
}
}
for(var i=0, len=children.length; i<len; i++) {
// This seems to not work without parseInt() even though
// typeof children[i]['type'] == 'number' and
// children[i]['type'] === parseInt(children[i]['type']),
// which sure seems like a bug to me
switch (parseInt(children[i].type)) {
case 0:
if (!type || type=='group') {
toReturn.push({
id: children[i].id,
name: children[i].groupName,
key: children[i].key,
type: 'group',
level: level,
parent: this.id
});
}
if (recursive) {
var descendents =
Zotero.Groups.get(children[i].id).
getChildren(true, nested, type, level+1);
if (nested) {
toReturn[toReturn.length-1].children = descendents;
}
else {
for (var j=0, len2=descendents.length; j<len2; j++) {
toReturn.push(descendents[j]);
}
}
}
break;
case 1:
if (!type || type=='item') {
toReturn.push({
id: children[i].id,
key: children[i].key,
type: 'item',
parent: this.id
});
}
break;
}
}
return toReturn;
}
/**
* Alias for the recursive mode of getChildren()
*/
Zotero.Group.prototype.getDescendents = function(nested, type, level) {
return this.getChildren(true, nested, type);
}
Zotero.Group.prototype._prepFieldChange = function (field) {
if (!this._changed) {
this._changed = {};
}
this._changed[field] = true;
// Save a copy of the data before changing
// TODO: only save previous data if group exists
if (this.id && this.exists() && !this._previousData) {
//this._previousData = this.serialize();
}
}

View file

@ -0,0 +1,58 @@
Zotero.Groups = new function () {
this.__defineGetter__('addGroupURL', function () ZOTERO_CONFIG.WWW_BASE_URL + 'groups/new/');
this.get = function (id) {
if (!id) {
throw ("groupID not provided in Zotero.Groups.get()");
}
var group = new Zotero.Group;
group.id = id;
if (!group.exists()) {
return false;
}
return group;
}
this.getAll = function () {
var groups = [];
var sql = "SELECT groupID FROM groups";
var groupIDs = Zotero.DB.columnQuery(sql);
if (!groupIDs) {
return groups;
}
for each(var groupID in groupIDs) {
var group = this.get(groupID);
groups.push(group);
}
return groups;
}
this.getByLibraryID = function (libraryID) {
var groupID = this.getGroupIDFromLibraryID(libraryID);
return this.get(groupID);
}
this.getGroupIDFromLibraryID = function (libraryID) {
var sql = "SELECT groupID FROM groups WHERE libraryID=?";
var groupID = Zotero.DB.valueQuery(sql, libraryID);
if (!groupID) {
throw ("Group with libraryID " + libraryID + " does not exist "
+ "in Zotero.Groups.getGroupIDFromLibraryID()");
}
return groupID;
}
this.getLibraryIDFromGroupID = function (groupID) {
var sql = "SELECT libraryID FROM groups WHERE groupID=?";
var libraryID = Zotero.DB.valueQuery(sql, groupID);
if (!libraryID) {
throw ("Group with groupID " + groupID + " does not exist "
+ "in Zotero.Groups.getLibraryIDFromGroupID()");
}
return libraryID;
}
}

File diff suppressed because it is too large Load diff

View file

@ -38,9 +38,29 @@ Zotero.Items = new function() {
this.getFirstCreatorSQL = getFirstCreatorSQL;
this.getSortTitle = getSortTitle;
this.__defineGetter__('primaryFields', function () {
if (!_primaryFields.length) {
_primaryFields = Zotero.DB.getColumns('items');
_primaryFields.splice(_primaryFields.indexOf('clientDateModified'), 1);
_primaryFields = _primaryFields.concat(
['firstCreator', 'numNotes', 'numAttachments']
);
}
// Make a copy of array
var fields = [];
for each(var field in _primaryFields) {
fields.push(field);
}
return fields;
});
this.__defineGetter__('linkedItemPredicate', function () "owl:sameAs");
// Private members
var _cachedFields = [];
var _firstCreatorSQL = '';
var _primaryFields = [];
/*
@ -173,7 +193,7 @@ Zotero.Items = new function() {
* var item = Zotero.Items.add('book', data);
*/
function add(itemTypeOrID, data) {
var item = new Zotero.Item(false, itemTypeOrID);
var item = new Zotero.Item(itemTypeOrID);
for (var field in data) {
if (field == 'creators') {
var i = 0;
@ -212,6 +232,11 @@ Zotero.Items = new function() {
}
this.isPrimaryField = function (field) {
return this.primaryFields.indexOf(field) != -1;
}
function cacheFields(fields, items) {
if (items && items.length == 0) {
return;
@ -236,7 +261,7 @@ Zotero.Items = new function() {
_cachedFields.push(field);
if (Zotero.Item.prototype.isPrimaryField(field)) {
if (this.isPrimaryField(field)) {
primaryFields.push(field);
}
else {

View file

@ -0,0 +1,44 @@
Zotero.Libraries = new function () {
this.exists = function (libraryID) {
var sql = "SELECT COUNT(*) FROM libraries WHERE libraryID=?";
return !!Zotero.DB.valueQuery(sql, [libraryID]);
}
this.add = function (libraryID, type) {
switch (type) {
case 'group':
break;
default:
throw ("Invalid library type '" + type + "' in Zotero.Libraries.add()");
}
var sql = "INSERT INTO libraries (libraryID, libraryType) VALUES (?, ?)";
Zotero.DB.query(sql, [libraryID, type]);
}
this.getName = function (libraryID) {
var type = this.getType(libraryID);
switch (type) {
case 'group':
var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
var group = Zotero.Groups.get(groupID);
return group.name;
default:
throw ("Unsupported library type '" + type + "' in Zotero.Libraries.getName()");
}
}
this.getType = function (libraryID) {
var sql = "SELECT libraryType FROM libraries WHERE libraryID=?";
var libraryType = Zotero.DB.valueQuery(sql, libraryID);
if (!libraryType) {
throw ("Library " + libraryID + " does not exist in Zotero.Libraries.getType()");
}
return libraryType;
}
}

View file

@ -0,0 +1,149 @@
Zotero.Relation = function () {
this._id = null;
this._libraryID = null;
this._subject = null;
this._predicate = null;
this._object = null;
this._clientDateModified = null;
this._loaded = false;
}
Zotero.Relation.prototype.__defineGetter__('objectType', function () 'relation');
Zotero.Relation.prototype.__defineGetter__('id', function () this._id);
Zotero.Relation.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
Zotero.Relation.prototype.__defineGetter__('libraryID', function () this._get('libraryID'));
Zotero.Relation.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
Zotero.Relation.prototype.__defineGetter__('key', function () this._id);
//Zotero.Relation.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
Zotero.Relation.prototype.__defineGetter__('dateModified', function () this._get('dateModified'));
Zotero.Relation.prototype.__defineGetter__('subject', function () this._get('subject'));
Zotero.Relation.prototype.__defineSetter__('subject', function (val) { this._set('subject', val); });
Zotero.Relation.prototype.__defineGetter__('predicate', function () this._get('predicate'));
Zotero.Relation.prototype.__defineSetter__('predicate', function (val) { this._set('predicate', val); });
Zotero.Relation.prototype.__defineGetter__('object', function () this._get('object'));
Zotero.Relation.prototype.__defineSetter__('object', function (val) { this._set('object', val); });
Zotero.Relation.prototype._get = function (field) {
if (this._id && !this._loaded) {
this.load();
}
return this['_' + field];
}
Zotero.Relation.prototype._set = function (field, val) {
switch (field) {
case 'id':
case 'libraryID':
if (field == 'libraryID' && !val) {
throw ("libraryID cannot be empty in Zotero.Relation._set()");
}
if (val == this['_' + field]) {
return;
}
if (this._loaded) {
throw ("Cannot set " + field + " after object is already loaded in Zotero.Relation._set()");
}
this['_' + field] = val;
return;
}
if (this.id) {
if (!this._loaded) {
this.load();
}
}
else {
this._loaded = true;
}
if (this['_' + field] != val) {
//this._prepFieldChange(field);
switch (field) {
default:
this['_' + field] = val;
}
}
}
/**
* Check if search exists in the database
*
* @return bool TRUE if the relation exists, FALSE if not
*/
Zotero.Relation.prototype.exists = function () {
if (this.id) {
var sql = "SELECT COUNT(*) FROM relations WHERE relationID=?";
return !!Zotero_DB::valueQuery(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);
}
throw ("ID or libraryID/subject/predicate/object not set in Zotero.Relation.exists()");
}
Zotero.Relation.prototype.load = function () {
var id = this._id;
if (!id) {
throw ("ID not set in Zotero.Relation.load()");
}
var sql = "SELECT * FROM relations WHERE ROWID=?";
var row = Zotero.DB.rowQuery(sql, id);
if (!row) {
return;
}
this._libraryID = row.libraryID;
this._subject = row.subject;
this._predicate = row.predicate;
this._object = row.object;
this._clientDateModified = row.clientDateModified;
this._loaded = true;
return true;
}
Zotero.Relation.prototype.save = function () {
if (this.id) {
throw ("Existing relations cannot currently be altered in Zotero.Relation.save()");
}
if (!this.subject) {
throw ("Missing subject in Zotero.Relation.save()");
}
if (!this.predicate) {
throw ("Missing predicate in Zotero.Relation.save()");
}
if (!this.object) {
throw ("Missing object in Zotero.Relation.save()");
}
var sql = "INSERT INTO relations (libraryID, subject, predicate, object) VALUES (?, ?, ?, ?)";
var insertID = Zotero.DB.query(sql, [this.libraryID, this.subject, this.predicate, this.object]);
return insertID;
}
Zotero.Relation.prototype.toXML = function () {
var xml = <relation/>;
xml.subject = this.subject;
xml.predicate = this.predicate;
xml.object = this.object;
return xml;
}

View file

@ -0,0 +1,149 @@
Zotero.Relations = new function () {
Zotero.DataObjects.apply(this, ['relation']);
this.constructor.prototype = new Zotero.DataObjects();
var _namespaces = {
owl: 'http://www.w3.org/2002/07/owl#'
};
this.get = function (id) {
if (typeof id != 'number') {
throw ("id '" + id + "' must be an integer in Zotero.Relations.get()");
}
var relation = new Zotero.Relation;
relation.id = id;
return relation;
}
/**
* @return {Object[]}
*/
this.getByURIs = function (subject, predicate, object) {
predicate = _getPrefixAndValue(predicate).join(':');
if (!subject && !predicate && !object) {
throw ("No values provided in Zotero.Relations.get()");
}
var sql = "SELECT ROWID FROM relations WHERE 1";
var params = [];
if (subject) {
sql += " AND subject=?";
params.push(subject);
}
if (predicate) {
sql += " AND predicate=?";
params.push(predicate);
}
if (object) {
sql += " AND object=?";
params.push(object);
}
var rows = Zotero.DB.columnQuery(sql, params);
if (!rows) {
return [];
}
var toReturn = [];
for each(var id in rows) {
var relation = new Zotero.Relation;
relation.id = id;
toReturn.push(relation);
}
return toReturn;
}
this.getSubject = function (subject, predicate, object) {
var subjects = [];
var relations = this.getByURIs(subject, predicate, object);
for each(var relation in relations) {
subjects.push(relation.subject);
}
return subjects;
}
this.getObject = function (subject, predicate, object) {
var objects = [];
var relations = this.getByURIs(subject, predicate, object);
for each(var relation in relations) {
objects.push(relation.object);
}
return objects;
}
this.add = function (libraryID, subject, predicate, object) {
predicate = _getPrefixAndValue(predicate).join(':');
var relation = new Zotero.Relation;
if (libraryID) {
relation.libraryID = parseInt(libraryID);
}
else {
libraryID = Zotero.libraryID;
if (!libraryID) {
libraryID = Zotero.getLocalUserKey(true);
}
relation.libraryID = parseInt(libraryID);
}
relation.subject = subject;
relation.predicate = predicate;
relation.object = object;
relation.save();
}
this.erase = function (id) {
Zotero.DB.beginTransaction();
var sql = "DELETE FROM relations WHERE ROWID=?";
Zotero.DB.query(sql, [id]);
// TODO: log to syncDeleteLog
Zotero.DB.commitTransaction();
}
this.xmlToRelation = function (xml) {
var relation = new Zotero.Relation;
var libraryID = xml.@libraryID.toString();
if (libraryID) {
relation.libraryID = parseInt(libraryID);
}
else {
libraryID = Zotero.libraryID;
if (!libraryID) {
libraryID = Zotero.getLocalUserKey(true);
}
relation.libraryID = parseInt(libraryID);
}
relation.subject = xml.subject.toString();
relation.predicate = xml.predicate.toString();
relation.object = xml.object.toString();
return relation;
}
function _getPrefixAndValue(uri) {
var [prefix, value] = uri.split(':');
if (prefix && value) {
if (!_namespaces[prefix]) {
throw ("Invalid prefix '" + prefix + "' in Zotero.Relations.add()");
}
return [prefix, value];
}
for (var prefix in namespaces) {
if (uri.indexOf(namespaces[prefix]) == 0) {
var value = uri.substr(namespaces[prefix].length - 1)
return [prefix, value];
}
}
throw ("Invalid namespace in URI '" + uri + "' in Zotero.Relations._getPrefixAndValue()");
}
}

View file

@ -21,18 +21,23 @@
*/
Zotero.Tag = function(tagID) {
this._tagID = tagID ? tagID : null;
Zotero.Tag = function () {
if (arguments[0]) {
throw ("Zotero.Tag constructor doesn't take any parameters");
}
this._init();
}
Zotero.Tag.prototype._init = function () {
// Public members for access by public methods -- do not access directly
this._id = null;
this._libraryID = null
this._key = null;
this._name = null;
this._type = null;
this._dateAdded = null;
this._dateModified = null;
this._key = null;
this._loaded = false;
this._changed = false;
@ -43,9 +48,13 @@ Zotero.Tag.prototype._init = function () {
}
Zotero.Tag.prototype.__defineGetter__('id', function () { return this._tagID; });
Zotero.Tag.prototype.__defineSetter__('tagID', function (val) { this._set('tagID', val); });
Zotero.Tag.prototype.__defineGetter__('objectType', function () { return 'tag'; });
Zotero.Tag.prototype.__defineGetter__('id', function () { return this._get('id'); });
Zotero.Tag.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
Zotero.Tag.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
Zotero.Tag.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
Zotero.Tag.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Tag.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
Zotero.Tag.prototype.__defineGetter__('name', function () { return this._get('name'); });
Zotero.Tag.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
Zotero.Tag.prototype.__defineGetter__('type', function () { return this._get('type'); });
@ -55,13 +64,12 @@ Zotero.Tag.prototype.__defineSetter__('dateAdded', function (val) { this._set('d
Zotero.Tag.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
Zotero.Tag.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
Zotero.Tag.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Tag.prototype.__defineSetter__('key', function (val) { this._set('key', val); });
Zotero.Tag.prototype.__defineSetter__('linkedItems', function (arr) { this._setLinkedItems(arr); });
Zotero.Tag.prototype._get = function (field) {
if (this.id && !this._loaded) {
if ((this._id || this._key) && !this._loaded) {
this.load();
}
return this['_' + field];
@ -69,17 +77,27 @@ Zotero.Tag.prototype._get = function (field) {
Zotero.Tag.prototype._set = function (field, val) {
if (field == 'name') {
val = Zotero.Utilities.prototype.trim(val);
}
switch (field) {
case 'id': // set using constructor
//case 'tagID': // set using constructor
throw ("Invalid field '" + field + "' in Zotero.Tag.set()");
case 'id':
case 'libraryID':
case 'key':
if (val == this['_' + field]) {
return;
}
if (this._loaded) {
throw ("Cannot set " + field + " after object is already loaded in Zotero.Tag._set()");
}
//this._checkValue(field, val);
this['_' + field] = val;
return;
case 'name':
val = Zotero.Utilities.prototype.trim(val);
break;
}
if (this.id) {
if (this.id || this.key) {
if (!this._loaded) {
this.load();
}
@ -118,14 +136,38 @@ Zotero.Tag.prototype.exists = function() {
* Build tag from database
*/
Zotero.Tag.prototype.load = function() {
Zotero.debug("Loading data for tag " + this.id + " in Zotero.Tag.load()");
var id = this._id;
var key = this._key;
var libraryID = this._libraryID;
var desc = id ? id : libraryID + "/" + key;
if (!this.id) {
throw ("tagID not set in Zotero.Tag.load()");
Zotero.debug("Loading data for tag " + desc + " in Zotero.Tag.load()");
if (!id && !key) {
throw ("ID or key not set in Zotero.Tag.load()");
}
var sql = "SELECT name, type, dateAdded, dateModified, key FROM tags WHERE tagID=?";
var row = Zotero.DB.rowQuery(sql, this.id);
var sql = "SELECT * FROM tags WHERE ";
if (id) {
sql += "tagID=?";
var params = id;
}
else {
sql += "key=?";
var params = [key];
if (libraryID) {
sql += " AND libraryID=?";
params.push(libraryID);
}
else {
sql += " AND libraryID IS NULL";
}
}
var row = Zotero.DB.rowQuery(sql, params);
if (!row) {
return;
}
this.loadFromRow(row);
return true;
@ -136,6 +178,18 @@ Zotero.Tag.prototype.loadFromRow = function (row) {
this._init();
for (var col in row) {
//Zotero.debug("Setting field '" + col + "' to '" + row[col] + "' for tag " + this.id);
switch (col) {
case 'clientDateModified':
continue;
case 'tagID':
this._id = row[col];
continue;
case 'libraryID':
this['_' + col] = row[col] ? row[col] : null;
continue;
}
this['_' + col] = (!row[col] && row[col] !== 0) ? '' : row[col];
}
this._loaded = true;
@ -220,12 +274,15 @@ Zotero.Tag.prototype.removeItem = function (itemID) {
Zotero.Tag.prototype.save = function (full) {
Zotero.Tags.editCheck(this);
// Default to manual tag
if (!this.type) {
this.type = 0;
}
if (this.type != 0 && this.type != 1) {
Zotero.debug(this);
throw ('Invalid tag type ' + this.type + ' for tag ' + this.id + ' in Zotero.Tag.save()');
}
@ -241,34 +298,6 @@ Zotero.Tag.prototype.save = function (full) {
Zotero.DB.beginTransaction();
// ID change
if (this._changed.tagID) {
var oldID = this._previousData.primary.tagID;
var params = [this.id, oldID];
Zotero.debug("Changing tagID " + oldID + " to " + this.id);
var row = Zotero.DB.rowQuery("SELECT * FROM tags WHERE tagID=?", oldID);
// Set type on old row to -1, since there's a UNIQUE on name/type
Zotero.DB.query("UPDATE tags SET type=-1 WHERE tagID=?", oldID);
// Add a new row so we can update the old rows despite FK checks
// Use temp key due to UNIQUE constraint on key column
Zotero.DB.query("INSERT INTO tags VALUES (?, ?, ?, ?, ?, ?)",
[this.id, row.name, row.type, row.dateAdded, row.dateModified, 'TEMPKEY']);
Zotero.DB.query("UPDATE itemTags SET tagID=? WHERE tagID=?", params);
Zotero.DB.query("DELETE FROM tags WHERE tagID=?", oldID);
Zotero.DB.query("UPDATE tags SET key=? WHERE tagID=?", [row.key, this.id]);
Zotero.Notifier.trigger('id-change', 'tag', oldID + '-' + this.id);
// update caches
}
var isNew = !this.id || !this.exists();
try {
@ -281,9 +310,16 @@ Zotero.Tag.prototype.save = function (full) {
var key = this.key ? this.key : this._generateKey();
var columns = [
'tagID', 'name', 'type', 'dateAdded', 'dateModified', 'key'
'tagID',
'name',
'type',
'dateAdded',
'dateModified',
'clientDateModified',
'libraryID',
'key'
];
var placeholders = ['?', '?', '?', '?', '?', '?'];
var placeholders = ['?', '?', '?', '?', '?', '?', '?', '?'];
var sqlValues = [
tagID ? { int: tagID } : null,
{ string: this.name },
@ -293,6 +329,8 @@ Zotero.Tag.prototype.save = function (full) {
// If date modified hasn't changed, use current timestamp
this._changed.dateModified ?
this.dateModified : Zotero.DB.transactionDateTime,
Zotero.DB.transactionDateTime,
this.libraryID ? this.libraryID : null,
key
];
@ -372,6 +410,8 @@ Zotero.Tag.prototype.save = function (full) {
insertStatement.execute();
}
catch (e) {
Zotero.debug("itemID: " + itemID);
Zotero.debug("tagID: " + tagID);
throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
}
}
@ -392,7 +432,7 @@ Zotero.Tag.prototype.save = function (full) {
// If successful, set values in object
if (!this.id) {
this._tagID = tagID;
this._id = tagID;
}
if (!this.key) {
@ -465,9 +505,10 @@ Zotero.Tag.prototype.serialize = function () {
var obj = {
primary: {
tagID: this.id,
libraryID: this.libraryID,
key: this.key,
dateAdded: this.dateAdded,
dateModified: this.dateModified,
key: this.key
dateModified: this.dateModified
},
fields: {
name: this.name,
@ -545,10 +586,20 @@ Zotero.Tag.prototype.erase = function () {
Zotero.Tag.prototype._loadLinkedItems = function() {
if (!this.id && !this.key) {
this._linkedItemsLoaded = true;
return;
}
if (!this._loaded) {
this.load();
}
if (!this.id) {
this._linkedItemsLoaded = true;
return;
}
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
var ids = Zotero.DB.columnQuery(sql, this.id);

View file

@ -73,25 +73,39 @@ Zotero.Tags = new function() {
/*
* Returns the tagID matching given tag and type
*/
function getID(name, type) {
function getID(name, type, libraryID) {
name = Zotero.Utilities.prototype.trim(name);
var lcname = name.toLowerCase();
if (_tags[type] && _tags[type]['_' + lcname]) {
return _tags[type]['_' + lcname];
if (!libraryID) {
libraryID = 0;
}
if (_tags[libraryID] && _tags[libraryID][type] && _tags[libraryID][type]['_' + lcname]) {
return _tags[libraryID][type]['_' + lcname];
}
// FIXME: COLLATE NOCASE doesn't work for Unicode characters, so this
// won't find Äbc if "äbc" is entered and will allow a duplicate tag
// to be created
var sql = 'SELECT tagID FROM tags WHERE name=? AND type=?';
var tagID = Zotero.DB.valueQuery(sql, [name, type]);
var sql = "SELECT tagID FROM tags WHERE name=? AND type=? AND libraryID";
var params = [name, type];
if (libraryID) {
sql += "=?";
params.push(libraryID);
}
else {
sql += " IS NULL";
}
var tagID = Zotero.DB.valueQuery(sql, params);
if (tagID) {
if (!_tags[type]) {
_tags[type] = [];
if (!_tags[libraryID]) {
_tags[libraryID] = {};
}
_tags[type]['_' + lcname] = tagID;
if (!_tags[libraryID][type]) {
_tags[libraryID][type] = [];
}
_tags[libraryID][type]['_' + lcname] = tagID;
}
return tagID;
@ -101,20 +115,36 @@ Zotero.Tags = new function() {
/*
* Returns all tagIDs for this tag (of all types)
*/
function getIDs(name) {
function getIDs(name, libraryID) {
name = Zotero.Utilities.prototype.trim(name);
var sql = 'SELECT tagID FROM tags WHERE name=?';
return Zotero.DB.columnQuery(sql, [name]);
var sql = "SELECT tagID FROM tags WHERE name=? AND libraryID";
var params = [name];
if (libraryID) {
sql += "=?";
params.push(libraryID);
}
else {
sql += " IS NULL";
}
return Zotero.DB.columnQuery(sql, params);
}
/*
* Returns an array of tag types for tags matching given tag
*/
function getTypes(name) {
function getTypes(name, libraryID) {
name = Zotero.Utilities.prototype.trim(name);
var sql = 'SELECT type FROM tags WHERE name=?';
return Zotero.DB.columnQuery(sql, [name]);
var sql = "SELECT type FROM tags WHERE name=? AND libraryID";
var params = [name];
if (libraryID) {
sql += "=?";
params.push(libraryID);
}
else {
sql += " IS NULL";
}
return Zotero.DB.columnQuery(sql, params);
}
@ -123,12 +153,25 @@ Zotero.Tags = new function() {
*
* _types_ is an optional array of tag types to fetch
*/
function getAll(types) {
var sql = "SELECT tagID, name FROM tags ";
if (types) {
sql += "WHERE type IN (" + types.join() + ") ";
function getAll(types, libraryID) {
var sql = "SELECT tagID, name FROM tags WHERE libraryID";
var params = [];
if (libraryID) {
sql += "=?";
params.push(libraryID);
}
else {
sql += " IS NULL";
}
if (types) {
sql += " AND type IN (" + types.join() + ")";
}
if (params.length) {
var tags = Zotero.DB.query(sql, params);
}
else {
var tags = Zotero.DB.query(sql);
}
var tags = Zotero.DB.query(sql);
if (!tags) {
return {};
}
@ -244,6 +287,7 @@ Zotero.Tags = new function() {
Zotero.DB.beginTransaction();
var tagObj = this.get(tagID);
var oldLibraryID = tagObj.libraryID;
var oldName = tagObj.name;
var oldType = tagObj.type;
var notifierData = {};
@ -278,8 +322,8 @@ Zotero.Tags = new function() {
// Manual purge of old tag
sql = "DELETE FROM tags WHERE tagID=?";
Zotero.DB.query(sql, tagID);
if (_tags[oldType]) {
delete _tags[oldType]['_' + oldName];
if (_tags[oldLibraryID] && _tags[oldLibraryID][oldType]) {
delete _tags[oldLibraryID][oldType]['_' + oldName];
}
delete this._objectCache[tagID];
Zotero.Notifier.trigger('delete', 'tag', tagID, notifierData);
@ -426,8 +470,9 @@ Zotero.Tags = new function() {
for each(var id in ids) {
var tag = this._objectCache[id];
delete this._objectCache[id];
if (tag && _tags[tag.type]) {
delete _tags[tag.type]['_' + tag.name];
var libraryID = tag.libraryID ? tag.libraryID : 0;
if (tag && _tags[libraryID] && _tags[libraryID][tag.type]) {
delete _tags[libraryID][tag.type]['_' + tag.name];
}
}
}

View file

@ -39,6 +39,9 @@ Zotero.getCollections = function(parent, recursive) {
var sql = "SELECT collectionID AS id, collectionName AS name FROM collections C "
+ "WHERE parentCollectionID " + (parent ? '=' + parent : 'IS NULL');
if (!parent) {
sql += " AND libraryID IS NULL";
}
var children = Zotero.DB.query(sql);
if (!children) {

View file

@ -148,6 +148,25 @@ Zotero.File = new function(){
}
/**
* @param {nsIFile} file
* @return {String} Base-64 representation of MD5 hash
*/
this.getFileHash = function (file) {
var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
fis.init(file, -1, -1, false);
var hash = Components.classes["@mozilla.org/security/hash;1"].
createInstance(Components.interfaces.nsICryptoHash);
hash.init(Components.interfaces.nsICryptoHash.MD5);
hash.updateFromStream(fis, 4294967295); // PR_UINT32_MAX
hash = hash.finish(true);
fis.close();
return hash;
}
/*
* Write string to a file, overwriting existing file if necessary
*/

View file

@ -136,7 +136,7 @@ Zotero.Fulltext = new function(){
return false;
}
versionFile = exec.parent;
var versionFile = exec.parent;
versionFile.append(fileName + '.version');
if (versionFile.exists()) {
var version = Zotero.File.getSample(versionFile).split(/[\r\n\s]/)[0];

View file

@ -31,12 +31,14 @@ Zotero.Ingester = new function() {
getService(Components.interfaces.nsIWindowWatcher).activeWindow;
frontWindow.Zotero_Browser.progress.show();
var saveLocation = null;
var libraryID = null;
var collection = null;
try {
saveLocation = frontWindow.ZoteroPane.getSelectedCollection();
libraryID = frontWindow.ZoteroPane.getSelectedLibraryID();
collection = frontWindow.ZoteroPane.getSelectedCollection();
} catch(e) {}
translation.setHandler("itemDone", function(obj, item) { frontWindow.Zotero_Browser.itemDone(obj, item, saveLocation) });
translation.setHandler("done", function(obj, item) { frontWindow.Zotero_Browser.finishScraping(obj, item, saveLocation) });
translation.setHandler("itemDone", function(obj, item) { frontWindow.Zotero_Browser.itemDone(obj, item, collection) });
translation.setHandler("done", function(obj, item) { frontWindow.Zotero_Browser.finishScraping(obj, item, collection) });
// attempt to retrieve translators
var translators = translation.getTranslators();
@ -48,7 +50,7 @@ Zotero.Ingester = new function() {
// translate using first available
translation.setTranslator(translators[0]);
translation.translate();
translation.translate(libraryID);
}
}

View file

@ -242,13 +242,6 @@ Zotero.ItemTreeView.prototype.refresh = function()
}
Zotero.ItemTreeView.prototype.__defineGetter__('readOnly', function () {
if (this._itemGroup.isTrash() || this._itemGroup.isShare()) {
return true;
}
return false;
});
/*
* Called by Zotero.Notifier on any changes to items in the data layer
*/
@ -264,6 +257,8 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
return;
}
var itemGroup = this._itemGroup;
var madeChanges = false;
var sort = false;
@ -272,7 +267,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// If refreshing a single item, just unselect and reselect it
if (action == 'refresh') {
if (type == 'share-items') {
if (this._itemGroup.isShare()) {
if (itemGroup.isShare()) {
this.refresh();
}
}
@ -299,13 +294,14 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
var quicksearch = this._ownerDocument.getElementById('zotero-tb-search');
// 'collection-item' ids are in the form collectionID-itemID
if (type == 'collection-item') {
var splitIDs = [];
for each(var id in ids) {
var split = id.split('-');
// Skip if not collection or not an item in this collection
if (!this._itemGroup.isCollection() || split[0] != this._itemGroup.ref.id) {
if (!itemGroup.isCollection() || split[0] != this._itemGroup.ref.id) {
continue;
}
splitIDs.push(split[1]);
@ -319,23 +315,16 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
}
}
if ((action == 'remove' && !this._itemGroup.isLibrary())
|| action == 'delete' || action == 'id-change' || action == 'trash') {
// We only care about the old ids
if (action == 'id-change') {
for (var i=0, len=ids.length; i<len; i++) {
ids[i] = ids[i].split('-')[0];
}
}
if ((action == 'remove' && !itemGroup.isLibrary(true))
|| action == 'delete' || action == 'trash') {
// Since a remove involves shifting of rows, we have to do it in order,
// so sort the ids by row
var rows = [];
for(var i=0, len=ids.length; i<len; i++)
{
if (action == 'delete' || action == 'trash' || action == 'id-change' ||
!this._itemGroup.ref.hasItem(ids[i])) {
if (action == 'delete' || action == 'trash' ||
!itemGroup.ref.hasItem(ids[i])) {
// Row might already be gone (e.g. if this is a child and
// 'modify' was sent to parent)
if (this._itemRowMap[ids[i]] != undefined) {
@ -365,7 +354,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
else if (action == 'modify')
{
// If trash or saved search, just re-run search
if (this._itemGroup.isTrash() || this._itemGroup.isSearch())
if (itemGroup.isTrash() || itemGroup.isSearch())
{
this.refresh();
madeChanges = true;
@ -375,9 +364,11 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// If no quicksearch, process modifications manually
else if (!quicksearch || quicksearch.value == '')
{
for(var i=0, len=ids.length; i<len; i++)
{
var row = this._itemRowMap[ids[i]];
var items = Zotero.Items.get(ids);
for each(var item in items) {
var id = item.id;
var row = this._itemRowMap[id];
// Item already exists in this view
if( row != null)
{
@ -401,25 +392,19 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
else if (!this.isContainer(row) && parentIndex != -1
&& !sourceItemID)
{
var item = Zotero.Items.get(ids[i]);
this._showItem(new Zotero.ItemTreeView.TreeRow(item, 0, false), this.rowCount);
this._treebox.rowCountChanged(this.rowCount-1, 1);
sort = ids[i];
sort = id;
}
// If not moved from under one item to another
else if (!(sourceItemID && parentIndex != -1 && this._itemRowMap[sourceItemID] != parentIndex)) {
sort = ids[i];
sort = id;
}
madeChanges = true;
}
else if (this._itemGroup.isLibrary() || this._itemGroup.ref.hasItem(ids[i])) {
var item = Zotero.Items.get(ids[i]);
if (!item) {
// DEBUG: this shouldn't really happen but could if a
// modify comes in after a delete
continue;
}
else if (((itemGroup.isLibrary() || itemGroup.isGroup()) && itemGroup.ref.libraryID == item.libraryID)
|| (itemGroup.isCollection() && item.inCollection(itemGroup.ref.id))) {
// Deleted items get a modify that we have to ignore when
// not viewing the trash
if (item.deleted) {
@ -452,7 +437,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
else if(action == 'add')
{
// If saved search or trash, just re-run search
if (this._itemGroup.isSearch() || this._itemGroup.isTrash()) {
if (itemGroup.isSearch() || itemGroup.isTrash()) {
this.refresh();
madeChanges = true;
sort = true;
@ -463,24 +448,21 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
else if (quicksearch && quicksearch.value == '')
{
var items = Zotero.Items.get(ids);
for (var i in items)
{
for each(var item in items) {
// if the item belongs in this collection
if((this._itemGroup.isLibrary() || items[i].inCollection(this._itemGroup.ref.id))
if ((((itemGroup.isLibrary() || itemGroup.isGroup()) && itemGroup.ref.libraryID == item.libraryID)
|| (itemGroup.isCollection() && item.inCollection(itemGroup.ref.id)))
// if we haven't already added it to our hash map
&& this._itemRowMap[items[i].id] == null
&& this._itemRowMap[item.id] == null
// Regular item or standalone note/attachment
&& (items[i].isRegularItem() || !items[i].getSource()))
{
this._showItem(new Zotero.ItemTreeView.TreeRow(items[i],0,false),this.rowCount);
&& (item.isRegularItem() || !item.getSource())) {
this._showItem(new Zotero.ItemTreeView.TreeRow(item, 0, false), this.rowCount);
this._treebox.rowCountChanged(this.rowCount-1,1);
madeChanges = true;
}
}
if (madeChanges) {
sort = (ids.length == 1) ? ids[0] : true;
sort = (items.length == 1) ? items[0].id : true;
}
}
// Otherwise re-run the search, which refreshes the item list
@ -510,6 +492,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// Reset to Info tab
this._ownerDocument.getElementById('zotero-view-tabs').selectedIndex = 0;
this.selectItem(ids[0]);
}
// If single item is selected and was modified
@ -548,7 +531,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
}
// On delete, select item at previous position
if (action == 'delete') {
if (action == 'delete' || action == 'remove') {
if (this._dataItems[previousRow]) {
this.selection.select(previousRow);
}
@ -571,6 +554,10 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
this.selectItem(selectItem);
}
if (Zotero.Sync.Server.syncInProgress) {
this.rememberSelection(savedSelection);
}
this.selection.selectEventsSuppressed = false;
}
@ -1085,11 +1072,16 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
///
////////////////////////////////////////////////////////////////////////////////
/*
* Select an item
*/
Zotero.ItemTreeView.prototype.selectItem = function(id, expand, noRecurse)
{
if (Zotero.Sync.Server.syncInProgress) {
return;
}
// If no row map, we're probably in the process of switching collections,
// so store the item to select on the item group for later
if (!this._itemRowMap) {
@ -1223,14 +1215,18 @@ Zotero.ItemTreeView.prototype.deleteSelection = function(eraseChildren, force)
ids.push(this._getItemAtRow(j).ref.id);
}
// Erase item(s) from DB
if (this._itemGroup.isLibrary() || force) {
var itemGroup = this._itemGroup;
if (itemGroup.isGroup() || (force && itemGroup.isWithinGroup())) {
Zotero.Items.erase(ids, eraseChildren);
}
else if (itemGroup.isLibrary() || force) {
Zotero.Items.trash(ids);
}
else if (this._itemGroup.isCollection()) {
this._itemGroup.ref.removeItems(ids);
else if (itemGroup.isCollection()) {
itemGroup.ref.removeItems(ids);
}
else if (this._itemGroup.isTrash()) {
else if (itemGroup.isTrash()) {
Zotero.Items.erase(ids, eraseChildren);
}
this._treebox.endUpdateBatch();
@ -1597,7 +1593,7 @@ Zotero.ItemTreeView.prototype.onDragStart = function (event, transferData, actio
var oldMethod = Zotero.isFx2 || Zotero.isFx30;
// Quick implementation of dragging of XML item format
if (this.readOnly) {
if (this._itemGroup.isShare()) {
var items = this.getSelectedItems();
var xml = <data/>;
@ -1985,7 +1981,7 @@ Zotero.ItemTreeView.prototype.canDrop = function(row, orient, dragData)
//Zotero.debug("Row is " + row + "; orient is " + orient);
if (row == -1 && orient == -1) {
return true;
//return true;
}
if (!dragData) {
@ -2001,6 +1997,8 @@ Zotero.ItemTreeView.prototype.canDrop = function(row, orient, dragData)
var ids = data;
}
var itemGroup = this._itemGroup;
// workaround... two different services call canDrop
// (nsDragAndDrop, and the tree) -- this is for the former,
// used when dragging between windows
@ -2010,53 +2008,56 @@ Zotero.ItemTreeView.prototype.canDrop = function(row, orient, dragData)
if (nsDragAndDrop.mDragSession.sourceNode!=row.target)
{
if (dataType == 'zotero/item') {
var items = Zotero.Items.get(ids);
// Check if at least one item (or parent item for children) doesn't
// already exist in target
for each(var id in ids)
{
var item = Zotero.Items.get(id);
for each(var item in items) {
// Skip non-top-level items
if (!item.isRegularItem() && item.getSource())
{
if (!item.isTopLevelItem()) {
continue;
}
// DISABLED: move parent on child drag
//var source = item.isRegularItem() ? false : item.getSource();
//if (!this._itemGroup.ref.hasItem(source ? source : id))
if (this._itemGroup.ref && !this._itemGroup.ref.hasItem(id))
{
// TODO: For now, disable cross-window cross-library drag
if (itemGroup.ref.libraryID != item.libraryID) {
return false;
}
if (itemGroup.ref && !itemGroup.ref.hasItem(item.id)) {
return true;
}
}
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
if (this._itemGroup.isSearch()) {
if (itemGroup.isSearch()) {
return false;
}
return true;
}
}
return false;
}
// Highlight the rows correctly on drag
if (orient == 0) {
var rowItem = this._getItemAtRow(row).ref; // the item we are dragging over
}
var rowItem = this._getItemAtRow(row).ref; //the item we are dragging over
if (dataType == 'zotero/item') {
var items = Zotero.Items.get(ids);
// Directly on a row
if (orient == 0)
{
if (orient == 0) {
var canDrop = false;
for each(var id in ids) {
var item = Zotero.Items.get(id);
for each(var item in items) {
// If any regular items, disallow drop
if (item.isRegularItem()) {
canDrop = false;
break;
return false;
}
// Disallow cross-library child drag
if (item.libraryID != itemGroup.ref.libraryID) {
return false;
}
// Only allow dragging of notes and attachments
@ -2070,14 +2071,19 @@ Zotero.ItemTreeView.prototype.canDrop = function(row, orient, dragData)
}
// In library, allow children to be dragged out of parent
else if (this._itemGroup.isLibrary() || this._itemGroup.isCollection())
{
for each(var id in ids)
{
else if (itemGroup.isLibrary(true) || itemGroup.isCollection()) {
for each(var item in items) {
// Don't allow drag if any top-level items
var item = Zotero.Items.get(id);
if (item.isRegularItem() || !item.getSource())
{
if (item.isTopLevelItem()) {
return false;
}
if (item.isWebAttachment()) {
return false;
}
// Disallow cross-library child drag
if (item.libraryID != itemGroup.ref.libraryID) {
return false;
}
}
@ -2092,7 +2098,8 @@ Zotero.ItemTreeView.prototype.canDrop = function(row, orient, dragData)
return false;
}
}
else if (this._itemGroup.isSearch()) {
// Don't allow drop into searches
else if (itemGroup.isSearch()) {
return false;
}
@ -2116,18 +2123,22 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
var dataType = dragData.dataType;
var data = dragData.data;
var itemGroup = this._itemGroup;
if (dataType == 'zotero/item') {
var ids = data;
var items = Zotero.Items.get(ids);
if (items.length < 1) {
return;
}
// Dropped directly on a row
if (orient == 0)
{
// If item was a top-level item and it exists in a collection,
// replace it in collections with the parent item
if (orient == 0) {
// Set drop target as the parent item for dragged items
//
// canDrop() limits this to child items
var rowItem = this._getItemAtRow(row).ref; // the item we are dragging over
for each(var id in ids)
{
var item = Zotero.Items.get(id);
for each(var item in items) {
item.setSource(rowItem.id);
item.save();
}
@ -2137,11 +2148,8 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
else
{
// Remove from parent and make top-level
if (this._itemGroup.isLibrary())
{
for each(var id in ids)
{
var item = Zotero.Items.get(id);
if (itemGroup.isLibrary(true)) {
for each(var item in items) {
if (!item.isRegularItem())
{
item.setSource();
@ -2152,30 +2160,49 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
// Add to collection
else
{
for each(var id in ids)
for each(var item in items)
{
var item = Zotero.Items.get(id);
var source = item.isRegularItem() ? false : item.getSource();
// Top-level item
if (source) {
item.setSource();
item.save()
}
this._itemGroup.ref.addItem(id);
itemGroup.ref.addItem(id);
}
}
}
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
// FIXME: temporarily disable dragging in of files
if (dataType == 'application/x-moz-file' && itemGroup.isWithinGroup()) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
ps.alert(null, "", "Files cannot currently be added to group libraries.");
return;
}
// Disallow drop into read-only libraries
if (!itemGroup.isEditable()) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotEditLibraryMessage();
return;
}
var sourceItemID = false;
var parentCollectionID = false;
var treerow = this._getItemAtRow(row);
if (orient == 0) {
sourceItemID = this._getItemAtRow(row).ref.id
sourceItemID = treerow.ref.id
}
else if (this._itemGroup.isCollection()) {
var parentCollectionID = this._itemGroup.ref.id;
else if (itemGroup.isCollection()) {
var parentCollectionID = itemGroup.ref.id;
}
else if (itemGroup.isLibrary(true)) {
var libraryID = itemGroup.ref.libraryID;
}
var unlock = Zotero.Notifier.begin(true);
@ -2207,7 +2234,15 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
// Still string, so remote URL
if (typeof file == 'string') {
Zotero.Attachments.importFromURL(url, sourceItemID, false, false, parentCollectionID);
if (sourceItemID) {
Zotero.Attachments.importFromURL(url, sourceItemID);
}
else {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.addItemFromURL(url);
}
continue;
}

View file

@ -25,7 +25,7 @@ Zotero.Notifier = new function(){
var _disabled = false;
var _types = [
'collection', 'creator', 'search', 'share', 'share-items', 'item',
'collection-item', 'item-tag', 'tag'
'collection-item', 'item-tag', 'tag', 'group'
];
var _inTransaction;
var _locked = false;
@ -87,7 +87,7 @@ Zotero.Notifier = new function(){
*
* event: 'add', 'modify', 'delete', 'move' ('c', for changing parent),
* 'remove' (ci, it), 'refresh', 'trash'
* type - 'collection', 'search', 'item', 'collection-item', 'item-tag', 'tag'
* type - 'collection', 'search', 'item', 'collection-item', 'item-tag', 'tag', 'group'
* ids - single id or array of ids
*
* Notes:

View file

@ -116,8 +116,8 @@ Zotero.Schema = new function(){
}
}
var up1 = _migrateUserDataSchema(dbVersion);
var up2 = _updateSchema('system');
var up1 = _migrateUserDataSchema(dbVersion);
var up3 = _updateSchema('triggers');
Zotero.DB.commitTransaction();
@ -2270,7 +2270,7 @@ Zotero.Schema = new function(){
Zotero.DB.query("DROP TABLE syncDeleteLogOld");
}
//
// 1.5 Sync Preview 3.7
if (i==48) {
Zotero.DB.query("CREATE TABLE deletedItems (\n itemID INTEGER PRIMARY KEY,\n dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL\n);");
}
@ -2300,10 +2300,73 @@ Zotero.Schema = new function(){
Zotero.DB.query("DROP TABLE tagsOld");
}
// 1.5 Beta 3
if (i==50) {
Zotero.DB.query("DELETE FROM proxyHosts");
Zotero.DB.query("DELETE FROM proxies");
}
if (i==51) {
Zotero.DB.query("ALTER TABLE collections RENAME TO collectionsOld");
Zotero.DB.query("DROP INDEX creators_creatorDataID");
Zotero.DB.query("ALTER TABLE creators RENAME TO creatorsOld");
Zotero.DB.query("ALTER TABLE items RENAME TO itemsOld")
Zotero.DB.query("ALTER TABLE savedSearches RENAME TO savedSearchesOld");
Zotero.DB.query("ALTER TABLE tags RENAME TO tagsOld");
Zotero.DB.query("CREATE TABLE collections (\n collectionID INTEGER PRIMARY KEY,\n collectionName TEXT NOT NULL,\n parentCollectionID INT DEFAULT 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,\n key TEXT NOT NULL,\n UNIQUE (libraryID, key),\n FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID)\n);");
Zotero.DB.query("CREATE TABLE creators (\n creatorID INTEGER PRIMARY KEY,\n creatorDataID 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,\n key TEXT NOT NULL,\n UNIQUE (libraryID, key),\n FOREIGN KEY (creatorDataID) REFERENCES creatorData(creatorDataID)\n);");
Zotero.DB.query("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,\n key TEXT NOT NULL,\n UNIQUE (libraryID, key)\n);");
Zotero.DB.query("CREATE TABLE savedSearches (\n savedSearchID INTEGER PRIMARY KEY,\n savedSearchName TEXT 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,\n key TEXT NOT NULL,\n UNIQUE (libraryID, key)\n);");
Zotero.DB.query("CREATE TABLE tags (\n tagID INTEGER PRIMARY KEY,\n name TEXT NOT NULL COLLATE NOCASE,\n type 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,\n key TEXT NOT NULL,\n UNIQUE (libraryID, name, type),\n UNIQUE (libraryID, key)\n);\n");
Zotero.DB.query("INSERT INTO collections SELECT collectionID, collectionName, parentCollectionID, dateAdded, dateModified, dateModified, NULL, key FROM collectionsOld");
Zotero.DB.query("INSERT INTO creators SELECT creatorID, creatorDataID, dateAdded, dateModified, dateModified, NULL, key FROM creatorsOld");
Zotero.DB.query("INSERT INTO items SELECT itemID, itemTypeID, dateAdded, dateModified, dateModified, NULL, key FROM itemsOld");
Zotero.DB.query("INSERT INTO savedSearches SELECT savedSearchID, savedSearchName, dateAdded, dateModified, dateModified, NULL, key FROM savedSearchesOld");
Zotero.DB.query("INSERT INTO tags SELECT tagID, name, type, dateAdded, dateModified, dateModified, NULL, key FROM tagsOld");
Zotero.DB.query("CREATE INDEX creators_creatorDataID ON creators(creatorDataID);");
Zotero.DB.query("DROP TABLE collectionsOld");
Zotero.DB.query("DROP TABLE creatorsOld");
Zotero.DB.query("DROP TABLE itemsOld");
Zotero.DB.query("DROP TABLE savedSearchesOld");
Zotero.DB.query("DROP TABLE tagsOld");
Zotero.DB.query("CREATE TABLE libraries (\n libraryID INTEGER PRIMARY KEY,\n libraryType TEXT NOT NULL\n);");
Zotero.DB.query("CREATE TABLE users (\n userID INTEGER PRIMARY KEY,\n username TEXT NOT NULL\n);");
Zotero.DB.query("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 FOREIGN KEY (libraryID) REFERENCES libraries(libraryID)\n);");
Zotero.DB.query("CREATE TABLE groupItems (\n itemID INTEGER PRIMARY KEY,\n createdByUserID INT NOT NULL,\n lastModifiedByUserID INT NOT NULL,\n FOREIGN KEY (createdByUserID) REFERENCES users(userID),\n FOREIGN KEY (lastModifiedByUserID) REFERENCES users(userID)\n);");
Zotero.DB.query("ALTER TABLE syncDeleteLog RENAME TO syncDeleteLogOld");
Zotero.DB.query("DROP INDEX syncDeleteLog_timestamp");
Zotero.DB.query("CREATE TABLE syncDeleteLog (\n syncObjectTypeID INT NOT NULL,\n libraryID INT,\n key TEXT NOT NULL,\n timestamp INT NOT NULL,\n UNIQUE (libraryID, key),\n FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)\n);");
Zotero.DB.query("INSERT INTO syncDeleteLog SELECT syncObjectTypeID, NULL, key, timestamp FROM syncDeleteLogOld");
Zotero.DB.query("CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp)");
Zotero.DB.query("DROP TABLE syncDeleteLogOld");
Zotero.DB.query("ALTER TABLE storageDeleteLog RENAME TO storageDeleteLogOld");
Zotero.DB.query("DROP INDEX storageDeleteLog_timestamp");
Zotero.DB.query("CREATE TABLE storageDeleteLog (\n libraryID INT,\n key TEXT NOT NULL,\n timestamp INT NOT NULL,\n PRIMARY KEY (libraryID, key)\n);");
Zotero.DB.query("INSERT INTO storageDeleteLog SELECT NULL, key, timestamp FROM storageDeleteLogOld");
Zotero.DB.query("CREATE INDEX storageDeleteLog_timestamp ON storageDeleteLog(timestamp)");
Zotero.DB.query("DROP TABLE storageDeleteLogOld");
Zotero.DB.query("CREATE TEMPORARY TABLE tmpUpdatedItems (itemID INTEGER PRIMARY KEY)");
Zotero.DB.query("INSERT INTO tmpUpdatedItems SELECT itemID FROM items NATURAL JOIN itemData WHERE fieldID=10 AND itemTypeID IN (2,9)");
Zotero.DB.query("UPDATE itemData SET fieldID=118 WHERE fieldID=10 AND itemID IN (SELECT itemID FROM tmpUpdatedItems)");
Zotero.DB.query("DROP TABLE tmpUpdatedItems");
}
if (i==52) {
Zotero.DB.query("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 (libraryID, subject, predicate, object)\n)");
Zotero.DB.query("CREATE INDEX relations_object ON relations(libraryID, object)")
}
if (i==53) {
Zotero.DB.query("DELETE FROM collectionItems WHERE itemID IN (SELECT itemID FROM items WHERE itemID IN (SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL) OR itemID IN (SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL))");
}
}
_updateDBVersion('userdata', toVersion);

View file

@ -20,20 +20,25 @@
***** END LICENSE BLOCK *****
*/
Zotero.Search = function(searchID) {
this._id = searchID ? searchID : null;
Zotero.Search = function() {
if (arguments[0]) {
throw ("Zotero.Search constructor doesn't take any parameters");
}
this._loaded = false;
this._init();
}
Zotero.Search.prototype._init = function () {
// Public members for access by public methods -- do not access directly
this._id = null;
this._libraryID = null;
this._key = null;
this._name = null;
this._dateAdded = null;
this._dateModified = null;
this._key = null;
this._loaded = false;
this._changed = false;
this._previousData = false;
@ -64,24 +69,25 @@ Zotero.Search.prototype.setName = function(val) {
}
Zotero.Search.prototype.__defineGetter__('id', function () { return this._id; });
Zotero.Search.prototype.__defineGetter__('objectType', function () { return 'search'; });
Zotero.Search.prototype.__defineGetter__('id', function () { return this._get('id'); });
Zotero.Search.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
Zotero.Search.prototype.__defineSetter__('searchID', function (val) { this._set('id', val); });
Zotero.Search.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
Zotero.Search.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
Zotero.Search.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Search.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
Zotero.Search.prototype.__defineGetter__('name', function () { return this._get('name'); });
Zotero.Search.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
Zotero.Search.prototype.__defineGetter__('dateAdded', function () { return this._get('dateAdded'); });
Zotero.Search.prototype.__defineSetter__('dateAdded', function (val) { this._set('dateAdded', val); });
Zotero.Search.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
Zotero.Search.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
Zotero.Search.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Search.prototype.__defineSetter__('key', function (val) { this._set('key', val); });
Zotero.Search.prototype.__defineGetter__('conditions', function (arr) { this.getSearchConditions(); });
Zotero.Search.prototype._get = function (field) {
if (this.id && !this._loaded) {
if ((this._id || this._key) && !this._loaded) {
this.load();
}
return this['_' + field];
@ -89,17 +95,27 @@ Zotero.Search.prototype._get = function (field) {
Zotero.Search.prototype._set = function (field, val) {
if (field == 'name') {
val = Zotero.Utilities.prototype.trim(val);
}
switch (field) {
//case 'id': // set using constructor
case 'searchID':
throw ("Invalid field '" + field + "' in Zotero.Search.set()");
case 'id':
case 'libraryID':
case 'key':
if (val == this['_' + field]) {
return;
}
if (this._loaded) {
throw ("Cannot set " + field + " after object is already loaded in Zotero.Search._set()");
}
//this._checkValue(field, val);
this['_' + field] = val;
return;
case 'name':
val = Zotero.Utilities.prototype.trim(val);
break;
}
if (this.id) {
if (this.id || this.key) {
if (!this._loaded) {
this.load();
}
@ -143,32 +159,51 @@ Zotero.Search.prototype.load = function() {
throw ('Parameter no longer allowed in Zotero.Search.load()');
}
var id = this._id;
var key = this._key;
var libraryID = this._libraryID;
var desc = id ? id : libraryID + "/" + key;
var sql = "SELECT S.*, "
+ "MAX(searchConditionID) AS maxID "
+ "FROM savedSearches S LEFT JOIN savedSearchConditions "
+ "USING (savedSearchID) WHERE savedSearchID=? "
+ "GROUP BY savedSearchID";
var data = Zotero.DB.rowQuery(sql, this.id);
+ "USING (savedSearchID) WHERE ";
if (id) {
sql += "savedSearchID=?";
var params = id;
}
else {
sql += "key=?";
var params = [key];
if (libraryID) {
sql += " AND libraryID=?";
params.push(libraryID);
}
else {
sql += " AND libraryID IS NULL";
}
}
sql += " GROUP BY savedSearchID";
var data = Zotero.DB.rowQuery(sql, params);
this._init();
this._loaded = true;
if (!data) {
return;
}
this._changed = false;
this._previousData = false;
this._init();
this._id = data.savedSearchID;
this._libraryID = data.libraryID;
this._key = data.key;
this._name = data.savedSearchName;
this._dateAdded = data.dateAdded;
this._dateModified = data.dateModified;
this._key = data.key;
this._maxSearchConditionID = data.maxID;
var sql = "SELECT * FROM savedSearchConditions "
+ "WHERE savedSearchID=? ORDER BY searchConditionID";
var conditions = Zotero.DB.query(sql, this.id);
var conditions = Zotero.DB.query(sql, this._id);
for (var i in conditions) {
// Parse "condition[/mode]"
@ -203,36 +238,14 @@ Zotero.Search.prototype.load = function() {
* For new searches, name must be set called before saving
*/
Zotero.Search.prototype.save = function(fixGaps) {
Zotero.Searches.editCheck(this);
if (!this.name) {
throw('Name not provided for saved search');
}
Zotero.DB.beginTransaction();
// ID change
if (this._changed.id) {
var oldID = this._previousData.primary.id;
var params = [this.id, oldID];
Zotero.debug("Changing search id " + oldID + " to " + this.id);
var row = Zotero.DB.rowQuery("SELECT * FROM savedSearches WHERE savedSearchID=?", oldID);
// Add a new row so we can update the old rows despite FK checks
// Use temp key due to UNIQUE constraint on key column
Zotero.DB.query("INSERT INTO savedSearches VALUES (?, ?, ?, ?, ?)",
[this.id, row.savedSearchName, row.dateAdded, row.dateModified, 'TEMPKEY']);
Zotero.DB.query("UPDATE savedSearchConditions SET savedSearchID=? WHERE savedSearchID=?", params);
Zotero.DB.query("DELETE FROM savedSearches WHERE savedSearchID=?", oldID);
Zotero.DB.query("UPDATE savedSearches SET key=? WHERE savedSearchID=?", [row.key, this.id]);
//Zotero.Searches.unload(oldID);
Zotero.Notifier.trigger('id-change', 'search', oldID + '-' + this.id);
// update caches
}
var isNew = !this.id || !this.exists();
try {
@ -243,9 +256,15 @@ Zotero.Search.prototype.save = function(fixGaps) {
var key = this.key ? this.key : this._generateKey();
var columns = [
'savedSearchID', 'savedSearchName', 'dateAdded', 'dateModified', 'key'
'savedSearchID',
'savedSearchName',
'dateAdded',
'dateModified',
'clientDateModified',
'libraryID',
'key'
];
var placeholders = ['?', '?', '?', '?', '?'];
var placeholders = ['?', '?', '?', '?', '?', '?', '?'];
var sqlValues = [
searchID ? { int: searchID } : null,
{ string: this.name },
@ -254,6 +273,8 @@ Zotero.Search.prototype.save = function(fixGaps) {
// If date modified hasn't changed, use current timestamp
this._changed.dateModified ?
this.dateModified : Zotero.DB.transactionDateTime,
Zotero.DB.transactionDateTime,
this.libraryID ? this.libraryID : this.libraryID,
key
];
@ -352,7 +373,7 @@ Zotero.Search.prototype.clone = function() {
Zotero.Search.prototype.addCondition = function(condition, operator, value, required) {
if (this.id && !this._loaded) {
if ((this.id || this.key) && !this._loaded) {
this.load();
}
@ -416,7 +437,7 @@ Zotero.Search.prototype.setScope = function (searchObj, includeChildren) {
Zotero.Search.prototype.updateCondition = function(searchConditionID, condition, operator, value, required){
if (this.id && !this._loaded) {
if ((this.id || this.key) && !this._loaded) {
this.load();
}
@ -445,7 +466,7 @@ Zotero.Search.prototype.updateCondition = function(searchConditionID, condition,
Zotero.Search.prototype.removeCondition = function(searchConditionID){
if (this.id && !this._loaded) {
if ((this.id || this.key) && !this._loaded) {
this.load();
}
@ -462,7 +483,7 @@ Zotero.Search.prototype.removeCondition = function(searchConditionID){
* for the given searchConditionID
*/
Zotero.Search.prototype.getSearchCondition = function(searchConditionID){
if (this.id && !this._loaded) {
if ((this.id || this.key) && !this._loaded) {
this.load();
}
return this._conditions[searchConditionID];
@ -474,7 +495,7 @@ Zotero.Search.prototype.getSearchCondition = function(searchConditionID){
* used in the search, indexed by searchConditionID
*/
Zotero.Search.prototype.getSearchConditions = function(){
if (this.id && !this._loaded) {
if ((this.id || this.key) && !this._loaded) {
this.load();
}
var conditions = [];
@ -495,7 +516,7 @@ Zotero.Search.prototype.getSearchConditions = function(){
Zotero.Search.prototype.hasPostSearchFilter = function() {
if (this.id && !this._loaded) {
if ((this.id || this.key) && !this._loaded) {
this.load();
}
for each(var i in this._conditions){
@ -511,7 +532,7 @@ Zotero.Search.prototype.hasPostSearchFilter = function() {
* Run the search and return an array of item ids for results
*/
Zotero.Search.prototype.search = function(asTempTable){
if (this.id && !this._loaded) {
if ((this.id || this.key) && !this._loaded) {
this.load();
}
@ -821,9 +842,10 @@ Zotero.Search.prototype.serialize = function() {
var obj = {
primary: {
id: this.id,
libraryID: this.libraryID,
key: this.key,
dateAdded: this.dateAdded,
dateModified: this.dateModified,
key: this.key
dateModified: this.dateModified
},
fields: {
name: this.name,
@ -1095,7 +1117,8 @@ Zotero.Search.prototype._buildQuery = function(){
condSQL += "NOT ";
}
condSQL += "IN (";
var search = new Zotero.Search(condition.value);
var search = new Zotero.Search();
search.id = condition.value;
// Check if there are any post-search filters
var hasFilter = search.hasPostSearchFilter();
@ -1346,10 +1369,14 @@ Zotero.Search.prototype._buildQuery = function(){
case 'isNot': // excluded with NOT IN above
// Automatically cast values which might
// have been stored as integers
if (condition.value
if (condition.value && typeof condition.value == 'string'
&& condition.value.match(/^[1-9]+[0-9]*$/)) {
condSQL += ' LIKE ?';
}
else if (condition.value === null) {
condSQL += ' IS NULL';
break;
}
else {
condSQL += '=?';
}
@ -1514,7 +1541,9 @@ Zotero.Searches = new function(){
function get(id) {
var sql = "SELECT COUNT(*) FROM savedSearches WHERE savedSearchID=?";
if (Zotero.DB.valueQuery(sql, id)) {
return new Zotero.Search(id);
var search = new Zotero.Search;
search.id = id;
return search;
}
return false;
}
@ -1548,7 +1577,8 @@ Zotero.Searches = new function(){
Zotero.DB.beginTransaction();
for each(var id in ids) {
var search = new Zotero.Search(id);
var search = new Zotero.Search;
search.id = id;
notifierData[id] = { old: search.serialize() };
var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
@ -1883,6 +1913,17 @@ Zotero.SearchConditions = new function(){
template: true // mark for special handling
},
{
name: 'libraryID',
operators: {
is: true,
isNot: true
},
table: 'items',
field: 'libraryID',
special: true
},
{
name: 'annotation',
operators: {

View file

@ -461,7 +461,7 @@ Zotero.Sync.Storage = new function () {
* Also marks missing files for downloading
*
* @param {Integer[]} itemIDs An optional set of item ids to check
* @param {Object} itemModTimes Item mod times indexed by item ids
* @param {Object} itemModTimes Item mod times indexed by item ids
* appearing in itemIDs; if set,
* items with stored mod times
* that differ from the provided
@ -497,8 +497,8 @@ Zotero.Sync.Storage = new function () {
do {
var chunk = itemIDs.splice(0, maxIDs);
var sql = "SELECT itemID, linkMode, path, storageModTime, syncState "
+ "FROM itemAttachments "
+ "WHERE linkMode IN (?,?) AND syncState IN (?,?)";
+ "FROM itemAttachments JOIN items USING (itemID) "
+ "WHERE linkMode IN (?,?) AND syncState IN (?,?) AND libraryID IS NULL";
var params = [
Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
Zotero.Attachments.LINK_MODE_IMPORTED_URL,
@ -665,7 +665,7 @@ Zotero.Sync.Storage = new function () {
*/
this.downloadFile = function (request) {
var key = request.name;
var item = Zotero.Items.getByKey(key);
var item = Zotero.Items.getByLibraryAndKey(null, key);
if (!item) {
_error("Item '" + key
+ "' not found in Zotero.Sync.Storage.downloadFile()");
@ -735,7 +735,7 @@ Zotero.Sync.Storage = new function () {
wbp.saveURI(uri, null, null, null, null, destFile);
}
catch (e) {
request.error(e.message);
request.error(e);
}
});
}
@ -918,7 +918,7 @@ Zotero.Sync.Storage = new function () {
}
var key = file.replace(/\.(zip|prop)$/, '');
var item = Zotero.Items.getByKey(key);
var item = Zotero.Items.getByLibraryAndKey(null, key);
if (item) {
Zotero.debug("Skipping existing file " + file);
continue;
@ -1006,7 +1006,8 @@ Zotero.Sync.Storage = new function () {
+ "Zotero.Sync.Storage.resetAllSyncStates()");
}
var sql = "UPDATE itemAttachments SET syncState=?";
var sql = "UPDATE itemAttachments SET syncState=? WHERE itemID IN "
+ "(SELECT itemID FROM items WHERE libraryID IS NULL)";
Zotero.DB.query(sql, [syncState]);
var sql = "DELETE FROM version WHERE schema='storage'";
@ -1184,7 +1185,7 @@ Zotero.Sync.Storage = new function () {
*/
function _createUploadFile(request) {
var key = request.name;
var item = Zotero.Items.getByKey(key);
var item = Zotero.Items.getByLibraryAndKey(null, key);
Zotero.debug("Creating zip file for item " + item.key);
try {
@ -1222,7 +1223,7 @@ Zotero.Sync.Storage = new function () {
return true;
}
catch (e) {
request.error(e.message);
request.error(e);
return false;
}
}
@ -1273,7 +1274,7 @@ Zotero.Sync.Storage = new function () {
*/
var request = data.request;
var item = Zotero.Items.getByKey(request.name);
var item = Zotero.Items.getByLibraryAndKey(null, request.name);
Zotero.Sync.Storage.getStorageModificationTime(item, function (item, mdate) {
if (!request.isRunning()) {
@ -1378,7 +1379,7 @@ Zotero.Sync.Storage = new function () {
channel.asyncOpen(listener, null);
}
catch (e) {
request.error(e.message);
request.error(e);
}
});
}
@ -1459,7 +1460,8 @@ Zotero.Sync.Storage = new function () {
* @return {Number[]} Array of attachment itemIDs
*/
function _getFilesToDownload() {
var sql = "SELECT itemID FROM itemAttachments WHERE syncState IN (?,?)";
var sql = "SELECT itemID FROM itemAttachments JOIN items USING (itemID) "
+ "WHERE syncState IN (?,?) AND libraryID IS NULL";
return Zotero.DB.columnQuery(sql,
[
Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD,
@ -1476,8 +1478,8 @@ Zotero.Sync.Storage = new function () {
* @return {Number[]} Array of attachment itemIDs
*/
function _getFilesToUpload() {
var sql = "SELECT itemID FROM itemAttachments WHERE syncState IN (?,?) "
+ "AND linkMode IN (?,?)";
var sql = "SELECT itemID FROM itemAttachments JOIN items USING (itemID) "
+ "WHERE syncState IN (?,?) AND linkMode IN (?,?) AND libraryID IS NULL";
return Zotero.DB.columnQuery(sql,
[
Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD,
@ -2371,7 +2373,7 @@ Zotero.Sync.Storage.QueueManager = new function () {
function _reconcileConflicts() {
var objectPairs = [];
for each(var conflict in _conflicts) {
var item = Zotero.Items.getByKey(conflict.name);
var item = Zotero.Items.getByLibraryAndKey(null, conflict.name);
var item1 = item.clone();
item1.setField('dateModified',
Zotero.Date.dateToSQL(new Date(conflict.localData.modTime * 1000), true));
@ -2406,7 +2408,7 @@ Zotero.Sync.Storage.QueueManager = new function () {
// Since we're only putting cloned items into the merge window,
// we have to manually set the ids
for (var i=0; i<_conflicts.length; i++) {
io.dataOut[i].id = Zotero.Items.getByKey(_conflicts[i].name).id;
io.dataOut[i].id = Zotero.Items.getByLibraryAndKey(null, _conflicts[i].name).id;
}
return io.dataOut;
@ -2876,6 +2878,8 @@ Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress,
Zotero.Sync.Storage.Request.prototype.error = function (msg) {
msg = typeof msg == 'object' ? msg.message : msg;
this.queue.logError(msg);
// DEBUG: ever need to stop channel?

File diff suppressed because it is too large Load diff

View file

@ -265,8 +265,8 @@ Zotero.Translator.prototype.logError = function(message, type, line, lineNumber,
* setItems). setting items disables export of collections.
* path - the path to the target; for web, this is the same as location
* string - the string content to be used as a file.
* saveItem - whether new items should be saved to the database. defaults to
* true; set using second argument of constructor.
* libraryID - libraryID (e.g., of a group) of saved database items. null for local items,
* or false not to save. defaults to null; set using second argument of constructor.
* newItems - items created when translate() was called
* newCollections - collections created when translate() was called
*
@ -305,7 +305,11 @@ Zotero.Translator.prototype.logError = function(message, type, line, lineNumber,
*
* output - export output (if no location has been specified)
*/
Zotero.Translate = function(type, saveItem, saveAttachments) {
Zotero.Translate = function(type) {
if (arguments.length > 1) {
throw ("Zotero.Translate only takes one parameter");
}
this.type = type;
// import = 0001 = 1
@ -335,9 +339,6 @@ Zotero.Translate = function(type, saveItem, saveAttachments) {
}
this._numericTypes = this._numericTypes.substr(1);
this.saveItem = !(saveItem === false);
this.saveAttachments = !(saveAttachments === false);
this._handlers = new Array();
this._streams = new Array();
}
@ -606,10 +607,15 @@ Zotero.Translate.prototype._loadTranslator = function() {
return true;
}
/*
/**
* does the actual translation
*
* @param {NULL|Integer|FALSE} [libraryID=null] Library in which to save items,
* or NULL for default library;
* if FALSE, don't save items
* @param {Boolean} [saveAttachments=true]
*/
Zotero.Translate.prototype.translate = function() {
Zotero.Translate.prototype.translate = function(libraryID, saveAttachments) {
/*
* initialize properties
*/
@ -629,6 +635,24 @@ Zotero.Translate.prototype.translate = function() {
throw("cannot translate: no location specified");
}
this.libraryID = (libraryID == undefined) ? null : libraryID;
this.saveAttachments = !(saveAttachments === false);
this.saveFiles = this.saveAttachments;
// If group filesEditable==false, don't save attachments
if (this.libraryID) {
var type = Zotero.Libraries.getType(this.libraryID);
switch (type) {
case 'group':
var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
var group = Zotero.Groups.get(groupID);
if (!group.filesEditable) {
this.saveFiles = false;
}
break;
}
}
// erroring should end
this.error = this._translationComplete;
@ -750,7 +774,7 @@ Zotero.Translate.prototype._generateSandbox = function() {
// for loading other translators and accessing their methods
this._sandbox.Zotero.loadTranslator = function(type) {
var translation = new Zotero.Translate(type, false);
var translation = new Zotero.Translate(type);
translation._parentTranslator = me;
if(type == "export" && (this.type == "web" || this.type == "search")) {
@ -781,7 +805,7 @@ Zotero.Translate.prototype._generateSandbox = function() {
}
}
return translation.translate()
return translation.translate(false);
};
safeTranslator.getTranslatorObject = function() {
// load the translator into our sandbox
@ -1134,7 +1158,7 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
this._itemsDone = true;
if(!this.saveItem) { // if we're not supposed to save the item, just
if(this.libraryID === false) { // if we're not supposed to save the item, just
// return the item array
// if a parent sandbox exists, use complete() function from that sandbox
@ -1151,7 +1175,8 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
var type = (item.itemType ? item.itemType : "webpage");
if(type == "note") { // handle notes differently
var newItem = new Zotero.Item(false, 'note');
var newItem = new Zotero.Item('note');
newItem.libraryID = this.libraryID ? this.libraryID : null;
newItem.setNote(item.note);
var myID = newItem.save();
// re-retrieve the item
@ -1248,7 +1273,8 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
}
} else {
var typeID = Zotero.ItemTypes.getID(type);
var newItem = new Zotero.Item(false, typeID);
var newItem = new Zotero.Item(typeID);
newItem.libraryID = this.libraryID ? this.libraryID : null;
}
// makes looping through easier
@ -1280,7 +1306,7 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
}
// Single-field mode
if (data[j].fieldMode == 1) {
if (data[j].fieldMode && data[j].fieldMode == 1) {
var fields = {
lastName: data[j].lastName,
fieldMode: 1
@ -1294,14 +1320,19 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
};
}
var creator = null;
var creatorDataID = Zotero.Creators.getDataID(fields);
if(creatorDataID) {
var linkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID);
// TODO: support identical creators via popup? ugh...
var creatorID = linkedCreators[0];
var creator = Zotero.Creators.get(creatorID);
} else {
var creator = new Zotero.Creator;
var linkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID, this.libraryID);
if (linkedCreators) {
// TODO: support identical creators via popup? ugh...
var creatorID = linkedCreators[0];
creator = Zotero.Creators.get(creatorID);
}
}
if(!creator) {
creator = new Zotero.Creator;
creator.libraryID = this.libraryID ? this.libraryID : null;
creator.setFields(fields);
var creatorID = creator.save();
}
@ -1375,7 +1406,8 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
// handle notes
if(item.notes) {
for each(var note in item.notes) {
var myNote = new Zotero.Item(false, 'note');
var myNote = new Zotero.Item('note');
myNote.libraryID = this.libraryID ? this.libraryID : null;
myNote.setNote(note.note);
if (myID) {
myNote.setSource(myID);
@ -1423,9 +1455,9 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
}
}
} else if(attachment.document
} else if(this.saveFiles && (attachment.document
|| (attachment.mimeType && attachment.mimeType == "text/html")
|| downloadAssociatedFiles) {
|| downloadAssociatedFiles)) {
// if snapshot is not explicitly set to false, retrieve snapshot
if(attachment.document) {
@ -1438,8 +1470,8 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
}
// Save attachment if snapshot pref enabled or not HTML
// (in which case downloadAssociatedFiles applies)
} else if(automaticSnapshots || !attachment.mimeType
|| attachment.mimeType != "text/html") {
} else if(this.saveFiles && (automaticSnapshots || !attachment.mimeType
|| attachment.mimeType != "text/html")) {
var mimeType = null;
var title = null;

View file

@ -0,0 +1,112 @@
Zotero.URI = new function () {
var _baseURI = ZOTERO_CONFIG.BASE_URI;
this.getCurrentUserURI = function () {
var userID = Zotero.userID;
if (userID) {
return _baseURI + "users/" + userID;
}
return _baseURI + "users/local/" + Zotero.getLocalUserKey(true);
}
this.getLibraryURI = function (libraryID) {
var libraryType = Zotero.Libraries.getType(libraryID);
switch (libraryType) {
case 'group':
var id = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
break;
case 'user':
throw ("User library ids are not supported in Zotero.URI.getLibraryURI");
default:
throw ("Unsupported library type '" + libraryType + "' in Zotero.URI.getLibraryURI()");
}
return _baseURI + libraryType + "s/" + id;
}
this.getItemURI = function (item) {
if (item.libraryID) {
var baseURI = this.getLibraryURI(item.libraryID);
}
else {
var baseURI = this.getCurrentUserURI();
}
return baseURI + "/items/" + item.key;
}
this.getGroupsURL = function () {
return ZOTERO_CONFIG.WWW_BASE_URL + "groups";
}
/**
* @param {Zotero.Group} group
* @return {String}
*/
this.getGroupURI = function (group, webRoot) {
var uri = _baseURI + "groups/" + group.id;
if (webRoot) {
uri = uri.replace(ZOTERO_CONFIG.BASE_URI, ZOTERO_CONFIG.WWW_BASE_URL);
}
return uri;
}
/**
* Convert an item URI into an item
*
* @param {String} itemURI
* @param {Zotero.Item|FALSE}
*/
this.getURIItem = function (itemURI) {
var libraryType = null;
// If this is a local URI, compare to the local user key
if (itemURI.match(/\/users\/local\//)) {
var currentUserURI = this.getCurrentUserURI() + "/";
if (itemURI.indexOf(currentUserURI) == 0) {
itemURI = itemURI.substr(currentUserURI.length);
var libraryType = 'user';
var libraryTypeID = null;
}
}
// If not found, try global URI
if (!libraryType) {
if (itemURI.indexOf(_baseURI) != 0) {
throw ("Invalid base URI '" + itemURI + "' in Zotero.URI.getURIItem()");
}
itemURI = itemURI.substr(_baseURI.length);
var typeRE = /^(users|groups)\/([0-9]+)\//;
var matches = itemURI.match(typeRE);
if (!matches) {
throw ("Invalid library URI '" + itemURI + "' in Zotero.URI.getURIItem()");
}
var libraryType = matches[1].substr(0, matches[1].length-1);
var libraryTypeID = matches[2];
itemURI = itemURI.replace(typeRE, '');
}
// TODO: itemID-based URI?
var matches = itemURI.match(/items\/([A-Z0-9]{8})/);
if (!matches) {
throw ("Invalid item URI '" + itemURI + "' in Zotero.URI.getURIItem()");
}
var itemKey = matches[1];
if (libraryType == 'user') {
return Zotero.Items.getByLibraryAndKey(null, itemKey);
}
if (libraryType == 'group') {
var libraryID = Zotero.Groups.getLibraryIDFromGroupID(libraryTypeID);
return Zotero.Items.getByLibraryAndKey(libraryID, itemKey);
}
}
}

View file

@ -27,6 +27,8 @@ const ZOTERO_CONFIG = {
REPOSITORY_CHECK_INTERVAL: 86400, // 24 hours
REPOSITORY_RETRY_INTERVAL: 3600, // 1 hour
FIRST_RUN_URL: 'http://www.zotero.org/support/quick_start_guide',
BASE_URI: 'http://zotero.org/',
WWW_BASE_URL: 'http://www.zotero.org/',
SYNC_URL: 'https://sync.zotero.org/'
};
@ -62,7 +64,6 @@ var Zotero = new function(){
this.hasValues = hasValues;
this.randomString = randomString;
this.moveToUnique = moveToUnique;
this.reloadDataObjects = reloadDataObjects;
// Public properties
this.initialized = false;
@ -77,6 +78,49 @@ var Zotero = new function(){
this.isWin;
this.initialURL; // used by Schema to show the changelog on upgrades
this.__defineGetter__('userID', function () {
var sql = "SELECT value FROM settings WHERE "
+ "setting='account' AND key='userID'";
return Zotero.DB.valueQuery(sql);
});
this.__defineSetter__('userID', function (val) {
var sql = "REPLACE INTO settings VALUES ('account', 'userID', ?)";
Zotero.DB.query(sql, parseInt(val));
});
this.__defineGetter__('libraryID', function () {
var sql = "SELECT value FROM settings WHERE "
+ "setting='account' AND key='libraryID'";
return Zotero.DB.valueQuery(sql);
});
this.__defineSetter__('libraryID', function (val) {
var sql = "REPLACE INTO settings VALUES ('account', 'libraryID', ?)";
Zotero.DB.query(sql, parseInt(val));
});
this.getLocalUserKey = function (generate) {
if (_localUserKey) {
return _localUserKey;
}
var sql = "SELECT value FROM settings WHERE "
+ "setting='account' AND key='localUserKey'";
var key = Zotero.DB.valueQuery(sql);
// Generate a local user key if we don't have a global library id
if (!key && generate) {
key = Zotero.randomString(8);
var sql = "INSERT INTO settings VALUES ('account', 'localUserKey', ?)";
Zotero.DB.query(sql, key);
}
_localUserKey = key;
return key;
};
var _startupError;
var _startupErrorHandler;
var _zoteroDirectory = false;
@ -85,7 +129,7 @@ var Zotero = new function(){
var _debugTime;
var _debugLastTime;
var _localizedStringBundle;
var _localUserKey;
/*
* Initialize the extension
@ -841,19 +885,21 @@ var Zotero = new function(){
* values and/or an arbitrary number of individual values
*/
function flattenArguments(args){
var isArguments = args.callee && args.length;
// Put passed scalar values into an array
if (typeof args!='object' || args===null){
if (args === null || (args.constructor.name != 'Array' && !isArguments)) {
args = [args];
}
var returns = new Array();
var returns = [];
for (var i=0; i<args.length; i++){
if (typeof args[i]=='object'){
if(args[i]) {
for (var j=0; j<args[i].length; j++){
returns.push(args[i][j]);
}
if (!args[i]) {
continue;
}
if (args[i].constructor.name == 'Array') {
for (var j=0; j<args[i].length; j++){
returns.push(args[i][j]);
}
}
else {
@ -987,7 +1033,7 @@ var Zotero = new function(){
}
function reloadDataObjects() {
this.reloadDataObjects = function () {
Zotero.Tags.reloadAll();
Zotero.Collections.reloadAll();
Zotero.Creators.reloadAll();
@ -2129,7 +2175,7 @@ Zotero.DragDrop = {
dragData.dataType = 'text/x-moz-url';
var urls = [];
for (var i=0; i<len; i++) {
var url = dt.getData("application/x-moz-url", i).split("\n")[0];
var url = dt.getData("text/x-moz-url").split("\n")[0];
urls.push(url);
}
dragData.data = urls;

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

View file

@ -69,7 +69,7 @@ zoteromergegroup {
overflow-y: auto;
}
zoteromergepane #delete-box {
zoteromergepane #trash-box, zoteromergepane #delete-box {
min-width: 15em;
-moz-box-align: center;
-moz-box-pack: center;

View file

@ -115,6 +115,12 @@ window[active="true"] #zotero-pane[fullscreenmode="true"][platform="mac"]
margin-top: -2px;
}
#zotero-tb-group-add
{
list-style-image: url('chrome://zotero/skin/group_add.png');
}
#zotero-tb-tag-selector
{
list-style-image: url(chrome://zotero/skin/tag-selector.png);
@ -283,6 +289,10 @@ window[active="true"] #zotero-pane[fullscreenmode="true"][platform="mac"]
list-style-image: url('chrome://zotero/skin/search-cancel-active.png');
}
#zotero-view-tabs tab
{
}
#zotero-view-item > vbox
{
overflow: auto;

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

View file

@ -109,13 +109,14 @@ function ChromeExtensionHandler() {
break;
case 'search':
var s = new Zotero.Search(ids);
var ids = s.search();
var s = new Zotero.Search();
s.id = ids;
ids = s.search();
break;
case 'items':
case 'item':
var ids = ids.split('-');
ids = ids.split('-');
break;
default:

View file

@ -30,8 +30,13 @@ var xpcomFiles = [
'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',
@ -57,6 +62,7 @@ var xpcomFiles = [
'storage',
'timeline',
'translate',
'uri',
'utilities',
'zeroconf'
];

View file

@ -6,7 +6,7 @@
<em:id>zotero@chnm.gmu.edu</em:id>
<em:name>Zotero</em:name>
<em:version>1.5b2.SVN</em:version>
<em:version>2.0b3.SVN</em:version>
<em:creator>Center for History and New Media<br/>George Mason University</em:creator>
<em:contributor>Dan Cohen</em:contributor>
<em:contributor>Sean Takats</em:contributor>

View file

@ -1,4 +1,4 @@
-- 22
-- 24
-- This file creates system tables that can be safely wiped and reinitialized
-- at any time, as long as existing ids are preserved.
@ -275,6 +275,7 @@ INSERT INTO fields VALUES (114,'proceedingsTitle',NULL);
INSERT INTO fields VALUES (115,'bookTitle',NULL);
INSERT INTO fields VALUES (116,'shortTitle',NULL);
INSERT INTO fields VALUES (117,'docketNumber',NULL);
INSERT INTO fields VALUES (118,'numPages',NULL);
INSERT INTO itemTypeFields VALUES (2, 110, NULL, 1);
INSERT INTO itemTypeFields VALUES (2, 90, NULL, 2);
@ -286,7 +287,7 @@ INSERT INTO itemTypeFields VALUES (2, 6, NULL, 7);
INSERT INTO itemTypeFields VALUES (2, 7, NULL, 8);
INSERT INTO itemTypeFields VALUES (2, 8, NULL, 9);
INSERT INTO itemTypeFields VALUES (2, 14, NULL, 10);
INSERT INTO itemTypeFields VALUES (2, 10, NULL, 11);
INSERT INTO itemTypeFields VALUES (2, 118, NULL, 11);
INSERT INTO itemTypeFields VALUES (2, 87, NULL, 12);
INSERT INTO itemTypeFields VALUES (2, 11, NULL, 13);
INSERT INTO itemTypeFields VALUES (2, 116, NULL, 14);
@ -408,7 +409,7 @@ INSERT INTO itemTypeFields VALUES (9, 90, NULL, 2);
INSERT INTO itemTypeFields VALUES (9, 66, NULL, 3);
INSERT INTO itemTypeFields VALUES (9, 7, NULL, 4);
INSERT INTO itemTypeFields VALUES (9, 14, NULL, 5);
INSERT INTO itemTypeFields VALUES (9, 10, NULL, 6);
INSERT INTO itemTypeFields VALUES (9, 118, NULL, 6);
INSERT INTO itemTypeFields VALUES (9, 87, NULL, 7);
INSERT INTO itemTypeFields VALUES (9, 116, NULL, 8);
INSERT INTO itemTypeFields VALUES (9, 1, NULL, 9);
@ -1253,3 +1254,4 @@ INSERT INTO "syncObjectTypes" VALUES(2, 'creator');
INSERT INTO "syncObjectTypes" VALUES(3, 'item');
INSERT INTO "syncObjectTypes" VALUES(4, 'search');
INSERT INTO "syncObjectTypes" VALUES(5, 'tag');
INSERT INTO "syncObjectTypes" VALUES(6, 'relations');

View file

@ -3,7 +3,7 @@
"translatorType":4,
"label":"zotero.org",
"creator":"Dan Stillman",
"target":"^https?://[^/]*zotero\\.org/[^/]+/[0-9]+/(items(/?[0-9]+?)?|items/collection/[0-9]+)(\\?.*)?$",
"target":"^https?://[^/]*zotero\\.net/(groups/)?[^/]+/[0-9]+/(items(/?[0-9]+?)?|items/collection/[0-9]+)(\\?.*)?$",
"minVersion":"1.0",
"maxVersion":"",
"priority":100,
@ -16,7 +16,7 @@ function detectWeb(doc, url) {
var nsResolver = namespace ? function(prefix) {
if (prefix == 'x') return namespace; else return null;
} : null;
var a = doc.evaluate('//div[@id="login-links"]/a[text()="My Library"]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext();
var a = doc.evaluate('//li[@class="topnav"]/a[text()="My Library"]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext();
// Skip current user's library
if (a && url.indexOf(a.href.match(/(^.+)\/items/)[1]) == 0) {
return false;
@ -131,10 +131,20 @@ function xmlToItem(xmlItem) {
}
function doWeb(doc, url) {
var userID = url.match(/^http:\/\/[^\/]*zotero\.org\/[^\/]+\/([0-9]+)/)[1];
var apiPrefix = "https://api.zotero.org/users/" + userID + "/";
var itemRe = /^http:\/\/[^\/]*zotero\.org\/[^\/]+\/[0-9]+\/items\/([0-9]+)/;
if (url.indexOf("/groups/") == -1) {
var userID = url.match(/^http:\/\/[^\/]*zotero\.net\/[^\/]+\/([0-9]+)/)[1];
var apiPrefix = "https://apidev.zotero.org/users/" + userID + "/";
var itemRe = /^http:\/\/[^\/]*zotero\.net\/[^\/]+\/[0-9]+\/items\/([0-9]+)/;
} else {
//var groupID = url.match(/^http:\/\/[^\/]*zotero\.net\/groups\/[^\/]+\/([0-9]+)/)[1]; // need slug url fix
var groupID = url.match(/^http:\/\/[^\/]*zotero\.net\/groups\/([0-9]+)/)[1];
var apiPrefix = "https://apidev.zotero.org/groups/" + groupID + "/";
//var itemRe = /^http:\/\/[^\/]*zotero\.net\/groups\/[^\/]+\/[0-9]+\/items\/([0-9]+)/;
var itemRe = /^http:\/\/[^\/]*zotero\.net\/groups\/[0-9]+\/items\/([0-9]+)/;
}
var nsAtom = new Namespace('http://www.w3.org/2005/Atom');
var nsZXfer = new Namespace('http://zotero.org/namespaces/transfer');
@ -143,7 +153,7 @@ function doWeb(doc, url) {
var nsResolver = namespace ? function(prefix) {
if (prefix == 'x') return namespace; else return null;
} : null;
var column = doc.evaluate('//table[@id="field-table"]//td[1][@class="title"]', doc, nsResolver, XPathResult.ANY_TYPE, null);
var column = doc.evaluate('//table[@id="field-table"]//td[1][@class="title"][not(contains(./a, "Unpublished Note"))]', doc, nsResolver, XPathResult.ANY_TYPE, null);
var elems = [], td;
while (td = column.iterateNext()) {
elems.push(td);

View file

@ -1,4 +1,4 @@
-- 4
-- 9
-- Triggers to validate date field
DROP TRIGGER IF EXISTS insert_date_field;
@ -97,6 +97,47 @@ CREATE TRIGGER fku_collections_collectionID_collections_parentCollectionID
UPDATE collections SET parentCollectionID=NEW.collectionID WHERE parentCollectionID=OLD.collectionID;
END;
-- Don't allow collection parents in different libraries
DROP TRIGGER IF EXISTS fki_collections_parentCollectionID_libraryID;
CREATE TRIGGER fki_collections_parentCollectionID_libraryID
BEFORE INSERT ON collections
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "collections" violates foreign key constraint "fki_collections_parentCollectionID_libraryID"')
WHERE NEW.parentCollectionID IS NOT NULL AND
(
(
NEW.libraryID IS NULL
AND
(SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID) IS NOT NULL
) OR (
NEW.libraryID IS NOT NULL
AND
(SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID) IS NULL
) OR
NEW.libraryID != (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID)
);
END;
DROP TRIGGER IF EXISTS fku_collections_parentCollectionID_libraryID;
CREATE TRIGGER fku_collections_parentCollectionID_libraryID
BEFORE UPDATE ON collections
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "collections" violates foreign key constraint "fku_collections_parentCollectionID_libraryID"')
WHERE NEW.parentCollectionID IS NOT NULL AND
(
(
NEW.libraryID IS NULL
AND
(SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID) IS NOT NULL
) OR (
NEW.libraryID IS NOT NULL
AND
(SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID) IS NULL
) OR
NEW.libraryID != (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID)
);
END;
-- collectionItems/collectionID
DROP TRIGGER IF EXISTS fki_collectionItems_collectionID_collections_collectionID;
CREATE TRIGGER fki_collectionItems_collectionID_collections_collectionID
@ -161,6 +202,76 @@ CREATE TRIGGER fku_items_itemID_collectionItems_itemID
UPDATE collectionItems SET collectionID=NEW.itemID WHERE collectionID=OLD.itemID;
END;
-- collectionItems libraryID
DROP TRIGGER IF EXISTS fki_collectionItems_libraryID;
CREATE TRIGGER fki_collectionItems_libraryID
BEFORE INSERT ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "collectionItems" violates foreign key constraint "fki_collectionItems_libraryID"')
WHERE (
(SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
) OR (
(SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
) OR
(SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);
END;
DROP TRIGGER IF EXISTS fku_collectionItems_libraryID;
CREATE TRIGGER fku_collectionItems_libraryID
BEFORE UPDATE ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "collectionItems" violates foreign key constraint "fku_collectionItems_libraryID"')
WHERE (
(SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
) OR (
(SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
) OR
(SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);
END;
-- Don't allow child items to exist explicitly in collections
DROP TRIGGER IF EXISTS fki_collectionItems_itemID_sourceItemID;
CREATE TRIGGER fki_collectionItems_itemID_sourceItemID
BEFORE INSERT ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "collectionItems" violates foreign key constraint "fki_collectionItems_itemID_sourceItemID"')
WHERE NEW.itemID IN (SELECT itemID FROM items WHERE itemID IN (SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL) OR itemID IN (SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL));
END;
DROP TRIGGER IF EXISTS fku_collectionItems_itemID_sourceItemID;
CREATE TRIGGER fku_collectionItems_itemID_sourceItemID
BEFORE UPDATE OF itemID ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "collectionItems" violates foreign key constraint "fku_collectionItems_itemID_sourceItemID"')
WHERE NEW.itemID IN (SELECT itemID FROM items WHERE itemID IN (SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL) OR itemID IN (SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL));
END;
DROP TRIGGER IF EXISTS fku_itemAttachments_sourceItemID_collectionItems_itemID;
CREATE TRIGGER fku_itemAttachments_sourceItemID_collectionItems_itemID
BEFORE UPDATE OF sourceItemID ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemAttachments" violates foreign key constraint "fku_itemAttachments_sourceItemID_collectionItems_itemID"')
WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM collectionItems WHERE itemID = NEW.itemID) > 0;
END;
DROP TRIGGER IF EXISTS fku_itemNotes_sourceItemID_collectionItems_itemID;
CREATE TRIGGER fku_itemNotes_sourceItemID_collectionItems_itemID
BEFORE UPDATE OF sourceItemID ON itemNotes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemNotes" violates foreign key constraint "fku_itemNotes_sourceItemID_collectionItems_itemID"')
WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM collectionItems WHERE itemID = NEW.itemID) > 0;
END;
-- creators/creatorDataID
DROP TRIGGER IF EXISTS fki_creators_creatorDataID_creatorData_creatorDataID;
CREATE TRIGGER fki_creators_creatorDataID_creatorData_creatorDataID
@ -292,6 +403,102 @@ CREATE TRIGGER fku_items_itemID_fulltextItemWords_itemID
UPDATE fulltextItemWords SET itemID=NEW.itemID WHERE itemID=OLD.itemID;
END;
-- groups/libraryID
DROP TRIGGER IF EXISTS fki_groups_libraryID_libraries_libraryID;
CREATE TRIGGER fki_groups_libraryID_libraries_libraryID
BEFORE INSERT ON groups
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "groups" violates foreign key constraint "fki_groups_libraryID_libraries_libraryID"')
WHERE NEW.libraryID IS NOT NULL AND (SELECT COUNT(*) FROM libraries WHERE libraryID = NEW.libraryID) = 0;
END;
DROP TRIGGER IF EXISTS fku_groups_libraryID_libraries_libraryID;
CREATE TRIGGER fku_groups_libraryID_libraries_libraryID
BEFORE UPDATE OF libraryID ON groups
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "groups" violates foreign key constraint "fku_groups_libraryID_libraries_libraryID"')
WHERE NEW.libraryID IS NOT NULL AND (SELECT COUNT(*) FROM libraries WHERE libraryID = NEW.libraryID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_groups_libraryID_libraries_libraryID;
CREATE TRIGGER fkd_groups_libraryID_libraries_libraryID
BEFORE DELETE ON libraries
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "libraries" violates foreign key constraint "fkd_groups_libraryID_libraries_libraryID"')
WHERE (SELECT COUNT(*) FROM groups WHERE libraryID = OLD.libraryID) > 0;
END;
DROP TRIGGER IF EXISTS fku_libraries_libraryID_groups_libraryID;
CREATE TRIGGER fku_libraries_libraryID_groups_libraryID
AFTER UPDATE OF libraryID ON libraries
FOR EACH ROW BEGIN
UPDATE groups SET libraryID=NEW.libraryID WHERE libraryID=OLD.libraryID;
END;
-- groupItems/createdByUserID
DROP TRIGGER IF EXISTS fki_groupItems_createdByUserID_users_userID;
CREATE TRIGGER fki_groupItems_createdByUserID_users_userID
BEFORE INSERT ON groupItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "groupItems" violates foreign key constraint "fki_groupItems_createdByUserID_users_userID"')
WHERE NEW.createdByUserID IS NOT NULL AND (SELECT COUNT(*) FROM users WHERE userID = NEW.createdByUserID) = 0;
END;
DROP TRIGGER IF EXISTS fku_groupItems_createdByUserID_users_userID;
CREATE TRIGGER fku_groupItems_createdByUserID_users_userID
BEFORE UPDATE OF createdByUserID ON groupItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "groupItems" violates foreign key constraint "fku_groupItems_createdByUserID_users_userID"')
WHERE NEW.createdByUserID IS NOT NULL AND (SELECT COUNT(*) FROM users WHERE userID = NEW.createdByUserID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_groupItems_createdByUserID_users_userID;
CREATE TRIGGER fkd_groupItems_createdByUserID_users_userID
BEFORE DELETE ON users
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "users" violates foreign key constraint "fkd_groupItems_createdByUserID_users_userID"')
WHERE (SELECT COUNT(*) FROM groupItems WHERE createdByUserID = OLD.userID) > 0;
END;
DROP TRIGGER IF EXISTS fku_users_userID_groupItems_createdByUserID;
CREATE TRIGGER fku_users_userID_groupItems_createdByUserID
AFTER UPDATE OF userID ON users
FOR EACH ROW BEGIN
UPDATE groupItems SET createdByUserID=NEW.userID WHERE createdByUserID=OLD.userID;
END;
-- groupItems/lastModifiedByUserID
DROP TRIGGER IF EXISTS fki_groupItems_lastModifiedByUserID_users_userID;
CREATE TRIGGER fki_groupItems_lastModifiedByUserID_users_userID
BEFORE INSERT ON groupItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "groupItems" violates foreign key constraint "fki_groupItems_lastModifiedByUserID_users_userID"')
WHERE NEW.lastModifiedByUserID IS NOT NULL AND (SELECT COUNT(*) FROM users WHERE userID = NEW.lastModifiedByUserID) = 0;
END;
DROP TRIGGER IF EXISTS fku_groupItems_lastModifiedByUserID_users_userID;
CREATE TRIGGER fku_groupItems_lastModifiedByUserID_users_userID
BEFORE UPDATE OF lastModifiedByUserID ON groupItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "groupItems" violates foreign key constraint "fku_groupItems_lastModifiedByUserID_users_userID"')
WHERE NEW.lastModifiedByUserID IS NOT NULL AND (SELECT COUNT(*) FROM users WHERE userID = NEW.lastModifiedByUserID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_groupItems_lastModifiedByUserID_users_userID;
CREATE TRIGGER fkd_groupItems_lastModifiedByUserID_users_userID
BEFORE DELETE ON users
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "users" violates foreign key constraint "fkd_groupItems_lastModifiedByUserID_users_userID"')
WHERE (SELECT COUNT(*) FROM groupItems WHERE lastModifiedByUserID = OLD.userID) > 0;
END;
DROP TRIGGER IF EXISTS fku_users_userID_groupItems_lastModifiedByUserID;
CREATE TRIGGER fku_users_userID_groupItems_lastModifiedByUserID
AFTER UPDATE OF userID ON users
FOR EACH ROW BEGIN
UPDATE groupItems SET lastModifiedByUserID=NEW.userID WHERE lastModifiedByUserID=OLD.userID;
END;
-- highlights/itemID
DROP TRIGGER IF EXISTS fki_highlights_itemID_itemAttachments_itemID;
CREATE TRIGGER fki_highlights_itemID_itemAttachments_itemID
@ -356,6 +563,49 @@ CREATE TRIGGER fku_items_itemID_itemAttachments_itemID
UPDATE itemAttachments SET itemID=NEW.itemID WHERE itemID=OLD.itemID;
END;
-- itemAttachments libraryID
DROP TRIGGER IF EXISTS fki_itemAttachments_libraryID;
CREATE TRIGGER fki_itemAttachments_libraryID
BEFORE INSERT ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemAttachments" violates foreign key constraint "fki_itemAttachments_libraryID"')
WHERE
NEW.sourceItemID IS NOT NULL AND (
(
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NOT NULL
) OR (
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NULL
) OR
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID)
);
END;
DROP TRIGGER IF EXISTS fku_itemAttachments_libraryID;
CREATE TRIGGER fku_itemAttachments_libraryID
BEFORE UPDATE ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemAttachments" violates foreign key constraint "fku_itemAttachments_libraryID"')
WHERE
NEW.sourceItemID IS NOT NULL AND (
(
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NOT NULL
) OR (
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NULL
) OR
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID)
);
END;
-- itemAttachments/sourceItemID
DROP TRIGGER IF EXISTS fki_itemAttachments_sourceItemID_items_itemID;
CREATE TRIGGER fki_itemAttachments_sourceItemID_items_itemID
@ -485,6 +735,43 @@ CREATE TRIGGER fku_creatorTypes_creatorTypeID_itemCreators_creatorTypeID
WHERE (SELECT COUNT(*) FROM itemCreators WHERE creatorTypeID = OLD.creatorTypeID) > 0;
END;
-- itemCreators libraryID
DROP TRIGGER IF EXISTS fki_itemCreators_libraryID;
CREATE TRIGGER fki_itemCreators_libraryID
BEFORE INSERT ON itemCreators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemCreators" violates foreign key constraint "fki_itemCreators_libraryID"')
WHERE (
(SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
) OR (
(SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
) OR
(SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);
END;
DROP TRIGGER IF EXISTS fku_itemCreators_libraryID;
CREATE TRIGGER fku_itemCreators_libraryID
BEFORE UPDATE ON itemCreators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemCreators" violates foreign key constraint "fku_itemCreators_libraryID"')
WHERE (
(SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
) OR (
(SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
) OR
(SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);
END;
-- itemData/itemID
DROP TRIGGER IF EXISTS fki_itemData_itemID_items_itemID;
CREATE TRIGGER fki_itemData_itemID_items_itemID
@ -615,6 +902,49 @@ CREATE TRIGGER fku_items_itemID_itemNotes_itemID
UPDATE itemNotes SET itemID=NEW.itemID WHERE itemID=OLD.itemID;
END;
-- itemNotes libraryID
DROP TRIGGER IF EXISTS fki_itemNotes_libraryID;
CREATE TRIGGER fki_itemNotes_libraryID
BEFORE INSERT ON itemNotes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemNotes" violates foreign key constraint "fki_itemNotes_libraryID"')
WHERE
NEW.sourceItemID IS NOT NULL AND (
(
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NOT NULL
) OR (
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NULL
) OR
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID)
);
END;
DROP TRIGGER IF EXISTS fku_itemNotes_libraryID;
CREATE TRIGGER fku_itemNotes_libraryID
BEFORE UPDATE ON itemNotes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemNotes" violates foreign key constraint "fku_itemNotes_libraryID"')
WHERE
NEW.sourceItemID IS NOT NULL AND (
(
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NOT NULL
) OR (
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NULL
) OR
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID)
);
END;
-- itemNotes/sourceItemID
DROP TRIGGER IF EXISTS fki_itemNotes_sourceItemID_items_itemID;
CREATE TRIGGER fki_itemNotes_sourceItemID_items_itemID
@ -647,6 +977,38 @@ CREATE TRIGGER fku_items_itemID_itemNotes_sourceItemID
UPDATE itemNotes SET sourceItemID=NEW.itemID WHERE sourceItemID=OLD.itemID;
END;
-- items/libraryID
DROP TRIGGER IF EXISTS fki_items_libraryID_libraries_libraryID;
CREATE TRIGGER fki_items_libraryID_libraries_libraryID
BEFORE INSERT ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "items" violates foreign key constraint "fki_items_libraryID_libraries_libraryID"')
WHERE NEW.libraryID IS NOT NULL AND (SELECT COUNT(*) FROM libraries WHERE libraryID = NEW.libraryID) = 0;
END;
DROP TRIGGER IF EXISTS fku_items_libraryID_libraries_libraryID;
CREATE TRIGGER fku_items_libraryID_libraries_libraryID
BEFORE UPDATE OF libraryID ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "items" violates foreign key constraint "fku_items_libraryID_libraries_libraryID"')
WHERE NEW.libraryID IS NOT NULL AND (SELECT COUNT(*) FROM libraries WHERE libraryID = NEW.libraryID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_items_libraryID_libraries_libraryID;
CREATE TRIGGER fkd_items_libraryID_libraries_libraryID
BEFORE DELETE ON libraries
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "libraries" violates foreign key constraint "fkd_items_libraryID_libraries_libraryID"')
WHERE (SELECT COUNT(*) FROM items WHERE libraryID = OLD.libraryID) > 0;
END;
DROP TRIGGER IF EXISTS fku_libraries_libraryID_items_libraryID;
CREATE TRIGGER fku_libraries_libraryID_items_libraryID
AFTER UPDATE OF libraryID ON libraries
FOR EACH ROW BEGIN
UPDATE items SET libraryID=NEW.libraryID WHERE libraryID=OLD.libraryID;
END;
-- itemSeeAlso/itemID
DROP TRIGGER IF EXISTS fki_itemSeeAlso_itemID_items_itemID;
CREATE TRIGGER fki_itemSeeAlso_itemID_items_itemID
@ -711,6 +1073,43 @@ CREATE TRIGGER fku_items_itemID_itemSeeAlso_linkedItemID
UPDATE itemSeeAlso SET linkedItemID=NEW.itemID WHERE linkedItemID=OLD.itemID;
END;
-- itemSeeAlso libraryID
DROP TRIGGER IF EXISTS fki_itemSeeAlso_libraryID;
CREATE TRIGGER fki_itemSeeAlso_libraryID
BEFORE INSERT ON itemSeeAlso
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemSeeAlso" violates foreign key constraint "fki_itemSeeAlso_libraryID"')
WHERE (
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID) IS NOT NULL
) OR (
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID) IS NULL
) OR
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID);
END;
DROP TRIGGER IF EXISTS fku_itemSeeAlso_libraryID;
CREATE TRIGGER fku_itemSeeAlso_libraryID
BEFORE UPDATE ON itemSeeAlso
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemSeeAlso" violates foreign key constraint "fku_itemSeeAlso_libraryID"')
WHERE (
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID) IS NOT NULL
) OR (
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID) IS NULL
) OR
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID);
END;
-- itemTags/itemID
DROP TRIGGER IF EXISTS fki_itemTags_itemID_items_itemID;
CREATE TRIGGER fki_itemTags_itemID_items_itemID
@ -743,6 +1142,43 @@ CREATE TRIGGER fkd_items_itemID_itemTags_itemID
UPDATE itemTags SET itemID=NEW.itemID WHERE itemID=OLD.itemID;
END;
-- itemTags libraryID
DROP TRIGGER IF EXISTS fki_itemTags_libraryID;
CREATE TRIGGER fki_itemTags_libraryID
BEFORE INSERT ON itemTags
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemTags" violates foreign key constraint "fki_itemTags_libraryID"')
WHERE (
(SELECT libraryID FROM tags WHERE tagID = NEW.tagID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
) OR (
(SELECT libraryID FROM tags WHERE tagID = NEW.tagID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
) OR
(SELECT libraryID FROM tags WHERE tagID = NEW.tagID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);
END;
DROP TRIGGER IF EXISTS fku_itemTags_libraryID;
CREATE TRIGGER fku_itemTags_libraryID
BEFORE UPDATE ON itemTags
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemTags" violates foreign key constraint "fku_itemTags_libraryID"')
WHERE (
(SELECT libraryID FROM tags WHERE tagID = NEW.tagID) IS NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
) OR (
(SELECT libraryID FROM tags WHERE tagID = NEW.tagID) IS NOT NULL
AND
(SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
) OR
(SELECT libraryID FROM tags WHERE tagID = NEW.tagID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);
END;
-- itemTags/tagID
DROP TRIGGER IF EXISTS fki_itemTags_tagID_tags_tagID;
CREATE TRIGGER fki_itemTags_tagID_tags_tagID
@ -775,6 +1211,7 @@ CREATE TRIGGER fku_tags_tagID_itemTags_tagID
UPDATE itemTags SET tagID=NEW.tagID WHERE tagID=OLD.tagID;
END;
-- savedSearchConditions/savedSearchID
DROP TRIGGER IF EXISTS fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID;
CREATE TRIGGER fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID
@ -807,9 +1244,7 @@ CREATE TRIGGER fku_savedSearches_savedSearchID_savedSearchConditions_savedSearch
UPDATE savedSearchConditions SET savedSearchID=NEW.savedSearchID WHERE savedSearchID=OLD.savedSearchID;
END;
-- deletedItems/itemID
-- savedSearchConditions/savedSearchID
DROP TRIGGER IF EXISTS fki_deletedItems_itemID_items_itemID;
CREATE TRIGGER fki_deletedItems_itemID_items_itemID
BEFORE INSERT ON deletedItems

View file

@ -7,7 +7,7 @@
<RDF:Seq>
<RDF:li>
<RDF:Description>
<version>1.5b2.SVN</version>
<version>2.0b3.SVN</version>
<targetApplication>
<RDF:Description>
<id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</id>

View file

@ -1,7 +1,7 @@
-- 50
-- 53
-- This file creates tables containing user-specific data -- any changes made
-- here must be mirrored in transition steps in schema.js::_migrateSchema()
-- This file creates tables containing user-specific data for new users --
-- any changes made here must be mirrored in transition steps in schema.js::_migrateSchema()
CREATE TABLE version (
@ -20,12 +20,17 @@ CREATE TABLE settings (
-- The foundational table; every item collected has a unique record here
CREATE TABLE items (
itemID INTEGER PRIMARY KEY,
itemTypeID INT,
dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP,
dateModified DATETIME DEFAULT CURRENT_TIMESTAMP,
key TEXT NOT NULL UNIQUE
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,
key TEXT NOT NULL,
UNIQUE (libraryID, key),
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID)
);
CREATE TABLE itemDataValues (
valueID INTEGER PRIMARY KEY,
value UNIQUE
@ -42,7 +47,7 @@ CREATE TABLE itemData (
FOREIGN KEY (valueID) REFERENCES itemDataValues(valueID)
);
-- Note data for note items
-- Note data for note and attachment items
CREATE TABLE itemNotes (
itemID INTEGER PRIMARY KEY,
sourceItemID INT,
@ -71,18 +76,19 @@ CREATE INDEX itemAttachments_sourceItemID ON itemAttachments(sourceItemID);
CREATE INDEX itemAttachments_mimeType ON itemAttachments(mimeType);
CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState);
-- Individual entries for each tag
CREATE TABLE tags (
tagID INTEGER PRIMARY KEY,
name TEXT COLLATE NOCASE,
name TEXT NOT NULL COLLATE NOCASE,
type INT NOT NULL,
dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,
key TEXT NOT NULL UNIQUE,
UNIQUE (name, type)
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)
);
-- Associates items with keywords
CREATE TABLE itemTags (
itemID INT,
tagID INT,
@ -101,18 +107,20 @@ CREATE TABLE itemSeeAlso (
);
CREATE INDEX itemSeeAlso_linkedItemID ON itemSeeAlso(linkedItemID);
CREATE TABLE creators (
creatorID INTEGER PRIMARY KEY,
creatorDataID INT NOT NULL,
dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,
key TEXT NOT NULL UNIQUE,
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);
-- Each individual creator
-- Unique creator data, which can be associated with more than one creator
CREATE TABLE creatorData (
creatorDataID INTEGER PRIMARY KEY,
firstName TEXT,
@ -122,7 +130,6 @@ CREATE TABLE creatorData (
birthYear INT
);
-- Associates single or multiple creators to items
CREATE TABLE itemCreators (
itemID INT,
creatorID INT,
@ -134,18 +141,19 @@ CREATE TABLE itemCreators (
FOREIGN KEY (creatorTypeID) REFERENCES creatorTypes(creatorTypeID)
);
-- Collections for holding items
CREATE TABLE collections (
collectionID INTEGER PRIMARY KEY,
collectionName TEXT,
parentCollectionID INT,
dateAdded DEFAULT CURRENT_TIMESTAMP NOT NULL,
dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,
key TEXT NOT NULL UNIQUE,
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,
key TEXT NOT NULL,
UNIQUE (libraryID, key),
FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID)
);
-- Associates items with the various collections they belong to
CREATE TABLE collectionItems (
collectionID INT,
itemID INT,
@ -158,10 +166,13 @@ CREATE INDEX itemID ON collectionItems(itemID);
CREATE TABLE savedSearches (
savedSearchID INTEGER PRIMARY KEY,
savedSearchName TEXT,
dateAdded DEFAULT CURRENT_TIMESTAMP NOT NULL,
dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,
key TEXT NOT NULL UNIQUE
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,
key TEXT NOT NULL,
UNIQUE (libraryID, key)
);
CREATE TABLE savedSearchConditions (
@ -180,6 +191,44 @@ CREATE TABLE deletedItems (
dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL
);
CREATE TABLE relations (
libraryID INT NOT NULL,
subject TEXT NOT NULL,
predicate TEXT NOT NULL,
object TEXT NOT NULL,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (subject, predicate, object)
);
CREATE INDEX relations_object ON relations(object);
CREATE TABLE libraries (
libraryID INTEGER PRIMARY KEY,
libraryType TEXT NOT NULL
);
CREATE TABLE users (
userID INTEGER PRIMARY KEY,
username TEXT NOT NULL
);
CREATE TABLE groups (
groupID INTEGER PRIMARY KEY,
libraryID INT NOT NULL UNIQUE,
name TEXT NOT NULL,
description TEXT NOT NULL,
editable INT NOT NULL,
filesEditable INT NOT NULL,
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID)
);
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)
);
CREATE TABLE fulltextItems (
itemID INTEGER PRIMARY KEY,
version INT,
@ -207,15 +256,19 @@ CREATE INDEX fulltextItemWords_itemID ON fulltextItemWords(itemID);
CREATE TABLE syncDeleteLog (
syncObjectTypeID INT NOT NULL,
key TEXT NOT NULL UNIQUE,
libraryID INT,
key TEXT NOT NULL,
timestamp INT NOT NULL,
UNIQUE (libraryID, key),
FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)
);
CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp);
CREATE TABLE storageDeleteLog (
key TEXT PRIMARY KEY,
timestamp INT NOT NULL
libraryID INT,
key TEXT NOT NULL,
timestamp INT NOT NULL,
PRIMARY KEY (libraryID, key)
);
CREATE INDEX storageDeleteLog_timestamp ON storageDeleteLog(timestamp);