Initial Zotero 1.5 Megacommit

Apologies for the massive (and, due to data_access.js splitting, difficult-to-follow) commit. Please note that external code that accesses the data layer may need to be tweaked for compatibility. Here's a comprehensive-as-possible changelog:

- Added server sync functionality (incomplete)
- Overhaul of data layer
  - Split data_access.js into separate files (item.js, items.js, creator.js, etc.)
  - Made creators and collections first-class objects, similar to items
  - Constructors now take id as first parameter, e.g. new Zotero.Item(1234, 'book'), to allow explicit id setting and id changing
  - Made various data layer operations (including attachment fields) require a save() rather than making direct DB changes
  - Better handling of unsaved objects
    - Item.setCreator() now takes creator objects instead of creator ids, and Item.save() will auto-save unsaved creators
    - clone() now works on unsaved objects
  - Newly created object instances are now disabled after save() to force refetch of globally accessible instance using Zotero.(Items|Creators|etc.).get()
  - Added secondary lookup key to data objects
  - Deprecated getID() and getItemType() methods in favor of .id and .itemTypeID properties
  - toArray() deprecated in favor of serialize(), which has a somewhat modified format
  - Added support for multiple creators with identical data -- currently unimplemented in interface and most of data layer
  - Added Item.diff() for comparing item metadata
- Database changes
  - Added SQLite triggers to enforce foreign key constraints
  - Added Zotero.DB.transactionVacuum flag to run a VACUUM after a transaction
  - Added Zotero.DB.transactionDate, .transactionDateTime, and transactionTimestamp to retrieve consistent timestamps for entire transaction
  - Properly store 64-bit integers
  - Set PRAGMA locking_mode=EXCLUSIVE on database
  - Set SQLite page size to 4096 on new databases
  - Set SQLite page cache to 8MB
  - Do some database cleanup and integrity checking on migration from 1.0 branch
  - Removed IF NOT EXISTS from userdata.sql CREATE statements -- userdata.sql is now processed only on DB initialization
  - Removed itemNoteTitles table and moved titles into itemNotes
- Abstracted metadata edit box and note box into flexible XBL bindings with various modes, including read-only states
- Massive speed-up of item tree view
- Several fixes from 1.0 branch for Fx3 compatibility
- Added Notifier observer to log delete events for syncing
- Zotero.Utilities changes
  - New methods getSQLDataType() and md5()
  - Removed onError from Zotero.Utilities.HTTP.doGet()
  - Don't display more than 1024 characters in doPost() debug output
  - Don't display passwords in doPost() debug output
- Added Zotero.Notifier.untrigger() -- currently unused
- Added Zotero.reloadDataObjects() to reset all in-memory objects
- Added |chars| parameter to Zotero.randomString(len, chars)
- Added Zotero.Date.getUnixTimestamp() and Date.toUnixTimestamp(JSDate)
- Adjusted zotero-service.js to simplify file inclusion

Various things (such as tags) are temporarily broken.
This commit is contained in:
Dan Stillman 2008-05-04 08:32:48 +00:00
parent ca91b534f2
commit 3de1789f26
59 changed files with 14186 additions and 7494 deletions

View file

@ -1,31 +0,0 @@
#zotero-editpane-dynamic-fields row > hbox,
#zotero-editpane-dynamic-fields row > vbox
{
margin-top: 0 !important;
margin-bottom: 0 !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
#zotero-editpane-dynamic-fields row > hbox > hbox
{
-moz-box-align: center;
}
#zotero-editpane-dynamic-fields row hbox hbox label
{
margin-top: 0;
margin-bottom: 0;
}
#zotero-editpane-dynamic-fields row > toolbarbutton
{
margin-right: 5px;
-moz-image-region: rect(2px, 14px, 18px, 0px);
}
#zotero-editpane-dynamic-fields row vbox[fieldname=abstractNote],
#zotero-editpane-dynamic-fields row vbox[fieldname=extra]
{
margin-left: 1px;
}

View file

@ -0,0 +1,31 @@
row > hbox,
row > vbox
{
margin-top: 0 !important;
margin-bottom: 0 !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
row > hbox > hbox
{
-moz-box-align: center;
}
row hbox hbox label
{
margin-top: 0;
margin-bottom: 0;
}
row > toolbarbutton
{
margin-right: 5px;
-moz-image-region: rect(2px, 14px, 18px, 0px);
}
row vbox[fieldname=abstractNote],
row vbox[fieldname=extra]
{
margin-left: 1px;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,443 @@
<?xml version="1.0"?>
<!--
***** 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 *****
-->
<!DOCTYPE bindings SYSTEM "chrome://zotero/locale/merge.dtd">
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="merge-group">
<resources>
<stylesheet src="chrome://zotero/skin/merge.css"/>
</resources>
<implementation>
<constructor>
<![CDATA[
this._leftpane = this._id('leftpane');
this._rightpane = this._id('rightpane');
this._mergepane = this._id('mergepane');
]]>
</constructor>
<field name="_type"/>
<property name="type" onget="return this._type;">
<setter>
<![CDATA[
this._type = val;
var hbox = document.getAnonymousNodes(this)[0];
hbox.setAttribute('mergetype', val);
]]>
</setter>
</property>
<property name="left" onget="return this._leftpane.ref;">
<setter>
<![CDATA[
// TODO: Make sure object is the correct type
if (val == 'deleted') {
this._leftpane.ref = 'deleted';
return;
}
// Clone object so changes in merge pane don't affect it
this._leftpane.ref = val.clone(true);
this._leftpane.original = val;
]]>
</setter>
</property>
<property name="right" onget="return this._rightpane.ref;">
<setter>
<![CDATA[
// TODO: make sure left is set
if (!this._leftpane.ref) {
throw ("Left object must be set before setting <zoteromergegroup>.right");
}
if (val == 'deleted') {
this._rightpane.ref = 'deleted';
}
else {
// TODO: Make sure object is the correct type
// Clone object so changes in merge pane don't affect it
this._rightpane.ref = val.clone(true);
this._rightpane.original = val;
}
this.refresh();
]]>
</setter>
</property>
<property name="merge" onget="return this._mergepane.ref">
<setter>
<![CDATA[
// TODO: Make sure object is the correct type
this._mergepane.ref = val;
]]>
</setter>
</property>
<property name="leftCaption" onset="this._leftpane.caption.label = val"/>
<property name="rightCaption" onset="this._rightpane.caption.label = val"/>
<property name="mergeCaption" onset="this._mergepane.caption.label = val"/>
<property name="leftpane" onget="return this._leftpane"/>
<property name="rightpane" onget="return this._rightpane"/>
<property name="mergepane" onget="return this._mergepane"/>
<field name="_leftpane"/>
<field name="_rightpane"/>
<field name="_mergepane"/>
<method name="refresh">
<body>
<![CDATA[
// Set merge pane to most recently changed object
// If one object was deleted, set merge pane to other
// TODO: use delete timestamp
if (this._leftpane.ref != 'deleted'
&& this._rightpane.ref != 'deleted') {
var dm1 = this._leftpane.ref.getField('dateModified');
if (dm1) {
dm1 = Zotero.Date.sqlToDate(dm1);
}
var dm2 = this._rightpane.ref.getField('dateModified');
if (dm2) {
dm2 = Zotero.Date.sqlToDate(dm2);
}
}
if (this._leftpane.ref == 'deleted' || dm2 > dm1) {
var mergeItem = this._rightpane.original;
this._leftpane.removeAttribute("selected");
this._rightpane.setAttribute("selected", "true");
}
else {
var mergeItem = this._leftpane.original;
this._rightpane.removeAttribute("selected");
this._leftpane.setAttribute("selected", "true");
}
this._mergepane.ref = mergeItem;
/*
Code to display only the different values -- not used
var diff = this._leftpane.ref.diff(this._rightpane.ref, true);
var fields = [];
var diffFields = [];
for (var field in diff[0].primary) {
fields.push(field);
if (diff[0].primary[field] != diff[1].primary[field]) {
diffFields.push(field);
}
}
for (var field in diff[0].fields) {
fields.push(field);
if (diff[0].fields[field] != diff[1].fields[field]) {
diffFields.push(field);
}
}
this._leftpane.objectbox.fieldOrder = fields;
this._rightpane.objectbox.fieldOrder = fields;
// Display merge pane if item types match
if (this._leftpane.ref.itemTypeID == this._rightpane.ref.itemTypeID) {
this._leftpane.objectbox.visibleFields = fields;
this._rightpane.objectbox.visibleFields = fields;
this._leftpane.objectbox.clickable = false;
this._rightpane.objectbox.clickable = false;
this._leftpane.objectbox.clickableFields = diffFields;
this._rightpane.objectbox.clickableFields = diffFields;
var mergeItem = new Zotero.Item(false, this._leftpane.ref.itemTypeID);
this._mergepane.ref = mergeItem;
this._mergepane.objectbox.visibleFields = fields;
}
// Otherwise only allow clicking on item types
else {
this._leftpane.objectbox.clickableFields = ['itemType'];
this._rightpane.objectbox.clickableFields = ['itemType'];
}
*/
this._mergepane.objectbox.editable = true;
/*
No need to refresh if not comparing fields
this._leftpane.objectbox.refresh();
this._rightpane.objectbox.refresh();
*/
]]>
</body>
</method>
<method name="_id">
<parameter name="id"/>
<body>
<![CDATA[
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
]]>
</body>
</method>
</implementation>
<content>
<xul:hbox id="merge-group" flex="1">
<xul:zoteromergepane id="leftpane" flex="1"/>
<xul:zoteromergepane id="rightpane" flex="1"/>
<xul:zoteromergepane id="mergepane" flex="1"/>
</xul:hbox>
</content>
</binding>
<binding id="merge-pane">
<resources>
<stylesheet src="chrome://zotero/skin/bindings/merge.css"/>
</resources>
<implementation>
<constructor>
<![CDATA[
this.parent = document.getBindingParent(this.parentNode);
]]>
</constructor>
<property name="type" onget="return this.parent.type" readonly="true"/>
<property name="caption" onget="return this._id('caption')" readonly="true"/>
<property name="objectbox" onget="return this._id('objectbox')" readonly="true"/>
<field name="_deleted"/>
<property name="deleted">
<setter>
<![CDATA[
this._deleted = !!val;
var placeholder = this._id('object-placeholder');
if (placeholder) {
placeholder.hidden = !!val;
}
else {
this._id('objectbox').hidden = !!true;
}
var deleteBox = this._id('delete-box');
deleteBox.hidden = !val;
]]>
</setter>
</property>
<property name="ref" onget="return this._deleted ? 'deleted' : this.objectbox.ref;">
<setter>
<![CDATA[
if (val == 'deleted') {
this.deleted = true;
return;
}
this.deleted = false;
// Replace XUL placeholder with XUL object box of given type
var elementName;
switch (this.type) {
case 'item':
elementName = 'zoteroitembox';
break;
case 'note':
elementName = 'zoteronoteeditor';
break;
default:
throw ("Object type '" + this.type
+ "' not supported in <zoteromergepane>.ref");
}
var objbox = document.createElement(elementName);
if (this._id('object-placeholder')) {
var placeholder = this._id('object-placeholder');
placeholder.parentNode.replaceChild(objbox, placeholder);
}
else {
var oldObjBox = this._id('objectbox');
oldObjBox.parentNode.replaceChild(objbox, oldObjBox);
}
objbox.setAttribute("id", "objectbox");
objbox.setAttribute("flex", "1");
if (this.getAttribute('id') == 'mergepane') {
objbox.mode = 'mergeedit';
}
else {
objbox.mode = 'merge';
objbox.clickHandler = this.chooseObj;
}
// Type-specific settings
switch (this.type) {
case 'note':
objbox.buttonCaption = 'Choose this version';
break;
}
objbox.ref = val;
]]>
</setter>
</property>
<field name="original"/> <!-- original object -->
<field name="parent"/>
<method name="chooseObj">
<parameter name="obj"/>
<body>
<![CDATA[
var pane = Zotero.getAncestorByTagName(obj, 'zoteromergepane');
var mergegroup = document.getBindingParent(pane);
var mergepane = mergegroup.mergepane;
if (pane.getAttribute('id') == 'leftpane') {
var position = 'left';
var otherPane = mergegroup.rightpane;
}
else {
var position = 'right';
var otherPane = mergegroup.leftpane;
}
pane.removeAttribute("selected");
otherPane.removeAttribute("selected");
pane.setAttribute("selected", "true");
if (pane.ref == 'deleted') {
mergepane.deleted = true;
}
else {
mergepane.ref = pane.original;
}
]]>
</body>
</method>
<!-- Unused -->
<method name="chooseField">
<parameter name="row"/>
<body>
<![CDATA[
// If used, has to be updated to handle original item
var fieldName = row.firstChild.getAttribute('fieldname');
// TODO: creator/date
var value = row.lastChild.firstChild.nodeValue
var mergegroup = document.getBindingParent(this.parentNode).parent;
var mergepane = mergegroup.mergepane;
var pane = document.getBindingParent(this.parentNode);
if (pane.getAttribute('id') == 'leftpane') {
var position = 'left';
var otherPane = mergegroup.rightpane;
}
else {
var position = 'right';
var otherPane = mergegroup.leftpane;
}
// Changing item type
if (fieldName == 'itemType') {
fieldName = 'itemTypeID';
value = row.lastChild.getAttribute('itemTypeID');
if (!mergepane.ref) {
mergepane.ref = new Zotero.Item(false, value);
}
else {
mergepane.objectbox.changeTypeTo(value, true);
}
pane.objectbox.clickableFields = [];
pane.objectbox.clickable = true;
var fieldIDs = Zotero.ItemFields.getItemTypeFields(value);
var fieldNames = ['itemType'];
for each(var field in fieldIDs) {
fieldNames.push(Zotero.ItemFields.getName(field));
}
otherPane.objectbox.clickableFields = fieldNames;
otherPane.objectbox.clickable = false;
pane.objectbox.refresh();
otherPane.objectbox.refresh();
}
// Changing another field
else {
mergepane.ref.setField(fieldName, value);
}
mergepane.objectbox.refresh();
]]>
</body>
</method>
<method name="_id">
<parameter name="id"/>
<body>
<![CDATA[
if (!document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id).length) {
return false;
}
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
]]>
</body>
</method>
</implementation>
<content>
<xul:groupbox id="merge-pane" flex="1">
<xul:caption id="caption"/>
<xul:box id="object-placeholder"/>
<xul:hbox id="delete-box" hidden="true" flex="1"
onclick="document.getBindingParent(this).chooseObj(this)">
<xul:label value="Deleted"/> <!-- TODO: localize -->
</xul:hbox>
</xul:groupbox>
</content>
</binding>
</bindings>

View file

@ -20,6 +20,7 @@
***** END LICENSE BLOCK *****
-->
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
@ -30,69 +31,209 @@
</resources>
<implementation>
<field name="itemRef">null</field>
<property name="item" onget="return this.itemRef;">
<!--
Public properties
-->
<field name="editable">false</field>
<field name="saveOnEdit">false</field>
<field name="displayTags">false</field>
<field name="displayRelated">false</field>
<field name="displayButton">false</field>
<field name="parentClickHandler"/>
<field name="keyDownHandler"/>
<field name="commandHandler"/>
<field name="clickHandler"/>
<field name="buttonCaption"/>
<!-- Modes are predefined settings groups for particular tasks -->
<field name="_mode">"view"</field>
<property name="mode" onget="return this._mode;">
<setter>
<![CDATA[
// Duplicate default property settings here
this.editable = false;
this.saveOnEdit = false;
this.displayTags = false;
this.displayRelated = false;
this.displayButton = false;
switch (val) {
case 'view':
break;
case 'edit':
this.editable = true;
this.saveOnEdit = true;
this.parentClickHandler = this.selectParent;
this.keyDownHandler = this.handleKeyDown;
this.commandHandler = this.save;
this.displayTags = true;
this.displayRelated = true;
break;
case 'merge':
this.displayButton = true;
break;
case 'mergeedit':
this.editable = true;
this.keyDownHandler = this.handleKeyDown;
break;
default:
throw ("Invalid mode '" + val + "' in noteeditor.xml");
}
this._mode = val;
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
]]>
</setter>
</property>
<field name="_parent"/>
<property name="parent" onget="return this._parent;">
<setter>
<![CDATA[
this.itemRef = val;
this._parent = val;
var citeLabel = this.id('citeLabel');
if(citeLabel.firstChild)
var citeLabel = this._id('citeLabel');
if (citeLabel.firstChild) {
citeLabel.removeChild(citeLabel.firstChild);
}
if(this.item && !this.getAttribute('notitle')=='1')
{
citeLabel.appendChild(document.createTextNode(this.item.getDisplayTitle(true)));
if (this.parent && !this.getAttribute('notitle') == '1') {
citeLabel.appendChild(document.createTextNode(this.parent.getDisplayTitle(true)));
}
]]>
</setter>
</property>
<field name="noteRef">null</field>
<property name="note" onget="return this.noteRef;">
<field name="_item"/>
<property name="item" onget="return this._item;">
<setter>
<![CDATA[
var scrollPos = this.id('noteField').inputField.scrollTop;
this._item = val;
this.noteRef = val;
var parent = this.item.getSource();
if (parent) {
this.parent = Zotero.Items.get(parent);
}
if(this.note.getSource())
this.item = Zotero.Items.get(this.note.getSource());
this._id('links').item = this.item;
this.id('noteField').value = this.note.getNote();
this.id('links').item = this.note;
this.id('noteField').inputField.scrollTop = scrollPos;
this.refresh();
]]>
</setter>
</property>
<field name="collectionRef">null</field>
<property name="collection" onget="return this.collectionRef;" onset="this.collectionRef = val;"/>
<property name="value" onget="return this.id('noteField').value;" onset="this.id('noteField').value = val;"/>
<property name="note"
onget="Zotero.debug('Getting note with .note deprecated -- use .item in zoteronoteeditor'); return this._item"
onset="Zotero.debug('Setting note with .note deprecated -- use .item in zoteronoteeditor'); this.item = val"/>
<property name="ref" onget="return this._item" onset="this.item = val"/>
<field name="collection"/>
<property name="noteField" onget="return this._id('noteField')" readonly="true"/>
<property name="value" onget="return this._id('noteField').value;" onset="this._id('noteField').value = val;"/>
<method name="refresh">
<body>
<![CDATA[
Zotero.debug('Refreshing note editor');
var parentbox = this._id('citeLabel');
var textbox = this._id('noteField');
var button = this._id('goButton');
var button = this._id('goButton');
if (this.editable) {
textbox.removeAttribute('readonly');
}
else {
textbox.setAttribute('readonly', 'true');
}
var scrollPos = textbox.inputField.scrollTop;
if (this.item) {
textbox.value = this.item.getNote();
}
textbox.inputField.scrollTop = scrollPos;
this._id('linksbox').hidden = !(this.displayTags && this.displayRelated);
if (this.parentClickHandler) {
parentbox.setAttribute('onclick',
'document.getBindingParent(this).clickHandler(this)');
}
else {
parentbox.removeAttribute('onclick');
}
if (this.keyDownHandler) {
textbox.setAttribute('onkeydown',
'document.getBindingParent(this).handleKeyDown(event)');
}
else {
textbox.removeAttribute('onkeydown');
}
if (this.commandHandler) {
textbox.setAttribute('oncommand',
'document.getBindingParent(this).commandHandler()');
}
else {
textbox.removeAttribute('oncommand');
}
if (this.displayButton) {
button.label = this.buttonCaption;
button.hidden = false;
button.setAttribute('oncommand',
'document.getBindingParent(this).clickHandler(this)');
}
else {
button.hidden = true;
}
]]>
</body>
</method>
<method name="save">
<body>
<![CDATA[
var noteField = this.id('noteField');
if(this.note) //Update note
{
this.note.updateNote(noteField.value);
}
else //Create new note
{
if(this.item)
var noteID = Zotero.Notes.add(noteField.value,this.item.getID()); //attached to an item
else
{
//independent note
var noteID = Zotero.Notes.add(noteField.value);
if(this.collection)
this.collection.addItem(noteID);
var noteField = this._id('noteField');
// Update note
if (this.item) {
this.item.setNote(noteField.value);
if (this.saveOnEdit) {
this.item.save();
}
this.note = Zotero.Items.get(noteID);
return;
}
// Create new note
var item = new Zotero.Item(false, 'note');
item.setNote(noteField.value);
if (this.parent) {
item.setSource(this.parent.id);
}
if (this.saveOnEdit) {
var id = item.save();
if (this.parent && this.collection) {
this.collection.addItem(id);
}
}
this.item = Zotero.Items.get(id);
]]>
</body>
</method>
<!-- Used to insert a tab manually -->
<method name="handleKeyDown">
<parameter name="event"/>
<body>
@ -110,7 +251,7 @@
if (controller && controller.isCommandEnabled(command)) {
controller = controller.QueryInterface(Components.interfaces.nsICommandController);
var params = Components.classes["@mozilla.org/embedcomp/command-params;1"]
.createInstance(Components.interfaces.nsICommandParams);
.createInstance(Components.interfaces.nsICommandParams);
params.setStringValue("state_data", "\t");
controller.doCommandWithParams(command, params);
}
@ -133,7 +274,7 @@
<method name="focus">
<body>
<![CDATA[
this.id('noteField').focus();
this._id('noteField').focus();
]]>
</body>
</method>
@ -141,7 +282,7 @@
<method name="selectParent">
<body>
<![CDATA[
if (!this.item.getID()) {
if (!this.item.id) {
return;
}
@ -173,12 +314,37 @@
}
zp.clearQuicksearch();
zp.selectItem(this.item.getID());
zp.selectItem(this.item.id);
]]>
</body>
</method>
<method name="id">
<method name="disableUndo">
<body>
<![CDATA[
this.noteField.editor.enableUndo(true);
]]>
</body>
</method>
<method name="enableUndo">
<body>
<![CDATA[
this.noteField.editor.enableUndo(false);
]]>
</body>
</method>
<method name="clearUndo">
<body>
<![CDATA[
this.disableUndo();
this.enableUndo();
]]>
</body>
</method>
<method name="_id">
<parameter name="id"/>
<body>
<![CDATA[
@ -190,15 +356,17 @@
<content>
<xul:vbox xbl:inherits="flex">
<xul:label id="citeLabel" onclick="document.getBindingParent(this).selectParent()"/>
<xul:textbox id="noteField" multiline="true" type="timed" timeout="1000" flex="1" onkeydown="document.getBindingParent(this).handleKeyDown(event)" oncommand="document.getBindingParent(this).save();"/>
<xul:hbox>
<xul:label id="citeLabel"/>
<xul:textbox id="noteField" multiline="true" type="timed" timeout="1000" flex="1"/>
<xul:hbox id="linksbox" hidden="true">
<xul:linksbox id="links" flex="1"/>
</xul:hbox>
<xul:button id="goButton" hidden="true"/>
</xul:vbox>
</content>
</binding>
<binding id="links-box">
<implementation>
<field name="itemRef"/>
@ -271,8 +439,8 @@
</implementation>
<content>
<xul:vbox xbl:inherits="flex">
<xul:label id="seeAlsoLabel" class="zotero-clicky" crop="end" onclick="this.parentNode.parentNode.seeAlsoClick();"/>
<xul:label id="tagsLabel" class="zotero-clicky" crop="end" onclick="this.parentNode.parentNode.tagsClick();"/>
<xul:label id="seeAlsoLabel" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).seeAlsoClick();"/>
<xul:label id="tagsLabel" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).tagsClick();"/>
<xul:popupset>
<xul:popup id="seeAlsoPopup" width="300" onpopupshowing="this.firstChild.reload();">
<xul:seealsobox id="seeAlso" flex="1"/>

View file

@ -268,11 +268,25 @@ var Zotero_Browser = new function() {
this.tabbrowser.addEventListener("resize",
function(e) { Zotero_Browser.resize(e) }, false);
// Resize on text zoom changes
document.getElementById('cmd_textZoomReduce').addEventListener("command",
// Fx2
var reduce = document.getElementById('cmd_textZoomReduce');
if (reduce) {
var enlarge = document.getElementById('cmd_textZoomEnlarge');
var reset = document.getElementById('cmd_textZoomReset');
}
// Fx3
else {
var reduce = document.getElementById('cmd_fullZoomReduce');
var enlarge = document.getElementById('cmd_fullZoomEnlarge');
var reset = document.getElementById('cmd_fullZoomReset');
}
reduce.addEventListener("command",
function(e) { Zotero_Browser.resize(e) }, false);
document.getElementById('cmd_textZoomEnlarge').addEventListener("command",
enlarge.addEventListener("command",
function(e) { Zotero_Browser.resize(e) }, false);
document.getElementById('cmd_textZoomReset').addEventListener("command",
reset.addEventListener("command",
function(e) { Zotero_Browser.resize(e) }, false);
}

View file

@ -244,7 +244,8 @@ var Zotero_File_Interface = new function() {
* collections
*/
function _importCollectionDone(obj, collection) {
collection.changeParent(_importCollection.getID());
collection.parent = _importCollection.id;
collection.save();
}
/*

File diff suppressed because it is too large Load diff

View file

@ -29,52 +29,10 @@
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="itemPane.js"/>
<deck id="zotero-view-item" flex="1" onselect="if (this.selectedIndex!==''){ ZoteroItemPane.loadPane(this.selectedIndex) }">
<scrollbox id="zotero-info" flex="1" orient="vertical" style="overflow:auto">
<popupset>
<popup id="zotero-creator-type-menu" position="after_start"
oncommand="var otherFields = ZoteroItemPane.getCreatorFields(document.popupNode.parentNode);
var typeID = event.explicitOriginalTarget.getAttribute('typeid');
document.popupNode.setAttribute('label',
Zotero.getString('creatorTypes.' + Zotero.CreatorTypes.getName(typeID)) + ':');
document.popupNode.setAttribute('typeid', typeID);
ZoteroItemPane.modifyCreator(document.popupNode.getAttribute('fieldname').split('-')[1],
'typeID', typeID, otherFields)"/>
<popup id="zotero-field-menu">
<menu label="&zotero.item.textTransform;">
<menupopup>
<menuitem label="&zotero.item.textTransform.lowercase;" class="menuitem-non-iconic"
oncommand="ZoteroItemPane.textTransform(document.popupNode, 'lower')"/>
<menuitem label="&zotero.item.textTransform.titlecase;" class="menuitem-non-iconic"
oncommand="ZoteroItemPane.textTransform(document.popupNode, 'title')"/>
</menupopup>
</menu>
</popup>
</popupset>
<hbox id="zotero-editpane-go-buttons" align="center">
<button id="zotero-go-to-url"
onfocus="ZoteroItemPane.ensureElementIsVisible(this)"
oncommand="ZoteroItemPane.onViewClick(this, event)" disabled="false"/>
<button id="zotero-openurl" label="&zotero.toolbar.openURL.label;"
tooltiptext="&zotero.toolbar.openURL.tooltip;"
onfocus="ZoteroItemPane.ensureElementIsVisible(this)"
oncommand="ZoteroItemPane.onOpenURLClick(event);"/>
</hbox>
<hbox align="center">
<menulist id="zotero-editpane-type-menu" oncommand="ZoteroItemPane.changeTypeTo(this.value, this)" flex="1"
onfocus="ZoteroItemPane.ensureElementIsVisible(this)"
onkeypress="if (event.keyCode == event.DOM_VK_TAB){ if (!event.shiftKey) { ZoteroItemPane.focusFirstField('info'); event.preventDefault(); } }">
<menupopup/>
</menulist>
</hbox>
<grid flex="1">
<columns>
<column/>
<column flex="1"/>
</columns>
<rows id="zotero-editpane-dynamic-fields" flex="1"/>
</grid>
</scrollbox>
<deck id="zotero-view-item" flex="1" onselect="if (this.selectedIndex !== '') { ZoteroItemPane.loadPane(this.selectedIndex); }">
<zoteroitembox id="zotero-editpane-item-box" flex="1"/>
<vbox flex="1">
<hbox align="center">
<label id="zotero-editpane-notes-label"/>

View file

@ -0,0 +1,214 @@
var Zotero_Merge_Window = new function () {
this.init = init;
this.onBack = onBack;
this.onNext = onNext;
this.onFinish = onFinish;
this.onCancel = onCancel;
var _wizard = null;
var _wizardPage = null;
var _mergeGroup = null;
var _numObjects = null;
var _initialized = false;
var _io = null;
var _objects = null;
var _merged = [];
var _pos = -1;
function init() {
_wizard = document.getElementsByTagName('wizard')[0];
_wizardPage = document.getElementsByTagName('wizardpage')[0];
_mergeGroup = document.getElementsByTagName('zoteromergegroup')[0];
if (screen.width > 1000) {
_wizard.setAttribute('zoterowidescreen', 'true');
}
// Set font size from pref
Zotero.setFontSize(_wizardPage);
// TODO: localize
_wizard.getButton('cancel').setAttribute('label', "Cancel Sync")
_io = window.arguments[0];
_objects = _io.dataIn.objects;
if (!_objects.length) {
// TODO: handle no objects
return;
}
var firstObj = _objects[0][0] == 'deleted' ? _objects[0][1] : _objects[0][0];
if (firstObj instanceof Zotero.Item) {
if (firstObj.isNote()) {
_mergeGroup.type = 'note';
}
else {
_mergeGroup.type = 'item';
}
}
else {
throw ("Invalid merge object type '" + firstObj.constructor.name
+ "' in Zotero_Merge_Window.init()");
}
_mergeGroup.leftCaption = _io.dataIn.captions[0];
_mergeGroup.rightCaption = _io.dataIn.captions[1];
_mergeGroup.mergeCaption = _io.dataIn.captions[2];
_numObjects = document.getElementById('zotero-merge-num-objects');
document.getElementById('zotero-merge-total-objects').value = _objects.length;
this.onNext();
}
function onBack() {
_pos--;
if (_pos == 0) {
_wizard.canRewind = false;
}
_merged[_pos + 1] = _getCurrentMergeObject();
_numObjects.value = _pos + 1;
_mergeGroup.left = _objects[_pos][0];
_mergeGroup.right = _objects[_pos][1];
// Restore previously merged object into merge pane
_mergeGroup.merge = _merged[_pos].ref;
_mergeGroup.leftpane.removeAttribute("selected");
_mergeGroup.rightpane.removeAttribute("selected");
_updateChangedCreators();
if (Zotero.isMac) {
_wizard.getButton("next").setAttribute("hidden", "false");
_wizard.getButton("finish").setAttribute("hidden", "true");
}
else {
var buttons = document.getAnonymousElementByAttribute(_wizard, "anonid", "Buttons");
var deck = document.getAnonymousElementByAttribute(buttons, "anonid", "WizardButtonDeck");
deck.selectedIndex = 1;
}
}
function onNext() {
if (_pos + 1 == _objects.length) {
return true;
}
_pos++;
if (_pos == 0) {
_wizard.canRewind = false;
}
else {
_wizard.canRewind = true;
// Save merged object to return array
_merged[_pos - 1] = _getCurrentMergeObject();
}
// Adjust counter
_numObjects.value = _pos + 1;
_mergeGroup.left = _objects[_pos][0];
_mergeGroup.right = _objects[_pos][1];
// Restore previously merged object into merge pane
if (_merged[_pos]) {
_mergeGroup.merge = _merged[_pos].ref;
_mergeGroup.leftpane.removeAttribute("selected");
_mergeGroup.rightpane.removeAttribute("selected");
}
_updateChangedCreators();
// On Windows the buttons don't move when one is hidden
if ((_pos + 1) != _objects.length) {
if (Zotero.isMac) {
_wizard.getButton("next").setAttribute("hidden", "false");
_wizard.getButton("finish").setAttribute("hidden", "true");
}
else {
var buttons = document.getAnonymousElementByAttribute(_wizard, "anonid", "Buttons");
var deck = document.getAnonymousElementByAttribute(buttons, "anonid", "WizardButtonDeck");
deck.selectedIndex = 1;
}
}
// Last object
else {
if (Zotero.isMac) {
_wizard.getButton("next").setAttribute("hidden", "true");
_wizard.getButton("finish").setAttribute("hidden", "false");
}
// Windows uses a deck to switch between the Next and Finish buttons
// TODO: check Linux
else {
var buttons = document.getAnonymousElementByAttribute(_wizard, "anonid", "Buttons");
var deck = document.getAnonymousElementByAttribute(buttons, "anonid", "WizardButtonDeck");
deck.selectedIndex = 0;
}
}
return false;
}
function onFinish() {
_merged[_pos] = _getCurrentMergeObject();
_io.dataOut = _merged;
return true;
}
function onCancel() {
// if already merged, ask
}
function _getCurrentMergeObject() {
var id = _mergeGroup.merge == 'deleted' ?
(_mergeGroup.left == 'deleted'
? _mergeGroup.right.id : _mergeGroup.left.id)
: _mergeGroup.merge.id;
return {
id: id,
ref: _mergeGroup.merge,
left: _mergeGroup.left,
right: _mergeGroup.right
};
}
// Hack to support creator reconciliation via item view
function _updateChangedCreators() {
if (_mergeGroup.type == 'item' && _io.dataIn.changedCreators) {
var originalCreators = _mergeGroup.rightpane.original.getCreators();
var clonedCreators = _mergeGroup.rightpane.ref.getCreators();
var refresh = false;
for (var i in originalCreators) {
if (_io.dataIn.changedCreators[originalCreators[i].ref.id]) {
var changedCreator = _io.dataIn.changedCreators[originalCreators[i].ref.id];
_mergeGroup.rightpane.original.setCreator(
i, changedCreator, originalCreators[i].creatorTypeID
);
clonedCreators[i].ref = changedCreator;
refresh = true;
}
}
if (refresh) {
_mergeGroup.rightpane.objectbox.refresh();
_mergeGroup.mergepane.objectbox.refresh();
}
}
}
}

View file

@ -0,0 +1,53 @@
<?xml version="1.0"?>
<!--
***** 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 *****
-->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/merge.css" type="text/css"?>
<!-- <!DOCTYPE window SYSTEM "chrome://zotero/locale/merge.dtd"> -->
<wizard
id="zotero-merge-window"
orient="vertical"
title=""
onwizardfinish="return Zotero_Merge_Window.onFinish()"
onwizardcancel="return Zotero_Merge_Window.onCancel()"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="include.js"/>
<script src="merge.js"/>
<wizardpage onpageshow="Zotero_Merge_Window.init()"
onpagerewound="Zotero_Merge_Window.onBack(); return false"
onpageadvanced="return Zotero_Merge_Window.onNext()"
label="Conflict Resolution">
<zoteromergegroup flex="1"/>
<separator class="thin"/>
<hbox id="zotero-step-count">
<label id="zotero-merge-num-objects"/>
<label value="of"/>
<label id="zotero-merge-total-objects"/>
</hbox>
</wizardpage>
</wizard>

View file

@ -23,15 +23,15 @@
var noteEditor;
var notifierUnregisterID;
function onLoad()
{
noteEditor = document.getElementById('note-editor');
function onLoad() {
noteEditor = document.getElementById('zotero-note-editor');
noteEditor.mode = 'edit';
noteEditor.focus();
// Set font size from pref
Zotero.setFontSize(noteEditor);
var params = new Array();
var params = [];
var b = document.location.href.substr(document.location.href.indexOf('?')+1).split('&');
for(var i = 0; i < b.length; i++)
{
@ -39,30 +39,33 @@ function onLoad()
params[b[i].substr(0,mid)] = b[i].substr(mid+1);
}
var itemID = params['id'];
var collectionID = params['coll'];
var parentItemID = params['p'];
var itemID = params.id;
var collectionID = params.coll;
var parentItemID = params.p;
if (itemID) {
var ref = Zotero.Items.get(itemID);
// Make sure Undo doesn't wipe out the note
if (!noteEditor.note || noteEditor.note.getID() != ref.getID()) {
noteEditor.id('noteField').editor.enableUndo(false);
if (!noteEditor.item || noteEditor.item.id != ref.id) {
noteEditor.disableUndo();
}
noteEditor.note = ref;
noteEditor.id('noteField').editor.enableUndo(true);
noteEditor.item = ref;
noteEditor.enableUndo();
document.title = ref.getNoteTitle();
}
else if (parentItemID) {
var ref = Zotero.Items.get(parentItemID);
noteEditor.item = ref;
}
else
{
if(collectionID && collectionID != '' && collectionID != 'undefined')
noteEditor.collection = Zotero.Collections.get(collectionID);
else {
if (parentItemID) {
var ref = Zotero.Items.get(parentItemID);
noteEditor.parent = ref;
}
else {
if (collectionID && collectionID != '' && collectionID != 'undefined') {
noteEditor.collection = Zotero.Collections.get(collectionID);
}
}
noteEditor.refresh();
}
notifierUnregisterID = Zotero.Notifier.registerObserver(NotifyCallback, 'item');
@ -79,20 +82,19 @@ function onUnload()
var NotifyCallback = {
notify: function(action, type, ids){
// DEBUG: why does this reset without checking the modified ids?
if (noteEditor.note) {
noteEditor.note = noteEditor.note;
if (noteEditor.item) {
noteEditor.item = noteEditor.item;
// If the document title hasn't yet been set, reset undo so
// undoing to empty isn't possible
var noteTitle = noteEditor.note.getNoteTitle();
if (!document.title && noteTitle != '') {
noteEditor.id('noteField').editor.enableUndo(false);
noteEditor.id('noteField').editor.enableUndo(true);
noteEditor.clearUndo();
document.title = noteTitle;
}
// Update the window name (used for focusing) in case this is a new note
window.name = 'zotero-note-' + noteEditor.note.getID();
window.name = 'zotero-note-' + noteEditor.item.id;
}
}
}

View file

@ -22,5 +22,5 @@
</keyset>
<command id="cmd_close" oncommand="window.close();"/>
<noteeditor id="note-editor" flex="1"/>
<zoteronoteeditor id="zotero-note-editor" flex="1"/>
</window>

View file

@ -504,7 +504,7 @@ var ZoteroPane = new function()
return false;
}
var item = new Zotero.Item(typeID);
var item = new Zotero.Item(false, typeID);
for (var i in data)
{
@ -514,13 +514,13 @@ var ZoteroPane = new function()
item.save();
if (this.itemsView && this.itemsView._itemGroup.isCollection()) {
this.itemsView._itemGroup.ref.addItem(item.getID());
this.itemsView._itemGroup.ref.addItem(item.id);
}
//set to Info tab
document.getElementById('zotero-view-item').selectedIndex = 0;
this.selectItem(item.getID());
this.selectItem(item.id);
return item;
}
@ -548,7 +548,10 @@ var ZoteroPane = new function()
newName.value = untitled;
}
Zotero.Collections.add(newName.value, parent);
var collection = new Zotero.Collection;
collection.name = newName.value;
collection.parent = parent;
collection.save();
}
function newSearch()
@ -737,7 +740,7 @@ var ZoteroPane = new function()
Zotero.Prefs.set('lastViewedFolder', 'L');
}
if (itemgroup.isCollection()) {
Zotero.Prefs.set('lastViewedFolder', 'C' + itemgroup.ref.getID());
Zotero.Prefs.set('lastViewedFolder', 'C' + itemgroup.ref.id);
}
else if (itemgroup.isSearch()) {
Zotero.Prefs.set('lastViewedFolder', 'S' + itemgroup.ref.id);
@ -760,22 +763,23 @@ var ZoteroPane = new function()
{
var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex);
if(item.isNote())
if(item.ref.isNote())
{
var noteEditor = document.getElementById('zotero-note-editor');
noteEditor.mode = 'edit';
// If loading new or different note, disable undo while we repopulate the text field
// so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
// undo content from another note into the current one.
if (!noteEditor.note || noteEditor.note.getID() != item.ref.getID()) {
noteEditor.id('noteField').editor.enableUndo(false);
if (!noteEditor.item || noteEditor.item.id != item.ref.id) {
noteEditor.disableUndo();
}
noteEditor.item = null;
noteEditor.note = item.ref;
noteEditor.parent = null;
noteEditor.item = item.ref;
noteEditor.id('noteField').editor.enableUndo(true);
noteEditor.enableUndo();
document.getElementById('zotero-view-note-button').setAttribute('noteID',item.ref.getID());
document.getElementById('zotero-view-note-button').setAttribute('noteID',item.ref.id);
if(item.ref.getSource())
{
document.getElementById('zotero-view-note-button').setAttribute('sourceID',item.ref.getSource());
@ -786,7 +790,7 @@ var ZoteroPane = new function()
}
document.getElementById('zotero-item-pane-content').selectedIndex = 2;
}
else if(item.isAttachment())
else if(item.ref.isAttachment())
{
// DEBUG: this is annoying -- we really want to use an abstracted
// version of createValueElement() from itemPane.js
@ -904,7 +908,7 @@ var ZoteroPane = new function()
document.getElementById('zotero-attachment-view').setAttribute('label', str);
// Display page count
var pages = Zotero.Fulltext.getPages(item.ref.getID());
var pages = Zotero.Fulltext.getPages(item.ref.id);
var pages = pages ? pages.total : null;
var pagesRow = document.getElementById('zotero-attachment-pages');
if (pages) {
@ -919,8 +923,9 @@ var ZoteroPane = new function()
this.updateItemIndexedState();
var noteEditor = document.getElementById('zotero-attachment-note-editor');
noteEditor.item = null;
noteEditor.note = item.ref;
noteEditor.mode = 'edit';
noteEditor.parent = null;
noteEditor.item = item.ref;
document.getElementById('zotero-item-pane-content').selectedIndex = 3;
}
@ -956,7 +961,7 @@ var ZoteroPane = new function()
var reindexButton = document.getElementById('zotero-attachment-reindex');
var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex);
var status = Zotero.Fulltext.getIndexedState(item.ref.getID());
var status = Zotero.Fulltext.getIndexedState(item.ref.id);
var str = 'fulltext.indexState.';
switch (status) {
case Zotero.Fulltext.INDEX_STATE_UNAVAILABLE:
@ -980,7 +985,7 @@ var ZoteroPane = new function()
var str = Zotero.getString('pane.items.menu.reindexItem');
reindexButton.setAttribute('tooltiptext', str);
if (Zotero.Fulltext.canReindex(item.ref.getID())) {
if (Zotero.Fulltext.canReindex(item.ref.id)) {
reindexButton.setAttribute('hidden', false);
}
else {
@ -999,7 +1004,7 @@ var ZoteroPane = new function()
if (!items[i].isAttachment()) {
continue;
}
var itemID = items[i].getID();
var itemID = items[i].id;
Zotero.Fulltext.indexItems(itemID, true);
}
this.updateItemIndexedState();
@ -1007,11 +1012,12 @@ var ZoteroPane = new function()
function duplicateSelectedItem() {
var newItemID = this.getSelectedItems()[0].clone();
var newItem = this.getSelectedItems()[0].clone();
var newItemID = newItem.save()
var newItem = Zotero.Items.get(newItemID);
if (this.itemsView._itemGroup.isCollection()) {
this.itemsView._itemGroup.ref.addItem(newItem.getID());
this.itemsView._itemGroup.ref.addItem(newItem.id);
this.selectItem(newItemID);
}
}
@ -1089,30 +1095,29 @@ var ZoteroPane = new function()
function editSelectedCollection()
{
if (this.collectionsView.selection.count > 0) {
var collection = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
var row = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
if(collection.isCollection())
{
if (row.isCollection()) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var newName = { value: collection.getName() };
var newName = { value: row.getName() };
var result = promptService.prompt(window, "",
Zotero.getString('pane.collections.rename'), newName, "", {});
if (result && newName.value)
{
collection.ref.rename(newName.value);
if (result && newName.value) {
row.ref.name = newName.value;
row.ref.save();
}
}
else
{
else {
var s = new Zotero.Search();
s.load(collection.ref['id']);
var io = {dataIn: {search: s, name: collection.getName()}, dataOut: null};
s.load(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)
if (io.dataOut) {
this.onCollectionSelected(); //reload itemsView
}
}
}
}
@ -1243,7 +1248,7 @@ var ZoteroPane = new function()
&& this.collectionsView.selection.currentIndex != -1) {
var collection = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
if (collection && collection.isCollection()) {
return asID ? collection.ref.getID() : collection.ref;
return asID ? collection.ref.id : collection.ref;
}
}
// If the Zotero pane hasn't yet been opened, use the lastViewedFolder pref
@ -1253,7 +1258,7 @@ var ZoteroPane = new function()
if (matches && matches[1] == 'C') {
var col = Zotero.Collections.get(matches[2]);
if (col) {
return asID ? col.getID() : col;
return asID ? col.id : col;
}
}
}
@ -1470,7 +1475,7 @@ var ZoteroPane = new function()
else
{
var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex).ref;
var itemID = item.getID();
var itemID = item.id;
menu.setAttribute('itemID', itemID);
// Show in Library
@ -1493,7 +1498,7 @@ var ZoteroPane = new function()
if (item.isAttachment()) {
hide.push(m.duplicateItem);
// If not linked URL, show reindex line
if (Zotero.Fulltext.canReindex(item.getID())) {
if (Zotero.Fulltext.canReindex(item.id)) {
show.push(m.sep4, m.reindexItem);
}
else {
@ -1757,7 +1762,12 @@ var ZoteroPane = new function()
}
catch (e){}
var itemID = Zotero.Notes.add(text, parent);
var item = new Zotero.Item(false, 'note');
item.setNote(text);
if (parent) {
item.setSource(parent);
}
var itemID = item.save();
if (this.itemsView && this.itemsView._itemGroup.isCollection()) {
this.itemsView._itemGroup.ref.addItem(itemID);
@ -1772,7 +1782,7 @@ var ZoteroPane = new function()
// TODO: _text_
var c = this.getSelectedCollection();
if (c) {
this.openNoteWindow(null, c.getID());
this.openNoteWindow(null, c.id);
}
else {
this.openNoteWindow();
@ -1796,10 +1806,13 @@ var ZoteroPane = new function()
var items = this.getSelectedItems();
if (this.itemsView.selection.count == 1 && items[0] && items[0].isNote()) {
var note = items[0].getNote()
items[0].updateNote(note == '' ? text : note + "\n\n" + text);
items[0].setNote(note == '' ? text : note + "\n\n" + text);
items[0].save();
var noteElem = document.getElementById('zotero-note-editor')
noteElem.focus();
noteElem.id('noteField').inputField.editor.
noteElem.noteField.inputField.editor.
selectionController.scrollSelectionIntoView(1,
1,
true);
@ -1892,17 +1905,17 @@ var ZoteroPane = new function()
var item = this.newItem(Zotero.ItemTypes.getID('webpage'), data);
// Automatically save snapshot if pref set
if (item.getID() && Zotero.Prefs.get('automaticSnapshots'))
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.getID(), true);
ZoteroPane.addAttachmentFromPage(false, item.id, true);
}
// Give progress window time to appear
setTimeout(f, 300);
}
return item.getID();
return item.id;
}
@ -1930,7 +1943,7 @@ var ZoteroPane = new function()
progressWin.startCloseTimer();
if (this.itemsView && this.itemsView._itemGroup.isCollection()) {
var parentCollectionID = this.itemsView._itemGroup.ref.getID();
var parentCollectionID = this.itemsView._itemGroup.ref.id;
}
}
@ -2026,7 +2039,7 @@ var ZoteroPane = new function()
}
}
else {
this.showAttachmentNotFoundDialog(attachment.getID())
this.showAttachmentNotFoundDialog(attachment.id)
}
}
}

View file

@ -82,7 +82,7 @@
<popup id="zotero-collectionmenu" onpopupshowing="ZoteroPane.buildCollectionContextMenu();">
<menuitem label="&zotero.toolbar.newCollection.label;" oncommand="ZoteroPane.newCollection()"/>
<menuitem label="&zotero.toolbar.newSavedSearch.label;" oncommand="ZoteroPane.newSearch()"/>
<menuitem label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane.newCollection(ZoteroPane.getSelectedCollection().getID())"/>
<menuitem label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane.newCollection(ZoteroPane.getSelectedCollection().id)"/>
<menuseparator/>
<menuitem oncommand="ZoteroPane.editSelectedCollection();"/>
<menuitem oncommand="ZoteroPane.deleteSelectedCollection();"/>
@ -125,6 +125,10 @@
<menuitem id="zotero-tb-actions-export" label="&zotero.toolbar.export.label;" oncommand="Zotero_File_Interface.exportFile();"/>
<menuseparator id="zotero-tb-actions-utilities-separator"/>
<menuitem id="zotero-tb-actions-timeline" label="&zotero.toolbar.timeline.label;" oncommand="Zotero_Timeline_Interface.loadTimeline()"/>
<menuseparator id="zotero-tb-actions-sync-separator"/>
<menuitem label="Clear Server Data" oncommand="Zotero.Sync.Server.clear()"/>
<menuitem label="Reset Server Lock" oncommand="Zotero.Sync.Server.resetServer()"/>
<menuitem label="Reset Client" oncommand="Zotero.Sync.Server.resetClient()"/>
<menuseparator id="zotero-tb-actions-separator"/>
<menuitem id="zotero-tb-actions-prefs" label="&zotero.toolbar.preferences.label;"
oncommand="window.openDialog('chrome://zotero/content/preferences/preferences.xul', 'zotero-prefs', 'chrome,titlebar,toolbar,' + Zotero.Prefs.get('browser.preferences.instantApply', true) ? 'dialog=no' : 'modal')"/>
@ -283,6 +287,8 @@
<vbox id="zotero-item-pane" persist="width">
<toolbar align="right">
<toolbarbutton tooltiptext="Sync with Zotero Server" image="chrome://zotero/skin/arrow_refresh.png" oncommand="Zotero.Sync.Server.sync()"/>
<toolbarseparator/>
<toolbarbutton id="zotero-tb-fullscreen" tooltiptext="&zotero.toolbar.fullscreen.tooltip;" oncommand="ZoteroPane.fullScreen();"/>
<toolbarbutton class="tabs-closebutton" oncommand="ZoteroPane.toggleDisplay()"/>
</toolbar>
@ -303,7 +309,7 @@
<deck id="zotero-view-item" flex="1"/>
<!-- Note info pane -->
<vbox id="zotero-view-note" flex="1">
<noteeditor id="zotero-note-editor" flex="1"/>
<zoteronoteeditor id="zotero-note-editor" flex="1"/>
<button id="zotero-view-note-button" label="&zotero.notes.separate;" oncommand="ZoteroPane.openNoteWindow(this.getAttribute('noteID')); if(this.hasAttribute('sourceID')) ZoteroPane.selectItem(this.getAttribute('sourceID'));"/>
</vbox>
<!-- Attachment info pane -->
@ -324,7 +330,7 @@
<toolbarbutton id="zotero-attachment-reindex" oncommand="ZoteroPane.reindexItem()"/>
</hbox>
<noteeditor id="zotero-attachment-note-editor" notitle="1" flex="1"/>
<zoteronoteeditor id="zotero-attachment-note-editor" notitle="1" flex="1"/>
</vbox>
</deck>
</groupbox>

View file

@ -153,6 +153,37 @@ To add a new preference:
</prefpane>
<!-- localize -->
<prefpane id="zotero-prefpane-sync"
label="Sync"
onpaneload="document.getElementById('sync-password').value = Zotero.Sync.Server.password;"
image="chrome://zotero/skin/prefs-sync.png">
<preferences>
<preference id="pref-sync-username" name="extensions.zotero.sync.server.username" type="string"/>
</preferences>
<grid>
<columns>
<columns/>
<columns/>
</columns>
<rows>
<row>
<label value="Username:"/>
<textbox preference="pref-sync-username"
onchange="Zotero.Prefs.set('sync.server.username', this.value); var pass = document.getElementById('sync-password'); if (pass.value) { Zotero.Sync.Server.password = pass.value; }"/>
</row>
<row>
<label value="Password:"/>
<textbox id="sync-password" type="password"
onchange="Zotero.Sync.Server.password = this.value"/>
</row>
</rows>
</grid>
</prefpane>
<prefpane id="zotero-prefpane-search"
label="&zotero.preferences.prefpane.search;"
onpaneload="updateIndexStats()"

View file

@ -51,10 +51,11 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item('attachment');
var attachmentItem = new Zotero.Item(false, 'attachment');
attachmentItem.setField('title', title);
attachmentItem.save();
var itemID = attachmentItem.getID();
attachmentItem.setSource(sourceItemID);
attachmentItem.attachmentLinkMode = this.LINK_MODE_IMPORTED_FILE;
var itemID = attachmentItem.save();
// Create directory for attachment files within storage directory
var destDir = this.createDirectoryForItem(itemID);
@ -69,8 +70,9 @@ Zotero.Attachments = new function(){
var mimeType = Zotero.MIME.getMIMETypeFromFile(newFile);
_addToDB(newFile, null, null, this.LINK_MODE_IMPORTED_FILE,
mimeType, null, sourceItemID, itemID);
attachmentItem.attachmentMIMEType = mimeType;
attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_FILE);
attachmentItem.save();
Zotero.DB.commitTransaction();
@ -124,14 +126,18 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item('attachment');
var attachmentItem = new Zotero.Item(false, 'attachment');
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
attachmentItem.setSource(sourceItemID);
attachmentItem.attachmentLinkMode = this.LINK_MODE_IMPORTED_URL;
attachmentItem.attachmentMIMEType = mimeType;
attachmentItem.attachmentCharset = charset;
// DEBUG: this should probably insert access date too so as to
// create a proper item, but at the moment this is only called by
// translate.js, which sets the metadata fields itself
attachmentItem.save();
var itemID = attachmentItem.getID();
var itemID = attachmentItem.save();
var storageDir = Zotero.getStorageDirectory();
file.parent.copyTo(storageDir, itemID);
@ -143,8 +149,9 @@ Zotero.Attachments = new function(){
newFile.append(itemID);
newFile.append(file.leafName);
_addToDB(newFile, url, null, this.LINK_MODE_IMPORTED_URL, mimeType,
charsetID, sourceItemID, itemID);
attachmentItem.path = this.getPath(newFile, this.LINK_MODE_IMPORTED_URL);
attachmentItem.save();
Zotero.DB.commitTransaction();
// Determine charset and build fulltext index
@ -245,11 +252,13 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item('attachment');
var attachmentItem = new Zotero.Item(false, 'attachment');
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
// Don't send a Notifier event on the incomplete item
attachmentItem.setSource(sourceItemID);
attachmentItem.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_URL;
attachmentItem.attachmentMIMEType = mimeType;
var itemID = attachmentItem.save();
// Add to collections
@ -271,18 +280,21 @@ Zotero.Attachments = new function(){
wbp.progressListener = new Zotero.WebProgressFinishListener(function(){
try {
var attachmentItem = Zotero.Items.get(itemID);
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 "
+ "'application/pdf' in Attachments.importFromURL()", 2);
var item = Zotero.Items.get(itemID);
item.erase();
attachmentItem.erase();
return;
}
_addToDB(file, url, title, Zotero.Attachments.LINK_MODE_IMPORTED_URL,
mimeType, null, sourceItemID, itemID);
attachmentItem.attachmentPath = Zotero.Attachments.getPath(
file, Zotero.Attachments.LINK_MODE_IMPORTED_URL, itemID
);
attachmentItem.save();
Zotero.Notifier.trigger('add', 'item', itemID);
@ -300,8 +312,7 @@ Zotero.Attachments = new function(){
}
catch (e) {
// Clean up
var item = Zotero.Items.get(itemID);
item.erase();
attachmentItem.erase();
throw (e);
}
@ -387,8 +398,15 @@ Zotero.Attachments = new function(){
var mimeType = obj.channel.contentType;
if (mimeType) {
var sql = "UPDATE itemAttachments SET mimeType=? WHERE itemID=?";
Zotero.DB.query(sql, [mimeType, itemID]);
var disabled = Zotero.Notifier.disable();
var item = Zotero.Items.get(itemID);
item.attachmentMIMEType = mimeType;
item.save();
if (disabled) {
Zotero.Notifier.enable();
}
}
Zotero.Notifier.trigger('add', 'item', itemID);
@ -466,11 +484,16 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item('attachment');
var attachmentItem = new Zotero.Item(false, 'attachment');
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
attachmentItem.setSource(sourceItemID);
attachmentItem.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_URL;
attachmentItem.attachmentCharset = charsetID;
attachmentItem.attachmentMIMEType = mimeType;
var itemID = attachmentItem.save();
attachmentItem = Zotero.Items.get(itemID);
// Create a new folder for this item in the storage directory
var destDir = this.createDirectoryForItem(itemID);
@ -513,8 +536,9 @@ Zotero.Attachments = new function(){
wpdDOMSaver.init(file.path, document);
wpdDOMSaver.saveHTMLDocument();
_addToDB(file, url, title, Zotero.Attachments.LINK_MODE_IMPORTED_URL,
mimeType, charsetID, sourceItemID, itemID);
var path = this.getPath(file, Zotero.Attachments.LINK_MODE_IMPORTED_URL, itemID);
attachmentItem.attachmentPath = path;
attachmentItem.save();
}
else {
Zotero.debug('Saving with saveURI()');
@ -529,8 +553,9 @@ Zotero.Attachments = new function(){
var nsIURL = ioService.newURI(url, null, null);
wbp.progressListener = new Zotero.WebProgressFinishListener(function () {
try {
_addToDB(file, url, title, Zotero.Attachments.LINK_MODE_IMPORTED_URL,
mimeType, charsetID, sourceItemID, itemID);
var path = this.getPath(file, Zotero.Attachments.LINK_MODE_IMPORTED_URL, itemID);
attachmentItem.attachmentPath = path;
attachmentItem.save();
Zotero.Notifier.trigger('add', 'item', itemID);
@ -640,7 +665,7 @@ Zotero.Attachments = new function(){
try {
// Create a new attachment
var attachmentItem = new Zotero.Item('attachment');
var attachmentItem = new Zotero.Item(false, 'attachment');
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
@ -879,10 +904,32 @@ Zotero.Attachments = new function(){
/*
* Gets a relative descriptor for imported attachments and a persistent
* descriptor for files outside the storage directory
*
* @param int missingItemID Item id to use if file is missing to
* generate suitable path
*/
function getPath(file, linkMode) {
if (!file.exists()) {
throw ('Zotero.Attachments.getPath() cannot be called on non-existent file');
function getPath(file, linkMode, missingItemID) {
var exists = file.exists();
// TODO: can we get the itemID from the path?
if (!missingItemID && !exists) {
throw ('Zotero.Attachments.getPath() cannot be called on non-existent file without missingItemID');
}
// If imported file doesn't exist, create one temporarily so we can get
// the relative path (which doesn't work on non-existent files)
if (!exists && (linkMode == self.LINK_MODE_IMPORTED_URL ||
linkMode == self.LINK_MODE_IMPORTED_FILE)) {
var missingFile = self.createDirectoryForItem(missingItemID);
missingFile.append(file.leafName);
missingFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
var descriptor = Zotero.Attachments.getPath(missingFile, linkMode);
var parentDir = missingFile.parent;
missingFile.remove(null);
parentDir.remove(null);
return descriptor;
}
file.QueryInterface(Components.interfaces.nsILocalFile);
@ -943,85 +990,34 @@ Zotero.Attachments = new function(){
/**
* Create a new item of type 'attachment' and add to the itemAttachments table
*
* Passing an itemID causes it to skip new item creation and use the specified
* item instead -- used when importing files (since we have to know
* the itemID before copying in a file and don't want to update the DB before
* the file is saved)
*
* Returns the itemID of the new attachment
**/
function _addToDB(file, url, title, linkMode, mimeType, charsetID, sourceItemID, itemID){
function _addToDB(file, url, title, linkMode, mimeType, charsetID, sourceItemID) {
Zotero.DB.beginTransaction();
if (sourceItemID){
var sourceItem = Zotero.Items.get(sourceItemID);
if (!sourceItem){
Zotero.DB.commitTransaction();
throw ("Cannot set attachment source to invalid item " + sourceItemID);
}
if (sourceItem.isAttachment()){
Zotero.DB.commitTransaction();
throw ("Cannot set attachment source to another file (" + sourceItemID + ")");
}
}
// If an itemID is provided, use that
if (itemID){
var attachmentItem = Zotero.Items.get(itemID);
if (!attachmentItem.isAttachment()){
throw ("Item " + itemID + " is not a valid attachment in _addToDB()");
}
}
// Otherwise create a new attachment
else {
var attachmentItem = new Zotero.Item('attachment');
attachmentItem.setField('title', title);
if (linkMode==self.LINK_MODE_IMPORTED_URL
|| linkMode==self.LINK_MODE_LINKED_URL){
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
}
attachmentItem.save();
var attachmentItem = new Zotero.Item(false, 'attachment');
attachmentItem.setField('title', title);
if (linkMode == self.LINK_MODE_IMPORTED_URL
|| linkMode == self.LINK_MODE_LINKED_URL) {
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
}
// Get path
if (file) {
if (file.exists()) {
var path = getPath(file, linkMode);
}
// If file doesn't exist, create one temporarily so we can get the
// relative path (since getPath() doesn't work on non-existent files)
else if (linkMode == self.LINK_MODE_IMPORTED_URL ||
linkMode == self.LINK_MODE_IMPORTED_FILE) {
var missingFile = self.createDirectoryForItem(attachmentItem.getID());
missingFile.append(file.leafName);
missingFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
var path = getPath(missingFile, linkMode);
var parentDir = missingFile.parent;
missingFile.remove(null);
parentDir.remove(null);
}
var path = Zotero.Attachments.getPath(file, linkMode, attachmentItem.id);
attachmentItem.attachmentPath = path;
}
var sql = "INSERT INTO itemAttachments (itemID, sourceItemID, linkMode, "
+ "mimeType, charsetID, path) VALUES (?,?,?,?,?,?)";
var bindParams = [
attachmentItem.getID(),
sourceItemID ? {int:sourceItemID} : null,
{int:linkMode},
mimeType ? {string:mimeType} : null,
charsetID ? {int:charsetID} : null,
path ? {string:path} : null
];
Zotero.DB.query(sql, bindParams);
if (sourceItemID){
sourceItem.incrementAttachmentCount();
Zotero.Notifier.trigger('modify', 'item', sourceItemID);
}
attachmentItem.setSource(sourceItemID);
attachmentItem.attachmentLinkMode = linkMode;
attachmentItem.attachmentMIMEType = mimeType;
attachmentItem.attachmentCharset = charsetID;
attachmentItem.save();
Zotero.DB.commitTransaction();
return attachmentItem.getID();
return attachmentItem.id;
}
@ -1050,10 +1046,15 @@ Zotero.Attachments = new function(){
Zotero.File.addCharsetListener(browser, new function(){
return function(charset, id){
var charsetID = Zotero.CharacterSets.getID(charset);
if (charsetID){
var sql = "UPDATE itemAttachments SET charsetID=" + charsetID
+ " WHERE itemID=" + itemID;
Zotero.DB.query(sql);
var disabled = Zotero.Notifier.disable();
var item = Zotero.Items.get(itemID);
item.attachmentCharset = charsetID;
item.save();
if (disabled) {
Zotero.Notifier.enable();
}
// Chain fulltext indexer inside the charset callback,

View file

@ -1822,7 +1822,7 @@ Zotero.CSL.Item = function(item) {
// don't return URL or accessed information for journal articles if a
// pages field exists
var itemType = Zotero.ItemTypes.getName(this.zoteroItem.getType());
var itemType = Zotero.ItemTypes.getName(this.zoteroItem.itemTypeID);
if(!Zotero.Prefs.get("export.citePaperJournalArticleURL")
&& ["journalArticle", "newspaperArticle", "magazineArticle"].indexOf(itemType) !== -1
&& this.zoteroItem.getField("pages")) {
@ -2140,7 +2140,7 @@ Zotero.CSL.Item._fallbackTypeMap = {
* Determines whether this item is of a given type
*/
Zotero.CSL.Item.prototype.isType = function(type) {
var zoteroType = Zotero.ItemTypes.getName(this.zoteroItem.getType());
var zoteroType = Zotero.ItemTypes.getName(this.zoteroItem.itemTypeID);
return (Zotero.CSL.Item._optionalTypeMap[zoteroType]
&& Zotero.CSL.Item._optionalTypeMap[zoteroType] == type)
@ -2153,7 +2153,7 @@ Zotero.CSL.Item.prototype.isType = function(type) {
Zotero.CSL.Item.prototype._separateNames = function() {
this._names = [];
var authorID = Zotero.CreatorTypes.getPrimaryIDForType(this.zoteroItem.getType());
var authorID = Zotero.CreatorTypes.getPrimaryIDForType(this.zoteroItem.itemTypeID);
var creators = this.zoteroItem.getCreators();
for each(var creator in creators) {

View file

@ -125,7 +125,7 @@ Zotero.CollectionTreeView.prototype.reload = function()
for (var i=0; i<this.rowCount; i++) {
if (this.isContainer(i) && this.isContainerOpen(i)) {
openCollections.push(this._getItemAtRow(i).ref.getID());
openCollections.push(this._getItemAtRow(i).ref.id);
}
}
@ -152,6 +152,11 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
return;
}
if (!this._collectionRowMap) {
Zotero.debug("Collection row map didn't exist in collectionTreeView.notify()");
return;
}
this.selection.selectEventsSuppressed = true;
var savedSelection = this.saveSelection();
@ -225,7 +230,7 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
{
case 'collection':
var collection = Zotero.Collections.get(ids);
var collectionID = collection.getID();
var collectionID = collection.id;
// Open container if creating subcollection
var parentID = collection.getParent();
if (parentID) {
@ -379,7 +384,7 @@ Zotero.CollectionTreeView.prototype.toggleOpenState = function(row)
}
else
{
var newRows = Zotero.getCollections(this._getItemAtRow(row).ref.getID()); //Get children
var newRows = Zotero.getCollections(this._getItemAtRow(row).ref.id); //Get children
for(var i = 0; i < newRows.length; i++)
{
@ -516,7 +521,7 @@ Zotero.CollectionTreeView.prototype.saveSelection = function()
return 'L';
}
else if (this._getItemAtRow(i).isCollection()) {
return 'C' + this._getItemAtRow(i).ref.getID();
return 'C' + this._getItemAtRow(i).ref.id;
}
else if (this._getItemAtRow(i).isSearch()) {
return 'S' + this._getItemAtRow(i).ref.id;
@ -568,7 +573,7 @@ Zotero.CollectionTreeView.prototype._refreshHashMap = function()
this._searchRowMap = [];
for(var i=0; i < this.rowCount; i++){
if (this.isCollection(i)){
this._collectionRowMap[this._getItemAtRow(i).ref.getID()] = i;
this._collectionRowMap[this._getItemAtRow(i).ref.id] = i;
}
else if (this.isSearch(i)){
this._searchRowMap[this._getItemAtRow(i).ref.id] = i;
@ -680,8 +685,8 @@ Zotero.CollectionTreeView.prototype.canDrop = function(row, orient)
return true;
}
else if (dataType == 'zotero/collection'
&& data.data != rowCollection.getID()
&& !Zotero.Collections.get(data.data).hasDescendent('collection', rowCollection.getID())) {
&& data.data != rowCollection.id
&& !Zotero.Collections.get(data.data).hasDescendent('collection', rowCollection.id)) {
return true;//collections cannot be dropped on themselves, nor in their children
}
}
@ -705,9 +710,10 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
{
var targetCollectionID;
if(this._getItemAtRow(row).isCollection())
targetCollectionID = this._getItemAtRow(row).ref.getID();
targetCollectionID = this._getItemAtRow(row).ref.id;
var droppedCollection = Zotero.Collections.get(data.data);
droppedCollection.changeParent(targetCollectionID);
droppedCollection.parent = targetCollectionID;
droppedCollection.save();
}
else if (dataType == 'zotero/item') {
var ids = data.data.split(',');
@ -730,7 +736,7 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
if (this._getItemAtRow(row).isCollection()) {
var parentCollectionID = this._getItemAtRow(row).ref.getID();
var parentCollectionID = this._getItemAtRow(row).ref.id;
}
else {
var parentCollectionID = false;
@ -804,7 +810,7 @@ Zotero.CollectionTreeView.prototype.onDragStart = function(evt,transferData,acti
transferData.data=new TransferData();
//attach ID
transferData.data.addDataForFlavour("zotero/collection",this._getItemAtRow(this.selection.currentIndex).ref.getID());
transferData.data.addDataForFlavour("zotero/collection",this._getItemAtRow(this.selection.currentIndex).ref.id);
}
/*
@ -881,14 +887,18 @@ Zotero.ItemGroup.prototype.isSearch = function()
Zotero.ItemGroup.prototype.getName = function()
{
if(this.isCollection())
return this.ref.getName();
else if(this.isLibrary())
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
}
else if (this.isSearch()) {
return this.ref.name;
}
else {
return "";
}
}
Zotero.ItemGroup.prototype.getChildItems = function()
@ -927,7 +937,7 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
}
else if (this.isCollection()) {
s.addCondition('noChildren', 'true');
s.addCondition('collectionID', 'is', this.ref.getID());
s.addCondition('collectionID', 'is', this.ref.id);
if (Zotero.Prefs.get('recursiveCollections')) {
s.addCondition('recursive', 'true');
}

View file

@ -0,0 +1,273 @@
/*
***** 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 *****
*/
/*
* Base function for retrieving ids and names of static types stored in the DB
* (e.g. creatorType, fileType, charset, itemType)
*
* Extend using the following code within a child constructor:
*
* Zotero.CachedTypes.apply(this, arguments);
* this.constructor.prototype = new Zotero.CachedTypes();
*
* And the following properties:
*
* this._typeDesc = 'c';
* this._idCol = '';
* this._nameCol = '';
* this._table = '';
* this._ignoreCase = false;
*
*/
Zotero.CachedTypes = function() {
var _types = [];
var _typesLoaded;
var self = this;
// Override these variables in child classes
this._typeDesc = '';
this._idCol = '';
this._nameCol = '';
this._table = '';
this._ignoreCase = false;
this.getName = getName;
this.getID = getID;
this.getTypes = getTypes;
function getName(idOrName) {
if (!_typesLoaded) {
_load();
}
if (this._ignoreCase) {
idOrName = idOrName + '';
idOrName = idOrName.toLowerCase();
}
if (!_types['_' + idOrName]) {
Zotero.debug('Invalid ' + this._typeDesc + ' ' + idOrName, 1);
return '';
}
return _types['_' + idOrName]['name'];
}
function getID(idOrName) {
if (!_typesLoaded) {
_load();
}
if (this._ignoreCase) {
idOrName = idOrName + '';
idOrName = idOrName.toLowerCase();
}
if (!_types['_' + idOrName]) {
Zotero.debug('Invalid ' + this._typeDesc + ' ' + idOrName, 1);
return false;
}
return _types['_' + idOrName]['id'];
}
function getTypes(where) {
return Zotero.DB.query('SELECT ' + this._idCol + ' AS id, '
+ this._nameCol + ' AS name FROM ' + this._table
+ (where ? ' ' + where : '') + ' ORDER BY ' + this._nameCol);
}
function _load() {
var types = self.getTypes();
for (var i in types) {
// Store as both id and name for access by either
var typeData = {
id: types[i]['id'],
name: types[i]['name']
}
_types['_' + types[i]['id']] = typeData;
if (self._ignoreCase) {
_types['_' + types[i]['name'].toLowerCase()] = _types['_' + types[i]['id']];
}
else {
_types['_' + types[i]['name']] = _types['_' + types[i]['id']];
}
}
_typesLoaded = true;
}
}
Zotero.CreatorTypes = new function() {
Zotero.CachedTypes.apply(this, arguments);
this.constructor.prototype = new Zotero.CachedTypes();
this.getTypesForItemType = getTypesForItemType;
this.isValidForItemType = isValidForItemType;
this.getPrimaryIDForType = getPrimaryIDForType;
this._typeDesc = 'creator type';
this._idCol = 'creatorTypeID';
this._nameCol = 'creatorType';
this._table = 'creatorTypes';
function getTypesForItemType(itemTypeID) {
var sql = "SELECT creatorTypeID AS id, creatorType AS name "
+ "FROM itemTypeCreatorTypes NATURAL JOIN creatorTypes "
// DEBUG: sort needs to be on localized strings in itemPane.js
// (though still put primary field at top)
+ "WHERE itemTypeID=? ORDER BY primaryField=1 DESC, name";
return Zotero.DB.query(sql, itemTypeID);
}
function isValidForItemType(creatorTypeID, itemTypeID) {
var sql = "SELECT COUNT(*) FROM itemTypeCreatorTypes "
+ "WHERE itemTypeID=? AND creatorTypeID=?";
return !!Zotero.DB.valueQuery(sql, [itemTypeID, creatorTypeID]);
}
function getPrimaryIDForType(itemTypeID) {
var sql = "SELECT creatorTypeID FROM itemTypeCreatorTypes "
+ "WHERE itemTypeID=? AND primaryField=1";
return Zotero.DB.valueQuery(sql, itemTypeID);
}
}
Zotero.ItemTypes = new function() {
Zotero.CachedTypes.apply(this, arguments);
this.constructor.prototype = new Zotero.CachedTypes();
this.getPrimaryTypes = getPrimaryTypes;
this.getSecondaryTypes = getSecondaryTypes;
this.getHiddenTypes = getHiddenTypes;
this.getLocalizedString = getLocalizedString;
this.getImageSrc = getImageSrc;
this._typeDesc = 'item type';
this._idCol = 'itemTypeID';
this._nameCol = 'typeName';
this._table = 'itemTypes';
function getPrimaryTypes() {
return this.getTypes('WHERE display=2');
}
function getSecondaryTypes() {
return this.getTypes('WHERE display=1');
}
function getHiddenTypes() {
return this.getTypes('WHERE display=0');
}
function getLocalizedString(typeIDOrName) {
var typeName = this.getName(typeIDOrName);
return Zotero.getString("itemTypes." + typeName);
}
function getImageSrc(itemType) {
// DEBUG: only have icons for some types so far
switch (itemType) {
case 'attachment-file':
case 'attachment-link':
case 'attachment-snapshot':
case 'attachment-web-link':
case 'attachment-pdf':
case 'artwork':
case 'audioRecording':
case 'blogPost':
case 'book':
case 'bookSection':
case 'computerProgram':
case 'conferencePaper':
case 'email':
case 'film':
case 'forumPost':
case 'interview':
case 'journalArticle':
case 'letter':
case 'magazineArticle':
case 'manuscript':
case 'map':
case 'newspaperArticle':
case 'note':
case 'podcast':
case 'radioBroadcast':
case 'report':
case 'thesis':
case 'tvBroadcast':
case 'videoRecording':
case 'webpage':
return "chrome://zotero/skin/treeitem-" + itemType + ".png";
}
return "chrome://zotero/skin/treeitem.png";
}
}
Zotero.FileTypes = new function() {
Zotero.CachedTypes.apply(this, arguments);
this.constructor.prototype = new Zotero.CachedTypes();
this._typeDesc = 'file type';
this._idCol = 'fileTypeID';
this._nameCol = 'fileType';
this._table = 'fileTypes';
this.getIDFromMIMEType = getIDFromMIMEType;
function getIDFromMIMEType(mimeType) {
var sql = "SELECT fileTypeID FROM fileTypeMIMETypes "
+ "WHERE ? LIKE mimeType || '%'";
return Zotero.DB.valueQuery(sql, [mimeType]);
}
}
Zotero.CharacterSets = new function() {
Zotero.CachedTypes.apply(this, arguments);
this.constructor.prototype = new Zotero.CachedTypes();
this._typeDesc = 'character set';
this._idCol = 'charsetID';
this._nameCol = 'charset';
this._table = 'charsets';
this._ignoreCase = true;
this.getAll = getAll;
function getAll() {
return this.getTypes();
}
}

View file

@ -0,0 +1,930 @@
/*
***** 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.Collection = function(collectionID) {
this._collectionID = collectionID ? collectionID : null;
this._init();
}
Zotero.Collection.prototype._init = function (collectionID) {
// Public members for access by public methods -- do not access directly
this._name = null;
this._parent = null;
this._dateModified = null;
this._key = null;
this._hasChildCollections = false;
this._childCollections = [];
this._childCollectionsLoaded = false;
this._hasChildItems = false;
this._childItems = [];
this._childItemsLoaded = false;
this._previousData = false;
}
Zotero.Collection.prototype.__defineGetter__('id', function () { return this._collectionID; });
Zotero.Collection.prototype.__defineSetter__('collectionID', function (val) { this._set('collectionID', 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__('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) {
this.load();
}
return this['_' + field];
}
Zotero.Collection.prototype._set = function (field, val) {
switch (field) {
case 'id': // set using constructor
//case 'collectionID': // set using constructor
throw ("Invalid field '" + field + "' in Zotero.Collection.set()");
}
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;
}
}
}
Zotero.Collection.prototype.getID = function() {
Zotero.debug('Collection.getID() deprecated -- use Collection.id');
return this.id;
}
Zotero.Collection.prototype.getName = function() {
Zotero.debug('Collection.getName() deprecated -- use Collection.name');
return this.name;
}
Zotero.Collection.prototype.getParent = function() {
Zotero.debug('Collection.getParent() deprecated -- use Collection.parent');
return this.parent;
}
/*
* Build collection from database
*/
Zotero.Collection.prototype.load = function() {
// 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);
this._init();
this._loaded = true;
if (!data) {
return;
}
this.loadFromRow(data);
}
/*
* Populate collection data from a database row
*/
Zotero.Collection.prototype.loadFromRow = function(row) {
this._loaded = true;
this._changed = false;
this._previousData = false;
this._collectionID = row.collectionID;
this._name = row.collectionName;
this._parent = row.parentCollectionID;
this._dateModified = row.dateModified;
this._key = row.key;
this._hasChildCollections = row.hasChildCollections;
this._hasChildItems = row.hasChildItems;
this._loadChildItems();
}
Zotero.Collection.prototype.isEmpty = function() {
return !(parseInt(this._hasChildCollections)) && !(parseInt(this._hasChildItems));
}
Zotero.Collection.prototype.hasChildCollections = function() {
return !!(parseInt(this._hasChildCollections));
}
Zotero.Collection.prototype.hasChildItems = function() {
return !!(parseInt(this._hasChildItems));
}
/**
* Check if collection exists in the database
*
* @return bool TRUE if the collection exists, FALSE if not
*/
Zotero.Collection.prototype.exists = function() {
if (!this.id) {
throw ('collectionID not set in Zotero.Collection.exists()');
}
var sql = "SELECT COUNT(*) FROM collections WHERE collectionID=?";
return !!Zotero.DB.valueQuery(sql, this.id);
}
/**
* Returns subcollections of this collection
*
* @param bool asIDs Return as collectionIDs
* @return array Array of Zotero.Collection instances
* or collectionIDs, or FALSE if none
*/
Zotero.Collection.prototype.getChildCollections = function (asIDs) {
if (!this._childCollectionsLoaded) {
this._loadChildCollections();
}
if (this._childCollections.length == 0) {
return false;
}
// Return collectionIDs
if (asIDs) {
var ids = [];
for each(var col in this._childCollections) {
ids.push(col.id);
}
return ids;
}
// Return Zotero.Collection objects
var objs = [];
for each(var col in this._childCollections) {
objs.push(col);
}
return objs;
}
/**
* Returns child items of this collection
*
* @param bool asIDs Return as itemIDs
* @return array Array of Zotero.Item instances or itemIDs,
* or FALSE if none
*/
Zotero.Collection.prototype.getChildItems = function (asIDs) {
if (!this._childItemsLoaded) {
this._loadChildItems();
}
if (this._childItems.length == 0) {
return false;
}
// Return itemIDs
if (asIDs) {
var ids = [];
for each(var item in this._childItems) {
ids.push(item.id);
}
return ids;
}
// Return Zotero.Item objects
var objs = [];
for each(var item in this._childItems) {
objs.push(item);
}
return objs;
}
Zotero.Collection.prototype.save = function () {
if (!this.name) {
throw ('Collection name is empty in Zotero.Collection.save()');
}
if (!this._changed) {
Zotero.debug("Collection " + this.id + " has not changed");
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.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.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.Collections.unload(oldID);
Zotero.Notifier.trigger('id-change', 'collection', oldID + '-' + this.id);
// update caches
}
var isNew = !this.id || !this.exists();
try {
// how to know if date modified changed (in server code too?)
var collectionID = this.id ? this.id : Zotero.ID.get('collections');
Zotero.debug("Saving collection " + this.id);
var key = this.key ? this.key : this._generateKey();
var columns = [
'collectionID', 'collectionName', 'parentCollectionID',
'dateModified', 'key'
];
var placeholders = ['?', '?', '?', '?', '?'];
var sqlValues = [
collectionID ? { int: collectionID } : null,
{ string: this.name },
this.parent ? { int: this.parent } : null,
// If date modified hasn't changed, use current timestamp
this._changed.dateModified ?
this.dateModified : Zotero.DB.transactionDateTime,
key
];
var sql = "REPLACE INTO collections (" + columns.join(', ') + ") VALUES ("
+ placeholders.join(', ') + ")";
var insertID = Zotero.DB.query(sql, sqlValues);
if (!collectionID) {
collectionID = insertID;
}
// Subcollections
if (this._changed.childCollections) {
var removed = [];
var newids = [];
var currentIDs = this.getChildCollections(true);
if (!currentIDs) {
currentIDs = [];
}
if (this._previousData.childCollections) {
for each(var id in this._previousData.childCollections) {
if (currentIDs.indexOf(id) == -1) {
removed.push(id);
}
}
}
for each(var id in currentIDs) {
if (this._previousData.childCollections &&
this._previousData.childCollections.indexOf(id) != -1) {
continue;
}
newids.push(id);
}
if (removed.length) {
var sql = "UPDATE collections SET parentCollectionID=NULL "
+ "WHERE collectionID IN ("
+ removed.map(function () '?').join()
+ ")";
Zotero.DB.query(sql, removed);
}
if (newids.length) {
var sql = "UPDATE collections SET parentCollectionID=? "
+ "WHERE collectionID IN ("
+ newids.map(function () '?').join()
+ ")";
Zotero.DB.query(sql, [collectionID].concat(newids));
}
// TODO: notifier
}
// Child items
if (this._changed.childItems) {
var removed = [];
var newids = [];
var currentIDs = this.getChildItems(true);
if (!currentIDs) {
currentIDs = [];
}
if (this._previousData.childItems) {
for each(var id in this._previousData.childItems) {
if (currentIDs.indexOf(id) == -1) {
removed.push(id);
}
}
}
for each(var id in currentIDs) {
if (this._previousData.childItems &&
this._previousData.childItems.indexOf(id) != -1) {
continue;
}
newids.push(id);
}
if (removed.length) {
var sql = "DELETE FROM collectionItems WHERE collectionID=? "
+ "AND itemID IN ("
+ removed.map(function () '?').join()
+ ")";
Zotero.DB.query(sql, [collectionID].concat(removed));
}
if (newids.length) {
var sql = "SELECT IFNULL(MAX(orderIndex)+1, 0) "
+ "FROM collectionItems WHERE collectionID=?"
var orderStatement = Zotero.DB.getStatement(sql);
var sql = "INSERT INTO collectionItems "
+ "(collectionID, itemID, orderIndex) VALUES (?,?,?)";
var insertStatement = Zotero.DB.getStatement(sql);
for each(var itemID in newids) {
orderStatement.bindInt32Parameter(0, collectionID);
try {
if (orderStatement.executeStep()) {
var orderIndex = orderStatement.getInt32(0);
}
}
catch (e) {
throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
}
orderStatement.reset();
insertStatement.bindInt32Parameter(0, collectionID);
insertStatement.bindInt32Parameter(1, itemID);
insertStatement.bindInt32Parameter(2,
orderIndex ? orderIndex : 0);
try {
insertStatement.execute();
}
catch (e) {
throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
}
}
}
//Zotero.Notifier.trigger('add', 'collection-item', this.id + '-' + itemID);
}
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.DB.rollbackTransaction();
throw (e);
}
// If successful, set values in object
if (!this.id) {
this._collectionID = collectionID;
}
if (!this.key) {
this._key = key;
}
Zotero.Collections.reloadAll();
if (isNew) {
Zotero.Notifier.trigger('add', 'collection', this.id);
}
else {
Zotero.Notifier.trigger('modify', 'collection', this.id, this._previousData);
}
if (this._changed.parent) {
var notifyIDs = [this.id];
if (this._previousData.parent) {
notifyIDs.push(this._previousData.parent);
}
if (this.parent) {
notifyIDs.push(this.parent);
}
//Zotero.Notifier.trigger('move', 'collection', notifyIDs, notifierData);
}
return this.id;
}
/**
* Add an item to the collection
**/
Zotero.Collection.prototype.addItem = function(itemID) {
var current = this.getChildItems(true);
if (current && current.indexOf(itemID) != -1) {
Zotero.debug("Item " + itemID + " already a child of collection "
+ this.id + " in Zotero.Collection.addItem()");
return false;
}
Zotero.DB.beginTransaction();
if (!Zotero.Items.get(itemID)) {
Zotero.DB.rollbackTransaction();
throw(itemID + ' is not a valid item id');
}
var sql = "SELECT IFNULL(MAX(orderIndex)+1, 0) "
+ "FROM collectionItems WHERE collectionID=?";
var nextOrderIndex = Zotero.DB.valueQuery(sql, this.id);
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]);
Zotero.DB.commitTransaction();
Zotero.Collections.reload(this.id);
Zotero.Notifier.trigger('add', 'collection-item', this.id + '-' + itemID);
return true;
}
/**
* Add multiple items to the collection in batch
*/
Zotero.Collection.prototype.addItems = function(itemIDs) {
if (!itemIDs || !itemIDs.length) {
return;
}
Zotero.DB.beginTransaction();
for (var i=0; i<itemIDs.length; i++) {
this.addItem(itemIDs[i]);
}
Zotero.DB.commitTransaction();
}
/**
* Remove an item from the collection (does not delete item from library)
**/
Zotero.Collection.prototype.removeItem = function(itemID) {
var index = this.getChildItems(true).indexOf(itemID);
if (index == -1) {
Zotero.debug("Item " + itemID + " not a child of collection "
+ this.id + " in Zotero.Collection.removeItem()");
return false;
}
Zotero.DB.beginTransaction();
var sql = "DELETE FROM collectionItems WHERE collectionID=? AND itemID=?";
Zotero.DB.query(sql, [this.id, itemID]);
sql = "UPDATE collections SET dateModified=? WHERE collectionID=?";
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, this.id])
Zotero.DB.commitTransaction();
Zotero.Collections.reload(this.id);
Zotero.Notifier.trigger('remove', 'collection-item', this.id + '-' + itemID);
return true;
}
/**
* Remove multiple items from the collection in batch
* (does not delete item from library)
*/
Zotero.Collection.prototype.removeItems = function(itemIDs) {
if (!itemIDs || !itemIDs.length) {
return;
}
Zotero.DB.beginTransaction();
for (var i=0; i<itemIDs.length; i++) {
this.removeItem(itemIDs[i]);
}
Zotero.DB.commitTransaction();
}
/**
* Check if an item belongs to the collection
**/
Zotero.Collection.prototype.hasItem = function(itemID) {
if (!this._childItemsLoaded) {
this._loadChildItems();
}
for each(var item in this._childItems) {
if (item.id == itemID) {
return true;
}
}
return false;
}
Zotero.Collection.prototype.hasDescendent = function(type, id) {
var descendents = this.getDescendents();
for (var i=0, len=descendents.length; i<len; i++) {
if (descendents[i].type == type && descendents[i].id == id) {
return true;
}
}
return false;
}
/**
* Deletes collection and all descendent collections (and optionally items)
**/
Zotero.Collection.prototype.erase = function(deleteItems) {
Zotero.DB.beginTransaction();
var descendents = this.getDescendents();
var collections = [this.id];
var items = [];
var notifierData = {};
notifierData[this.id] = { old: this.serialize() };
for(var i=0, len=descendents.length; i<len; i++) {
// Descendent collections
if (descendents[i].type == 'collection') {
collections.push(descendents[i].id);
var c = Zotero.Collections.get(descendents[i].id);
if (c) {
notifierData[c.id] = { old: c.serialize() };
}
}
// Descendent items
else {
if (deleteItems) {
// Delete items from DB
Zotero.Items.get(descendents[i].id).erase();
}
}
}
var placeholders = collections.map(function () '?').join();
// Remove item associations for all descendent collections
Zotero.DB.query('DELETE FROM collectionItems WHERE collectionID IN '
+ '(' + placeholders + ')', collections);
// Remove parent definitions first for FK check
Zotero.DB.query('UPDATE collections SET parentCollectionID=NULL '
+ 'WHERE parentCollectionID IN (' + placeholders + ')', collections);
// And delete all descendent collections
Zotero.DB.query('DELETE FROM collections WHERE collectionID IN '
+ '(' + placeholders + ')', collections);
Zotero.DB.commitTransaction();
// Clear deleted collection from internal memory
Zotero.Collections.unload(collections);
Zotero.Collections.reloadAll();
Zotero.Notifier.trigger('delete', 'collection', collections, notifierData);
}
Zotero.Collection.prototype.isCollection = function() {
return true;
}
Zotero.Collection.prototype.toArray = function() {
Zotero.debug('Collection.toArray() is deprecated -- use Collection.serialize()');
return this.serialize();
}
Zotero.Collection.prototype.serialize = function(nested) {
var obj = {
primary: {
collectionID: this.id,
dateModified: this.dateModified,
key: this.key
},
name: this.name,
parent: this.parent,
childCollections: this.getChildCollections(true),
childItems: this.getChildItems(true),
descendents: this.getDescendents(nested)
};
return obj;
}
/**
* Returns an array of descendent collections and items
* (rows of 'id', 'type' ('item' or 'collection'), 'parent', and,
* if collection, 'name' and the nesting 'level')
*
* @param bool recursive Descend into subcollections
* @param bool nested Return multidimensional array with 'children'
* nodes instead of flat array
* @param string type 'item', 'collection', or FALSE for both
*/
Zotero.Collection.prototype.getChildren = function(recursive, nested, type, level) {
var toReturn = [];
if (!level) {
level = 1;
}
// 0 == collection
// 1 == item
var children = Zotero.DB.query('SELECT collectionID AS id, '
+ "0 AS type, collectionName AS collectionName "
+ 'FROM collections WHERE parentCollectionID=?1'
+ ' UNION SELECT itemID AS id, 1 AS type, NULL AS collectionName '
+ 'FROM collectionItems WHERE collectionID=?1', this.id);
if (type) {
switch (type) {
case 'item':
case 'collection':
break;
default:
throw ("Invalid type '" + type + "' in Collection.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=='collection') {
toReturn.push({
id: children[i].id,
name: children[i].collectionName,
type: 'collection',
level: level,
parent: this.id
});
}
if (recursive) {
var descendents =
Zotero.Collections.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,
type: 'item',
parent: this.id
});
}
break;
}
}
return toReturn;
}
/**
* Alias for the recursive mode of getChildren()
*/
Zotero.Collection.prototype.getDescendents = function(nested, type, level) {
return this.getChildren(true, nested, type);
}
Zotero.Collection.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 collection exists
if (this.id && this.exists() && !this._previousData) {
this._previousData = this.serialize();
}
}
Zotero.Collection.prototype._setChildCollections = function (collectionIDs) {
this._setChildren('collection', collectionIDs);
}
Zotero.Collection.prototype._setChildItems = function (itemIDs) {
this._setChildren('item', itemIDs);
}
Zotero.Collection.prototype._setChildren = function (type, ids) {
if (type != 'collection' && type != 'item') {
throw ("Invalid type '" + type + "' in Zotero.Collection._setChildren()");
}
var Type = type.charAt(0).toUpperCase() + type.substr(1);
var Types = Type + 's'; // 'Items'
var types = type + 's'; // 'items'
if (!this['_child' + Types + 'Loaded']) {
this['_loadChild' + Types]();
}
if (ids.constructor.name != 'Array') {
throw (type + 'IDs must be an array in Zotero.Collection._setChildren()');
}
var currentIDs = this['getChild' + Types](true);
if (!currentIDs) {
currentIDs = [];
}
var oldIDs = []; // children being kept
var newIDs = []; // new children
if (ids.length == 0) {
if (this['_child' + Types].length == 0) {
Zotero.debug('No child ' + types + ' added', 4);
return false;
}
}
else {
for (var i in ids) {
var id = parseInt(ids[i]);
if (isNaN(id)) {
throw ("Invalid " + type + "ID '" + ids[i]
+ "' in Zotero.Collection._setChildren()");
}
if (currentIDs.indexOf(id) != -1) {
Zotero.debug(Type + " " + ids[i]
+ " is already a child of collection " + this.id);
oldIDs.push(id);
continue;
}
newIDs.push(id);
}
}
// Mark as changed if new or removed ids
if (newIDs.length > 0 || oldIDs.length != this['_child' + Types].length) {
this._prepFieldChange('child' + Types);
}
else {
Zotero.debug('Child ' + types + ' not changed', 4);
return false;
}
newIDs = oldIDs.concat(newIDs);
this['_child' + Types] = [];
// Items.get() can take an array
if (type == 'item') {
this._childItems = Zotero.Items.get(newIDs);
}
else {
for (var id in newIDs) {
this['_child' + Types].push(Zotero[Types].get(id));
}
}
return true;
}
Zotero.Collection.prototype._loadChildCollections = function () {
var sql = "SELECT collectionID FROM collections WHERE parentCollectionID=?";
var ids = Zotero.DB.columnQuery(sql, this.id);
this._childCollections = [];
if (ids) {
for each(var id in ids) {
this._childCollections.push(Zotero.Collections.get(id));
}
}
this._childCollectionsLoaded = true;
}
Zotero.Collection.prototype._loadChildItems = function() {
var sql = "SELECT itemID FROM collectionItems WHERE collectionID=?";
var ids = Zotero.DB.columnQuery(sql, this.id);
this._childItems = [];
if (ids) {
for each(var id in ids) {
this._childItems.push(Zotero.Items.get(id));
}
}
this._childItemsLoaded = true;
}
Zotero.Collection.prototype._generateKey = function () {
return Zotero.ID.getKey();
}

View file

@ -0,0 +1,184 @@
/*
***** 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 *****
*/
/*
* Primary interface for accessing Zotero collection
*/
Zotero.Collections = new function() {
var _collections = {};
var _collectionsLoaded = false;
this.get = get;
this.add = add;
this.getUpdated = getUpdated;
this.getCollectionsContainingItems = getCollectionsContainingItems;
this.reload = reload;
this.reloadAll = reloadAll;
this.erase = erase;
this.unload = unload;
/*
* Returns a Zotero.Collection object for a collectionID
*/
function get(id) {
if (!_collectionsLoaded) {
this.reloadAll();
}
return (typeof _collections[id]!='undefined') ? _collections[id] : false;
}
/**
* Add new collection to DB and return Collection object
*
* _name_ is non-empty string
* _parent_ is optional collectionID -- creates root collection by default
*
* Returns true on success; false on error
**/
function add(name, parent) {
var col = new Zotero.Collection;
col.name = name;
col.parent = parent;
var id = col.save();
return this.get(id);
}
function getUpdated(date) {
var sql = "SELECT collectionID FROM collections";
if (date) {
sql += " WHERE dateModified>?";
return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
}
return Zotero.DB.columnQuery(sql);
}
function getCollectionsContainingItems(itemIDs, asIDs) {
var sql = "SELECT collectionID FROM collections WHERE ";
var sqlParams = [];
for each(var id in itemIDs) {
sql += "collectionID IN (SELECT collectionID FROM collectionItems "
+ "WHERE itemID=?) AND "
sqlParams.push(id);
}
sql = sql.substring(0, sql.length - 5);
var collectionIDs = Zotero.DB.columnQuery(sql, sqlParams);
if (asIDs) {
return collectionIDs;
}
return Zotero.Collections.get(collectionIDs);
}
function reload(id) {
if (!_collectionsLoaded) {
this.reloadAll();
return;
}
if (!_collections[id]) {
_collections[id] = new Zotero.Collection(id);
}
_collections[id].load();
}
/**
* Loads collection data from DB and adds to internal cache
**/
function reloadAll() {
Zotero.debug('Loading all collections');
// This should be the same as the query in Zotero.Collection.load(),
// just without a specific collectionID
var sql = "SELECT C.*, "
+ "(SELECT COUNT(*) FROM collections WHERE "
+ "parentCollectionID=C.collectionID)!=0 AS hasChildCollections, "
+ "(SELECT COUNT(*) FROM collectionItems WHERE "
+ "collectionID=C.collectionID)!=0 AS hasChildItems "
+ "FROM collections C";
var result = Zotero.DB.query(sql);
var collectionIDs = [];
if (result) {
for (var i=0; i<result.length; i++) {
var collectionID = result[i].collectionID;
collectionIDs.push(collectionID);
// If collection doesn't exist, create new object and stuff in array
if (!_collections[collectionID]) {
_collections[collectionID] = new Zotero.Collection;
}
_collections[collectionID].loadFromRow(result[i]);
}
}
// Remove old collections that no longer exist
for each(var c in _collections) {
if (collectionIDs.indexOf(c.id) == -1) {
this.unload(c.id);
}
}
_collectionsLoaded = true;
}
function erase(ids) {
ids = Zotero.flattenArguments(ids);
Zotero.DB.beginTransaction();
for each(var id in ids) {
var collection = this.get(id);
if (collection) {
collection.erase();
}
collection = undefined;
}
this.unload(ids);
Zotero.DB.commitTransaction();
}
/**
* Clear collection from internal cache (used by Zotero.Collection.erase())
*
* Can be passed ids as individual parameters or as an array of ids, or both
**/
function unload() {
var ids = Zotero.flattenArguments(arguments);
for(var i=0; i<ids.length; i++) {
delete _collections[ids[i]];
}
}
}

View file

@ -0,0 +1,451 @@
/*
***** 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.Creator = function (creatorID) {
this._creatorID = creatorID ? creatorID : null;
this._init();
}
Zotero.Creator.prototype._init = function () {
this._firstName = null;
this._lastName = null;
this._fieldMode = null;
this._birthYear = null;
this._key = null;
this._dateModified = null;
this._creatorDataID = null;
this._loaded = false;
this._changed = false;
this._previousData = false;
}
Zotero.Creator.prototype.__defineGetter__('id', function () { return this._creatorID; });
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'); });
Zotero.Creator.prototype.__defineSetter__('lastName', function (val) { this._set('lastName', val); });
Zotero.Creator.prototype.__defineGetter__('fieldMode', function () { return this._get('fieldMode'); });
Zotero.Creator.prototype.__defineSetter__('fieldMode', function (val) { this._set('fieldMode', val); });
Zotero.Creator.prototype.__defineGetter__('birthYear', function () { return this._get('birthYear'); });
Zotero.Creator.prototype.__defineSetter__('birthYear', function (val) { this._set('birthYear', val); });
Zotero.Creator.prototype.__defineGetter__('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._get = function (field) {
if (this.id && !this._loaded) {
this.load();
}
return this['_' + field];
}
Zotero.Creator.prototype._set = function (field, val) {
switch (field) {
case 'id': // set using constructor
//case 'creatorID': // set using constructor
case 'creatorDataID':
throw ("Invalid field '" + field + "' in Zotero.Creator.set()");
}
if (this.id) {
if (!this._loaded) {
this.load();
}
}
else {
this._loaded = true;
}
this._checkValue(field, val);
if (this['_' + field] != val) {
if (!this._changed) {
this._changed = {};
}
this._changed[field] = true;
if (this.id && this.exists() && !this._previousData) {
this._previousData = this.serialize();
}
this['_' + field] = val;
}
}
Zotero.Creator.prototype.setFields = function(fields) {
for (var field in fields) {
this[field] = fields[field];
}
}
/**
* Check if creator exists in the database
*
* @return bool TRUE if the creator exists, FALSE if not
*/
Zotero.Creator.prototype.exists = function() {
if (!this.id) {
throw ('creatorID not set in Zotero.Creator.exists()');
}
var sql = "SELECT COUNT(*) FROM creators WHERE creatorID=?";
return !!Zotero.DB.valueQuery(sql, this.id);
}
Zotero.Creator.prototype.hasChanged = function () {
return this._changed;
}
Zotero.Creator.prototype.save = function () {
if (!this.firstName && !this.lastName) {
throw ('First and last name are empty in Zotero.Creator.save()');
}
if (!this.hasChanged()) {
Zotero.debug("Creator " + this.id + " has not changed");
return false;
}
if (this.fieldMode == 1 && this.firstName) {
throw ("First name ('" + this.firstName + "') must be empty in single-field mode in Zotero.Creator.save()");
}
Zotero.DB.beginTransaction();
// 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.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.Creators.unload(oldID);
Zotero.Notifier.trigger('id-change', 'creator', oldID + '-' + this.id);
// update caches
}
var isNew = !this.id || !this.exists();
try {
// how to know if date modified changed (in server code too?)
var creatorID = this.id ? this.id : Zotero.ID.get('creators');
Zotero.debug("Saving creator " + this.id);
var key = this.key ? this.key : this._generateKey();
// If this was the only creator with the previous data,
// see if we can reuse or remove the old data row
if (this.creatorDataID) {
var count = Zotero.Creators.countCreatorsWithData(this.creatorDataID);
if (count == 1) {
var newCreatorDataID = Zotero.Creators.getDataID(this);
// Data hasn't changed
if (this.creatorDataID == newCreatorDataID) {
var creatorDataID = this.creatorDataID;
}
// Existing data row with the new data -- switch to that
// and delete old row
else if (newCreatorDataID) {
var creatorDataID = newCreatorDataID;
Zotero.Creators.deleteData(this.creatorDataID);
}
// Update current data row with new data
else {
Zotero.Creators.updateData(this.creatorDataID, this);
var creatorDataID = this.creatorDataID;
}
}
}
if (!creatorDataID) {
var creatorDataID = Zotero.Creators.getDataID(this, true);
}
var columns = ['creatorID', 'creatorDataID', 'dateModified', 'key'];
var placeholders = ['?', '?', '?', '?'];
var sqlValues = [
creatorID ? { int: creatorID } : null,
{ int: creatorDataID },
// If date modified hasn't changed, use current timestamp
this._changed.dateModified ?
this.dateModified : Zotero.DB.transactionDateTime,
key
];
var sql = "REPLACE INTO creators (" + columns.join(', ') + ") VALUES ("
+ placeholders.join(', ') + ")";
var insertID = Zotero.DB.query(sql, sqlValues);
if (!creatorID) {
creatorID = insertID;
}
this.updateLinkedItems(creatorID);
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.DB.rollbackTransaction();
throw (e);
}
// If successful, set values in object
if (!this.id) {
this._creatorID = creatorID;
}
if (!this.key) {
this._key = key;
}
if (!this.creatorDataID) {
this._creatorDataID = creatorDataID;
}
Zotero.Creators.reload(this.id);
if (isNew) {
Zotero.Notifier.trigger('add', 'creator', this.id);
}
else {
Zotero.Notifier.trigger('modify', 'creator', this.id, this._previousData);
}
return this.id;
}
Zotero.Creator.prototype.countLinkedItems = function() {
var sql = "SELECT COUNT(*) FROM itemCreators WHERE creatorID=?";
return Zotero.DB.valueQuery(sql, this.id);
}
Zotero.Creator.prototype.getLinkedItems = function () {
var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
return Zotero.DB.columnQuery(sql, this.id);
}
Zotero.Creator.prototype.updateLinkedItems = function () {
Zotero.DB.beginTransaction();
var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
var changedItemIDs = Zotero.DB.columnQuery(sql, this.id);
if (!changedItemIDs) {
Zotero.DB.commitTransaction();
return;
}
var notifierData = {};
for each(var id in changedItemIDs) {
var item = Zotero.Items.get(id);
if (item) {
notifierData[item.id] = { old: item.serialize() };
}
}
sql = "UPDATE items SET dateModified=? WHERE itemID IN "
+ "(SELECT itemID FROM itemCreators WHERE creatorID=?)";
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, this.id]);
Zotero.Items.reload(changedItemIDs);
Zotero.DB.commitTransaction();
Zotero.Notifier.trigger('modify', 'item', changedItemIDs, notifierData);
}
Zotero.Creator.prototype.equals = function (creator) {
return (creator.firstName == this.firstName) &&
(creator.lastName == this.lastName) &&
(creator.fieldMode == this.fieldMode) &&
(creator.shortName == this.shortName) &&
(creator.birthYear == this.birthYear);
}
Zotero.Creator.prototype.serialize = function () {
var obj = {};
obj.primary = {};
obj.primary.creatorID = this.id;
obj.primary.dateModified = this.dateModified;
obj.primary.key = this.key;
obj.fields = {};
if (this.fieldMode == 1) {
obj.fields.name = this.lastName;
}
else {
obj.fields.firstName = this.firstName;
obj.fields.lastName = this.lastName;
}
obj.fields.fieldMode = this.fieldMode;
obj.fields.shortName = this.shortName;
obj.fields.birthYear = this.birthYear;
return obj;
}
/**
* Remove creator from all linked items
*
* Creators.erase() should be used instead of this
*
* Actual deletion of creator occurs in Zotero.Creators.purge(),
* which is called by Creators.erase()
*/
Zotero.Creator.prototype.erase = function () {
if (!this.id) {
return false;
}
Zotero.debug("Deleting creator " + this.id);
var changedItems = [];
var changedItemsNotifierData = {};
Zotero.DB.beginTransaction();
var toSave = {};
var linkedItemIDs = this.getLinkedItems();
for each(var itemID in linkedItemIDs) {
var item = Zotero.Items.get(itemID)
if (!item) {
throw ('Linked item not found in Zotero.Creator.erase()');
}
var pos = item.getCreatorPosition(this.id);
if (!pos) {
throw ('Creator not found in linked item in Zotero.Creator.erase()');
}
item.removeCreator(pos);
if (!toSave[item.id]) {
toSave[item.id] = item;
}
}
for each(var item in toSave) {
item.save();
}
Zotero.DB.commitTransaction();
}
// Also called from Zotero.Creators.reload()
Zotero.Creator.prototype.load = function () {
Zotero.debug("Loading data for creator " + this.id + " in Zotero.Creator.load()");
if (!this.id) {
throw ("creatorID not set in Zotero.Creator.load()");
}
var sql = "SELECT key, dateModified, creatorDataID, CD.* "
+ "FROM creators C NATURAL JOIN creatorData CD WHERE creatorID=?";
var data = Zotero.DB.rowQuery(sql, this.id);
this._init();
this._loaded = true;
if (!data) {
return;
}
for (var key in data) {
this['_' + key] = data[key];
}
}
Zotero.Creator.prototype._checkValue = function (field, value) {
if (this['_' + field] === undefined) {
throw ("Invalid property " + field + " in Zotero.Creator._checkValue()");
}
// Data validation
switch (field) {
case 'fieldMode':
if (value !== 0 && value !== 1) {
this._invalidValueError(field, value);
}
break;
case 'key':
var re = /^[23456789ABCDEFGHIJKMNPQRSTUVWXTZ]{8}$/
if (!re.test(value)) {
this._invalidValueError(field, value);
}
break;
case 'dateModified':
if (value !== '' && !Zotero.Date.isSQLDateTime(value)) {
this._invalidValueError(field, value);
}
break;
}
}
Zotero.Creator.prototype._generateKey = function () {
return Zotero.ID.getKey();
}
Zotero.Creator.prototype._invalidValueError = function (field, value) {
throw ("Invalid '" + field + "' value '" + value + "' in Zotero.Creator._invalidValueError()");
}

View file

@ -0,0 +1,346 @@
/*
***** 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.Creators = new function() {
var _creatorsByID = {}; // Zotero.Creator objects indexed by creatorID
var _creatorDataHash = {}; // creatorDataIDs indexed by md5 hash of data
this.get = get;
this.getUpdated = getUpdated;
this.getDataID = getDataID;
this.getCreatorsWithData = getCreatorsWithData;
this.countCreatorsWithData = countCreatorsWithData;
this.updateData = updateData;
this.deleteData = deleteData;
this.reload = reload;
this.reloadAll = reloadAll;
this.erase = erase;
this.purge = purge;
this.unload = unload;
this.fields = ['firstName', 'lastName', 'fieldMode', 'birthYear'];
var self = this;
/*
* Returns a Zotero.Creator object for a given creatorID
*/
function get(creatorID) {
if (_creatorsByID[creatorID]) {
return _creatorsByID[creatorID];
}
var sql = 'SELECT * FROM creators WHERE creatorID=?';
var result = Zotero.DB.rowQuery(sql, creatorID);
if (!result) {
return false;
}
_creatorsByID[creatorID] = new Zotero.Creator(result.creatorID);
return _creatorsByID[creatorID];
}
function getUpdated(date) {
var sql = "SELECT creatorID FROM creators";
if (date) {
sql += " WHERE dateModified>?";
return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
}
return Zotero.DB.columnQuery(sql);
}
/**
* Returns the creatorDataID matching given fields
*
* @param array fields
* @param bool create If no matching creatorDataID, create one
*/
function getDataID(fields, create) {
fields = _cleanFields(fields);
if (!fields.firstName && !fields.lastName) {
throw ("First or last name must be provided in Zotero.Creators.getDataID()");
}
var hash = _getHash(fields);
if (_creatorDataHash[hash]) {
return _creatorDataHash[hash];
}
Zotero.DB.beginTransaction();
var params = [
fields.firstName,
fields.lastName,
'',
fields.fieldMode,
fields.birthYear
];
var sql = "SELECT creatorDataID FROM creatorData WHERE "
+ "firstName=? AND lastName=? AND shortName=? "
+ "AND fieldMode=? AND birthYear=?";
var id = Zotero.DB.valueQuery(sql, params);
if (!id && create) {
id = Zotero.ID.get('creatorData');
params.unshift(id);
sql = "INSERT INTO creatorData (creatorDataID, "
+ "firstName, lastName, shortName, fieldMode, birthYear) "
+ "VALUES (?, ?, ?, ?, ?, ?)";
var insertID = Zotero.DB.query(sql, params);
if (!id) {
id = insertID;
}
}
Zotero.DB.commitTransaction();
if (id) {
_creatorDataHash[hash] = id;
}
return id;
}
function getCreatorsWithData(creatorDataID) {
var sql = "SELECT creatorID FROM creators WHERE creatorDataID=?";
return Zotero.DB.columnQuery(sql, creatorDataID);
}
function countCreatorsWithData(creatorDataID) {
var sql = "SELECT COUNT(*) FROM creators WHERE creatorDataID=?";
return Zotero.DB.valueQuery(sql, creatorDataID);
}
function updateData(creatorDataID, fields) {
fields = _cleanFields(fields);
var sqlFields = [];
var sqlParams = [];
for (var field in fields) {
// Skip fields not specified as changeable creator fields
if (this.fields.indexOf(field) == -1) {
continue;
}
sqlFields.push(field + '=?');
sqlParams.push(fields[field]);
}
var sql = "UPDATE creatorData SET " + sqlFields.join(', ')
+ " WHERE creatorDataID=?";
sqlParams.push(creatorDataID);
Zotero.DB.query(sql, sqlParams);
_updateCachedData(creatorDataID);
}
function deleteData(creatorDataID) {
var sql = "DELETE FROM creatorData WHERE creatorDataID=?";
Zotero.DB.query(sql, creatorDataID);
_updateCachedData(creatorDataID);
}
/*
* Reloads data for specified creators into internal array
*
* Can be passed ids as individual parameters or as an array of ids, or both
*/
function reload() {
if (!arguments[0]) {
return false;
}
var ids = Zotero.flattenArguments(arguments);
Zotero.debug('Reloading creators ' + ids);
for each(var id in ids) {
if (!_creatorsByID[id]) {
this.get(id);
}
else {
_creatorsByID[id].load();
}
}
return true;
}
function reloadAll() {
Zotero.debug("Reloading all creators");
_creatorDataHash = {};
for (var id in _creatorsByID) {
_creatorsByID[id].load();
var realID = _creatorsByID[id].id;
if (realID != id) {
Zotero.debug("Clearing cache entry for creator " + id);
delete _creatorsByID[id];
}
}
}
/**
* Remove creator(s) from all linked items and call this.purge()
* to delete creator rows
*/
function erase(ids) {
ids = Zotero.flattenArguments(ids);
var unlock = Zotero.Notifier.begin(true);
Zotero.UnresponsiveScriptIndicator.disable();
try {
Zotero.DB.beginTransaction();
for each(var id in ids) {
var creator = this.get(id);
if (!creator) {
Zotero.debug('Creator ' + id + ' does not exist in Creators.erase()!', 1);
Zotero.Notifier.trigger('delete', 'creator', id);
continue;
}
creator.erase();
creator = undefined;
}
this.purge();
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.DB.rollbackTransaction();
throw (e);
}
finally {
Zotero.Notifier.commit(unlock);
Zotero.UnresponsiveScriptIndicator.enable();
}
}
/*
* Delete obsolete creator/creatorData rows from database
* and clear internal array entries
*/
function purge() {
Zotero.debug("Purging creator tables");
// Purge unused creators
var sql = 'SELECT creatorID FROM creators WHERE creatorID NOT IN '
+ '(SELECT creatorID FROM itemCreators)';
var toDelete = Zotero.DB.columnQuery(sql);
if (toDelete) {
// Clear creator entries in internal array
for each(var creatorID in toDelete) {
delete _creatorsByID[creatorID];
}
var sql = "DELETE FROM creators WHERE creatorID NOT IN "
+ "(SELECT creatorID FROM itemCreators)";
Zotero.DB.query(sql);
}
// Purge unused creatorData rows
var sql = 'SELECT creatorDataID FROM creatorData WHERE creatorDataID NOT IN '
+ '(SELECT creatorDataID FROM creators)';
var toDelete = Zotero.DB.columnQuery(sql);
if (toDelete) {
// Clear creator entries in internal array
for each(var creatorDataID in toDelete) {
_updateCachedData(creatorDataID);
}
var sql = "DELETE FROM creatorData WHERE creatorDataID NOT IN "
+ "(SELECT creatorDataID FROM creators)";
Zotero.DB.query(sql);
}
}
/**
* Clear creator from internal array
*
* @param int id creatorID
*/
function unload(id) {
delete _creatorsByID[id];
}
function _cleanFields(fields) {
var cleanedFields = {
firstName: '',
lastName: '',
fieldMode: 0,
birthYear: ''
};
for (var field in fields) {
if (fields[field]) {
cleanedFields[field] = fields[field];
}
}
return cleanedFields;
}
function _getHash(fields) {
var hashFields = [];
for each(var field in Zotero.Creators.fields) {
hashFields.push(fields[field]);
}
var ZU = new Zotero.Utilities;
return ZU.md5(hashFields.join('_'));
}
function _getDataFromID(creatorDataID) {
var sql = "SELECT * FROM creatorData WHERE creatorDataID=?";
return Zotero.DB.rowQuery(sql, creatorDataID);
}
function _updateCachedData(creatorDataID) {
for (var hash in _creatorDataHash) {
if (_creatorDataHash[hash] == creatorDataID) {
delete _creatorDataHash[hash];
}
}
var creators = getCreatorsWithData(creatorDataID);
for each(var creatorID in creators) {
if (_creatorsByID[creatorID]) {
_creatorsByID[creatorID].load();
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,391 @@
/*
***** 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.ItemFields = new function() {
// Private members
var _fields = {};
var _fieldsLoaded;
var _fieldFormats = [];
var _itemTypeFields = [];
var _baseTypeFields = [];
var _typeFieldIDsByBase = {};
var _typeFieldNamesByBase = {};
var self = this;
// Privileged methods
this.getName = getName;
this.getID = getID;
this.getLocalizedString = getLocalizedString;
this.isValidForType = isValidForType;
this.isInteger = isInteger;
this.getItemTypeFields = getItemTypeFields;
this.isBaseField = isBaseField;
this.isFieldOfBase = isFieldOfBase;
this.getBaseMappedFields = getBaseMappedFields;
this.getFieldIDFromTypeAndBase = getFieldIDFromTypeAndBase;
this.getBaseIDFromTypeAndField = getBaseIDFromTypeAndField;
this.getTypeFieldsFromBase = getTypeFieldsFromBase;
/*
* Return the fieldID for a passed fieldID or fieldName
*/
function getID(field) {
if (!_fieldsLoaded) {
_loadFields();
}
if (typeof field == 'number') {
return field;
}
return _fields[field] ? _fields[field]['id'] : false;
}
/*
* Return the fieldName for a passed fieldID or fieldName
*/
function getName(field) {
if (!_fieldsLoaded) {
_loadFields();
}
return _fields[field] ? _fields[field]['name'] : false;
}
function getLocalizedString(itemType, field) {
// unused currently
//var typeName = Zotero.ItemTypes.getName(itemType);
var fieldName = this.getName(field);
// Fields in the items table are special cases
switch (field) {
case 'dateAdded':
case 'dateModified':
case 'itemType':
fieldName = field;
}
// TODO: different labels for different item types
return Zotero.getString("itemFields." + fieldName);
}
function isValidForType(fieldID, itemTypeID) {
if (!_fieldsLoaded) {
_loadFields();
}
_fieldCheck(fieldID, 'isValidForType');
if (!_fields[fieldID]['itemTypes']) {
return false;
}
return !!_fields[fieldID]['itemTypes'][itemTypeID];
}
function isInteger(fieldID) {
if (!_fieldsLoaded) {
_loadFields();
}
_fieldCheck(fieldID, 'isInteger');
var ffid = _fields[fieldID]['formatID'];
return _fieldFormats[ffid] ? _fieldFormats[ffid]['isInteger'] : false;
}
/*
* Returns an array of fieldIDs for a given item type
*/
function getItemTypeFields(itemTypeID) {
if (_itemTypeFields[itemTypeID]) {
return _itemTypeFields[itemTypeID];
}
if (!itemTypeID) {
throw("Invalid item type id '" + itemTypeID
+ "' provided to getItemTypeFields()");
}
var sql = 'SELECT fieldID FROM itemTypeFields '
+ 'WHERE itemTypeID=' + itemTypeID + ' ORDER BY orderIndex';
var fields = Zotero.DB.columnQuery(sql);
_itemTypeFields[itemTypeID] = fields ? fields : [];
return _itemTypeFields[itemTypeID];
}
function isBaseField(field) {
if (!_fieldsLoaded) {
_loadFields();
}
_fieldCheck(field, arguments.callee.name);
return _fields[field]['isBaseField'];
}
function isFieldOfBase(field, baseField) {
var fieldID = _fieldCheck(field, 'isFieldOfBase');
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
throw ("Invalid field '" + baseField + '" for base field in ItemFields.getFieldIDFromTypeAndBase()');
}
if (fieldID == baseFieldID) {
return true;
}
var typeFields = this.getTypeFieldsFromBase(baseFieldID);
return typeFields.indexOf(fieldID) != -1;
}
function getBaseMappedFields() {
return Zotero.DB.columnQuery("SELECT DISTINCT fieldID FROM baseFieldMappings");
}
/*
* Returns the fieldID of a type-specific field for a given base field
* or false if none
*
* Examples:
*
* 'audioRecording' and 'publisher' returns label's fieldID
* 'book' and 'publisher' returns publisher's fieldID
* 'audioRecording' and 'number' returns false
*
* Accepts names or ids
*/
function getFieldIDFromTypeAndBase(itemType, baseField) {
if (!_fieldsLoaded) {
_loadFields();
}
var itemTypeID = Zotero.ItemTypes.getID(itemType);
if (!itemTypeID) {
throw ("Invalid item type '" + itemType + "' in ItemFields.getFieldIDFromTypeAndBase()");
}
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
throw ("Invalid field '" + baseField + '" for base field in ItemFields.getFieldIDFromTypeAndBase()');
}
return _baseTypeFields[itemTypeID][baseFieldID];
}
/*
* Returns the fieldID of the base field for a given type-specific field
* or false if none
*
* Examples:
*
* 'audioRecording' and 'label' returns publisher's fieldID
* 'book' and 'publisher' returns publisher's fieldID
* 'audioRecording' and 'runningTime' returns false
*
* Accepts names or ids
*/
function getBaseIDFromTypeAndField(itemType, typeField) {
var itemTypeID = Zotero.ItemTypes.getID(itemType);
var typeFieldID = this.getID(typeField);
if (!itemTypeID) {
throw ("Invalid item type '" + itemType + "' in ItemFields.getBaseIDFromTypeAndField()");
}
_fieldCheck(typeField, 'getBaseIDFromTypeAndField');
if (!this.isValidForType(typeFieldID, itemTypeID)) {
throw ("'" + typeField + "' is not a valid field for '" + itemType + "' in ItemFields.getBaseIDFromTypeAndField()");
}
// If typeField is already a base field, just return that
if (this.isBaseField(typeFieldID)) {
return typeFieldID;
}
return Zotero.DB.valueQuery("SELECT baseFieldID FROM baseFieldMappings "
+ "WHERE itemTypeID=? AND fieldID=?", [itemTypeID, typeFieldID]);
}
/*
* Returns an array of fieldIDs associated with a given base field
*
* e.g. 'publisher' returns fieldIDs for [university, studio, label, network]
*/
function getTypeFieldsFromBase(baseField, asNames) {
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
throw ("Invalid base field '" + baseField + '" in ItemFields.getTypeFieldsFromBase()');
}
if (asNames) {
return _typeFieldNamesByBase[baseFieldID] ?
_typeFieldNamesByBase[baseFieldID] : false;
}
return _typeFieldIDsByBase[baseFieldID] ?
_typeFieldIDsByBase[baseFieldID] : false;
}
/**
* Check whether a field is valid, throwing an exception if not
* (since it should never actually happen)
**/
function _fieldCheck(field, func) {
var fieldID = self.getID(field);
if (!fieldID) {
throw ("Invalid field '" + field + (func ? "' in ItemFields." + func + "()" : "'"));
}
return fieldID;
}
/*
* Returns hash array of itemTypeIDs for which a given field is valid
*/
function _getFieldItemTypes() {
var sql = 'SELECT fieldID, itemTypeID FROM itemTypeFields';
var results = Zotero.DB.query(sql);
if (!results) {
throw ('No fields in itemTypeFields!');
}
var fields = new Array();
for (var i=0; i<results.length; i++) {
if (!fields[results[i]['fieldID']]) {
fields[results[i]['fieldID']] = new Array();
}
fields[results[i]['fieldID']][results[i]['itemTypeID']] = true;
}
return fields;
}
/*
* Build a lookup table for base field mappings
*/
function _loadBaseTypeFields() {
// Grab all fields, base field or not
var sql = "SELECT IT.itemTypeID, F.fieldID AS baseFieldID, BFM.fieldID "
+ "FROM itemTypes IT LEFT JOIN fields F "
+ "LEFT JOIN baseFieldMappings BFM"
+ " ON (IT.itemTypeID=BFM.itemTypeID AND F.fieldID=BFM.baseFieldID)";
var rows = Zotero.DB.query(sql);
var sql = "SELECT DISTINCT baseFieldID FROM baseFieldMappings";
var baseFields = Zotero.DB.columnQuery(sql);
var fields = [];
for each(var row in rows) {
if (!fields[row.itemTypeID]) {
fields[row.itemTypeID] = [];
}
if (row.fieldID) {
fields[row.itemTypeID][row.baseFieldID] = row.fieldID;
}
// If a base field and already valid for the type, just use that
else if (isBaseField(row.baseFieldID) &&
isValidForType(row.baseFieldID, row.itemTypeID)) {
fields[row.itemTypeID][row.baseFieldID] = row.baseFieldID;
}
// Set false for other fields so that we don't need to test for
// existence
else {
fields[row.itemTypeID][row.baseFieldID] = false;
}
}
_baseTypeFields = fields;
var sql = "SELECT baseFieldID, fieldID, fieldName "
+ "FROM baseFieldMappings JOIN fields USING (fieldID)";
var rows = Zotero.DB.query(sql);
for each(var row in rows) {
if (!_typeFieldIDsByBase[row['baseFieldID']]) {
_typeFieldIDsByBase[row['baseFieldID']] = [];
_typeFieldNamesByBase[row['baseFieldID']] = [];
}
_typeFieldIDsByBase[row['baseFieldID']].push(row['fieldID']);
_typeFieldNamesByBase[row['baseFieldID']].push(row['fieldName']);
}
}
/*
* Load all fields into an internal hash array
*/
function _loadFields() {
var result = Zotero.DB.query('SELECT * FROM fieldFormats');
for (var i=0; i<result.length; i++) {
_fieldFormats[result[i]['fieldFormatID']] = {
regex: result[i]['regex'],
isInteger: result[i]['isInteger']
};
}
var fields = Zotero.DB.query('SELECT * FROM fields');
var fieldItemTypes = _getFieldItemTypes();
var sql = "SELECT DISTINCT baseFieldID FROM baseFieldMappings";
var baseFields = Zotero.DB.columnQuery(sql);
for each(var field in fields) {
_fields[field['fieldID']] = {
id: field['fieldID'],
name: field['fieldName'],
isBaseField: (baseFields.indexOf(field['fieldID']) != -1),
formatID: field['fieldFormatID'],
itemTypes: fieldItemTypes[field['fieldID']]
};
// Store by name as well as id
_fields[field['fieldName']] = _fields[field['fieldID']];
}
_fieldsLoaded = true;
_loadBaseTypeFields();
}
}

View file

@ -0,0 +1,577 @@
/*
***** 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 *****
*/
/*
* Primary interface for accessing Zotero items
*/
Zotero.Items = new function() {
// Privileged methods
this.get = get;
this.getAll = getAll;
this.getUpdated = getUpdated;
this.add = add;
this.reload = reload;
this.reloadAll = reloadAll;
this.cacheFields = cacheFields;
this.erase = erase;
this.purge = purge;
this.unload = unload;
this.getFirstCreatorSQL = getFirstCreatorSQL;
this.getSortTitle = getSortTitle;
// Private members
var _items = [];
var _itemsLoaded = false;
var _cachedFields = [];
var _firstCreatorSQL = '';
/*
* Retrieves (and loads, if necessary) an arbitrary number of items
*
* Can be passed ids as individual parameters or as an array of ids, or both
*
* If only one argument and it's an id, return object directly;
* otherwise, return array
*/
function get() {
var toLoad = [];
var loaded = [];
if (!arguments[0]) {
Zotero.debug('No arguments provided to Items.get()');
return false;
}
var ids = Zotero.flattenArguments(arguments);
for (var i=0; i<ids.length; i++) {
// Check if already loaded
if (!_items[ids[i]]) {
toLoad.push(ids[i]);
}
}
// New items to load
if (toLoad.length) {
_load(toLoad);
}
// If single id, return the object directly
if (arguments[0] && typeof arguments[0]!='object'
&& typeof arguments[1]=='undefined') {
if (!_items[arguments[0]]) {
Zotero.debug("Item " + arguments[0] + " doesn't exist", 2);
return false;
}
return _items[arguments[0]];
}
// Otherwise, build return array
for (i=0; i<ids.length; i++) {
if (!_items[ids[i]]) {
Zotero.debug("Item " + ids[i] + " doesn't exist", 2);
continue;
}
loaded.push(_items[ids[i]]);
}
return loaded;
}
/*
* Returns all items in the database
*
* If |onlyTopLevel|, don't include child items
*/
function getAll(onlyTopLevel) {
var sql = 'SELECT A.itemID FROM items A';
if (onlyTopLevel) {
sql += ' LEFT JOIN itemNotes B USING (itemID) '
+ 'LEFT JOIN itemAttachments C ON (C.itemID=A.itemID) '
+ 'WHERE B.sourceItemID IS NULL AND C.sourceItemID IS NULL';
}
var ids = Zotero.DB.columnQuery(sql);
return this.get(ids);
}
function getUpdated(date) {
var s = new Zotero.Search();
if (date) {
s.addCondition('dateModified', 'isAfter', Zotero.Date.dateToSQL(date, true));
}
return s.search();
}
/*
* Create a new item with optional metadata and pass back the primary reference
*
* Using "var item = new Zotero.Item()" and "item.save()" directly results
* in an orphaned reference to the created item. If other code retrieves the
* new item with Zotero.Items.get() and modifies it, the original reference
* will not reflect the changes.
*
* Using this method avoids the need to call Zotero.Items.get() after save()
* in order to get the primary item reference. Since it accepts metadata
* as a JavaScript object, it also offers a simpler syntax than
* item.setField() and item.setCreator().
*
* Callers with no need for an up-to-date reference after save() (or who
* don't mind doing an extra Zotero.Items.get()) can use Zotero.Item
* directly if they prefer.
*
* Sample usage:
*
* var data = {
* title: "Shakespeare: The Invention of the Human",
* publisher: "Riverhead Hardcover",
* date: '1998-10-26',
* ISBN: 1573221201,
* pages: 745,
* creators: [
* ['Harold', 'Bloom', 'author']
* ]
* };
* var item = Zotero.Items.add('book', data);
*/
function add(itemTypeOrID, data) {
var item = new Zotero.Item(false, itemTypeOrID);
for (var field in data) {
if (field == 'creators') {
var i = 0;
for each(var creator in data.creators) {
// TODO: accept format from toArray()
var fields = {
firstName: creator[0],
lastName: creator[1],
fieldMode: creator[3] ? creator[3] : 0
};
var creatorDataID = Zotero.Creators.getDataID(fields);
if (creatorDataID) {
var linkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID);
// TODO: identical creators?
var creatorID = linkedCreators[0];
}
else {
var creatorObj = new Zotero.Creator;
creatorObj.setFields(fields);
var creatorID = creatorObj.save();
}
item.setCreator(i, Zotero.Creators.get(creatorID), creator[2]);
i++;
}
}
else {
item.setField(field, data[field]);
}
}
var id = item.save();
return this.get(id);
}
/*
* Reloads data for specified items into internal array
*
* Can be passed ids as individual parameters or as an array of ids, or both
*/
function reload() {
if (!arguments[0]) {
return false;
}
var ids = Zotero.flattenArguments(arguments);
Zotero.debug('Reloading ' + ids);
_load(ids);
return true;
}
function reloadAll() {
Zotero.debug("Loading all items");
_items = [];
_itemsLoaded = false;
_load();
}
function cacheFields(fields, items) {
Zotero.debug("Caching fields [" + fields.join() + "]"
+ (items ? " for " + items + " items" : ''));
_load(items);
var primaryFields = [];
var fieldIDs = [];
for each(var field in fields) {
// Check if field already cached
if (_cachedFields.indexOf(field) != -1) {
continue;
}
_cachedFields.push(field);
if (Zotero.Item.prototype.isPrimaryField(field)) {
primaryFields.push(field);
}
else {
fieldIDs.push(Zotero.ItemFields.getID(field));
if (Zotero.ItemFields.isBaseField(field)) {
fieldIDs = fieldIDs.concat(Zotero.ItemFields.getTypeFieldsFromBase(field));
}
}
}
if (primaryFields.length) {
var sql = "SELECT itemID, " + primaryFields.join(', ') + " FROM items";
if (items) {
sql += " WHERE itemID IN (" + items.join() + ")";
}
var rows = Zotero.DB.query(sql);
for each(var row in rows) {
//Zotero.debug('Calling loadFromRow for item ' + row.itemID);
_items[row.itemID].loadFromRow(row);
}
}
// All fields already cached
if (!fieldIDs.length) {
return;
}
var allItemIDs = Zotero.DB.columnQuery("SELECT itemID FROM items");
var itemFieldsCached = {};
var sql = "SELECT itemID, fieldID, value FROM itemData "
+ "NATURAL JOIN itemDataValues WHERE ";
if (items) {
sql += "itemID IN (" + items.join() + ") AND ";
}
sql += "fieldID IN (" + fieldIDs.join() + ")";
var itemDataRows = Zotero.DB.query(sql);
for each(var row in itemDataRows) {
//Zotero.debug('Setting field ' + row.fieldID + ' for item ' + row.itemID);
if (_items[row.itemID]) {
_items[row.itemID].setField(row.fieldID, row.value, true);
}
else {
if (!missingItems) {
var missingItems = {};
}
if (!missingItems[row.itemID]) {
missingItems[row.itemID] = true;
Components.utils.reportError("itemData row references nonexistent item " + row.itemID);
}
}
if (!itemFieldsCached[row.itemID]) {
itemFieldsCached[row.itemID] = {};
}
itemFieldsCached[row.itemID][row.fieldID] = true;
}
// If 'title' is one of the fields, load in note titles
if (fields.indexOf('title') != -1) {
var titleFieldID = Zotero.ItemFields.getID('title');
var sql = "SELECT itemID, title FROM itemNotes WHERE itemID"
+ " NOT IN (SELECT itemID FROM itemAttachments)";
if (items) {
sql += " AND itemID IN (" + items.join() + ")";
}
var rows = Zotero.DB.query(sql);
for each(var row in rows) {
//Zotero.debug('Setting title for note ' + row.itemID);
if (_items[row.itemID]) {
_items[row.itemID].setField(titleFieldID, row['title'], true);
}
else {
if (!missingItems) {
var missingItems = {};
}
if (!missingItems[row.itemID]) {
missingItems[row.itemID] = true;
Components.utils.reportError("itemData row references nonexistent item " + row.itemID);
}
}
}
}
// Set nonexistent fields in the cache list to false (instead of null)
for each(var itemID in allItemIDs) {
for each(var fieldID in fieldIDs) {
if (Zotero.ItemFields.isValidForType(fieldID, _items[itemID].itemTypeID)) {
if (!itemFieldsCached[itemID] || !itemFieldsCached[itemID][fieldID]) {
//Zotero.debug('Setting field ' + fieldID + ' to false for item ' + itemID);
_items[itemID].setField(fieldID, false, true);
}
}
}
}
}
/**
* Delete item(s) from database and clear from internal array
*
* If _eraseChildren_ is true, erase child items as well
**/
function erase(ids, eraseChildren) {
ids = Zotero.flattenArguments(ids);
Zotero.UnresponsiveScriptIndicator.disable();
try {
Zotero.DB.beginTransaction();
for each(var id in ids) {
var item = this.get(id);
if (!item) {
Zotero.debug('Item ' + id + ' does not exist in Items.erase()!', 1);
Zotero.Notifier.trigger('delete', 'item', id);
continue;
}
item.erase(eraseChildren); // calls unload()
item = undefined;
}
this.purge();
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.DB.rollbackTransaction();
throw (e);
}
finally {
Zotero.UnresponsiveScriptIndicator.enable();
}
}
/*
* Clear entries from various tables that no longer exist
*
* This is called automatically by Items.erase() but must be called
* manually after Item.erase()
*/
function purge() {
Zotero.Creators.purge();
Zotero.Tags.purge();
Zotero.Fulltext.purgeUnusedWords();
// Purge unused values
var sql = "DELETE FROM itemDataValues WHERE valueID NOT IN "
+ "(SELECT valueID FROM itemData)";
Zotero.DB.query(sql);
}
/**
* Clear item from internal array (used by Zotero.Item.erase())
**/
function unload(id) {
delete _items[id];
}
/*
* Generate SQL to retrieve firstCreator field
*
* Why do we do this entirely in SQL? Because we're crazy. Crazy like foxes.
*/
function getFirstCreatorSQL() {
if (_firstCreatorSQL) {
return _firstCreatorSQL;
}
/* This whole block is to get the firstCreator */
var localizedAnd = Zotero.getString('general.and');
var sql = "COALESCE(" +
// First try for primary creator types
"CASE (" +
"SELECT COUNT(*) FROM itemCreators IC " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
"ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
"WHERE itemID=I.itemID AND primaryField=1" +
") " +
"WHEN 0 THEN NULL " +
"WHEN 1 THEN (" +
"SELECT lastName FROM itemCreators IC NATURAL JOIN creators " +
"NATURAL JOIN creatorData " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
"ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
"WHERE itemID=I.itemID AND primaryField=1" +
") " +
"WHEN 2 THEN (" +
"SELECT " +
"(SELECT lastName FROM itemCreators IC NATURAL JOIN creators " +
"NATURAL JOIN creatorData " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
"ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
"WHERE itemID=I.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" +
" || ' " + localizedAnd + " ' || " +
"(SELECT lastName FROM itemCreators IC NATURAL JOIN creators " +
"NATURAL JOIN creatorData " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
"ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
"WHERE itemID=I.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1,1)" +
") " +
"ELSE (" +
"SELECT " +
"(SELECT lastName FROM itemCreators IC NATURAL JOIN creators " +
"NATURAL JOIN creatorData " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
"ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
"WHERE itemID=I.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" +
" || ' et al.' " +
") " +
"END, " +
// Then try editors
"CASE (" +
"SELECT COUNT(*) FROM itemCreators " +
"NATURAL JOIN creatorTypes WHERE itemID=I.itemID AND creatorTypeID IN (3)" +
") " +
"WHEN 0 THEN NULL " +
"WHEN 1 THEN (" +
"SELECT lastName FROM itemCreators NATURAL JOIN creators " +
"NATURAL JOIN creatorData " +
"WHERE itemID=I.itemID AND creatorTypeID IN (3)" +
") " +
"WHEN 2 THEN (" +
"SELECT " +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
"WHERE itemID=I.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1)" +
" || ' " + localizedAnd + " ' || " +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
"WHERE itemID=I.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1,1) " +
") " +
"ELSE (" +
"SELECT " +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
"WHERE itemID=I.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1)" +
" || ' et al.' " +
") " +
"END, " +
// Then try contributors
"CASE (" +
"SELECT COUNT(*) FROM itemCreators " +
"NATURAL JOIN creatorTypes WHERE itemID=I.itemID AND creatorTypeID IN (2)" +
") " +
"WHEN 0 THEN NULL " +
"WHEN 1 THEN (" +
"SELECT lastName FROM itemCreators NATURAL JOIN creators " +
"NATURAL JOIN creatorData " +
"WHERE itemID=I.itemID AND creatorTypeID IN (2)" +
") " +
"WHEN 2 THEN (" +
"SELECT " +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
"WHERE itemID=I.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1)" +
" || ' " + localizedAnd + " ' || " +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
"WHERE itemID=I.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1,1) " +
") " +
"ELSE (" +
"SELECT " +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
"WHERE itemID=I.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1)" +
" || ' et al.' " +
") " +
"END" +
") AS firstCreator";
_firstCreatorSQL = sql;
return sql;
}
function getSortTitle(title) {
if (!title) {
return '';
}
if (typeof title == 'number') {
return title + '';
}
return title.replace(/^[\[\'\"](.*)[\'\"\]]?$/, '$1')
}
function _load() {
if (!arguments[0] && _itemsLoaded) {
return;
}
// Should be the same as parts in Zotero.Item.loadPrimaryData
var sql = 'SELECT I.*, '
+ getFirstCreatorSQL() + ', '
+ "(SELECT COUNT(*) FROM itemNotes WHERE sourceItemID=I.itemID) AS numNotes, "
+ "(SELECT COUNT(*) FROM itemAttachments WHERE sourceItemID=I.itemID) AS numAttachments "
+ 'FROM items I WHERE 1';
if (arguments[0]) {
sql += ' AND I.itemID IN (' + Zotero.join(arguments,',') + ')';
}
var itemsRows = Zotero.DB.query(sql);
var itemIDs = [];
for each(var row in itemsRows) {
var itemID = row.itemID;
itemIDs.push(itemID);
// Item doesn't exist -- create new object and stuff in array
if (!_items[row.itemID]) {
var item = new Zotero.Item();
item.loadFromRow(row, true);
_items[row.itemID] = item;
}
// Existing item -- reload in place
else {
_items[row.itemID].loadFromRow(row, true);
}
}
// If loading all items, remove old items that no longer exist
if (!arguments[0]) {
for each(var c in _items) {
if (itemIDs.indexOf(c.id) == -1) {
this.unload(c.id);
}
}
}
if (!arguments[0]) {
_itemsLoaded = true;
_cachedFields = ['itemID', 'itemTypeID', 'dateAdded', 'dateModified',
'firstCreator', 'numNotes', 'numAttachments', 'numChildren'];
}
}
}

View file

@ -0,0 +1,43 @@
/*
***** 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.Notes = new function() {
this.noteToTitle = noteToTitle;
this.__defineGetter__("MAX_TITLE_LENGTH", function() { return 80; });
/**
* Return first line (or first MAX_LENGTH characters) of note content
**/
function noteToTitle(text) {
var max = this.MAX_TITLE_LENGTH;
var t = text.substring(0, max);
var ln = t.indexOf("\n");
if (ln>-1 && ln<max) {
t = t.substring(0, ln);
}
return t;
}
}

View file

@ -0,0 +1,418 @@
/*
***** 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 *****
*/
/*
* Same structure as Zotero.Creators -- make changes in both places if possible
*/
Zotero.Tags = new function() {
var _tags = []; // indexed by tag text
var _tagsByID = []; // indexed by tagID
this.get = get;
this.getName = getName;
this.getID = getID;
this.getIDs = getIDs;
this.getTypes = getTypes;
this.getAll = getAll;
this.getAllWithinSearch = getAllWithinSearch;
this.getTagItems = getTagItems;
this.search = search;
this.add = add;
this.rename = rename;
this.remove = remove;
this.purge = purge;
this.toArray = toArray;
/*
* Returns a tag and type for a given tagID
*/
function get(tagID) {
if (_tagsByID[tagID]) {
return _tagsByID[tagID];
}
var sql = 'SELECT tag, tagType FROM tags WHERE tagID=?';
var result = Zotero.DB.rowQuery(sql, tagID);
if (!result) {
return false;
}
_tagsByID[tagID] = {
tag: result.tag,
type: result.tagType
};
return result;
}
/*
* Returns a tag for a given tagID
*/
function getName(tagID) {
if (_tagsByID[tagID]) {
return _tagsByID[tagID].tag;
}
var tag = this.get(tagID);
return _tagsByID[tagID] ? _tagsByID[tagID].tag : false;
}
/*
* Returns the tagID matching given tag and type
*/
function getID(tag, type) {
if (_tags[type] && _tags[type]['_' + tag]) {
return _tags[type]['_' + tag];
}
var sql = 'SELECT tagID FROM tags WHERE tag=? AND tagType=?';
var tagID = Zotero.DB.valueQuery(sql, [tag, type]);
if (tagID) {
if (!_tags[type]) {
_tags[type] = [];
}
_tags[type]['_' + tag] = tagID;
}
return tagID;
}
/*
* Returns all tagIDs for this tag (of all types)
*/
function getIDs(tag) {
var sql = 'SELECT tagID FROM tags WHERE tag=?';
return Zotero.DB.columnQuery(sql, [tag]);
}
/*
* Returns an array of tagTypes for tags matching given tag
*/
function getTypes(tag) {
var sql = 'SELECT tagType FROM tags WHERE tag=?';
return Zotero.DB.columnQuery(sql, [tag]);
}
/**
* Get all tags indexed by tagID
*
* _types_ is an optional array of tagTypes to fetch
*/
function getAll(types) {
var sql = "SELECT tagID, tag, tagType FROM tags ";
if (types) {
sql += "WHERE tagType IN (" + types.join() + ") ";
}
var tags = Zotero.DB.query(sql);
if (!tags) {
return {};
}
var collation = Zotero.getLocaleCollation();
tags.sort(function(a, b) {
return collation.compareString(1, a.tag, b.tag);
});
var indexed = {};
for (var i=0; i<tags.length; i++) {
indexed[tags[i].tagID] = {
tag: tags[i].tag,
type: tags[i].tagType
};
}
return indexed;
}
/*
* Get all tags within the items of a Zotero.Search object
*
* _types_ is an optional array of tagTypes to fetch
*/
function getAllWithinSearch(search, types) {
// Save search results to temporary table
try {
var tmpTable = search.search(true);
}
catch (e) {
if (e.match(/Saved search [0-9]+ does not exist/)) {
Zotero.DB.rollbackTransaction();
Zotero.debug(e, 2);
}
else {
throw (e);
}
}
if (!tmpTable) {
return {};
}
var sql = "SELECT DISTINCT tagID, tag, tagType FROM itemTags "
+ "NATURAL JOIN tags WHERE itemID IN "
+ "(SELECT itemID FROM " + tmpTable + ") ";
if (types) {
sql += "AND tagType IN (" + types.join() + ") ";
}
var tags = Zotero.DB.query(sql);
Zotero.DB.query("DROP TABLE " + tmpTable);
if (!tags) {
return {};
}
var collation = Zotero.getLocaleCollation();
tags.sort(function(a, b) {
return collation.compareString(1, a.tag, b.tag);
});
var indexed = {};
for (var i=0; i<tags.length; i++) {
indexed[tags[i].tagID] = {
tag: tags[i].tag,
type: tags[i].tagType
};
}
return indexed;
}
function getTagItems(tagID) {
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
return Zotero.DB.columnQuery(sql, tagID);
}
function search(str) {
var sql = 'SELECT tagID, tag, tagType FROM tags';
if (str) {
sql += ' WHERE tag LIKE ?';
}
sql += ' ORDER BY tag COLLATE NOCASE';
var tags = Zotero.DB.query(sql, str ? '%' + str + '%' : undefined);
var indexed = {};
for each(var tag in tags) {
indexed[tag.tagID] = {
tag: tag.tag,
type: tag.tagType
};
}
return indexed;
}
/*
* Add a new tag to the database
*
* Returns new tagID
*/
function add(tag, type) {
if (type != 0 && type != 1) {
throw ('Invalid tag type ' + type + ' in Tags.add()');
}
if (!type) {
type = 0;
}
Zotero.debug('Adding new tag of type ' + type, 4);
Zotero.DB.beginTransaction();
var sql = 'INSERT INTO tags VALUES (?,?,?)';
var rnd = Zotero.ID.get('tags');
Zotero.DB.query(sql, [{int: rnd}, {string: tag}, {int: type}]);
Zotero.DB.commitTransaction();
Zotero.Notifier.trigger('add', 'tag', rnd);
return rnd;
}
function rename(tagID, tag) {
Zotero.debug('Renaming tag', 4);
Zotero.DB.beginTransaction();
var tagObj = this.get(tagID);
var oldName = tagObj.tag;
var oldType = tagObj.type;
var notifierData = {};
notifierData[this.id] = { old: this.toArray() };
if (oldName == tag) {
// Convert unchanged automatic tags to manual
if (oldType != 0) {
var sql = "UPDATE tags SET tagType=0 WHERE tagID=?";
Zotero.DB.query(sql, tagID);
Zotero.Notifier.trigger('modify', 'tag', tagID, notifierData);
}
Zotero.DB.commitTransaction();
return;
}
// Check if the new tag already exists
var sql = "SELECT tagID FROM tags WHERE tag=? AND tagType=0";
var existingTagID = Zotero.DB.valueQuery(sql, tag);
if (existingTagID) {
var itemIDs = this.getTagItems(tagID);
var existingItemIDs = this.getTagItems(existingTagID);
// Would be easier to just call removeTag(tagID) and addTag(existingID)
// here, but this is considerably more efficient
var sql = "UPDATE OR REPLACE itemTags SET tagID=? WHERE tagID=?";
Zotero.DB.query(sql, [existingTagID, tagID]);
// Manual purge of old tag
var sql = "DELETE FROM tags WHERE tagID=?";
Zotero.DB.query(sql, tagID);
if (_tags[oldType]) {
delete _tags[oldType]['_' + oldName];
}
delete _tagsByID[tagID];
Zotero.Notifier.trigger('delete', 'tag', tagID, notifierData);
// Simulate tag removal on items that used old tag
var itemTags = [];
for (var i in itemIDs) {
itemTags.push(itemIDs[i] + '-' + tagID);
}
Zotero.Notifier.trigger('remove', 'item-tag', itemTags);
// And send tag add for new tag (except for those that already had it)
var itemTags = [];
for (var i in itemIDs) {
if (existingItemIDs.indexOf(itemIDs[i]) == -1) {
itemTags.push(itemIDs[i] + '-' + existingTagID);
}
}
Zotero.Notifier.trigger('add', 'item-tag', itemTags);
Zotero.Notifier.trigger('modify', 'item', itemIDs);
Zotero.DB.commitTransaction();
return;
}
// 0 == user tag -- we set all renamed tags to 0
var sql = "UPDATE tags SET tag=?, tagType=0 WHERE tagID=?";
Zotero.DB.query(sql, [{string: tag}, tagID]);
var itemIDs = this.getTagItems(tagID);
if (_tags[oldType]) {
delete _tags[oldType]['_' + oldName];
}
delete _tagsByID[tagID];
Zotero.DB.commitTransaction();
Zotero.Notifier.trigger('modify', 'item', itemIDs);
Zotero.Notifier.trigger('modify', 'tag', tagID, notifierData);
}
function remove(tagID) {
Zotero.DB.beginTransaction();
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
var itemIDs = Zotero.DB.columnQuery(sql, tagID);
if (!itemIDs) {
Zotero.DB.commitTransaction();
return;
}
var sql = "DELETE FROM itemTags WHERE tagID=?";
Zotero.DB.query(sql, tagID);
Zotero.Notifier.trigger('modify', 'item', itemIDs)
var itemTags = [];
for (var i in itemIDs) {
itemTags.push(itemIDs[i] + '-' + tagID);
}
Zotero.Notifier.trigger('remove', 'item-tag', itemTags);
this.purge();
Zotero.DB.commitTransaction();
return;
}
/*
* Delete obsolete tags from database and clear internal array entries
*
* Returns removed tagIDs on success
*/
function purge() {
Zotero.DB.beginTransaction();
var sql = 'SELECT tagID, tag, tagType FROM tags WHERE tagID '
+ 'NOT IN (SELECT tagID FROM itemTags);';
var toDelete = Zotero.DB.query(sql);
if (!toDelete) {
Zotero.DB.commitTransaction();
return false;
}
var purged = [];
var notifierData = {};
// Clear tag entries in internal array
for each(var tag in toDelete) {
notifierData[tag.tagID] = { old: Zotero.Tags.toArray(tag.tagID) }
purged.push(tag.tagID);
if (_tags[tag.tagType]) {
delete _tags[tag.tagType]['_' + tag.tag];
}
delete _tagsByID[tag.tagID];
}
sql = 'DELETE FROM tags WHERE tagID NOT IN '
+ '(SELECT tagID FROM itemTags);';
var result = Zotero.DB.query(sql);
Zotero.DB.commitTransaction();
Zotero.Notifier.trigger('delete', 'tag', purged, notifierData);
return toDelete;
}
function toArray(tagID) {
var obj = this.get(tagID);
obj.id = tagID;
return obj;
}
}

File diff suppressed because it is too large Load diff

View file

@ -26,11 +26,33 @@ Zotero.DBConnection = function(dbName) {
}
this.skipBackup = false;
this.transactionVacuum = false;
// JS Date
this.__defineGetter__('transactionDate', function () {
if (this._transactionDate) {
return this._transactionDate;
}
// Use second granularity rather than millisecond
// for comparison purposes
return new Date(Math.floor(new Date / 1000) * 1000);
});
// SQL DATETIME
this.__defineGetter__('transactionDateTime', function () {
var d = this.transactionDate;
return Zotero.Date.dateToSQL(d, true);
});
// Unix timestamp
this.__defineGetter__('transactionTimestamp', function () {
var d = this.transactionDate;
return Zotero.Date.toUnixTimestamp(d);
});
// Private members
this._dbName = dbName;
this._shutdown = false;
this._connection = null;
this._transactionDate = null;
this._transactionRollback = null;
this._transactionNestingLevel = 0;
this._callbacks = { begin: [], commit: [], rollback: [] };
@ -76,7 +98,7 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
}
// If SELECT statement, return result
if (op=='select') {
if (op == 'select') {
// Until the native dataset methods work (or at least exist),
// we build a multi-dimensional associative array manually
@ -105,7 +127,7 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
db.executeSimpleSQL(sql);
}
if (op=='insert') {
if (op == 'insert' || op == 'replace') {
return db.lastInsertRowID;
}
// DEBUG: Can't get affected rows for UPDATE or DELETE?
@ -201,6 +223,12 @@ Zotero.DBConnection.prototype.getStatement = function (sql, params) {
params = [params];
}
var matches = sql.match(/\?([^0-9]|$)/g);
if (matches && matches.length != params.length) {
throw ('Incorrect number of parameters in query ('
+ params.length + ', expecting ' + matches.length + ')');
}
for (var i=0; i<params.length; i++) {
// Integer
if (params[i]!==null && typeof params[i]['int'] != 'undefined') {
@ -241,9 +269,28 @@ Zotero.DBConnection.prototype.getStatement = function (sql, params) {
// Bind the parameter as the correct type
switch (type) {
case 'int':
this._debug('Binding parameter ' + (i+1)
+ ' of type int: ' + value, 5);
statement.bindInt32Parameter(i, value);
var intVal = parseInt(value);
if (isNaN(intVal)) {
throw ("Invalid integer value '" + value + "'")
}
// Store as 32-bit signed integer
if (intVal <= 2147483647) {
this._debug('Binding parameter ' + (i+1)
+ ' of type int: ' + value, 5);
statement.bindInt32Parameter(i, intVal);
}
// Store as 64-bit signed integer
// 2^53 is JS's upper-bound for decimal integers
else if (intVal < 9007199254740992) {
this._debug('Binding parameter ' + (i+1)
+ ' of type int64: ' + value, 5);
statement.bindInt64Parameter(i, intVal);
}
else {
throw ("Integer value '" + intVal + "' too large");
}
break;
case 'string':
@ -294,6 +341,9 @@ Zotero.DBConnection.prototype.beginTransaction = function () {
this._debug('Beginning DB transaction', 5);
db.beginTransaction();
// Set a timestamp for this transaction
this._transactionDate = new Date(Math.floor(new Date / 1000) * 1000);
// Run callbacks
for (var i=0; i<this._callbacks.begin.length; i++) {
if (this._callbacks.begin[i]) {
@ -317,9 +367,19 @@ Zotero.DBConnection.prototype.commitTransaction = function () {
}
else {
this._debug('Committing transaction',5);
// Clear transaction timestamp
this._transactionDate = null;
try {
db.commitTransaction();
if (this.transactionVacuum) {
Zotero.debug('Vacuuming database');
db.executeSimpleSQL('VACUUM');
this.transactionVacuum = false;
}
// Run callbacks
for (var i=0; i<this._callbacks.commit.length; i++) {
if (this._callbacks.commit[i]) {
@ -882,7 +942,10 @@ Zotero.DBConnection.prototype._getDBConnection = function () {
throw (e);
}
// Register shutdown handler to call this.onShutdown() for DB backup
// Get exclusive lock on DB
Zotero.DB.query("PRAGMA locking_mode=EXCLUSIVE");
// Register shutdown handler to call this.observe() for DB backup
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "xpcom-shutdown", false);
@ -921,7 +984,5 @@ Zotero.DBConnection.prototype._getTypedValue = function (statement, i) {
}
// Initialize main database connection
Zotero.DB = new Zotero.DBConnection('zotero');

View file

@ -113,7 +113,7 @@ Zotero.File = new function(){
is.close();
return contents.join();
return contents.join('');
}

View file

@ -194,25 +194,39 @@ Zotero.Fulltext = new function(){
return false;
}
var sqlQues = [];
var sqlParams = [];
for each(var word in words){
sqlQues.push('?');
sqlParams.push({string:word});
}
var existing = [];
var done = 0;
var maxWords = 500;
var numWords = words.length;
Zotero.DB.beginTransaction();
var sql = "SELECT word, wordID from fulltextWords WHERE word IN ("
sql += sqlQues.join() + ")";
var wordIDs = Zotero.DB.query(sql, sqlParams);
var origWords = [];
var existing = [];
for (var i in wordIDs){
// Underscore avoids problems with JS reserved words
existing['_' + wordIDs[i]['word']] = wordIDs[i]['wordID'];
do {
var chunk = words.splice(0, maxWords);
origWords = origWords.concat(chunk);
var sqlQues = [];
var sqlParams = [];
for each(var word in chunk) {
sqlQues.push('?');
sqlParams.push( { string: word } );
}
var sql = "SELECT word, wordID from fulltextWords WHERE word IN ("
sql += sqlQues.join() + ")";
var wordIDs = Zotero.DB.query(sql, sqlParams);
for (var i in wordIDs) {
// Underscore avoids problems with JS reserved words
existing['_' + wordIDs[i].word] = wordIDs[i].wordID;
}
done += chunk.length;
}
while (done < numWords);
Zotero.DB.query("REPLACE INTO fulltextItems (itemID, version) VALUES (?,?)",
[itemID, FULLTEXT_VERSION]);
@ -221,7 +235,7 @@ Zotero.Fulltext = new function(){
var statement1 = Zotero.DB.getStatement("INSERT INTO fulltextWords (word) VALUES (?)");
var statement2 = Zotero.DB.getStatement("INSERT OR IGNORE INTO fulltextItemWords VALUES (?,?)");
for each(var word in words){
for each(var word in origWords) {
if (existing['_' + word]){
var wordID = existing['_' + word];
}

View file

@ -0,0 +1,286 @@
/*
***** 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.ID = new function () {
this.get = get;
this.getKey = getKey;
this.getBigInt = getBigInt;
_available = {};
/*
* Gets an unused primary key id for a DB table
*/
function get(table, notNull, skip) {
switch (table) {
// Autoincrement tables
//
// Callers need to handle a potential NULL for these unless they
// pass |notNull|
case 'items':
case 'creators':
case 'creatorData':
case 'collections':
case 'savedSearches':
var id = _getNextAvailable(table, skip);
if (!id && notNull) {
return _getNext(table, skip);
}
return id;
// Non-autoincrement tables
//
// TODO: use autoincrement instead where available in 1.5
case 'itemDataValues':
case 'tags':
var id = _getNextAvailable(table, skip);
if (!id) {
// If we can't find an empty id quickly, just use MAX() + 1
return _getNext(table, skip);
}
return id;
default:
throw ("Unsupported table '" + table + "' in Zotero.ID.get()");
}
}
function getKey() {
var baseString = "23456789ABCDEFGHIJKMNPQRSTUVWXTZ";
return Zotero.randomString(8, baseString);
}
function getBigInt() {
return Math.floor(Math.random() * (9007199254740991)) + 1;
}
/*
* Returns the lowest available unused primary key id for table
*/
function _getNextAvailable(table, skip) {
if (!_available[table]) {
_loadAvailable(table);
}
var arr = _available[table];
for (var i in arr) {
var id = arr[i][0];
if (skip && skip.indexOf(id) != -1) {
continue;
}
// End of range -- remove range
if (id == arr[i][1]) {
arr.shift();
}
// Within range -- increment
else {
arr[i][0]++;
}
// Prepare table for refresh if all rows used
if (arr.length == 0) {
delete _available[table];
}
return id;
}
return null;
}
/*
* Get MAX(id) + 1 from table
*/
function _getNext(table, skip) {
var column = _getTableColumn(table);
var sql = 'SELECT MAX(';
if (skip && skip.length) {
var max = Math.max.apply(this, skip);
sql += 'MAX(' + column + ', ' + max + ')';
}
else {
sql += column;
}
sql += ')+1 FROM ' + table;
return Zotero.DB.valueQuery(sql);
}
/*
* Loads available ids for table into memory
*/
function _loadAvailable(table) {
Zotero.debug("Loading available ids for table '" + table + "'");
var numIDs = 3; // Number of ids to compare against at a time
var maxTries = 3; // Number of times to try increasing the maxID
var maxToFind = 1000;
var column = _getTableColumn(table);
switch (table) {
case 'creators':
case 'creatorData':
case 'items':
case 'itemDataValues':
case 'tags':
break;
case 'collections':
case 'savedSearches':
var maxToFind = 100;
break;
default:
throw ("Unsupported table '" + table + "' in Zotero.ID._loadAvailable()");
}
var maxID = numIDs;
var sql = "SELECT " + column + " FROM " + table
+ " WHERE " + column + "<=? ORDER BY " + column;
var ids = Zotero.DB.columnQuery(sql, maxID);
// If no ids found, we have maxID unused ids
if (!ids) {
Zotero.debug('none found');
var found = Math.min(maxID, maxToFind);
Zotero.debug("Found " + found + " available ids in table '" + table + "'");
_available[table] = [[1, found]];
return;
}
// If we didn't find any unused ids, try increasing maxID a few times
while (ids.length == maxID && maxTries>0) {
Zotero.debug('nope');
maxID = maxID + numIDs;
ids = Zotero.DB.columnQuery(sql, maxID);
maxTries--;
}
// Didn't find any unused ids
if (ids.length == maxID) {
Zotero.debug('none!');
Zotero.debug("Found 0 available ids in table '" + table + "'");
_available[table] = [];
return;
}
var available = [], found = 0, j=0, availableStart = null;
for (var i=1; i<=maxID && found<maxToFind; i++) {
// We've gone past the found ids, so all remaining ids up to maxID
// are available
if (!ids[j]) {
Zotero.debug('all remaining are available');
available.push([i, maxID]);
found += (maxID - i) + 1;
break;
}
// Skip ahead while ids are occupied
if (ids[j] == i) {
Zotero.debug('skipping');
j++;
continue;
}
// Advance counter while it's below the next used id
while (ids[j] > i && i<=maxID) {
Zotero.debug('b');
if (!availableStart) {
availableStart = i;
}
i++;
if ((found + (i - availableStart) + 1) > maxToFind) {
break;
}
}
if (availableStart) {
available.push([availableStart, i-1]);
// Keep track of how many empties we've found
found += ((i-1) - availableStart) + 1;
availableStart = null;
}
j++;
}
Zotero.debug("Found " + found + " available ids in table '" + table + "'");
_available[table] = available;
Zotero.debug(available);
}
/**
* Find a unique random id for use in a DB table
*
* (No longer used)
**/
function _getRandomID(table, max){
var column = _getTableColumn(table);
var sql = 'SELECT COUNT(*) FROM ' + table + ' WHERE ' + column + '= ?';
if (!max){
max = 16383;
}
max--; // since we use ceil(), decrement max by 1
var tries = 3; // # of tries to find a unique id
for (var i=0; i<tries; i++) {
var rnd = Math.ceil(Math.random() * max);
var exists = Zotero.DB.valueQuery(sql, { int: rnd });
if (!exists) {
return rnd;
}
}
// If no luck after number of tries, try a larger range
var sql = 'SELECT MAX(' + column + ') + 1 FROM ' + table;
return Zotero.valueQuery(sql);
}
function _getTableColumn(table) {
switch (table) {
case 'itemDataValues':
return 'valueID';
case 'savedSearches':
return 'savedSearchID';
case 'creatorData':
return 'creatorDataID';
default:
return table.substr(0, table.length - 1) + 'ID';
}
}
}

View file

@ -114,12 +114,12 @@ Zotero.ItemTreeView.prototype.setTree = function(treebox)
var key = String.fromCharCode(event.which);
if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
expandAllRows(treebox);
obj.expandAllRows(treebox);
return;
}
else if (key == '-' && !(event.shiftKey || event.ctrlKey ||
event.altKey || event.metaKey)) {
collapseAllRows(treebox);
obj.collapseAllRows(treebox);
return;
}
}, false);
@ -152,6 +152,7 @@ Zotero.ItemTreeView.prototype.setTree = function(treebox)
*/
Zotero.ItemTreeView.prototype.refresh = function()
{
Zotero.debug('Refreshing items list');
this._searchMode = this._itemGroup.isSearchMode();
var oldRows = this.rowCount;
@ -160,6 +161,7 @@ Zotero.ItemTreeView.prototype.refresh = function()
this._searchParentIDs = {};
this.rowCount = 0;
var cacheFields = ['title', 'date'];
// Cache the visible fields so they don't load individually
try {
var visibleFields = this.getVisibleFields();
@ -168,6 +170,7 @@ Zotero.ItemTreeView.prototype.refresh = function()
catch (e) {
return;
}
for (var i=0; i<visibleFields.length; i++) {
var field = visibleFields[i];
if (field == 'year') {
@ -201,7 +204,7 @@ Zotero.ItemTreeView.prototype.refresh = function()
this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], 0, false), added + 1); //item ref, before row
added++;
}
this._searchItemIDs[newRows[i].getID()] = true;
this._searchItemIDs[newRows[i].id] = true;
}
// Add parents of matches if not matches themselves
@ -218,7 +221,8 @@ Zotero.ItemTreeView.prototype.refresh = function()
this._refreshHashMap();
// Update the treebox's row count
var diff = this.rowCount - oldRows;
// this.rowCount isn't always up-to-date, so use the view's count
var diff = this._treebox.view.rowCount - oldRows;
if (diff != 0) {
this._treebox.rowCountChanged(0, diff);
}
@ -235,6 +239,11 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
return;
}
if (!this._itemRowMap) {
Zotero.debug("Item row map didn't exist in itemTreeView.notify()");
return;
}
var madeChanges = false;
var sort = false;
@ -267,7 +276,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
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.getID()) {
if (!this._itemGroup.isCollection() || split[0] != this._itemGroup.ref.id) {
continue;
}
splitIDs.push(split[1]);
@ -281,15 +290,23 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
}
}
if((action == 'remove' && !this._itemGroup.isLibrary()) || action == 'delete')
{
//Since a remove involves shifting of rows, we have to do it in order
if ((action == 'remove' && !this._itemGroup.isLibrary())
|| action == 'delete' || action == 'id-change') {
//sort the ids by row
var rows = new Array();
// 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];
}
}
// 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' || !this._itemGroup.ref.hasItem(ids[i])) {
if (action == 'delete' || action == 'id-change' ||
!this._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) {
@ -417,9 +434,9 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
for (var i in items)
{
// if the item belongs in this collection
if((this._itemGroup.isLibrary() || items[i].inCollection(this._itemGroup.ref.getID()))
if((this._itemGroup.isLibrary() || items[i].inCollection(this._itemGroup.ref.id))
// if we haven't already added it to our hash map
&& this._itemRowMap[items[i].getID()] == null
&& this._itemRowMap[items[i].id] == null
// Regular item or standalone note/attachment
&& (items[i].isRegularItem() || !items[i].getSource()))
{
@ -489,13 +506,28 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
}
else
{
var previousRow = this._itemRowMap[ids[0]];
if (sort) {
this.sort(typeof sort == 'number' ? sort : false);
}
else {
this._refreshHashMap();
}
this.rememberSelection(savedSelection);
// On delete, select item at previous position
if (action == 'delete') {
if (this._dataItems[previousRow]) {
this.selection.select(previousRow);
}
// If no item at previous position, select last item in list
else if (this._dataItems[this._dataItems.length - 1]) {
this.selection.select(this._dataItems.length - 1);
}
}
else {
this.rememberSelection(savedSelection);
}
}
this._treebox.invalidate();
@ -528,17 +560,20 @@ Zotero.ItemTreeView.prototype.unregister = function()
Zotero.ItemTreeView.prototype.getCellText = function(row, column)
{
var obj = this._getItemAtRow(row);
var val;
if(column.id == "zotero-items-column-numChildren")
{
var c = obj.numChildren();
if(c) //don't display '0'
// Don't display '0'
if(c && parseInt(c) > 0) {
val = c;
}
}
else if(column.id == "zotero-items-column-type")
{
val = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(obj.getType()));
val = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(obj.ref.itemTypeID));
}
// Year column is just date field truncated
else if (column.id == "zotero-items-column-year") {
@ -595,7 +630,7 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
Zotero.ItemTreeView.prototype.isContainer = function(row)
{
return this._getItemAtRow(row).isRegularItem();
return this._getItemAtRow(row).ref.isRegularItem();
}
Zotero.ItemTreeView.prototype.isContainerOpen = function(row)
@ -643,7 +678,7 @@ Zotero.ItemTreeView.prototype.hasNextSibling = function(row,afterIndex)
}
}
Zotero.ItemTreeView.prototype.toggleOpenState = function(row)
Zotero.ItemTreeView.prototype.toggleOpenState = function(row, skipItemMapRefresh)
{
// Shouldn't happen but does if an item is dragged over a closed
// container until it opens and then released, since the container
@ -655,16 +690,16 @@ Zotero.ItemTreeView.prototype.toggleOpenState = function(row)
var count = 0; //used to tell the tree how many rows were added/removed
var thisLevel = this.getLevel(row);
if(this.isContainerOpen(row))
{
// Close
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
{
// Open
else {
var item = this._getItemAtRow(row).ref;
//Get children
var attachments = item.getAttachments();
@ -683,19 +718,35 @@ Zotero.ItemTreeView.prototype.toggleOpenState = function(row)
for(var i = 0; i < newRows.length; i++)
{
// If item already exists elsewhere in the tree, we have to
// remove it -- this can happen when moving an item into a
// collection if the collection gets the modify event before
// the item
var existingRow = this._itemRowMap[newRows[i].id];
if (existingRow != null) {
/*
this._hideItem(existingRow);
this._treebox.rowCountChanged(existingRow + 1, -1);
if (existingRow < row) {
row--;
}
*/
throw ("Item already exists outside of collection in Zotero.ItemTreeView.toggleOpenRow()");
}
count++;
this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], thisLevel + 1, false), row + i + 1); // item ref, before row
}
}
}
this._treebox.beginUpdateBatch();
this._dataItems[row].isOpen = !this._dataItems[row].isOpen;
this._treebox.rowCountChanged(row+1, count); //tell treebox to repaint these
this._treebox.invalidateRow(row);
this._treebox.endUpdateBatch();
this._refreshHashMap();
if (!skipItemMapRefresh) {
Zotero.debug('Refreshing hash map');
this._refreshHashMap();
}
}
@ -786,7 +837,7 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
// Get the display field for a row (which might be a placeholder title)
function getField(row) {
var field;
var type = row.getType();
var type = row.ref.itemTypeID;
if (columnField == 'title') {
if (type == 8 || type == 10) { // 'letter' and 'interview' itemTypeIDs
field = row.ref.getDisplayTitle();
@ -817,8 +868,8 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
switch (columnField) {
case 'type':
var typeA = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(a.getType()));
var typeB = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(b.getType()));
var typeA = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(a.ref.itemTypeID));
var typeB = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(b.ref.itemTypeID));
cmp = (typeA > typeB) ? -1 : (typeA < typeB) ? 1 : 0;
if (cmp) {
@ -914,10 +965,11 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
for (var i=0; i<this._dataItems.length; i++) {
if(this.isContainer(i) && this.isContainerOpen(i))
{
openRows.push(this._getItemAtRow(i).ref.getID());
this.toggleOpenState(i);
openRows.push(this._getItemAtRow(i).ref.id);
this.toggleOpenState(i, true);
}
}
this._refreshHashMap();
// Single-row sort
if (itemID) {
@ -962,12 +1014,12 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
}
}
this._refreshHashMap();
// Reopen closed containers
for (var i = 0; i < openRows.length; i++) {
this.toggleOpenState(this._itemRowMap[openRows[i]]);
this.toggleOpenState(this._itemRowMap[openRows[i]], true);
}
this._refreshHashMap();
}
////////////////////////////////////////////////////////////////////////////////
@ -1070,7 +1122,7 @@ Zotero.ItemTreeView.prototype.getSelectedItems = function(asIDs)
this.selection.getRangeAt(i,start,end);
for (var j=start.value; j<=end.value; j++) {
if (asIDs) {
items.push(this._getItemAtRow(j).ref.getID());
items.push(this._getItemAtRow(j).ref.id);
}
else {
items.push(this._getItemAtRow(j).ref);
@ -1088,16 +1140,21 @@ Zotero.ItemTreeView.prototype.getSelectedItems = function(asIDs)
*/
Zotero.ItemTreeView.prototype.deleteSelection = function(eraseChildren, force)
{
if(this.selection.count == 0)
if (this.selection.count == 0) {
return;
}
//collapse open items
for(var i=0; i<this.rowCount; i++)
if(this.selection.isSelected(i) && this.isContainer(i) && this.isContainerOpen(i))
this.toggleOpenState(i);
this._treebox.beginUpdateBatch();
// Collapse open items
for (var i=0; i<this.rowCount; i++) {
if (this.selection.isSelected(i) && this.isContainer(i) && this.isContainerOpen(i)) {
this.toggleOpenState(i, true);
}
}
this._refreshHashMap();
//create an array of selected items
// Create an array of selected items
var ids = [];
var start = {};
var end = {};
@ -1105,12 +1162,9 @@ Zotero.ItemTreeView.prototype.deleteSelection = function(eraseChildren, force)
{
this.selection.getRangeAt(i,start,end);
for (var j=start.value; j<=end.value; j++)
ids.push(this._getItemAtRow(j).ref.getID());
ids.push(this._getItemAtRow(j).ref.id);
}
//iterate and erase...
this._treebox.beginUpdateBatch();
// Erase item(s) from DB
if (this._itemGroup.isLibrary() || force) {
Zotero.Items.erase(ids, eraseChildren);
@ -1197,12 +1251,12 @@ Zotero.ItemTreeView.prototype._getItemAtRow = function(row)
*/
Zotero.ItemTreeView.prototype._refreshHashMap = function()
{
this._itemRowMap = new Array();
for(var i=0; i < this.rowCount; i++)
{
var rowMap = {};
for (var i=0, len=this.rowCount; i<len; i++) {
var row = this._getItemAtRow(i);
this._itemRowMap[row.ref.getID()] = i;
rowMap[row.ref.id] = i;
}
this._itemRowMap = rowMap;
}
/*
@ -1223,7 +1277,7 @@ Zotero.ItemTreeView.prototype.saveSelection = function()
if (!item) {
continue;
}
savedSelection.push(item.ref.getID());
savedSelection.push(item.ref.id);
}
}
return savedSelection;
@ -1269,7 +1323,7 @@ Zotero.ItemTreeView.prototype.saveOpenState = function() {
var ids = [];
for (var i=0, len=this.rowCount; i<len; i++) {
if (this.isContainer(i) && this.isContainerOpen(i)) {
ids.push(this._getItemAtRow(i).ref.getID());
ids.push(this._getItemAtRow(i).ref.id);
}
}
return ids;
@ -1277,33 +1331,50 @@ Zotero.ItemTreeView.prototype.saveOpenState = function() {
Zotero.ItemTreeView.prototype.rememberOpenState = function(ids) {
var hash = {};
for each(var id in ids) {
var row = this._itemRowMap[id];
if (row == undefined || !this.isContainer(row) || this.isContainerOpen(row)) {
continue;
}
this.toggleOpenState(row);
hash[id] = true;
}
this._treebox.beginUpdateBatch();
for (var i=0; i<this.rowCount; i++) {
var id = this._getItemAtRow(i).ref.id;
if (hash[id] && this.isContainer(i) && this.isContainerOpen(i)) {
this.toggleOpenState(i, true);
}
}
this._refreshHashMap();
this._treebox.endUpdateBatch();
}
Zotero.ItemTreeView.prototype.expandMatchParents = function () {
// Expand parents of child matches
if (this._searchMode) {
var view = this._treebox.view;
for (var id in this._searchParentIDs) {
if (!view.isContainerOpen(this._itemRowMap[id])) {
view.toggleOpenState(this._itemRowMap[id]);
}
if (!this._searchMode) {
return;
}
var hash = {};
for (var id in this._searchParentIDs) {
hash[id] = true;
}
this._treebox.beginUpdateBatch();
for (var i=0; i<this.rowCount; i++) {
var id = this._getItemAtRow(i).ref.id;
if (hash[id] && this.isContainer(i) && !this.isContainerOpen(i)) {
this.toggleOpenState(i, true);
}
}
this._refreshHashMap();
this._treebox.endUpdateBatch();
}
Zotero.ItemTreeView.prototype.saveFirstRow = function() {
var row = this._treebox.getFirstVisibleRow();
if (row) {
return this._getItemAtRow(row).ref.getID();
return this._getItemAtRow(row).ref.id;
}
return false;
}
@ -1317,26 +1388,26 @@ Zotero.ItemTreeView.prototype.rememberFirstRow = function(firstRow) {
Zotero.ItemTreeView.prototype.expandAllRows = function(treebox) {
var view = treebox.view;
treebox.beginUpdateBatch();
for (var i=0; i<view.rowCount; i++) {
if (view.isContainer(i) && !view.isContainerOpen(i)) {
view.toggleOpenState(i);
this._treebox.beginUpdateBatch();
for (var i=0; i<this.rowCount; i++) {
if (this.isContainer(i) && !this.isContainerOpen(i)) {
this.toggleOpenState(i, true);
}
}
treebox.endUpdateBatch();
this._refreshHashMap();
this._treebox.endUpdateBatch();
}
Zotero.ItemTreeView.prototype.collapseAllRows = function(treebox) {
var view = treebox.view;
treebox.beginUpdateBatch();
for (var i=0; i<view.rowCount; i++) {
if (view.isContainer(i) && view.isContainerOpen(i)) {
view.toggleOpenState(i);
this._treebox.beginUpdateBatch();
for (var i=0; i<this.rowCount; i++) {
if (this.isContainer(i) && this.isContainerOpen(i)) {
this.toggleOpenState(i, true);
}
}
treebox.endUpdateBatch();
this._refreshHashMap();
this._treebox.endUpdateBatch();
}
@ -1358,7 +1429,7 @@ Zotero.ItemTreeView.prototype.getVisibleFields = function() {
Zotero.ItemTreeView.prototype.getSortedItems = function() {
var ids = [];
for each(var item in this._dataItems) {
ids.push(item.ref.getID());
ids.push(item.ref.id);
}
return ids;
}
@ -1441,7 +1512,7 @@ Zotero.ItemTreeView.prototype.onDragStart = function (evt,transferData,action)
// enable dragging to file system
for (var i=0; i<items.length; i++) {
if (items[i].isAttachment() &&
items[i].getAttachmentLinkMode() != Zotero.Attachments.LINK_MODE_LINKED_URL
items[i].attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL
&& items[i].getFile()) {
transferData.data.addDataForFlavour("application/x-moz-file-promise",
new Zotero.ItemTreeView.fileDragDataProvider(), 0, Components.interfaces.nsISupports);
@ -1542,7 +1613,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = {
for (var i=0; i<items.length; i++) {
// TODO create URL?
if (!items[i].isAttachment() ||
items[i].getAttachmentLinkMode() == Zotero.Attachments.LINK_MODE_LINKED_URL) {
items[i].attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
continue;
}
@ -1550,7 +1621,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = {
// Determine if we need to copy multiple files for this item
// (web page snapshots)
if (items[i].getAttachmentLinkMode() != Zotero.Attachments.LINK_MODE_LINKED_FILE) {
if (items[i].attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_FILE) {
var parentDir = file.parent;
var files = parentDir.directoryEntries;
var numFiles = 0;
@ -1565,7 +1636,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = {
// Create folder if multiple files
if (numFiles > 1) {
var dirName = Zotero.Attachments.getFileBaseNameFromItem(items[i].getID());
var dirName = Zotero.Attachments.getFileBaseNameFromItem(items[i].id);
try {
if (useTemp) {
var copiedFile = destDir.clone();
@ -1597,7 +1668,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = {
catch (e) {
if (e.name == 'NS_ERROR_FILE_ALREADY_EXISTS') {
// Keep track of items that already existed
existingItems.push(items[i].getID());
existingItems.push(items[i].id);
existingFileNames.push(dirName);
}
else {
@ -1637,7 +1708,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = {
}
catch (e) {
if (e.name == 'NS_ERROR_FILE_ALREADY_EXISTS') {
existingItems.push(items[i].getID());
existingItems.push(items[i].id);
existingFileNames.push(items[i].getFile().leafName);
}
else {
@ -1813,7 +1884,7 @@ Zotero.ItemTreeView.prototype.canDrop = function(row, orient)
// Only allow dragging of notes and attachments
// that aren't already children of the item
if (item.getSource() != rowItem.getID()) {
if (item.getSource() != rowItem.id) {
canDrop = true;
}
}
@ -1893,7 +1964,8 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
for each(var id in ids)
{
var item = Zotero.Items.get(id);
item.setSource(rowItem.getID());
item.setSource(rowItem.id);
item.save();
}
}
@ -1909,6 +1981,7 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
if (!item.isRegularItem())
{
item.setSource();
item.save()
}
}
}
@ -1923,6 +1996,7 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
// Top-level item
if (source) {
item.setSource();
item.save()
}
this._itemGroup.ref.addItem(id);
}
@ -1934,10 +2008,10 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
var parentCollectionID = false;
if (orient == 0) {
sourceItemID = this._getItemAtRow(row).ref.getID()
sourceItemID = this._getItemAtRow(row).ref.id
}
else if (this._itemGroup.isCollection()) {
var parentCollectionID = this._itemGroup.ref.getID();
var parentCollectionID = this._itemGroup.ref.id;
}
var unlock = Zotero.Notifier.begin(true);
@ -2019,7 +2093,7 @@ Zotero.ItemTreeView.prototype.getColumnProperties = function(col, prop) { }
/* Mark items not matching search as context rows, displayed in gray */
Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
if (this._searchMode && !this._searchItemIDs[this._getItemAtRow(row).ref.getID()]) {
if (this._searchMode && !this._searchItemIDs[this._getItemAtRow(row).ref.id]) {
var aServ = Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
prop.AppendElement(aServ.getAtom("contextRow"));
@ -2033,34 +2107,14 @@ Zotero.ItemTreeView.TreeRow = function(ref, level, isOpen)
this.isOpen = isOpen;
}
Zotero.ItemTreeView.TreeRow.prototype.isNote = function()
{
return this.ref.isNote();
}
Zotero.ItemTreeView.TreeRow.prototype.isAttachment = function()
{
return this.ref.isAttachment();
}
Zotero.ItemTreeView.TreeRow.prototype.isRegularItem = function()
{
return this.ref.isRegularItem();
}
Zotero.ItemTreeView.TreeRow.prototype.getField = function(field, unformatted)
{
return this.ref.getField(field, unformatted, true);
}
Zotero.ItemTreeView.TreeRow.prototype.getType = function()
{
return this.ref.getType();
}
Zotero.ItemTreeView.TreeRow.prototype.numChildren = function()
{
if(this.isRegularItem())
if(this.ref.isRegularItem())
return this.ref.numChildren();
else
return 0;
@ -2068,7 +2122,7 @@ Zotero.ItemTreeView.TreeRow.prototype.numChildren = function()
Zotero.ItemTreeView.TreeRow.prototype.numNotes = function()
{
if(this.isRegularItem())
if(this.ref.isRegularItem())
return this.ref.numNotes();
else
return 0;
@ -2076,7 +2130,7 @@ Zotero.ItemTreeView.TreeRow.prototype.numNotes = function()
Zotero.ItemTreeView.TreeRow.prototype.numAttachments = function()
{
if(this.isRegularItem())
if(this.ref.isRegularItem())
return this.ref.numAttachments();
else
return 0;

View file

@ -23,7 +23,10 @@
Zotero.Notifier = new function(){
var _observers = new Zotero.Hash();
var _disabled = false;
var _types = ['collection', 'search', 'item', 'collection-item', 'item-tag', 'tag'];
var _types = [
'collection', 'creator', 'search', 'item',
'collection-item', 'item-tag', 'tag'
];
var _inTransaction;
var _locked = false;
var _queue = [];
@ -31,6 +34,7 @@ Zotero.Notifier = new function(){
this.registerObserver = registerObserver;
this.unregisterObserver = unregisterObserver;
this.trigger = trigger;
this.untrigger = untrigger;
this.begin = begin;
this.commit = commit;
this.reset = reset;
@ -108,9 +112,6 @@ Zotero.Notifier = new function(){
if (!extraData) {
throw ("Extra data must be supplied with Notifier type '" + type + "'");
}
if (extraData.constructor.name != 'Array') {
extraData = [extraData];
}
}
ids = Zotero.flattenArguments(ids);
@ -120,6 +121,7 @@ Zotero.Notifier = new function(){
Zotero.debug("Notifier.trigger('" + event + "', '" + type + "', " + '[' + ids.join() + '])'
+ (queue ? " queued" : " called " + "[observers: " + _observers.length + "]"));
// Merge with existing queue
if (queue) {
if (!_queue[type]) {
_queue[type] = [];
@ -129,11 +131,18 @@ Zotero.Notifier = new function(){
}
if (!_queue[type][event].ids) {
_queue[type][event].ids = [];
_queue[type][event].data = [];
_queue[type][event].data = {};
}
// Merge ids
_queue[type][event].ids = _queue[type][event].ids.concat(ids);
_queue[type][event].data = _queue[type][event].data.concat(extraData);
// Merge extraData keys
if (extraData) {
for (var dataID in extraData) {
_queue[type][event].data[dataID] = extraData[dataID];
}
}
return true;
}
@ -148,6 +157,7 @@ Zotero.Notifier = new function(){
_observers.get(i).ref.notify(event, type, ids, extraData);
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
}
}
@ -157,6 +167,26 @@ Zotero.Notifier = new function(){
}
function untrigger(event, type, ids) {
if (!_inTransaction) {
throw ("Zotero.Notifier.untrigger() called with no active event queue")
}
ids = Zotero.flattenArguments(ids);
for each(var id in ids) {
var index = _queue[type][event].ids.indexOf(id);
if (index == -1) {
Zotero.debug(event + '-' + type + ' id ' + id +
' not found in queue in Zotero.Notifier.untrigger()');
continue;
}
_queue[type][event].ids.splice(index, 1);
delete _queue[type][event].data[id];
}
}
/*
* Begin queueing event notifications (i.e. don't notify the observers)
*
@ -219,13 +249,13 @@ Zotero.Notifier = new function(){
for (var event in _queue[type]) {
runQueue[type][event] = {
ids: [],
data: []
data: {}
};
// Remove redundant ids
for (var i=0; i<_queue[type][event].ids.length; i++) {
var id = _queue[type][event].ids[i];
var data = _queue[type][event].data[i];
var data = _queue[type][event].data[id];
// Don't send modify on nonexistent items or tags
if (event == 'modify') {
@ -239,7 +269,7 @@ Zotero.Notifier = new function(){
if (runQueue[type][event].ids.indexOf(id) == -1) {
runQueue[type][event].ids.push(id);
runQueue[type][event].data.push(data);
runQueue[type][event].data[id] = data;
}
}

View file

@ -290,6 +290,9 @@ Zotero.ProgressWindow = function(_window){
}
function _move() {
// sizeToContent() fails in FF3 with multiple lines
// if we don't change the height
_progressWindow.outerHeight = _progressWindow.outerHeight + 1;
_progressWindow.sizeToContent();
Zotero.ProgressWindowSet.tile(_progressWindow);
}

View file

@ -136,7 +136,6 @@ Zotero.QuickCopy = new function() {
if (mode == 'export') {
var translation = new Zotero.Translate("export");
Zotero.debug(items);
translation.setItems(items);
translation.setTranslator(format);
translation.setHandler("done", callback);

View file

@ -121,7 +121,8 @@ Zotero.Schema = new function(){
var up1 = _migrateUserDataSchema(dbVersion);
var up2 = _updateSchema('system');
var up3 = _updateSchema('scrapers');
var up3 = _updateSchema('triggers');
var up4 = _updateSchema('scrapers');
Zotero.DB.commitTransaction();
}
@ -149,7 +150,7 @@ Zotero.Schema = new function(){
}
}
if (up2 || up3) {
if (up2 || up3 || up4) {
// Run a manual scraper update if upgraded and pref set
if (Zotero.Prefs.get('automaticScraperUpdates')){
this.updateScrapersRemote(2);
@ -331,11 +332,6 @@ Zotero.Schema = new function(){
* Retrieve the DB schema version
*/
function _getDBVersion(schema){
// Default to schema.sql
if (!schema){
schema = 'schema';
}
if (_dbVersions[schema]){
return _dbVersions[schema];
}
@ -487,39 +483,44 @@ Zotero.Schema = new function(){
Zotero.DB.beginTransaction();
try {
// Enable auto-vacuuming
Zotero.DB.query("PRAGMA page_size = 4096");
Zotero.DB.query("PRAGMA encoding = 'UTF-8'");
Zotero.DB.query("PRAGMA auto_vacuum = 1");
Zotero.DB.query(_getSchemaSQL('userdata'));
_updateFailsafeSchema();
_updateDBVersion('userdata', _getSchemaSQLVersion('userdata'));
Zotero.DB.query(_getSchemaSQL('system'));
_updateDBVersion('system', _getSchemaSQLVersion('system'));
Zotero.DB.query(_getSchemaSQL('userdata'));
Zotero.DB.query(_getSchemaSQL('triggers'));
Zotero.DB.query(_getSchemaSQL('scrapers'));
_updateDBVersion('system', _getSchemaSQLVersion('system'));
_updateDBVersion('userdata', _getSchemaSQLVersion('userdata'));
_updateDBVersion('triggers', _getSchemaSQLVersion('triggers'));
_updateDBVersion('scrapers', _getSchemaSQLVersion('scrapers'));
var sql = "INSERT INTO items VALUES(123456789, 14, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)";
/*
TODO: uncomment for release
var sql = "INSERT INTO items VALUES(1, 14, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'AJ4PT6IT')";
Zotero.DB.query(sql);
var sql = "INSERT INTO itemAttachments VALUES(123456789, NULL, 3, 'text/html', 25, NULL, NULL)";
var sql = "INSERT INTO itemAttachments VALUES (1, NULL, 3, 'text/html', 25, NULL, NULL)";
Zotero.DB.query(sql);
var sql = "INSERT INTO itemDataValues VALUES (?, ?)";
Zotero.DB.query(sql, [1, "Zotero - " + Zotero.getString('install.quickStartGuide')]);
var sql = "INSERT INTO itemData VALUES(123456789, 110, 1)";
var sql = "INSERT INTO itemData VALUES (1, 110, 1)";
Zotero.DB.query(sql);
var sql = "INSERT INTO itemDataValues VALUES (2, 'http://www.zotero.org/documentation/quick_start_guide')";
Zotero.DB.query(sql);
var sql = "INSERT INTO itemData VALUES(123456789, 1, 2)";
var sql = "INSERT INTO itemData VALUES (1, 1, 2)";
Zotero.DB.query(sql);
var sql = "INSERT INTO itemDataValues VALUES (3, CURRENT_TIMESTAMP)";
Zotero.DB.query(sql);
var sql = "INSERT INTO itemData VALUES(123456789, 27, 3)";
var sql = "INSERT INTO itemData VALUES (1, 27, 3)";
Zotero.DB.query(sql);
var sql = "INSERT INTO itemNotes (itemID, sourceItemID, note) VALUES(123456789, NULL, ?)";
var sql = "INSERT INTO itemNotes (itemID, sourceItemID, note) VALUES (1, NULL, ?)";
var msg = Zotero.getString('install.quickStartGuide.message.welcome')
+ " " + Zotero.getString('install.quickStartGuide.message.clickViewPage')
+ "\n\n" + Zotero.getString('install.quickStartGuide.message.thanks');
Zotero.DB.query(sql, msg);
*/
Zotero.DB.commitTransaction();
self.dbInitialized = true;
@ -788,6 +789,8 @@ Zotero.Schema = new function(){
Zotero.debug('Updating user data tables from version ' + fromVersion + ' to ' + toVersion);
var ZU = new Zotero.Utilities;
Zotero.DB.beginTransaction();
try {
@ -1222,10 +1225,214 @@ Zotero.Schema = new function(){
Zotero.DB.query("ALTER TABLE fulltextItems ADD totalChars INT");
Zotero.DB.query("DELETE FROM version WHERE schema='fulltext'");
}
// 1.5
if (i==37) {
// Some data cleanup from the pre-FK-trigger days
Zotero.DB.query("DELETE FROM annotations WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM collectionItems WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM fulltextItems WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM fulltextItemWords WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM highlights WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM itemAttachments WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM itemCreators WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM itemData WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM itemNotes WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM itemNoteTitles WHERE itemID NOT IN (SELECT itemID FROM itemNotes)");
Zotero.DB.query("DELETE FROM itemSeeAlso WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM itemSeeAlso WHERE linkedItemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM itemTags WHERE itemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("DELETE FROM itemTags WHERE tagID NOT IN (SELECT tagID FROM tags)");
Zotero.DB.query("DELETE FROM savedSearchConditions WHERE savedSearchID NOT IN (select savedSearchID FROM savedSearches)");
Zotero.DB.query("DELETE FROM itemData WHERE valueID NOT IN (SELECT valueID FROM itemDataValues)");
Zotero.DB.query("DELETE FROM fulltextItemWords WHERE wordID NOT IN (SELECT wordID FROM fulltextWords)");
Zotero.DB.query("DELETE FROM collectionItems WHERE collectionID NOT IN (SELECT collectionID FROM collections)");
Zotero.DB.query("DELETE FROM itemCreators WHERE creatorID NOT IN (SELECT creatorID FROM creators)");
Zotero.DB.query("DELETE FROM itemTags WHERE tagID NOT IN (SELECT tagID FROM tags)");
Zotero.DB.query("DELETE FROM itemData WHERE fieldID NOT IN (SELECT fieldID FROM fields)");
Zotero.DB.query("DELETE FROM itemData WHERE valueID NOT IN (SELECT valueID FROM itemDataValues)");
Zotero.DB.query("DROP TABLE IF EXISTS userFieldMask");
Zotero.DB.query("DROP TABLE IF EXISTS userItemTypes");
Zotero.DB.query("DROP TABLE IF EXISTS userItemTypeMask");
Zotero.DB.query("DROP TABLE IF EXISTS userFields");
Zotero.DB.query("DROP TABLE IF EXISTS userItemTypeFields");
var wordIDs = Zotero.DB.columnQuery("SELECT GROUP_CONCAT(wordID) AS wordIDs FROM fulltextWords GROUP BY word HAVING COUNT(*)>1");
if (wordIDs.length) {
Zotero.DB.query("CREATE TEMPORARY TABLE deleteWordIDs (wordID INTEGER PRIMARY KEY)");
for (var j=0, len=wordIDs.length; j<len; j++) {
var ids = wordIDs[j].split(',');
for (var k=1; k<ids.length; k++) {
Zotero.DB.query("INSERT INTO deleteWordIDs VALUES (?)", ids[k]);
}
}
Zotero.DB.query("DELETE FROM fulltextWords WHERE wordID IN (SELECT wordID FROM deleteWordIDs)");
Zotero.DB.query("DROP TABLE deleteWordIDs");
}
Zotero.DB.query("REINDEX");
Zotero.DB.transactionVacuum = true;
// Set page cache size to 8MB
var pageSize = Zotero.DB.valueQuery("PRAGMA page_size");
var cacheSize = 8192000 / pageSize;
Zotero.DB.query("PRAGMA default_cache_size=" + cacheSize);
Zotero.DB.query("UPDATE itemAttachments SET sourceItemID=NULL WHERE sourceItemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("UPDATE itemNotes SET sourceItemID=NULL WHERE sourceItemID NOT IN (SELECT itemID FROM items)");
Zotero.DB.query("CREATE TABLE syncDeleteLog (\n syncObjectTypeID INT NOT NULL,\n objectID INT NOT NULL,\n key TEXT NOT NULL,\n timestamp INT NOT NULL,\n FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)\n);");
Zotero.DB.query("CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp);");
// Note titles
Zotero.DB.query("ALTER TABLE itemNotes ADD COLUMN title TEXT");
var notes = Zotero.DB.query("SELECT itemID, title FROM itemNoteTitles");
if (notes) {
var statement = Zotero.DB.getStatement("UPDATE itemNotes SET title=? WHERE itemID=?");
for (var j=0, len=notes.length; j<len; j++) {
statement.bindUTF8StringParameter(0, notes[j].title);
statement.bindInt32Parameter(1, notes[j].itemID);
try {
statement.execute();
}
catch (e) {
throw (Zotero.DB.getLastErrorString());
}
}
statement.reset();
}
Zotero.DB.query("DROP TABLE itemNoteTitles");
// Creator data
Zotero.DB.query("CREATE TABLE creatorData (\n creatorDataID INTEGER PRIMARY KEY,\n firstName TEXT,\n lastName TEXT,\n shortName TEXT,\n fieldMode INT,\n birthYear INT\n)");
Zotero.DB.query("INSERT INTO creatorData SELECT NULL, firstName, lastName, NULL, fieldMode, NULL FROM creators WHERE creatorID IN (SELECT creatorID FROM itemCreators)");
var creatorsOld = Zotero.DB.query("SELECT * FROM creators");
Zotero.DB.query("DROP TABLE creators");
Zotero.DB.query("CREATE TABLE creators (\n creatorID INTEGER PRIMARY KEY,\n creatorDataID INT,\n dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,\n key TEXT NOT NULL,\n FOREIGN KEY (creatorDataID) REFERENCES creatorData(creatorDataID)\n);");
var data = Zotero.DB.query("SELECT * FROM creatorData");
if (data) {
var oldCreatorIDHash = {};
for (var j=0, len=creatorsOld.length; j<len; j++) {
oldCreatorIDHash[
ZU.md5(
creatorsOld[j].firstName + '_' +
creatorsOld[j].lastName + '_' +
creatorsOld[j].fieldMode
)
] = creatorsOld[j].creatorID;
}
var updatedIDs = {};
var insertStatement = Zotero.DB.getStatement("INSERT INTO creators (creatorID, creatorDataID, key) VALUES (?, ?, ?)");
var updateStatement = Zotero.DB.getStatement("UPDATE itemCreators SET creatorID=? WHERE creatorID=?");
for (var j=0, len=data.length; j<len; j++) {
insertStatement.bindInt32Parameter(0, data[j].creatorDataID);
insertStatement.bindInt32Parameter(1, data[j].creatorDataID);
var key = Zotero.ID.getKey();
insertStatement.bindStringParameter(2, key);
var oldCreatorID = oldCreatorIDHash[
ZU.md5(
data[j].firstName + '_' +
data[j].lastName + '_' +
data[j].fieldMode
)
];
if (updatedIDs[oldCreatorID]) {
continue;
}
updatedIDs[oldCreatorID] = true;
updateStatement.bindInt32Parameter(0, data[j].creatorDataID);
updateStatement.bindInt32Parameter(1, oldCreatorID);
try {
insertStatement.execute();
updateStatement.execute();
}
catch (e) {
throw (Zotero.DB.getLastErrorString());
}
}
insertStatement.reset();
updateStatement.reset();
}
Zotero.DB.query("CREATE INDEX creators_creatorDataID ON creators(creatorDataID)");
// Items
Zotero.DB.query("ALTER TABLE items ADD COLUMN key TEXT");
var items = Zotero.DB.query("SELECT itemID, itemTypeID, dateAdded FROM items");
var titles = Zotero.DB.query("SELECT itemID, value FROM itemData NATURAL JOIN itemDataValues WHERE fieldID BETWEEN 110 AND 112");
var statement = Zotero.DB.getStatement("UPDATE items SET key=? WHERE itemID=?");
for (var j=0, len=items.length; j<len; j++) {
var key = Zotero.ID.getKey();
statement.bindStringParameter(0, key);
statement.bindInt32Parameter(1, items[j].itemID);
try {
statement.execute();
}
catch (e) {
throw (Zotero.DB.getLastErrorString());
}
}
statement.reset();
Zotero.DB.query("CREATE UNIQUE INDEX items_key ON items(key)");
// Collections
var collections = Zotero.DB.query("SELECT * FROM collections");
Zotero.DB.query("DROP TABLE collections");
Zotero.DB.query("CREATE TABLE collections (\n collectionID INTEGER PRIMARY KEY,\n collectionName TEXT,\n parentCollectionID INT,\n dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,\n key TEXT NOT NULL UNIQUE,\n FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID)\n);");
var statement = Zotero.DB.getStatement("INSERT INTO collections (collectionID, collectionName, parentCollectionID, key) VALUES (?,?,?,?)");
for (var j=0, len=collections.length; j<len; j++) {
statement.bindInt32Parameter(0, collections[j].collectionID);
statement.bindUTF8StringParameter(1, collections[j].collectionName);
if (collections[j].parentCollectionID) {
statement.bindInt32Parameter(2, collections[j].parentCollectionID);
}
else {
statement.bindNullParameter(2);
}
var key = Zotero.ID.getKey();
statement.bindStringParameter(3, key);
try {
statement.execute();
}
catch (e) {
throw (Zotero.DB.getLastErrorString());
}
}
statement.reset();
// Saved searches
var searches = Zotero.DB.query("SELECT * FROM savedSearches");
Zotero.DB.query("DROP TABLE savedSearches");
Zotero.DB.query("CREATE TABLE savedSearches (\n savedSearchID INTEGER PRIMARY KEY,\n savedSearchName TEXT,\n dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,\n key TEXT NOT NULL UNIQUE\n);");
var statement = Zotero.DB.getStatement("INSERT INTO savedSearches (savedSearchID, savedSearchName, key) VALUES (?,?,?)");
for (var j=0, len=searches.length; j<len; j++) {
statement.bindInt32Parameter(0, searches[j].savedSearchID);
statement.bindUTF8StringParameter(1, searches[j].savedSearchName);
var key = Zotero.ID.getKey();
statement.bindStringParameter(2, key);
try {
statement.execute();
}
catch (e) {
throw (Zotero.DB.getLastErrorString());
}
}
statement.reset();
}
}
_updateSchema('userdata');
_updateFailsafeSchema();
_updateDBVersion('userdata', toVersion);
Zotero.DB.commitTransaction();
}
@ -1236,41 +1443,4 @@ Zotero.Schema = new function(){
return true;
}
function _updateFailsafeSchema(){
// This is super-annoying, but SQLite didn't have IF [NOT] EXISTS
// on trigger statements until 3.3.8, which didn't make it into
// Firefox 2.0, so we just throw the triggers at the DB on every
// userdata update and catch errors individually
//
try { Zotero.DB.query("DROP TRIGGER insert_date_field"); } catch (e) {}
try { Zotero.DB.query("DROP TRIGGER update_date_field"); } catch (e) {}
var itemDataTrigger = " FOR EACH ROW WHEN NEW.fieldID IN (14, 27, 52, 96, 100)\n"
+ " BEGIN\n"
+ " SELECT CASE\n"
+ " CAST(SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 1, 4) AS INT) BETWEEN 0 AND 9999 AND\n"
+ " SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 5, 1) = '-' AND\n"
+ " CAST(SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 6, 2) AS INT) BETWEEN 0 AND 12 AND\n"
+ " SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 8, 1) = '-' AND\n"
+ " CAST(SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 9, 2) AS INT) BETWEEN 0 AND 31\n"
+ " WHEN 0 THEN RAISE (ABORT, 'Date field must begin with SQL date') END;\n"
+ " END;\n";
try {
var sql = "CREATE TRIGGER insert_date_field BEFORE INSERT ON itemData\n"
+ itemDataTrigger;
Zotero.DB.query(sql);
}
catch (e){}
try {
var sql = "CREATE TRIGGER update_date_field BEFORE UPDATE ON itemData\n"
+ itemDataTrigger;
Zotero.DB.query(sql);
}
catch (e){}
}
}

View file

@ -99,14 +99,19 @@ Zotero.Search.prototype.load = function(savedSearchID){
Zotero.Search.prototype.getID = function(){
Zotero.debug('Zotero.Search.getName() is deprecated -- use Search.id');
return this._savedSearchID;
}
Zotero.Search.prototype.__defineGetter__('id', function () { return this._savedSearchID; });
Zotero.Search.prototype.getName = function(){
Zotero.Search.prototype.getName = function() {
Zotero.debug('Zotero.Search.getName() is deprecated -- use Search.name');
return this._savedSearchName;
}
Zotero.Search.prototype.__defineGetter__('name', function () { return this._savedSearchName; });
/*
* Save the search to the DB and return a savedSearchID
@ -634,11 +639,11 @@ Zotero.Search.prototype.search = function(asTempTable){
//Zotero.debug('Final result set');
//Zotero.debug(ids);
if (asTempTable) {
if (!ids) {
return false;
}
if (!ids || !ids.length) {
return false;
}
if (asTempTable) {
return this._idsToTempTable(ids);
}
@ -947,7 +952,7 @@ Zotero.Search.prototype._buildQuery = function(){
case 'creator':
condSQL += "creatorID IN (SELECT creatorID FROM creators "
+ "WHERE ";
+ "NATURAL JOIN creatorData WHERE ";
openParens++;
break;
@ -1329,6 +1334,7 @@ Zotero.Searches = new function(){
}
Zotero.SearchConditions = new function(){
this.get = get;
this.getStandardConditions = getStandardConditions;
@ -1338,7 +1344,7 @@ Zotero.SearchConditions = new function(){
this.parseCondition = parseCondition;
var _initialized = false;
var _conditions = [];
var _conditions = {};
var _standardConditions = [];
var self = this;
@ -1375,7 +1381,7 @@ Zotero.SearchConditions = new function(){
* - template
*/
function _init(){
_conditions = [
var conditions = [
//
// Special conditions
//
@ -1658,19 +1664,17 @@ Zotero.SearchConditions = new function(){
},
special: false
}
];
// Index conditions by name and aliases
for (var i in _conditions){
_conditions[_conditions[i]['name']] = _conditions[i];
if (_conditions[i]['aliases']){
for (var j in _conditions[i]['aliases']){
_conditions[_conditions[i]['aliases'][j]] = _conditions[i];
for (var i in conditions) {
_conditions[conditions[i]['name']] = conditions[i];
if (conditions[i]['aliases']) {
for (var j in conditions[i]['aliases']) {
_conditions[conditions[i]['aliases'][j]] = conditions[i];
}
}
_conditions[_conditions[i]['name']] = _conditions[i];
delete _conditions[i];
_conditions[conditions[i]['name']] = conditions[i];
}
var sortKeys = [];

File diff suppressed because it is too large Load diff

View file

@ -1011,7 +1011,7 @@ Zotero.Translate.prototype._closeStreams = function() {
Zotero.Translate.prototype._itemTagsAndSeeAlso = function(item, newItem) {
// add to ID map
if(item.itemID) {
this._IDMap[item.itemID] = newItem.getID();
this._IDMap[item.itemID] = newItem.id;
}
// add see alsos
if(item.seeAlso) {
@ -1087,7 +1087,10 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
var type = (item.itemType ? item.itemType : "webpage");
if(type == "note") { // handle notes differently
var myID = Zotero.Notes.add(item.note);
var item = new Zotero.Item(false, 'note');
item.setNote(item.note);
var myID = item.save();
// re-retrieve the item
var newItem = Zotero.Items.get(myID);
} else {
@ -1173,11 +1176,11 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
// add note if necessary
if(item.note) {
newItem.updateNote(item.note);
newItem.setNote(item.note);
}
} else {
var typeID = Zotero.ItemTypes.getID(type);
var newItem = new Zotero.Item(typeID);
var newItem = new Zotero.Item(false, typeID);
}
// makes looping through easier
@ -1196,17 +1199,32 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
if(data) { // if field has content
if(field == "creators") { // creators are a special case
for(var j in data) {
var creatorType = 1;
// try to assign correct creator type
if(data[j].creatorType) {
try {
var creatorType = Zotero.CreatorTypes.getID(data[j].creatorType);
} catch(e) {
Zotero.debug("Translate: invalid creator type "+data[j].creatorType+" for creator index "+j);
}
var creatorTypeID = Zotero.CreatorTypes.getID(data[j].creatorType);
}
if(!creatorTypeID) {
var creatorTypeID = 1;
}
newItem.setCreator(j, data[j].firstName, data[j].lastName, creatorType);
var fields = {
firstName: data[j].firstName,
lastName: data[j].lastName
};
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;
creator.setFields(fields);
var creatorID = creator.save();
}
newItem.setCreator(j, creator, creatorTypeID);
}
} else if(field == "seeAlso") {
newItem.translateSeeAlso = data;
@ -1270,14 +1288,19 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
} else {
var myID = newItem.save();
if(myID == true || !myID) {
myID = newItem.getID();
myID = newItem.id;
}
}
// handle notes
if(item.notes) {
for each(var note in item.notes) {
var noteID = Zotero.Notes.add(note.note, myID);
var item = new Zotero.Item(false, 'note');
item.setNote(note.note);
if (myID) {
item.setSource(myID);
}
var noteID = item.save();
// handle see also
var myNote = Zotero.Items.get(noteID);
@ -1420,7 +1443,11 @@ Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
}
}
if(!attachedTo) this.runHandler("itemDone", newItem);
if(!attachedTo) {
// Re-retrieve item before passing to handler
newItem = Zotero.Items.get(newItem.id);
this.runHandler("itemDone", newItem);
}
delete item;
}
@ -1439,7 +1466,7 @@ Zotero.Translate.prototype._collectionDone = function(collection) {
*/
Zotero.Translate.prototype._processCollection = function(collection, parentID) {
var newCollection = Zotero.Collections.add(collection.name, parentID);
var myID = newCollection.getID();
var myID = newCollection.id;
this.newCollections.push(myID);
@ -1756,7 +1783,7 @@ Zotero.Translate.prototype._export = function() {
if(this.configOptions.getCollections) {
// get child collections
this._collectionsLeft = Zotero.getCollections(this.collection.getID(), true);
this._collectionsLeft = Zotero.getCollections(this.collection.id, true);
// get items in child collections
for each(var collection in this._collectionsLeft) {
this._itemsLeft = this._itemsLeft.concat(collection.getChildItems());
@ -1976,7 +2003,7 @@ Zotero.Translate.prototype._exportToArray = function(returnItem) {
returnItemArray.uniqueFields = new Object();
// get base fields, not just the type-specific ones
var itemTypeID = returnItem.getType();
var itemTypeID = returnItem.itemTypeID;
var allFields = Zotero.ItemFields.getItemTypeFields(itemTypeID);
for each(var field in allFields) {
var fieldName = Zotero.ItemFields.getName(field);

View file

@ -253,6 +253,56 @@ Zotero.Utilities.prototype.isInt = function(x) {
return false;
}
/**
* Determine the necessary data type for SQLite parameter binding
*
* @return int 0 for string, 32 for int32, 64 for int64
*/
Zotero.Utilities.prototype.getSQLDataType = function(value) {
var strVal = value + '';
if (strVal.match(/^[1-9]+[0-9]*$/)) {
// These upper bounds also specified in Zotero.DB
//
// Store as 32-bit signed integer
if (value <= 2147483647) {
return 32;
}
// Store as 64-bit signed integer
// 2^53 is JS's upper-bound for decimal integers
else if (value < 9007199254740992) {
return 64;
}
}
return 0;
}
/*
* From http://developer.mozilla.org/en/docs/nsICryptoHash#Computing_the_Hash_of_a_String
*/
Zotero.Utilities.prototype.md5 = function(str) {
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var result = {};
var data = converter.convertToByteArray(str, result);
var ch = Components.classes["@mozilla.org/security/hash;1"]
.createInstance(Components.interfaces.nsICryptoHash);
ch.init(ch.MD5);
ch.update(data, data.length);
var hash = ch.finish(false);
// return the two-digit hexadecimal code for a byte
function toHexString(charCode) {
return ("0" + charCode.toString(16)).slice(-2);
}
// convert the binary hash data to a hex string.
return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
}
/*
* Get current zotero version
*/
@ -562,8 +612,10 @@ Zotero.Utilities.HTTP = new function() {
*
* doGet can be called as:
* Zotero.Utilities.HTTP.doGet(url, onDone)
*
* Returns the XMLHTTPRequest object
**/
function doGet(url, onDone, onError, responseCharset) {
function doGet(url, onDone, responseCharset) {
Zotero.debug("HTTP GET "+url);
if (this.browserIsOffline()){
return false;
@ -580,7 +632,7 @@ Zotero.Utilities.HTTP = new function() {
xmlhttp.send(null);
return true;
return xmlhttp;
}
@ -591,9 +643,19 @@ Zotero.Utilities.HTTP = new function() {
*
* doPost can be called as:
* Zotero.Utilities.HTTP.doPost(url, body, onDone)
*
* Returns the XMLHTTPRequest object
**/
function doPost(url, body, onDone, requestContentType, responseCharset) {
Zotero.debug("HTTP POST "+body+" to "+url);
var bodyStart = body.substr(0, 1024);
// Don't display password in console
bodyStart = bodyStart.replace(/password=[^&]+/, 'password=********');
Zotero.debug("HTTP POST "
+ (body.length > 1024 ?
bodyStart + '... (' + body.length + ' chars)' : bodyStart)
+ " to " + url);
if (this.browserIsOffline()){
return false;
}
@ -610,7 +672,7 @@ Zotero.Utilities.HTTP = new function() {
xmlhttp.send(body);
return true;
return xmlhttp;
}
@ -631,7 +693,7 @@ Zotero.Utilities.HTTP = new function() {
xmlhttp.send(null);
return true;
return xmlhttp;
}
@ -641,8 +703,7 @@ Zotero.Utilities.HTTP = new function() {
* doOptions can be called as:
* Zotero.Utilities.HTTP.doOptions(url, body, onDone)
*
* The status handler, which doesn't really serve a very noticeable purpose
* in our code, is required for compatiblity with the Piggy Bank project
* Returns the XMLHTTPRequest object
**/
function doOptions(url, body, onDone) {
Zotero.debug("HTTP OPTIONS "+url);
@ -661,7 +722,7 @@ Zotero.Utilities.HTTP = new function() {
xmlhttp.send(body);
return true;
return xmlhttp;
}

View file

@ -60,6 +60,7 @@ var Zotero = new function(){
this.hasValues = hasValues;
this.randomString = randomString;
this.moveToUnique = moveToUnique;
this.reloadDataObjects = reloadDataObjects;
// Public properties
this.initialized = false;
@ -251,6 +252,8 @@ var Zotero = new function(){
Zotero.Integration.SOAP.init();
Zotero.Integration.init();
Zotero.Sync.init();
this.initialized = true;
return true;
@ -467,8 +470,7 @@ var Zotero = new function(){
* |type| is a string with one of the flag types in nsIScriptError:
* 'error', 'warning', 'exception', 'strict'
*/
function log(message, type, sourceName, sourceLine, lineNumber,
columnNumber, category) {
function log(message, type, sourceName, sourceLine, lineNumber, columnNumber) {
var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService);
var scriptError = Components.classes["@mozilla.org/scripterror;1"]
@ -486,7 +488,7 @@ var Zotero = new function(){
lineNumber != undefined ? lineNumber : null,
columnNumber != undefined ? columnNumber : null,
flags,
category
'XUL javascript' // DEBUG: this doesn't seem to work
);
consoleService.logMessage(scriptError);
}
@ -798,9 +800,11 @@ var Zotero = new function(){
/**
* Generate a random string of length 'len' (defaults to 8)
**/
function randomString(len) {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
if (!len){
function randomString(len, chars) {
if (!chars) {
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
}
if (!len) {
len = 8;
}
var randomstring = '';
@ -821,6 +825,13 @@ var Zotero = new function(){
file.moveTo(newFile.parent, newName);
return file;
}
function reloadDataObjects() {
Zotero.Collections.reloadAll();
Zotero.Creators.reloadAll();
Zotero.Items.reloadAll();
}
};
@ -1163,6 +1174,8 @@ Zotero.Date = new function(){
this.sqlHasYear = sqlHasYear;
this.sqlHasMonth = sqlHasMonth;
this.sqlHasDay = sqlHasDay;
this.getUnixTimestamp = getUnixTimestamp;
this.toUnixTimestamp = toUnixTimestamp;
this.getFileDateString = getFileDateString;
this.getFileTimeString = getFileTimeString;
this.getLocaleDateOrder = getLocaleDateOrder;
@ -1223,12 +1236,7 @@ Zotero.Date = new function(){
var seconds = date.getUTCSeconds();
}
else {
var year = date.getFullYear();
var month = date.getMonth();
var day = date.getDate();
var hours = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
return date.toLocaleFormat('%Y-%m-%d %T');
}
var utils = new Zotero.Utilities();
@ -1483,9 +1491,12 @@ Zotero.Date = new function(){
}
// Regexes for multipart and SQL dates
var _multipartRE = /^[0-9]{4}\-[0-9]{2}\-[0-9]{2} /;
var _sqldateRE = /^[0-9]{4}\-[0-9]{2}\-[0-9]{2}/;
var _sqldatetimeRE = /^[0-9]{4}\-[0-9]{2}\-[0-9]{2} ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])/;
// Allow zeroes in multipart dates
var _multipartRE = /^\-?[0-9]{4}\-[0-9]{2}\-[0-9]{2} /;
//var _sqldateRE = /^\-?[0-9]{4}\-[0-9]{2}\-[0-9]{2}/;
//var _sqldatetimeRE = /^\-?[0-9]{4}\-[0-9]{2}\-[0-9]{2} ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])/;
var _sqldateRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31)$/;
var _sqldatetimeRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31) ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])$/;
/**
* Tests if a string is a multipart date string
@ -1555,6 +1566,20 @@ Zotero.Date = new function(){
}
function getUnixTimestamp() {
return Math.round(Date.now() / 1000);
}
function toUnixTimestamp(date) {
if (date === null || typeof date != 'object' ||
date.constructor.name != 'Date') {
throw ('Not a valid date in Zotero.Date.toUnixTimestamp()');
}
return Math.round(date.getTime() / 1000);
}
function getFileDateString(file){
var date = new Date();
date.setTime(file.lastModifiedTime);

View file

@ -0,0 +1,104 @@
scrollbox
{
overflow: visible;
}
row, tagsbox row
{
margin: 0 0 1px;
min-height: 1em;
}
textbox, tagsbox textbox
{
margin-top: 0;
margin-bottom: -1px;
}
#go-buttons button
{
list-style-image: url('chrome://zotero/skin/toolbar-go-arrow.png');
-moz-box-direction: reverse;
-moz-box-flex: 1;
}
#go-buttons button[disabled=true]
{
list-style-image: url('chrome://zotero/skin/toolbar-go-arrow-disabled.png');
}
/* DEBUG: this doesn't seem to work, unfortunately
label[singleField=false]:after
{
content:",";
}
*/
/* metadata field names */
row > label:first-child,
row > toolbarbutton .toolbarbutton-text /* creator type menu */
{
text-align: right;
font-weight: bold;
}
row label:first-child[isButton=true]:hover
{
cursor: pointer;
}
row label
{
-moz-user-focus: ignore;
}
/* creator type menu */
row > toolbarbutton
{
margin: 0 2px 0 0;
padding: 0 0 0 5px;
list-style-image: url("chrome://browser/skin/dropmark-nav.png");
-moz-image-region: rect(3px, 14px, 19px, 0px);
}
row > toolbarbutton .toolbarbutton-text
{
margin-top: -1px;
}
row > toolbarbutton .toolbarbutton-icon,
row > toolbarbutton .toolbarbutton-menu-dropmarker
{
margin: 0;
padding: 0;
}
/* no space between last name and comma */
row hbox label:first-child
{
margin-right: 1px;
}
row hbox label.comma
{
}
row vbox[fieldname=abstractNote],
row vbox[fieldname=extra]
{
margin-top: 1px;
margin-left: 6px;
}
hbox.zotero-date-field-status
{
margin-right: 5px;
}
hbox.zotero-date-field-status label
{
font-weight: bold;
color: #666;
margin: 0 0 0 1px;
}

View file

@ -1,12 +1,8 @@
/* Don't collapse blank note parent labels, since it prevents access to parent */
#citeLabel
{
min-height: 1.25em;
}
#citeLabel:hover
#citeLabel[onclick]:hover
{
cursor: pointer !important;
min-height: 1.25em;
}
#tagsPopup {

View file

@ -1,85 +0,0 @@
#zotero-editpane-dynamic-fields row, tagsbox row
{
margin: 0 0 1px;
}
#zotero-editpane-dynamic-fields textbox, tagsbox textbox
{
margin-top: 0;
margin-bottom: -1px;
}
/* DEBUG: this doesn't seem to work, unfortunately
#zotero-editpane-dynamic-fields label[singleField=false]:after
{
content:",";
}
*/
/* metadata field names */
#zotero-editpane-dynamic-fields row > label:first-child,
#zotero-editpane-dynamic-fields row > toolbarbutton .toolbarbutton-text /* creator type menu */
{
text-align: right;
font-weight: bold;
}
#zotero-editpane-dynamic-fields row label:first-child[isButton=true]:hover
{
cursor: pointer;
}
#zotero-editpane-dynamic-fields row label
{
-moz-user-focus: ignore;
}
/* creator type menu */
#zotero-editpane-dynamic-fields row > toolbarbutton
{
margin: 0 2px 0 0;
padding: 0 0 0 5px;
list-style-image: url("chrome://browser/skin/dropmark-nav.png");
-moz-image-region: rect(3px, 14px, 19px, 0px);
}
#zotero-editpane-dynamic-fields row > toolbarbutton .toolbarbutton-text
{
margin-top: -1px;
}
#zotero-editpane-dynamic-fields row > toolbarbutton .toolbarbutton-icon,
#zotero-editpane-dynamic-fields row > toolbarbutton .toolbarbutton-menu-dropmarker
{
margin: 0;
padding: 0;
}
/* no space between last name and comma */
#zotero-editpane-dynamic-fields row hbox label:first-child
{
margin-right: 1px;
}
#zotero-editpane-dynamic-fields row hbox label.comma
{
margin-left: 0;
}
#zotero-editpane-dynamic-fields row vbox[fieldname=abstractNote],
#zotero-editpane-dynamic-fields row vbox[fieldname=extra]
{
margin-top: 1px;
margin-left: 6px;
}
#zotero-editpane-dynamic-fields hbox.zotero-date-field-status
{
margin-right: 5px;
}
#zotero-editpane-dynamic-fields hbox.zotero-date-field-status label
{
font-weight: bold;
color: #666;
margin: 0 0 0 1px;
}

View file

@ -0,0 +1,97 @@
/* merge.xul */
wizard {
padding-left: 10px;
padding-right: 10px;
}
wizard {
height: 550px;
}
wizardpage {
min-width: 770px;
min-height: 300px;
}
wizard[zoterowidescreen=true] {
height: 718px;
width: 974px;
}
wizard .wizard-header label.wizard-header-label {
margin-left: 0;
}
/* different order on windows */
wizard > hbox button[dlgtype=cancel] {
margin-left: 0;
}
wizard > hbox button[dlgtype=next][disabled=false],
wizard > hbox button[dlgtype=next]:not([disabled]) {
margin-right: 0;
}
wizard > hbox button:last-child {
margin-right: 0;
}
wizard > deck {
margin: 0;
padding: 0;
}
#zotero-step-count {
-moz-box-pack: end;
}
#zotero-step-count label:first-child {
margin-left: 0;
font-weight: bold;
}
#zotero-step-count label {
font-size: 1.1em;
}
#zotero-step-count label:last-child {
margin-right: 1em;
font-weight: bold;
}
/* merge.xml */
zoteromergegroup {
margin: 0;
padding: 0;
overflow-y: auto;
}
zoteromergepane #delete-box {
min-width: 15em;
-moz-box-align: center;
-moz-box-pack: center;
font-weight: bold;
}
zoteromergepane[selected=true] groupbox caption {
color: red;
font-weight: bold;
}
zoteromergepane[id=leftpane]:not([selected=true]):hover groupbox caption,
zoteromergepane[id=rightpane]:not([selected=true]):hover groupbox caption {
/* font-weight: bold; */
}
hbox:not([mergetype=note]) zoteromergepane:active[id=leftpane] groupbox caption,
hbox:not([mergetype=note]) zoteromergepane:active[id=rightpane] groupbox caption {
color: red;
font-weight: bold;
}
zoteromergepane[id=mergepane] {
min-width: 30em;
}

View file

@ -229,7 +229,6 @@
list-style-image: url('chrome://zotero/skin/search-cancel-active.png');
}
#zotero-go-to-url, #zotero-openurl,
#zotero-attachment-view, #zotero-attachment-show
{
list-style-image: url('chrome://zotero/skin/toolbar-go-arrow.png');
@ -258,11 +257,6 @@
}
#zotero-go-to-url[disabled=true], #zotero-openurl[disabled=true]
{
list-style-image: url('chrome://zotero/skin/toolbar-go-arrow-disabled.png');
}
#zotero-view-item > vbox
{
overflow: auto;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -68,7 +68,7 @@ textbox[type="styled"]
-moz-binding: url('chrome://zotero/content/bindings/styled-textbox.xml#styled-textbox');
}
noteeditor
zoteronoteeditor
{
-moz-binding: url('chrome://zotero/content/bindings/noteeditor.xml#note-editor');
}
@ -121,6 +121,18 @@ zoterosearchagefield
-moz-binding: url('chrome://zotero/content/bindings/zoterosearch.xml#search-in-the-last');
}
zoteroitembox {
-moz-binding: url('chrome://zotero/content/bindings/itembox.xml#item-box');
}
zoteromergegroup {
-moz-binding: url('chrome://zotero/content/bindings/merge.xml#merge-group');
}
zoteromergepane {
-moz-binding: url('chrome://zotero/content/bindings/merge.xml#merge-pane');
}
.zotero-clicky
{
-moz-border-radius: 6px;

View file

@ -63,6 +63,11 @@ ZoteroAutoCompleteResult.prototype.getCommentAt = function(index){
}
ZoteroAutoCompleteResult.prototype.getImageAt = function(index) {
return null;
}
ZoteroAutoCompleteResult.prototype.getStyleAt = function(index){
return null;
}
@ -151,7 +156,7 @@ ZoteroAutoComplete.prototype.startSearch = function(searchString, searchParam,
{
var sql = "SELECT DISTINCT CASE fieldMode WHEN 1 THEN lastName "
+ "WHEN 0 THEN firstName || ' ' || lastName END AS name "
+ "FROM creators WHERE CASE fieldMode "
+ "FROM creators NATURAL JOIN creatorData WHERE CASE fieldMode "
+ "WHEN 1 THEN lastName "
+ "WHEN 0 THEN firstName || ' ' || lastName END "
+ "LIKE ? ORDER BY name";
@ -179,9 +184,10 @@ ZoteroAutoComplete.prototype.startSearch = function(searchString, searchParam,
+ "ELSE 2 END AS creatorID";
}
var fromSQL = " FROM creators WHERE " + searchParts[2]
+ " LIKE ?1 " + "AND fieldMode=?2";
var sqlParams = [searchString + '%', parseInt(fieldMode)];
var fromSQL = " FROM creators NATURAL JOIN creatorData "
+ "WHERE " + searchParts[2] + " LIKE ?1 " + "AND fieldMode=?2";
var sqlParams = [searchString + '%',
fieldMode ? parseInt(fieldMode) : 0];
if (itemID){
fromSQL += " AND creatorID NOT IN (SELECT creatorID FROM "
+ "itemCreators WHERE itemID=?3)";

View file

@ -14,101 +14,19 @@ var ZoteroWrapped = this;
* Include the core objects to be stored within XPCOM
*********************************************************************/
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/zotero.js");
var xpcomFiles = [ 'zotero',
'annotate', 'attachments', 'cite', 'cite_compat', 'collectionTreeView',
'data_access', 'data/item', 'data/items', 'data/collection', 'data/collections',
'data/cachedTypes', 'data/creator', 'data/creators', 'data/itemFields',
'data/notes', 'data/tags', 'db', 'file', 'fulltext', 'id', 'ingester', 'integration',
'itemTreeView', 'mime', 'notifier', 'progressWindow', 'quickCopy', 'report',
'schema', 'search', 'sync', 'timeline', 'translate', 'utilities'];
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/db.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/schema.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/data_access.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/attachments.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/notifier.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/history.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/search.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/ingester.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/translate.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/cite.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/cite_compat.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/quickCopy.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/report.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/timeline.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/utilities.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/integration.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/file.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/fulltext.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/mime.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/itemTreeView.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/collectionTreeView.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/progressWindow.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/annotate.js");
for (var i=0; i<xpcomFiles.length; i++) {
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFiles[i] + ".js");
}
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)

View file

@ -70,3 +70,7 @@ pref("extensions.zotero.integration.autoRegenerate", -1); // -1 = ask; 0 = no; 1
// Annotation settings
pref("extensions.zotero.annotations.warnOnClose", true);
// Server
pref("extensions.zotero.sync.server.username", '');
pref("extensions.zotero.sync.server.compressData", true);

View file

@ -1,4 +1,4 @@
-- 20
-- 21
-- This file creates system tables that can be safely wiped and reinitialized
-- at any time, as long as existing ids are preserved.
@ -101,6 +101,13 @@ CREATE TABLE itemTypeCreatorTypes (
FOREIGN KEY (creatorTypeID) REFERENCES creatorTypes(creatorTypeID)
);
DROP TABLE IF EXISTS syncObjectTypes;
CREATE TABLE syncObjectTypes (
syncObjectTypeID INTEGER PRIMARY KEY,
name TEXT
);
CREATE INDEX syncObjectTypes_name ON syncObjectTypes(name);
DROP TABLE IF EXISTS transactionSets;
CREATE TABLE transactionSets (
transactionSetID INTEGER PRIMARY KEY,
@ -1237,3 +1244,8 @@ INSERT INTO "charsets" VALUES(165, 'x-unicode-2-0-utf-7');
INSERT INTO "charsets" VALUES(166, 'x-x-big5');
INSERT INTO "charsets" VALUES(167, 'x0201');
INSERT INTO "charsets" VALUES(168, 'x0212');
INSERT INTO "syncObjectTypes" VALUES(1, 'collection');
INSERT INTO "syncObjectTypes" VALUES(2, 'creator');
INSERT INTO "syncObjectTypes" VALUES(3, 'item');
INSERT INTO "syncObjectTypes" VALUES(4, 'savedSearch');

659
triggers.sql Normal file
View file

@ -0,0 +1,659 @@
-- 1
-- Triggers to validate date field
DROP TRIGGER IF EXISTS insert_date_field;
CREATE TRIGGER insert_date_field BEFORE INSERT ON itemData
FOR EACH ROW WHEN NEW.fieldID IN (14, 27, 52, 96, 100)
BEGIN
SELECT CASE
CAST(SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 1, 4) AS INT) BETWEEN 0 AND 9999 AND
SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 5, 1) = '-' AND
CAST(SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 6, 2) AS INT) BETWEEN 0 AND 12 AND
SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 8, 1) = '-' AND
CAST(SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 9, 2) AS INT) BETWEEN 0 AND 31
WHEN 0 THEN RAISE (ABORT, 'Date field must begin with SQL date') END;
END;
DROP TRIGGER IF EXISTS update_date_field;
CREATE TRIGGER update_date_field BEFORE UPDATE ON itemData
FOR EACH ROW WHEN NEW.fieldID IN (14, 27, 52, 96, 100)
BEGIN
SELECT CASE
CAST(SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 1, 4) AS INT) BETWEEN 0 AND 9999 AND
SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 5, 1) = '-' AND
CAST(SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 6, 2) AS INT) BETWEEN 0 AND 12 AND
SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 8, 1) = '-' AND
CAST(SUBSTR((SELECT value FROM itemDataValues WHERE valueID=NEW.valueID), 9, 2) AS INT) BETWEEN 0 AND 31
WHEN 0 THEN RAISE (ABORT, 'Date field must begin with SQL date') END;
END;
--
-- Fake foreign key constraint checks using triggers
--
-- annotations/itemID
DROP TRIGGER IF EXISTS fki_annotations_itemID_itemAttachments_itemID;
CREATE TRIGGER fki_annotations_itemID_itemAttachments_itemID
BEFORE INSERT ON annotations
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "annotations" violates foreign key constraint "fki_annotations_itemID_itemAttachments_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM itemAttachments WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_annotations_itemID_itemAttachments_itemID;
CREATE TRIGGER fku_annotations_itemID_itemAttachments_itemID
BEFORE UPDATE OF itemID ON annotations
FOR EACH ROW
BEGIN
SELECT RAISE(ABORT, 'update on table "annotations" violates foreign key constraint "fku_annotations_itemID_itemAttachments_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM itemAttachments WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_annotations_itemID_itemAttachments_itemID;
CREATE TRIGGER fkd_annotations_itemID_itemAttachments_itemID
BEFORE DELETE ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "itemAttachments" violates foreign key constraint "fkd_annotations_itemID_itemAttachments_itemID"')
WHERE (SELECT COUNT(*) FROM annotations WHERE itemID = OLD.itemID) > 0;
END;
-- collections/parentCollectionID
DROP TRIGGER IF EXISTS fki_collections_parentCollectionID_collections_collectionID;
CREATE TRIGGER fki_collections_parentCollectionID_collections_collectionID
BEFORE INSERT ON collections
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "collections" violates foreign key constraint "fki_collections_parentCollectionID_collections_collectionID"')
WHERE NEW.parentCollectionID IS NOT NULL AND (SELECT COUNT(*) FROM collections WHERE collectionID = NEW.parentCollectionID) = 0;
END;
DROP TRIGGER IF EXISTS fku_collections_parentCollectionID_collections_collectionID;
CREATE TRIGGER fku_collections_parentCollectionID_collections_collectionID
BEFORE UPDATE OF parentCollectionID ON collections
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "collections" violates foreign key constraint "fku_collections_parentCollectionID_collections_collectionID"')
WHERE NEW.parentCollectionID IS NOT NULL AND (SELECT COUNT(*) FROM collections WHERE collectionID = NEW.parentCollectionID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_collections_parentCollectionID_collections_collectionID;
CREATE TRIGGER fkd_collections_parentCollectionID_collections_collectionID
BEFORE DELETE ON collections
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "collections" violates foreign key constraint "fkd_collections_parentCollectionID_collections_collectionID"')
WHERE (SELECT COUNT(*) FROM collections WHERE parentCollectionID = OLD.collectionID) > 0;
END;
-- collectionItems/collectionID
DROP TRIGGER IF EXISTS fki_collectionItems_collectionID_collections_collectionID;
CREATE TRIGGER fki_collectionItems_collectionID_collections_collectionID
BEFORE INSERT ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "collectionItems" violates foreign key constraint "fki_collectionItems_collectionID_collections_collectionID"')
WHERE NEW.collectionID IS NOT NULL AND (SELECT COUNT(*) FROM collections WHERE collectionID = NEW.collectionID) = 0;
END;
DROP TRIGGER IF EXISTS fku_collectionItems_collectionID_collections_collectionID;
CREATE TRIGGER fku_collectionItems_collectionID_collections_collectionID
BEFORE UPDATE OF collectionID ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "collectionItems" violates foreign key constraint "fku_collectionItems_collectionID_collections_collectionID"')
WHERE NEW.collectionID IS NOT NULL AND (SELECT COUNT(*) FROM collections WHERE collectionID = NEW.collectionID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_collectionItems_collectionID_collections_collectionID;
CREATE TRIGGER fkd_collectionItems_collectionID_collections_collectionID
BEFORE DELETE ON collections
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "collections" violates foreign key constraint "fkd_collectionItems_collectionID_collections_collectionID"')
WHERE (SELECT COUNT(*) FROM collectionItems WHERE collectionID = OLD.collectionID) > 0;
END;
-- collectionItems/itemID
DROP TRIGGER IF EXISTS fki_collectionItems_itemID_items_itemID;
CREATE TRIGGER fki_collectionItems_itemID_items_itemID
BEFORE INSERT ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "collectionItems" violates foreign key constraint "fki_collectionItems_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_collectionItems_itemID_items_itemID;
CREATE TRIGGER fku_collectionItems_itemID_items_itemID
BEFORE UPDATE OF itemID ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "collectionItems" violates foreign key constraint "fku_collectionItems_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_collectionItems_itemID_items_itemID;
CREATE TRIGGER fkd_collectionItems_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_collectionItems_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM collectionItems WHERE itemID = OLD.itemID) > 0;
END;
-- creators/creatorDataID
DROP TRIGGER IF EXISTS fki_creators_creatorDataID_creatorData_creatorDataID;
CREATE TRIGGER fki_creators_creatorDataID_creatorData_creatorDataID
BEFORE INSERT ON creators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "creators" violates foreign key constraint "fki_creators_creatorDataID_creatorData_creatorDataID"')
WHERE NEW.creatorDataID IS NOT NULL AND (SELECT COUNT(*) FROM creatorData WHERE creatorDataID = NEW.creatorDataID) = 0;
END;
DROP TRIGGER IF EXISTS fku_creators_creatorDataID_creatorData_creatorDataID;
CREATE TRIGGER fku_creators_creatorDataID_creatorData_creatorDataID
BEFORE UPDATE OF creatorDataID ON creators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "creators" violates foreign key constraint "fku_creators_creatorDataID_creatorData_creatorDataID"')
WHERE NEW.creatorDataID IS NOT NULL AND (SELECT COUNT(*) FROM creatorData WHERE creatorDataID = NEW.creatorDataID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_creators_creatorDataID_creatorData_creatorDataID;
CREATE TRIGGER fkd_creators_creatorDataID_creatorData_creatorDataID
BEFORE DELETE ON creatorData
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "creatorData" violates foreign key constraint "fkd_creators_creatorDataID_creatorData_creatorDataID"')
WHERE (SELECT COUNT(*) FROM creators WHERE creatorDataID = OLD.creatorDataID) > 0;
END;
-- fulltextItems/itemID
DROP TRIGGER IF EXISTS fki_fulltextItems_itemID_items_itemID;
CREATE TRIGGER fki_fulltextItems_itemID_items_itemID
BEFORE INSERT ON fulltextItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "fulltextItems" violates foreign key constraint "fki_fulltextItems_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_fulltextItems_itemID_items_itemID;
CREATE TRIGGER fku_fulltextItems_itemID_items_itemID
BEFORE UPDATE OF itemID ON fulltextItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "fulltextItems" violates foreign key constraint "fku_fulltextItems_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_fulltextItems_itemID_items_itemID;
CREATE TRIGGER fkd_fulltextItems_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_fulltextItems_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM fulltextItems WHERE itemID = OLD.itemID) > 0;
END;
-- fulltextItemWords/wordID
DROP TRIGGER IF EXISTS fki_fulltextItemWords_wordID_fulltextWords_wordID;
CREATE TRIGGER fki_fulltextItemWords_wordID_fulltextWords_wordID
BEFORE INSERT ON fulltextItemWords
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "fulltextItemWords" violates foreign key constraint "fki_fulltextItemWords_wordID_fulltextWords_wordID"')
WHERE NEW.wordID IS NOT NULL AND (SELECT COUNT(*) FROM fulltextWords WHERE wordID = NEW.wordID) = 0;
END;
DROP TRIGGER IF EXISTS fku_fulltextItemWords_wordID_fulltextWords_wordID;
CREATE TRIGGER fku_fulltextItemWords_wordID_fulltextWords_wordID
BEFORE UPDATE OF wordID ON fulltextItemWords
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "fulltextItemWords" violates foreign key constraint "fku_fulltextItemWords_wordID_fulltextWords_wordID"')
WHERE NEW.wordID IS NOT NULL AND (SELECT COUNT(*) FROM fulltextWords WHERE wordID = NEW.wordID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_fulltextItemWords_wordID_fulltextWords_wordID;
CREATE TRIGGER fkd_fulltextItemWords_wordID_fulltextWords_wordID
BEFORE DELETE ON fulltextWords
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "fulltextWords" violates foreign key constraint "fkd_fulltextItemWords_wordID_fulltextWords_wordID"')
WHERE (SELECT COUNT(*) FROM fulltextItemWords WHERE wordID = OLD.wordID) > 0;
END;
-- fulltextItemWords/itemID
DROP TRIGGER IF EXISTS fki_fulltextItemWords_itemID_items_itemID;
CREATE TRIGGER fki_fulltextItemWords_itemID_items_itemID
BEFORE INSERT ON fulltextItemWords
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "fulltextItemWords" violates foreign key constraint "fki_fulltextItemWords_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_fulltextItemWords_itemID_items_itemID;
CREATE TRIGGER fku_fulltextItemWords_itemID_items_itemID
BEFORE UPDATE OF itemID ON fulltextItemWords
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "fulltextItemWords" violates foreign key constraint "fku_fulltextItemWords_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_fulltextItemWords_itemID_items_itemID;
CREATE TRIGGER fkd_fulltextItemWords_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_fulltextItemWords_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM fulltextItemWords WHERE itemID = OLD.itemID) > 0;
END;
-- highlights/itemID
DROP TRIGGER IF EXISTS fki_highlights_itemID_itemAttachments_itemID;
CREATE TRIGGER fki_highlights_itemID_itemAttachments_itemID
BEFORE INSERT ON highlights
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "highlights" violates foreign key constraint "fki_highlights_itemID_itemAttachments_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM itemAttachments WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_highlights_itemID_itemAttachments_itemID;
CREATE TRIGGER fku_highlights_itemID_itemAttachments_itemID
BEFORE UPDATE OF itemID ON highlights
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "highlights" violates foreign key constraint "fku_highlights_itemID_itemAttachments_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM itemAttachments WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_highlights_itemID_itemAttachments_itemID;
CREATE TRIGGER fkd_highlights_itemID_itemAttachments_itemID
BEFORE DELETE ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "itemAttachments" violates foreign key constraint "fkd_highlights_itemID_itemAttachments_itemID"')
WHERE (SELECT COUNT(*) FROM highlights WHERE itemID = OLD.itemID) > 0;
END;
-- itemAttachments/itemID
DROP TRIGGER IF EXISTS fki_itemAttachments_itemID_items_itemID;
CREATE TRIGGER fki_itemAttachments_itemID_items_itemID
BEFORE INSERT ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemAttachments" violates foreign key constraint "fki_itemAttachments_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_items_itemID;
CREATE TRIGGER fku_itemAttachments_itemID_items_itemID
BEFORE UPDATE OF itemID ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemAttachments" violates foreign key constraint "fku_itemAttachments_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemAttachments_itemID_items_itemID;
CREATE TRIGGER fkd_itemAttachments_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemAttachments_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM itemAttachments WHERE itemID = OLD.itemID) > 0;
END;
-- itemAttachments/sourceItemID
DROP TRIGGER IF EXISTS fki_itemAttachments_sourceItemID_items_itemID;
CREATE TRIGGER fki_itemAttachments_sourceItemID_items_itemID
BEFORE INSERT ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemAttachments" violates foreign key constraint "fki_itemAttachments_sourceItemID_items_sourceItemID"')
WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.sourceItemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemAttachments_sourceItemID_items_itemID;
CREATE TRIGGER fku_itemAttachments_sourceItemID_items_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_items_sourceItemID"')
WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.sourceItemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemAttachments_sourceItemID_items_itemID;
CREATE TRIGGER fkd_itemAttachments_sourceItemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemAttachments_sourceItemID_items_sourceItemID"')
WHERE (SELECT COUNT(*) FROM itemAttachments WHERE sourceItemID = OLD.itemID) > 0;
END;
-- itemCreators/itemID
DROP TRIGGER IF EXISTS fki_itemCreators_itemID_items_itemID;
CREATE TRIGGER fki_itemCreators_itemID_items_itemID
BEFORE INSERT ON itemCreators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemCreators" violates foreign key constraint "fki_itemCreators_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemCreators_itemID_items_itemID;
CREATE TRIGGER fku_itemCreators_itemID_items_itemID
BEFORE UPDATE OF itemID ON itemCreators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemCreators" violates foreign key constraint "fku_itemCreators_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemCreators_itemID_items_itemID;
CREATE TRIGGER fkd_itemCreators_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemCreators_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM itemCreators WHERE itemID = OLD.itemID) > 0;
END;
-- itemCreators/creatorID
DROP TRIGGER IF EXISTS fki_itemCreators_creatorID_creators_creatorID;
CREATE TRIGGER fki_itemCreators_creatorID_creators_creatorID
BEFORE INSERT ON itemCreators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemCreators" violates foreign key constraint "fki_itemCreators_creatorID_creators_creatorID"')
WHERE NEW.creatorID IS NOT NULL AND (SELECT COUNT(*) FROM creators WHERE creatorID = NEW.creatorID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemCreators_creatorID_creators_creatorID;
CREATE TRIGGER fku_itemCreators_creatorID_creators_creatorID
BEFORE UPDATE OF creatorID ON itemCreators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemCreators" violates foreign key constraint "fku_itemCreators_creatorID_creators_creatorID"')
WHERE NEW.creatorID IS NOT NULL AND (SELECT COUNT(*) FROM creators WHERE creatorID = NEW.creatorID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemCreators_creatorID_creators_creatorID;
CREATE TRIGGER fkd_itemCreators_creatorID_creators_creatorID
BEFORE DELETE ON creators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "creators" violates foreign key constraint "fkd_itemCreators_creatorID_creators_creatorID"')
WHERE (SELECT COUNT(*) FROM itemCreators WHERE creatorID = OLD.creatorID) > 0;
END;
-- itemCreators/creatorTypeID
DROP TRIGGER IF EXISTS fki_itemCreators_creatorTypeID_creatorTypes_creatorTypeID;
CREATE TRIGGER fki_itemCreators_creatorTypeID_creatorTypes_creatorTypeID
BEFORE INSERT ON itemCreators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemCreators" violates foreign key constraint "fki_itemCreators_creatorTypeID_creatorTypes_creatorTypeID"')
WHERE NEW.creatorTypeID IS NOT NULL AND (SELECT COUNT(*) FROM creatorTypes WHERE creatorTypeID = NEW.creatorTypeID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemCreators_creatorTypeID_creatorTypes_creatorTypeID;
CREATE TRIGGER fku_itemCreators_creatorTypeID_creatorTypes_creatorTypeID
BEFORE UPDATE OF creatorTypeID ON itemCreators
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemCreators" violates foreign key constraint "fku_itemCreators_creatorTypeID_creatorTypes_creatorTypeID"')
WHERE NEW.creatorTypeID IS NOT NULL AND (SELECT COUNT(*) FROM creatorTypes WHERE creatorTypeID = NEW.creatorTypeID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemCreators_creatorTypeID_creatorTypes_creatorTypeID;
CREATE TRIGGER fkd_itemCreators_creatorTypeID_creatorTypes_creatorTypeID
BEFORE DELETE ON creatorTypes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "creatorTypes" violates foreign key constraint "fkd_itemCreators_creatorTypeID_creatorTypes_creatorTypeID"')
WHERE (SELECT COUNT(*) FROM itemCreators WHERE creatorTypeID = OLD.creatorTypeID) > 0;
END;
-- itemData/itemID
DROP TRIGGER IF EXISTS fki_itemData_itemID_items_itemID;
CREATE TRIGGER fki_itemData_itemID_items_itemID
BEFORE INSERT ON itemData
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemData" violates foreign key constraint "fki_itemData_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemData_itemID_items_itemID;
CREATE TRIGGER fku_itemData_itemID_items_itemID
BEFORE UPDATE OF itemID ON itemData
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemData" violates foreign key constraint "fku_itemData_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemData_itemID_items_itemID;
CREATE TRIGGER fkd_itemData_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemData_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM itemData WHERE itemID = OLD.itemID) > 0;
END;
-- itemData/fieldID
DROP TRIGGER IF EXISTS fki_itemData_fieldID_fields_fieldID;
CREATE TRIGGER fki_itemData_fieldID_fields_fieldID
BEFORE INSERT ON itemData
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemData" violates foreign key constraint "fki_itemData_fieldID_fields_fieldID"')
WHERE NEW.fieldID IS NOT NULL AND (SELECT COUNT(*) FROM fields WHERE fieldID = NEW.fieldID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemData_fieldID_fields_fieldID;
CREATE TRIGGER fku_itemData_fieldID_fields_fieldID
BEFORE UPDATE OF fieldID ON itemData
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemData" violates foreign key constraint "fku_itemData_fieldID_fields_fieldID"')
WHERE NEW.fieldID IS NOT NULL AND (SELECT COUNT(*) FROM fields WHERE fieldID = NEW.fieldID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemData_fieldID_fields_fieldID;
CREATE TRIGGER fkd_itemData_fieldID_fields_fieldID
BEFORE DELETE ON FIELDS
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "fields" violates foreign key constraint "fkd_itemData_fieldID_fields_fieldID"')
WHERE (SELECT COUNT(*) FROM itemData WHERE fieldID = OLD.fieldID) > 0;
END;
-- itemData/valueID
DROP TRIGGER IF EXISTS fki_itemData_valueID_itemDataValues_valueID;
CREATE TRIGGER fki_itemData_valueID_itemDataValues_valueID
BEFORE INSERT ON itemData
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemData" violates foreign key constraint "fki_itemData_valueID_itemDataValues_valueID"')
WHERE NEW.valueID IS NOT NULL AND (SELECT COUNT(*) FROM itemDataValues WHERE valueID = NEW.valueID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemData_valueID_itemDataValues_valueID;
CREATE TRIGGER fku_itemData_valueID_itemDataValues_valueID
BEFORE UPDATE OF valueID ON itemData
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemData" violates foreign key constraint "fku_itemData_valueID_itemDataValues_valueID"')
WHERE NEW.valueID IS NOT NULL AND (SELECT COUNT(*) FROM itemDataValues WHERE valueID = NEW.valueID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemData_valueID_itemDataValues_valueID;
CREATE TRIGGER fkd_itemData_valueID_itemDataValues_valueID
BEFORE DELETE ON itemDataValues
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "itemDataValues" violates foreign key constraint "fkd_itemData_valueID_itemDataValues_valueID"')
WHERE (SELECT COUNT(*) FROM itemData WHERE valueID = OLD.valueID) > 0;
END;
-- itemNotes/itemID
DROP TRIGGER IF EXISTS fki_itemNotes_itemID_items_itemID;
CREATE TRIGGER fki_itemNotes_itemID_items_itemID
BEFORE INSERT ON itemNotes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemNotes" violates foreign key constraint "fki_itemNotes_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemNotes_itemID_items_itemID;
CREATE TRIGGER fku_itemNotes_itemID_items_itemID
BEFORE UPDATE OF itemID ON itemNotes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemNotes" violates foreign key constraint "fku_itemNotes_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemNotes_itemID_items_itemID;
CREATE TRIGGER fkd_itemNotes_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemNotes_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM itemNotes WHERE itemID = OLD.itemID) > 0;
END;
-- itemNotes/sourceItemID
DROP TRIGGER IF EXISTS fki_itemNotes_sourceItemID_items_itemID;
CREATE TRIGGER fki_itemNotes_sourceItemID_items_itemID
BEFORE INSERT ON itemNotes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemNotes" violates foreign key constraint "fki_itemNotes_sourceItemID_items_itemID"')
WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.sourceItemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemNotes_sourceItemID_items_itemID;
CREATE TRIGGER fku_itemNotes_sourceItemID_items_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_items_itemID"')
WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.sourceItemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemNotes_sourceItemID_items_itemID;
CREATE TRIGGER fkd_itemNotes_sourceItemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemNotes_sourceItemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM itemNotes WHERE sourceItemID = OLD.itemID) > 0;
END;
-- itemSeeAlso/itemID
DROP TRIGGER IF EXISTS fki_itemSeeAlso_itemID_items_itemID;
CREATE TRIGGER fki_itemSeeAlso_itemID_items_itemID
BEFORE INSERT ON itemSeeAlso
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemSeeAlso" violates foreign key constraint "fki_itemSeeAlso_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemSeeAlso_itemID_items_itemID;
CREATE TRIGGER fku_itemSeeAlso_itemID_items_itemID
BEFORE UPDATE OF itemID ON itemSeeAlso
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemSeeAlso" violates foreign key constraint "fku_itemSeeAlso_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemSeeAlso_itemID_items_itemID;
CREATE TRIGGER fkd_itemSeeAlso_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemSeeAlso_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM itemSeeAlso WHERE itemID = OLD.itemID) > 0;
END;
-- itemSeeAlso/linkedItemID
DROP TRIGGER IF EXISTS fki_itemSeeAlso_linkedItemID_items_itemID;
CREATE TRIGGER fki_itemSeeAlso_linkedItemID_items_itemID
BEFORE INSERT ON itemSeeAlso
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemSeeAlso" violates foreign key constraint "fki_itemSeeAlso_linkedItemID_items_itemID"')
WHERE NEW.linkedItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.linkedItemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemSeeAlso_linkedItemID_items_itemID;
CREATE TRIGGER fku_itemSeeAlso_linkedItemID_items_itemID
BEFORE UPDATE OF linkedItemID ON itemSeeAlso
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemSeeAlso" violates foreign key constraint "fku_itemSeeAlso_linkedItemID_items_itemID"')
WHERE NEW.linkedItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.linkedItemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemSeeAlso_linkedItemID_items_itemID;
CREATE TRIGGER fkd_itemSeeAlso_linkedItemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemSeeAlso_linkedItemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM itemSeeAlso WHERE linkedItemID = OLD.itemID) > 0;
END;
-- itemTags/itemID
DROP TRIGGER IF EXISTS fki_itemTags_itemID_items_itemID;
CREATE TRIGGER fki_itemTags_itemID_items_itemID
BEFORE INSERT ON itemTags
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemTags" violates foreign key constraint "fki_itemTags_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemTags_itemID_items_itemID;
CREATE TRIGGER fku_itemTags_itemID_items_itemID
BEFORE UPDATE OF itemID ON itemTags
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemTags" violates foreign key constraint "fku_itemTags_itemID_items_itemID"')
WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemTags_itemID_items_itemID;
CREATE TRIGGER fkd_itemTags_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemTags_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM itemTags WHERE itemID = OLD.itemID) > 0;
END;
-- itemTags/tagID
DROP TRIGGER IF EXISTS fki_itemTags_tagID_tags_tagID;
CREATE TRIGGER fki_itemTags_tagID_tags_tagID
BEFORE INSERT ON itemTags
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemTags" violates foreign key constraint "fki_itemTags_tagID_tags_tagID"')
WHERE NEW.tagID IS NOT NULL AND (SELECT COUNT(*) FROM tags WHERE tagID = NEW.tagID) = 0;
END;
DROP TRIGGER IF EXISTS fku_itemTags_tagID_tags_tagID;
CREATE TRIGGER fku_itemTags_tagID_tags_tagID
BEFORE UPDATE OF tagID ON itemTags
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemTags" violates foreign key constraint "fku_itemTags_tagID_tags_tagID"')
WHERE NEW.tagID IS NOT NULL AND (SELECT COUNT(*) FROM tags WHERE tagID = NEW.tagID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_itemTags_tagID_tags_tagID;
CREATE TRIGGER fkd_itemTags_tagID_tags_tagID
BEFORE DELETE ON tags
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "tags" violates foreign key constraint "fkd_itemTags_tagID_tags_tagID"')
WHERE (SELECT COUNT(*) FROM itemTags WHERE tagID = OLD.tagID) > 0;
END;
-- savedSearchConditions/searchConditionID
DROP TRIGGER IF EXISTS fki_savedSearchConditions_searchConditionID_savedSearches_savedSearchID;
CREATE TRIGGER fki_savedSearchConditions_searchConditionID_savedSearches_savedSearchID
BEFORE INSERT ON savedSearchConditions
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "savedSearchConditions" violates foreign key constraint "fki_savedSearchConditions_searchConditionID_savedSearches_savedSearchID"')
WHERE NEW.searchConditionID IS NOT NULL AND (SELECT COUNT(*) FROM savedSearches WHERE savedSearchID = NEW.searchConditionID) = 0;
END;
DROP TRIGGER IF EXISTS fku_savedSearchConditions_searchConditionID_savedSearches_savedSearchID;
CREATE TRIGGER fku_savedSearchConditions_searchConditionID_savedSearches_savedSearchID
BEFORE UPDATE OF searchConditionID ON savedSearchConditions
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "savedSearchConditions" violates foreign key constraint "fku_savedSearchConditions_searchConditionID_savedSearches_savedSearchID"')
WHERE NEW.searchConditionID IS NOT NULL AND (SELECT COUNT(*) FROM savedSearches WHERE savedSearchID = NEW.searchConditionID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_savedSearchConditions_searchConditionID_savedSearches_savedSearchID;
CREATE TRIGGER fkd_savedSearchConditions_searchConditionID_savedSearches_savedSearchID
BEFORE DELETE ON savedSearches
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "savedSearches" violates foreign key constraint "fkd_savedSearchConditions_searchConditionID_savedSearches_savedSearchID"')
WHERE (SELECT COUNT(*) FROM savedSearchConditions WHERE searchConditionID = OLD.savedSearchID) > 0;
END;
-- syncDeleteLog/syncObjectTypeID
DROP TRIGGER IF EXISTS fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID;
CREATE TRIGGER fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID
BEFORE INSERT ON syncDeleteLog
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "syncDeleteLog" violates foreign key constraint "fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID"')
WHERE (SELECT COUNT(*) FROM syncObjectTypes WHERE syncObjectTypeID = NEW.syncObjectTypeID) = 0;
END;
DROP TRIGGER IF EXISTS fku_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID;
CREATE TRIGGER fku_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID
BEFORE UPDATE OF syncObjectTypeID ON syncDeleteLog
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "syncDeleteLog" violates foreign key constraint "fku_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID"')
WHERE (SELECT COUNT(*) FROM syncObjectTypes WHERE syncObjectTypeID = NEW.syncObjectTypeID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID;
CREATE TRIGGER fkd_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID
BEFORE DELETE ON syncObjectTypes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "syncObjectTypes" violates foreign key constraint "fkd_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID"')
WHERE (SELECT COUNT(*) FROM syncDeleteLog WHERE syncObjectTypeID = OLD.syncObjectTypeID) > 0;
END;

View file

@ -1,78 +1,38 @@
-- 36
-- 37
-- This file creates tables containing user-specific data -- any changes
-- to existing tables made here must be mirrored in transition steps in
-- schema.js::_migrateSchema()
-- This file creates tables containing user-specific data -- any changes made
-- here must be mirrored in transition steps in schema.js::_migrateSchema()
CREATE TABLE IF NOT EXISTS version (
CREATE TABLE version (
schema TEXT PRIMARY KEY,
version INT NOT NULL
);
CREATE INDEX IF NOT EXISTS schema ON version(schema);
CREATE INDEX schema ON version(schema);
CREATE TABLE IF NOT EXISTS settings (
CREATE TABLE settings (
setting TEXT,
key TEXT,
value,
PRIMARY KEY (setting, key)
);
-- Show or hide pre-mapped fields for system item types
CREATE TABLE IF NOT EXISTS userFieldMask (
itemTypeID INT,
fieldID INT,
hide INT,
PRIMARY KEY (itemTypeID, fieldID),
FOREIGN KEY (itemTypeID, fieldID) REFERENCES itemTypeFields(itemTypeID, fieldID)
);
-- User-defined item types -- itemTypeIDs must be >= 1000
CREATE TABLE IF NOT EXISTS userItemTypes (
itemTypeID INTEGER PRIMARY KEY,
typeName TEXT,
templateItemTypeID INT
);
-- Control visibility and placement of system and user item types
CREATE TABLE IF NOT EXISTS userItemTypeMask (
itemTypeID INTEGER PRIMARY KEY,
display INT, -- 0 == hide, 1 == show, 2 == primary
FOREIGN KEY (itemTypeID) REFERENCES userItemTypes(itemTypeID)
);
-- User-defined fields
CREATE TABLE IF NOT EXISTS userFields (
userFieldID INTEGER PRIMARY KEY,
fieldName TEXT
);
-- Map custom fields to system and custom item types
CREATE TABLE IF NOT EXISTS userItemTypeFields (
itemTypeID INT,
userFieldID INT,
orderIndex INT,
PRIMARY KEY (itemTypeID, userFieldID),
FOREIGN KEY (userFieldID) REFERENCES userFields(userFieldID)
);
-- The foundational table; every item collected has a unique record here
CREATE TABLE IF NOT EXISTS items (
CREATE TABLE items (
itemID INTEGER PRIMARY KEY,
itemTypeID INT,
dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP,
dateModified DATETIME DEFAULT CURRENT_TIMESTAMP
dateModified DATETIME DEFAULT CURRENT_TIMESTAMP,
key TEXT NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS itemDataValues (
CREATE TABLE itemDataValues (
valueID INTEGER PRIMARY KEY,
value
);
-- Type-specific data for individual items
--
-- Triggers specified in schema.js due to lack of trigger IF [NOT] EXISTS in Firefox 2.0
CREATE TABLE IF NOT EXISTS itemData (
CREATE TABLE itemData (
itemID INT,
fieldID INT,
valueID,
@ -83,23 +43,18 @@ CREATE TABLE IF NOT EXISTS itemData (
);
-- Note data for note items
CREATE TABLE IF NOT EXISTS itemNotes (
CREATE TABLE itemNotes (
itemID INTEGER PRIMARY KEY,
sourceItemID INT,
note TEXT,
title TEXT,
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (sourceItemID) REFERENCES items(itemID)
);
CREATE INDEX IF NOT EXISTS itemNotes_sourceItemID ON itemNotes(sourceItemID);
CREATE TABLE IF NOT EXISTS itemNoteTitles (
itemID INTEGER PRIMARY KEY,
title TEXT,
FOREIGN KEY (itemID) REFERENCES itemNotes(itemID)
);
CREATE INDEX itemNotes_sourceItemID ON itemNotes(sourceItemID);
-- Metadata for attachment items
CREATE TABLE IF NOT EXISTS itemAttachments (
CREATE TABLE itemAttachments (
itemID INTEGER PRIMARY KEY,
sourceItemID INT,
linkMode INT,
@ -110,11 +65,11 @@ CREATE TABLE IF NOT EXISTS itemAttachments (
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (sourceItemID) REFERENCES items(sourceItemID)
);
CREATE INDEX IF NOT EXISTS itemAttachments_sourceItemID ON itemAttachments(sourceItemID);
CREATE INDEX IF NOT EXISTS itemAttachments_mimeType ON itemAttachments(mimeType);
CREATE INDEX itemAttachments_sourceItemID ON itemAttachments(sourceItemID);
CREATE INDEX itemAttachments_mimeType ON itemAttachments(mimeType);
-- Individual entries for each tag
CREATE TABLE IF NOT EXISTS tags (
CREATE TABLE tags (
tagID INTEGER PRIMARY KEY,
tag TEXT,
tagType INT,
@ -122,34 +77,46 @@ CREATE TABLE IF NOT EXISTS tags (
);
-- Associates items with keywords
CREATE TABLE IF NOT EXISTS itemTags (
CREATE TABLE itemTags (
itemID INT,
tagID INT,
PRIMARY KEY (itemID, tagID),
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (tagID) REFERENCES tags(tagID)
);
CREATE INDEX IF NOT EXISTS itemTags_tagID ON itemTags(tagID);
CREATE INDEX itemTags_tagID ON itemTags(tagID);
CREATE TABLE IF NOT EXISTS itemSeeAlso (
CREATE TABLE itemSeeAlso (
itemID INT,
linkedItemID INT,
PRIMARY KEY (itemID, linkedItemID),
FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (linkedItemID) REFERENCES items(itemID)
);
CREATE INDEX IF NOT EXISTS itemSeeAlso_linkedItemID ON itemSeeAlso(linkedItemID);
CREATE INDEX itemSeeAlso_linkedItemID ON itemSeeAlso(linkedItemID);
-- Names of each individual "creator" (inc. authors, editors, etc.)
CREATE TABLE IF NOT EXISTS creators (
CREATE TABLE creators (
creatorID INTEGER PRIMARY KEY,
creatorDataID INT,
dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,
key TEXT NOT NULL UNIQUE,
FOREIGN KEY (creatorDataID) REFERENCES creatorData(creatorDataID)
);
CREATE INDEX creators_creatorDataID ON creators(creatorDataID);
-- Each individual creator
CREATE TABLE creatorData (
creatorDataID INTEGER PRIMARY KEY,
firstName TEXT,
lastName TEXT,
fieldMode INT
shortName TEXT,
fieldMode INT,
birthYear INT
);
-- Associates single or multiple creators to items
CREATE TABLE IF NOT EXISTS itemCreators (
CREATE TABLE itemCreators (
itemID INT,
creatorID INT,
creatorTypeID INT DEFAULT 1,
@ -161,15 +128,17 @@ CREATE TABLE IF NOT EXISTS itemCreators (
);
-- Collections for holding items
CREATE TABLE IF NOT EXISTS collections (
CREATE TABLE collections (
collectionID INTEGER PRIMARY KEY,
collectionName TEXT,
parentCollectionID INT,
dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,
key TEXT NOT NULL UNIQUE,
FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID)
);
-- Associates items with the various collections they belong to
CREATE TABLE IF NOT EXISTS collectionItems (
CREATE TABLE collectionItems (
collectionID INT,
itemID INT,
orderIndex INT DEFAULT 0,
@ -177,14 +146,16 @@ CREATE TABLE IF NOT EXISTS collectionItems (
FOREIGN KEY (collectionID) REFERENCES collections(collectionID),
FOREIGN KEY (itemID) REFERENCES items(itemID)
);
CREATE INDEX IF NOT EXISTS itemID ON collectionItems(itemID);
CREATE INDEX itemID ON collectionItems(itemID);
CREATE TABLE IF NOT EXISTS savedSearches (
CREATE TABLE savedSearches (
savedSearchID INTEGER PRIMARY KEY,
savedSearchName TEXT
savedSearchName TEXT,
dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,
key TEXT NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS savedSearchConditions (
CREATE TABLE savedSearchConditions (
savedSearchID INT,
searchConditionID INT,
condition TEXT,
@ -195,7 +166,7 @@ CREATE TABLE IF NOT EXISTS savedSearchConditions (
FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID)
);
CREATE TABLE IF NOT EXISTS fulltextItems (
CREATE TABLE fulltextItems (
itemID INTEGER PRIMARY KEY,
version INT,
indexedPages INT,
@ -204,24 +175,33 @@ CREATE TABLE IF NOT EXISTS fulltextItems (
totalChars INT,
FOREIGN KEY (itemID) REFERENCES items(itemID)
);
CREATE INDEX IF NOT EXISTS fulltextItems_version ON fulltextItems(version);
CREATE INDEX fulltextItems_version ON fulltextItems(version);
CREATE TABLE IF NOT EXISTS fulltextWords (
CREATE TABLE fulltextWords (
wordID INTEGER PRIMARY KEY,
word TEXT UNIQUE
);
CREATE INDEX IF NOT EXISTS fulltextWords_word ON fulltextWords(word);
CREATE INDEX fulltextWords_word ON fulltextWords(word);
CREATE TABLE IF NOT EXISTS fulltextItemWords (
CREATE TABLE fulltextItemWords (
wordID INT,
itemID INT,
PRIMARY KEY (wordID, itemID),
FOREIGN KEY (wordID) REFERENCES fulltextWords(wordID),
FOREIGN KEY (itemID) REFERENCES items(itemID)
);
CREATE INDEX IF NOT EXISTS fulltextItemWords_itemID ON fulltextItemWords(itemID);
CREATE INDEX fulltextItemWords_itemID ON fulltextItemWords(itemID);
CREATE TABLE IF NOT EXISTS translators (
CREATE TABLE syncDeleteLog (
syncObjectTypeID INT NOT NULL,
objectID INT NOT NULL,
key TEXT NOT NULL UNIQUE,
timestamp INT NOT NULL,
FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)
);
CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp);
CREATE TABLE translators (
translatorID TEXT PRIMARY KEY,
minVersion TEXT,
maxVersion TEXT,
@ -235,16 +215,16 @@ CREATE TABLE IF NOT EXISTS translators (
detectCode TEXT,
code TEXT
);
CREATE INDEX IF NOT EXISTS translators_type ON translators(translatorType);
CREATE INDEX translators_type ON translators(translatorType);
CREATE TABLE IF NOT EXISTS csl (
CREATE TABLE csl (
cslID TEXT PRIMARY KEY,
updated DATETIME,
title TEXT,
csl TEXT
);
CREATE TABLE IF NOT EXISTS annotations (
CREATE TABLE annotations (
annotationID INTEGER PRIMARY KEY,
itemID INT,
parent TEXT,
@ -259,9 +239,9 @@ CREATE TABLE IF NOT EXISTS annotations (
dateModified DATE,
FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)
);
CREATE INDEX IF NOT EXISTS annotations_itemID ON annotations(itemID);
CREATE INDEX annotations_itemID ON annotations(itemID);
CREATE TABLE IF NOT EXISTS highlights (
CREATE TABLE highlights (
highlightID INTEGER PRIMARY KEY,
itemID INTEGER,
startParent TEXT,
@ -273,4 +253,4 @@ CREATE TABLE IF NOT EXISTS highlights (
dateModified DATE,
FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)
);
CREATE INDEX IF NOT EXISTS highlights_itemID ON highlights(itemID);
CREATE INDEX highlights_itemID ON highlights(itemID);