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:
parent
ca91b534f2
commit
3de1789f26
59 changed files with 14186 additions and 7494 deletions
|
@ -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;
|
||||
}
|
31
chrome/content/zotero-platform/win/itembox.css
Normal file
31
chrome/content/zotero-platform/win/itembox.css
Normal 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;
|
||||
}
|
2076
chrome/content/zotero/bindings/itembox.xml
Normal file
2076
chrome/content/zotero/bindings/itembox.xml
Normal file
File diff suppressed because it is too large
Load diff
443
chrome/content/zotero/bindings/merge.xml
Normal file
443
chrome/content/zotero/bindings/merge.xml
Normal 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>
|
|
@ -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"/>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
@ -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"/>
|
||||
|
|
214
chrome/content/zotero/merge.js
Normal file
214
chrome/content/zotero/merge.js
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
chrome/content/zotero/merge.xul
Normal file
53
chrome/content/zotero/merge.xul
Normal 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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
273
chrome/content/zotero/xpcom/data/cachedTypes.js
Normal file
273
chrome/content/zotero/xpcom/data/cachedTypes.js
Normal 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();
|
||||
}
|
||||
}
|
||||
|
930
chrome/content/zotero/xpcom/data/collection.js
Normal file
930
chrome/content/zotero/xpcom/data/collection.js
Normal 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();
|
||||
}
|
184
chrome/content/zotero/xpcom/data/collections.js
Normal file
184
chrome/content/zotero/xpcom/data/collections.js
Normal 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]];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
451
chrome/content/zotero/xpcom/data/creator.js
Normal file
451
chrome/content/zotero/xpcom/data/creator.js
Normal 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()");
|
||||
}
|
346
chrome/content/zotero/xpcom/data/creators.js
Normal file
346
chrome/content/zotero/xpcom/data/creators.js
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3392
chrome/content/zotero/xpcom/data/item.js
Normal file
3392
chrome/content/zotero/xpcom/data/item.js
Normal file
File diff suppressed because it is too large
Load diff
391
chrome/content/zotero/xpcom/data/itemFields.js
Normal file
391
chrome/content/zotero/xpcom/data/itemFields.js
Normal 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();
|
||||
}
|
||||
}
|
||||
|
577
chrome/content/zotero/xpcom/data/items.js
Normal file
577
chrome/content/zotero/xpcom/data/items.js
Normal 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'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
43
chrome/content/zotero/xpcom/data/notes.js
Normal file
43
chrome/content/zotero/xpcom/data/notes.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
418
chrome/content/zotero/xpcom/data/tags.js
Normal file
418
chrome/content/zotero/xpcom/data/tags.js
Normal 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
|
@ -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');
|
||||
|
|
|
@ -113,7 +113,7 @@ Zotero.File = new function(){
|
|||
|
||||
is.close();
|
||||
|
||||
return contents.join();
|
||||
return contents.join('');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
286
chrome/content/zotero/xpcom/id.js
Normal file
286
chrome/content/zotero/xpcom/id.js
Normal 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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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){}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
1852
chrome/content/zotero/xpcom/sync.js
Normal file
1852
chrome/content/zotero/xpcom/sync.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
104
chrome/skin/default/zotero/bindings/itembox.css
Normal file
104
chrome/skin/default/zotero/bindings/itembox.css
Normal 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;
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
97
chrome/skin/default/zotero/merge.css
Normal file
97
chrome/skin/default/zotero/merge.css
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
|
BIN
chrome/skin/default/zotero/prefs-sync.png
Normal file
BIN
chrome/skin/default/zotero/prefs-sync.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
|
@ -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;
|
||||
|
|
|
@ -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)";
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
14
system.sql
14
system.sql
|
@ -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
659
triggers.sql
Normal 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;
|
160
userdata.sql
160
userdata.sql
|
@ -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);
|
Loading…
Reference in a new issue