- Fixes tag editing
- Adds tag syncing - Fixes a few other things No tag CR yet Requires new 1.0 DB upgrade
This commit is contained in:
parent
eb134e6fe4
commit
c52604883f
|
@ -28,16 +28,49 @@
|
||||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
<binding id="tags-box">
|
<binding id="tags-box">
|
||||||
<implementation>
|
<implementation>
|
||||||
<field name="itemRef"/>
|
<field name="clickHandler"/>
|
||||||
<property name="item" onget="return this.itemRef;">
|
|
||||||
|
<!-- Modes are predefined settings groups for particular tasks -->
|
||||||
|
<field name="_mode">"view"</field>
|
||||||
|
<property name="mode" onget="return this._mode;">
|
||||||
<setter>
|
<setter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
this.itemRef = val;
|
this.clickable = false;
|
||||||
|
this.editable = false;
|
||||||
|
|
||||||
|
switch (val) {
|
||||||
|
case 'view':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit':
|
||||||
|
this.clickable = true;
|
||||||
|
this.editable = true;
|
||||||
|
this.clickHandler = this.showEditor;
|
||||||
|
this.blurHandler = this.hideEditor;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw ("Invalid mode '" + val + "' in tagsbox.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._mode = val;
|
||||||
|
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
|
||||||
|
]]>
|
||||||
|
</setter>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<field name="_item"/>
|
||||||
|
<property name="item" onget="return this._item;">
|
||||||
|
<setter>
|
||||||
|
<![CDATA[
|
||||||
|
this._item = val;
|
||||||
this.reload();
|
this.reload();
|
||||||
]]>
|
]]>
|
||||||
</setter>
|
</setter>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<property name="count"/>
|
<property name="count"/>
|
||||||
|
|
||||||
<property name="summary">
|
<property name="summary">
|
||||||
<getter>
|
<getter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
@ -50,7 +83,7 @@
|
||||||
{
|
{
|
||||||
for(var i = 0; i < tags.length; i++)
|
for(var i = 0; i < tags.length; i++)
|
||||||
{
|
{
|
||||||
r = r + tags[i].tag + ", ";
|
r = r + tags[i].name + ", ";
|
||||||
}
|
}
|
||||||
r = r.substr(0,r.length-2);
|
r = r.substr(0,r.length-2);
|
||||||
}
|
}
|
||||||
|
@ -60,10 +93,13 @@
|
||||||
]]>
|
]]>
|
||||||
</getter>
|
</getter>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<method name="reload">
|
<method name="reload">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
//Zotero.debug('Reloading tags');
|
Zotero.debug('Reloading tags');
|
||||||
|
|
||||||
var rows = this.id('tagRows');
|
var rows = this.id('tagRows');
|
||||||
while(rows.hasChildNodes())
|
while(rows.hasChildNodes())
|
||||||
|
@ -88,6 +124,8 @@
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="addDynamicRow">
|
<method name="addDynamicRow">
|
||||||
<parameter name="tagObj"/>
|
<parameter name="tagObj"/>
|
||||||
<parameter name="tabindex"/>
|
<parameter name="tabindex"/>
|
||||||
|
@ -95,11 +133,11 @@
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
if (tagObj) {
|
if (tagObj) {
|
||||||
var tagID = tagObj.id;
|
var tagID = tagObj.id;
|
||||||
var tag = tagObj.tag;
|
var name = tagObj.name;
|
||||||
var type = tagObj.type;
|
var type = tagObj.type;
|
||||||
}
|
}
|
||||||
if (!tag) {
|
if (!name) {
|
||||||
tag = '';
|
name = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tabindex)
|
if (!tabindex)
|
||||||
|
@ -128,7 +166,7 @@
|
||||||
// DEBUG: Why won't just this.nextSibling.blur() work?
|
// DEBUG: Why won't just this.nextSibling.blur() work?
|
||||||
icon.setAttribute('onclick','if (this.nextSibling.inputField){ this.nextSibling.inputField.blur() }');
|
icon.setAttribute('onclick','if (this.nextSibling.inputField){ this.nextSibling.inputField.blur() }');
|
||||||
|
|
||||||
var label = ZoteroItemPane.createValueElement(tag, 'tag', tabindex);
|
var label = this.createValueElement(name, tabindex);
|
||||||
|
|
||||||
var remove = document.createElement("label");
|
var remove = document.createElement("label");
|
||||||
remove.setAttribute('value','-');
|
remove.setAttribute('value','-');
|
||||||
|
@ -159,6 +197,284 @@
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="createValueElement">
|
||||||
|
<parameter name="valueText"/>
|
||||||
|
<parameter name="tabindex"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
var valueElement = document.createElement("label");
|
||||||
|
valueElement.setAttribute('fieldname', 'tag');
|
||||||
|
valueElement.setAttribute('flex', 1);
|
||||||
|
|
||||||
|
if (this.clickable) {
|
||||||
|
valueElement.setAttribute('ztabindex', tabindex);
|
||||||
|
valueElement.addEventListener('click', function (event) {
|
||||||
|
/* Skip right-click on Windows */
|
||||||
|
if (event.button) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getBindingParent(this).clickHandler(this);
|
||||||
|
}, false);
|
||||||
|
valueElement.className = 'zotero-clicky';
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tabIndexMaxTagsFields = Math.max(this._tabIndexMaxTagsFields, tabindex);
|
||||||
|
|
||||||
|
var firstSpace;
|
||||||
|
if (typeof valueText == 'string') {
|
||||||
|
firstSpace = valueText.indexOf(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 29 == arbitrary length at which to chop uninterrupted text
|
||||||
|
if ((firstSpace == -1 && valueText.length > 29 ) || firstSpace > 29) {
|
||||||
|
valueElement.setAttribute('crop', 'end');
|
||||||
|
valueElement.setAttribute('value',valueText);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Wrap to multiple lines
|
||||||
|
valueElement.appendChild(document.createTextNode(valueText));
|
||||||
|
}
|
||||||
|
|
||||||
|
return valueElement;
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="showEditor">
|
||||||
|
<parameter name="elem"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
// Blur any active fields
|
||||||
|
/*
|
||||||
|
if (this._dynamicFields) {
|
||||||
|
this._dynamicFields.focus();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Zotero.debug('Showing editor');
|
||||||
|
|
||||||
|
var fieldName = 'tag';
|
||||||
|
var tabindex = elem.getAttribute('ztabindex');
|
||||||
|
|
||||||
|
var tagID = elem.parentNode.getAttribute('id').split('-')[1];
|
||||||
|
var value = tagID ? Zotero.Tags.getName(tagID) : '';
|
||||||
|
var itemID = Zotero.getAncestorByTagName(elem, 'tagsbox').item.id;
|
||||||
|
|
||||||
|
var t = document.createElement("textbox");
|
||||||
|
t.setAttribute('value', value);
|
||||||
|
t.setAttribute('fieldname', fieldName);
|
||||||
|
t.setAttribute('ztabindex', tabindex);
|
||||||
|
t.setAttribute('flex', '1');
|
||||||
|
|
||||||
|
// Add auto-complete
|
||||||
|
t.setAttribute('type', 'autocomplete');
|
||||||
|
t.setAttribute('autocompletesearch', 'zotero');
|
||||||
|
var suffix = itemID ? itemID : '';
|
||||||
|
t.setAttribute('autocompletesearchparam', fieldName + '/' + suffix);
|
||||||
|
|
||||||
|
var box = elem.parentNode;
|
||||||
|
box.replaceChild(t, elem);
|
||||||
|
|
||||||
|
// Prevent error when clicking between a changed field
|
||||||
|
// and another -- there's probably a better way
|
||||||
|
if (!t.select) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.select();
|
||||||
|
|
||||||
|
t.addEventListener('blur', function () {
|
||||||
|
document.getBindingParent(this).blurHandler(this);
|
||||||
|
}, false);
|
||||||
|
t.setAttribute('onkeypress', "return document.getBindingParent(this).handleKeyPress(event)");
|
||||||
|
|
||||||
|
this._tabDirection = false;
|
||||||
|
this._lastTabIndex = tabindex;
|
||||||
|
|
||||||
|
return t;
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="handleKeyPress">
|
||||||
|
<parameter name="event"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
var target = event.target;
|
||||||
|
var focused = document.commandDispatcher.focusedElement;
|
||||||
|
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case event.DOM_VK_RETURN:
|
||||||
|
var fieldname = 'tag';
|
||||||
|
|
||||||
|
// Prevent blur on containing textbox
|
||||||
|
// DEBUG: what happens if this isn't present?
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// If last tag row, create new one
|
||||||
|
var row = target.parentNode.parentNode;
|
||||||
|
if (row == row.parentNode.lastChild) {
|
||||||
|
this._tabDirection = 1;
|
||||||
|
var lastTag = true;
|
||||||
|
}
|
||||||
|
focused.blur();
|
||||||
|
|
||||||
|
// Return focus to items pane
|
||||||
|
if (!lastTag) {
|
||||||
|
var tree = document.getElementById('zotero-items-tree');
|
||||||
|
if (tree) {
|
||||||
|
tree.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case event.DOM_VK_ESCAPE:
|
||||||
|
// Reset field to original value
|
||||||
|
target.value = target.getAttribute('value');
|
||||||
|
|
||||||
|
var tagsbox = Zotero.getAncestorByTagName(focused, 'tagsbox');
|
||||||
|
|
||||||
|
focused.blur();
|
||||||
|
|
||||||
|
if (tagsbox) {
|
||||||
|
tagsbox.closePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return focus to items pane
|
||||||
|
var tree = document.getElementById('zotero-items-tree');
|
||||||
|
if (tree) {
|
||||||
|
tree.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case event.DOM_VK_TAB:
|
||||||
|
this._tabDirection = event.shiftKey ? -1 : 1;
|
||||||
|
// Blur the old manually -- not sure why this is necessary,
|
||||||
|
// but it prevents an immediate blur() on the next tag
|
||||||
|
focused.blur();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="hideEditor">
|
||||||
|
<parameter name="textbox"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
Zotero.debug('Hiding editor');
|
||||||
|
/*
|
||||||
|
var textbox = Zotero.getAncestorByTagName(t, 'textbox');
|
||||||
|
if (!textbox){
|
||||||
|
Zotero.debug('Textbox not found in hideEditor');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: get rid of this?
|
||||||
|
//var saveChanges = this.saveOnEdit;
|
||||||
|
var saveChanges = true;
|
||||||
|
|
||||||
|
var fieldName = 'tag';
|
||||||
|
var tabindex = textbox.getAttribute('ztabindex');
|
||||||
|
|
||||||
|
//var value = t.value;
|
||||||
|
var value = textbox.value;
|
||||||
|
|
||||||
|
var elem;
|
||||||
|
|
||||||
|
var tagsbox = Zotero.getAncestorByTagName(textbox, 'tagsbox');
|
||||||
|
if (!tagsbox)
|
||||||
|
{
|
||||||
|
Zotero.debug('Tagsbox not found', 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var row = textbox.parentNode;
|
||||||
|
var rows = row.parentNode;
|
||||||
|
|
||||||
|
// Tag id encoded as 'tag-1234'
|
||||||
|
var id = row.getAttribute('id').split('-')[1];
|
||||||
|
|
||||||
|
if (saveChanges) {
|
||||||
|
if (id) {
|
||||||
|
if (value) {
|
||||||
|
// If trying to replace with another existing tag
|
||||||
|
// (which causes a delete of the row),
|
||||||
|
// clear the tab direction so we don't advance
|
||||||
|
// when the notifier kicks in
|
||||||
|
var existing = Zotero.Tags.getID(value, 0);
|
||||||
|
if (existing && id != existing) {
|
||||||
|
this._tabDirection = false;
|
||||||
|
}
|
||||||
|
var changed = tagsbox.replace(id, value);
|
||||||
|
if (changed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tagsbox.remove(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// New tag
|
||||||
|
else {
|
||||||
|
// If this is an existing automatic tag, it's going to be
|
||||||
|
// deleted and the number of rows will stay the same,
|
||||||
|
// so we have to compensate
|
||||||
|
var existingTypes = Zotero.Tags.getTypes(value);
|
||||||
|
if (existingTypes && existingTypes.indexOf(1) != -1) {
|
||||||
|
this._lastTabIndex--;
|
||||||
|
}
|
||||||
|
var id = tagsbox.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
elem = this.createValueElement(
|
||||||
|
value,
|
||||||
|
'tag',
|
||||||
|
tabindex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Just remove the row
|
||||||
|
//
|
||||||
|
// If there's an open popup, this throws NODE CANNOT BE FOUND
|
||||||
|
try {
|
||||||
|
var row = rows.removeChild(row);
|
||||||
|
}
|
||||||
|
catch (e) {}
|
||||||
|
tagsbox.fixPopup();
|
||||||
|
tagsbox.closePopup();
|
||||||
|
|
||||||
|
this._tabDirection = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var focusMode = 'tags';
|
||||||
|
var focusBox = tagsbox;
|
||||||
|
|
||||||
|
var box = textbox.parentNode;
|
||||||
|
box.replaceChild(elem,textbox);
|
||||||
|
|
||||||
|
if (this._tabDirection) {
|
||||||
|
this._focusNextField(focusBox, this._lastTabIndex, this._tabDirection == -1);
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="new">
|
<method name="new">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
@ -167,18 +483,21 @@
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="add">
|
<method name="add">
|
||||||
<parameter name="value"/>
|
<parameter name="value"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
if (value)
|
if (value) {
|
||||||
{
|
|
||||||
return this.item.addTag(value);
|
return this.item.addTag(value);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="replace">
|
<method name="replace">
|
||||||
<parameter name="oldTagID"/>
|
<parameter name="oldTagID"/>
|
||||||
<parameter name="newTag"/>
|
<parameter name="newTag"/>
|
||||||
|
@ -196,6 +515,8 @@
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="remove">
|
<method name="remove">
|
||||||
<parameter name="id"/>
|
<parameter name="id"/>
|
||||||
<body>
|
<body>
|
||||||
|
@ -204,6 +525,8 @@
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="updateCount">
|
<method name="updateCount">
|
||||||
<parameter name="count"/>
|
<parameter name="count"/>
|
||||||
<body>
|
<body>
|
||||||
|
@ -235,14 +558,8 @@
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
<method name="id">
|
|
||||||
<parameter name="id"/>
|
|
||||||
<body>
|
|
||||||
<![CDATA[
|
|
||||||
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
|
|
||||||
]]>
|
|
||||||
</body>
|
|
||||||
</method>
|
|
||||||
<method name="fixPopup">
|
<method name="fixPopup">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
@ -262,6 +579,8 @@
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="closePopup">
|
<method name="closePopup">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
@ -271,10 +590,78 @@
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
<method name="getScrollBox">
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Advance the field focus forward or backward
|
||||||
|
|
||||||
|
Note: We're basically replicating the built-in tabindex functionality,
|
||||||
|
which doesn't work well with the weird label/textbox stuff we're doing.
|
||||||
|
(The textbox being tabbed away from is deleted before the blur()
|
||||||
|
completes, so it doesn't know where it's supposed to go next.)
|
||||||
|
-->
|
||||||
|
<method name="_focusNextField">
|
||||||
|
<parameter name="box"/>
|
||||||
|
<parameter name="tabindex"/>
|
||||||
|
<parameter name="back"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
return document.getAnonymousNodes(this)[0];
|
tabindex = parseInt(tabindex);
|
||||||
|
if (back) {
|
||||||
|
switch (tabindex) {
|
||||||
|
case 1:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
var nextIndex = tabindex - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (tabindex) {
|
||||||
|
case this._tabIndexMaxTagsFields:
|
||||||
|
// In tags box, keep going to create new row
|
||||||
|
var nextIndex = tabindex + 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
var nextIndex = tabindex + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.debug('Looking for tabindex ' + nextIndex, 4);
|
||||||
|
|
||||||
|
var next = document.getAnonymousNodes(box)[0].
|
||||||
|
getElementsByAttribute('ztabindex', nextIndex);
|
||||||
|
if (!next[0]) {
|
||||||
|
next[0] = box.addDynamicRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
next[0].click();
|
||||||
|
this.ensureElementIsVisible(next[0]);
|
||||||
|
return true;
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="ensureElementIsVisible">
|
||||||
|
<parameter name="elem"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
var scrollbox = document.getAnonymousNodes(this)[0];
|
||||||
|
var sbo = scrollbox.boxObject;
|
||||||
|
sbo.QueryInterface(Components.interfaces.nsIScrollBoxObject);
|
||||||
|
sbo.ensureElementIsVisible(elem);
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="id">
|
||||||
|
<parameter name="id"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
|
@ -181,7 +181,7 @@
|
||||||
for (var tagID in this._tags) {
|
for (var tagID in this._tags) {
|
||||||
// If the last tag was the same, add this tagID and tagType to it
|
// If the last tag was the same, add this tagID and tagType to it
|
||||||
if (tagsToggleBox.lastChild &&
|
if (tagsToggleBox.lastChild &&
|
||||||
tagsToggleBox.lastChild.getAttribute('value') == this._tags[tagID].tag) {
|
tagsToggleBox.lastChild.getAttribute('value') == this._tags[tagID].name) {
|
||||||
tagsToggleBox.lastChild.setAttribute('tagID', tagsToggleBox.lastChild.getAttribute('tagID') + '-' + tagID);
|
tagsToggleBox.lastChild.setAttribute('tagID', tagsToggleBox.lastChild.getAttribute('tagID') + '-' + tagID);
|
||||||
tagsToggleBox.lastChild.setAttribute('tagType', tagsToggleBox.lastChild.getAttribute('tagType') + '-' + this._tags[tagID].type);
|
tagsToggleBox.lastChild.setAttribute('tagType', tagsToggleBox.lastChild.getAttribute('tagType') + '-' + this._tags[tagID].type);
|
||||||
continue;
|
continue;
|
||||||
|
@ -190,7 +190,7 @@
|
||||||
var label = document.createElement('label');
|
var label = document.createElement('label');
|
||||||
label.setAttribute('onclick', "this.parentNode.parentNode.parentNode.handleTagClick(event, this)");
|
label.setAttribute('onclick', "this.parentNode.parentNode.parentNode.handleTagClick(event, this)");
|
||||||
label.className = 'zotero-clicky';
|
label.className = 'zotero-clicky';
|
||||||
label.setAttribute('value', this._tags[tagID].tag);
|
label.setAttribute('value', this._tags[tagID].name);
|
||||||
label.setAttribute('tagID', tagID);
|
label.setAttribute('tagID', tagID);
|
||||||
label.setAttribute('tagType', this._tags[tagID].type);
|
label.setAttribute('tagType', this._tags[tagID].type);
|
||||||
label.setAttribute('context', 'tag-menu');
|
label.setAttribute('context', 'tag-menu');
|
||||||
|
|
|
@ -120,16 +120,15 @@ var ZoteroItemPane = new function() {
|
||||||
|
|
||||||
// Info pane
|
// Info pane
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
var itembox = document.getElementById('zotero-editpane-item-box');
|
|
||||||
// Hack to allow read-only mode in right pane -- probably a better
|
// Hack to allow read-only mode in right pane -- probably a better
|
||||||
// way to allow access to this
|
// way to allow access to this
|
||||||
if (mode) {
|
if (mode) {
|
||||||
itembox.mode = mode;
|
_itemBox.mode = mode;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
itembox.mode = 'edit';
|
_itemBox.mode = 'edit';
|
||||||
}
|
}
|
||||||
itembox.item = _itemBeingEdited;
|
_itemBox.item = _itemBeingEdited;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notes pane
|
// Notes pane
|
||||||
|
@ -147,7 +146,9 @@ var ZoteroItemPane = new function() {
|
||||||
icon.setAttribute('src','chrome://zotero/skin/treeitem-note.png');
|
icon.setAttribute('src','chrome://zotero/skin/treeitem-note.png');
|
||||||
|
|
||||||
var label = document.createElement('label');
|
var label = document.createElement('label');
|
||||||
label.setAttribute('value',_noteToTitle(notes[i].getNote()));
|
var title = Zotero.Notes.noteToTitle(notes[i].getNote());
|
||||||
|
title = title ? title : Zotero.getString('pane.item.notes.untitled');
|
||||||
|
label.setAttribute('value', title);
|
||||||
label.setAttribute('flex','1'); //so that the long names will flex smaller
|
label.setAttribute('flex','1'); //so that the long names will flex smaller
|
||||||
label.setAttribute('crop','end');
|
label.setAttribute('crop','end');
|
||||||
|
|
||||||
|
@ -236,6 +237,13 @@ var ZoteroItemPane = new function() {
|
||||||
// Tags pane
|
// Tags pane
|
||||||
else if(index == 3)
|
else if(index == 3)
|
||||||
{
|
{
|
||||||
|
if (mode) {
|
||||||
|
_tagsBox.mode = mode;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_tagsBox.mode = 'edit';
|
||||||
|
}
|
||||||
|
|
||||||
var focusMode = 'tags';
|
var focusMode = 'tags';
|
||||||
var focusBox = _tagsBox;
|
var focusBox = _tagsBox;
|
||||||
_tagsBox.item = _itemBeingEdited;
|
_tagsBox.item = _itemBeingEdited;
|
||||||
|
@ -269,27 +277,6 @@ var ZoteroItemPane = new function() {
|
||||||
ZoteroPane.openNoteWindow(null, null, _itemBeingEdited.id);
|
ZoteroPane.openNoteWindow(null, null, _itemBeingEdited.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _noteToTitle(text)
|
|
||||||
{
|
|
||||||
var MAX_LENGTH = 100;
|
|
||||||
|
|
||||||
var t = text.substring(0, MAX_LENGTH);
|
|
||||||
var ln = t.indexOf("\n");
|
|
||||||
if (ln>-1 && ln<MAX_LENGTH)
|
|
||||||
{
|
|
||||||
t = t.substring(0, ln);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(t == "")
|
|
||||||
{
|
|
||||||
return Zotero.getString('pane.item.notes.untitled');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _updateNoteCount()
|
function _updateNoteCount()
|
||||||
{
|
{
|
||||||
var c = _notesList.childNodes.length;
|
var c = _notesList.childNodes.length;
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
<overlay
|
<overlay
|
||||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
|
||||||
|
<script src="include.js"/>
|
||||||
<script src="itemPane.js"/>
|
<script src="itemPane.js"/>
|
||||||
|
|
||||||
<deck id="zotero-view-item" flex="1" onselect="if (this.selectedIndex !== '') { ZoteroItemPane.loadPane(this.selectedIndex); }">
|
<deck id="zotero-view-item" flex="1" onselect="if (this.selectedIndex !== '') { ZoteroItemPane.loadPane(this.selectedIndex); }">
|
||||||
|
@ -46,6 +47,7 @@
|
||||||
<rows id="zotero-editpane-dynamic-notes" flex="1"/>
|
<rows id="zotero-editpane-dynamic-notes" flex="1"/>
|
||||||
</grid>
|
</grid>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
|
||||||
<vbox flex="1">
|
<vbox flex="1">
|
||||||
<hbox align="center">
|
<hbox align="center">
|
||||||
<label id="zotero-editpane-attachments-label"/>
|
<label id="zotero-editpane-attachments-label"/>
|
||||||
|
@ -66,7 +68,9 @@
|
||||||
<rows id="zotero-editpane-dynamic-attachments" flex="1"/>
|
<rows id="zotero-editpane-dynamic-attachments" flex="1"/>
|
||||||
</grid>
|
</grid>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
|
||||||
<tagsbox id="zotero-editpane-tags" flex="1"/>
|
<tagsbox id="zotero-editpane-tags" flex="1"/>
|
||||||
|
|
||||||
<seealsobox id="zotero-editpane-related" flex="1"/>
|
<seealsobox id="zotero-editpane-related" flex="1"/>
|
||||||
</deck>
|
</deck>
|
||||||
</overlay>
|
</overlay>
|
|
@ -785,8 +785,7 @@ var ZoteroPane = new function()
|
||||||
{
|
{
|
||||||
var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex);
|
var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex);
|
||||||
|
|
||||||
if(item.ref.isNote())
|
if(item.ref.isNote()) {
|
||||||
{
|
|
||||||
var noteEditor = document.getElementById('zotero-note-editor');
|
var noteEditor = document.getElementById('zotero-note-editor');
|
||||||
if (this.itemsView.readOnly) {
|
if (this.itemsView.readOnly) {
|
||||||
noteEditor.mode = 'view';
|
noteEditor.mode = 'view';
|
||||||
|
@ -817,8 +816,8 @@ var ZoteroPane = new function()
|
||||||
}
|
}
|
||||||
document.getElementById('zotero-item-pane-content').selectedIndex = 2;
|
document.getElementById('zotero-item-pane-content').selectedIndex = 2;
|
||||||
}
|
}
|
||||||
else if(item.ref.isAttachment())
|
|
||||||
{
|
else if(item.ref.isAttachment()) {
|
||||||
// DEBUG: this is annoying -- we really want to use an abstracted
|
// DEBUG: this is annoying -- we really want to use an abstracted
|
||||||
// version of createValueElement() from itemPane.js
|
// version of createValueElement() from itemPane.js
|
||||||
// (ideally in an XBL binding)
|
// (ideally in an XBL binding)
|
||||||
|
@ -956,6 +955,8 @@ var ZoteroPane = new function()
|
||||||
|
|
||||||
document.getElementById('zotero-item-pane-content').selectedIndex = 3;
|
document.getElementById('zotero-item-pane-content').selectedIndex = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regular item
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ZoteroItemPane.viewItem(item.ref, this.itemsView.readOnly ? 'view' : false);
|
ZoteroItemPane.viewItem(item.ref, this.itemsView.readOnly ? 'view' : false);
|
||||||
|
|
|
@ -551,12 +551,15 @@ Zotero.Collection.prototype.addItems = function(itemIDs) {
|
||||||
* Remove an item from the collection (does not delete item from library)
|
* Remove an item from the collection (does not delete item from library)
|
||||||
**/
|
**/
|
||||||
Zotero.Collection.prototype.removeItem = function(itemID) {
|
Zotero.Collection.prototype.removeItem = function(itemID) {
|
||||||
var index = this.getChildItems(true).indexOf(itemID);
|
var childItems = this.getChildItems(true);
|
||||||
|
if (childItems) {
|
||||||
|
var index = childItems.indexOf(itemID);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
Zotero.debug("Item " + itemID + " not a child of collection "
|
Zotero.debug("Item " + itemID + " not a child of collection "
|
||||||
+ this.id + " in Zotero.Collection.removeItem()");
|
+ this.id + " in Zotero.Collection.removeItem()");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Zotero.DB.beginTransaction();
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
|
|
@ -353,6 +353,7 @@ Zotero.Creator.prototype.erase = function () {
|
||||||
|
|
||||||
Zotero.debug("Deleting creator " + this.id);
|
Zotero.debug("Deleting creator " + this.id);
|
||||||
|
|
||||||
|
// TODO: notifier
|
||||||
var changedItems = [];
|
var changedItems = [];
|
||||||
var changedItemsNotifierData = {};
|
var changedItemsNotifierData = {};
|
||||||
|
|
||||||
|
|
|
@ -50,14 +50,14 @@ Zotero.Creators = new function() {
|
||||||
return _creatorsByID[creatorID];
|
return _creatorsByID[creatorID];
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = 'SELECT * FROM creators WHERE creatorID=?';
|
var sql = 'SELECT COUNT(*) FROM creators WHERE creatorID=?';
|
||||||
var result = Zotero.DB.rowQuery(sql, creatorID);
|
var result = Zotero.DB.valueQuery(sql, creatorID);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_creatorsByID[creatorID] = new Zotero.Creator(result.creatorID);
|
_creatorsByID[creatorID] = new Zotero.Creator(creatorID);
|
||||||
return _creatorsByID[creatorID];
|
return _creatorsByID[creatorID];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2364,12 +2364,12 @@ Zotero.Item.prototype.getBestSnapshot = function() {
|
||||||
//
|
//
|
||||||
// save() is not required for tag functions
|
// save() is not required for tag functions
|
||||||
//
|
//
|
||||||
Zotero.Item.prototype.addTag = function(tag, type) {
|
Zotero.Item.prototype.addTag = function(name, type) {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
throw ('Cannot add tag to unsaved item in Item.addTag()');
|
throw ('Cannot add tag to unsaved item in Item.addTag()');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tag) {
|
if (!name) {
|
||||||
Zotero.debug('Not saving empty tag in Item.addTag()', 2);
|
Zotero.debug('Not saving empty tag in Item.addTag()', 2);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2378,18 +2378,13 @@ Zotero.Item.prototype.addTag = function(tag, type) {
|
||||||
type = 0;
|
type = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type !=0 && type !=1) {
|
|
||||||
throw ('Invalid tag type in Item.addTag()');
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.DB.beginTransaction();
|
Zotero.DB.beginTransaction();
|
||||||
var tagID = Zotero.Tags.getID(tag, type);
|
|
||||||
var existingTypes = Zotero.Tags.getTypes(tag);
|
|
||||||
|
|
||||||
|
var existingTypes = Zotero.Tags.getTypes(name);
|
||||||
if (existingTypes) {
|
if (existingTypes) {
|
||||||
// If existing automatic and adding identical user, remove automatic
|
// If existing automatic and adding identical user, remove automatic
|
||||||
if (type == 0 && existingTypes.indexOf(1) != -1) {
|
if (type == 0 && existingTypes.indexOf(1) != -1) {
|
||||||
this.removeTag(Zotero.Tags.getID(tag, 1));
|
this.removeTag(Zotero.Tags.getID(name, 1));
|
||||||
}
|
}
|
||||||
// If existing user and adding automatic, skip
|
// If existing user and adding automatic, skip
|
||||||
else if (type == 1 && existingTypes.indexOf(0) != -1) {
|
else if (type == 1 && existingTypes.indexOf(0) != -1) {
|
||||||
|
@ -2399,8 +2394,12 @@ Zotero.Item.prototype.addTag = function(tag, type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tagID = Zotero.Tags.getID(name, type);
|
||||||
if (!tagID) {
|
if (!tagID) {
|
||||||
var tagID = Zotero.Tags.add(tag, type);
|
var tag = new Zotero.Tag;
|
||||||
|
tag.name = name;
|
||||||
|
tag.type = type;
|
||||||
|
var tagID = tag.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -2433,38 +2432,20 @@ Zotero.Item.prototype.addTags = function (tags, type) {
|
||||||
|
|
||||||
Zotero.Item.prototype.addTagByID = function(tagID) {
|
Zotero.Item.prototype.addTagByID = function(tagID) {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
throw ('Cannot add tag to unsaved item in Item.addTagByID()');
|
throw ('Cannot add tag to unsaved item in Zotero.Item.addTagByID()');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tagID) {
|
if (!tagID) {
|
||||||
Zotero.debug('Not saving nonexistent tag in Item.addTagByID()', 2);
|
throw ('tagID not provided in Zotero.Item.addTagByID()');
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = "SELECT COUNT(*) FROM tags WHERE tagID = ?";
|
var tag = Zotero.Tags.get(tagID);
|
||||||
var count = !!Zotero.DB.valueQuery(sql, tagID);
|
if (!tag) {
|
||||||
|
throw ('Cannot add invalid tag ' + tagID + ' in Zotero.Item.addTagByID()');
|
||||||
if (!count) {
|
|
||||||
throw ('Cannot add invalid tag id ' + tagID + ' in Item.addTagByID()');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.DB.beginTransaction();
|
tag.addItem(this.id);
|
||||||
|
tag.save();
|
||||||
// If INSERT OR IGNORE gave us affected rows, we wouldn't need this...
|
|
||||||
if (this.hasTag(tagID)) {
|
|
||||||
Zotero.debug('Item ' + this.id + ' already has tag ' + tagID + ' in Item.addTagByID()');
|
|
||||||
Zotero.DB.commitTransaction();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql = "INSERT INTO itemTags VALUES (?,?)";
|
|
||||||
Zotero.DB.query(sql, [this.id, tagID]);
|
|
||||||
|
|
||||||
Zotero.DB.commitTransaction();
|
|
||||||
Zotero.Notifier.trigger('modify', 'item', this.id);
|
|
||||||
Zotero.Notifier.trigger('add', 'item-tag', this.id + '-' + tagID);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Item.prototype.hasTag = function(tagID) {
|
Zotero.Item.prototype.hasTag = function(tagID) {
|
||||||
|
@ -2484,28 +2465,38 @@ Zotero.Item.prototype.hasTags = function(tagIDs) {
|
||||||
return !!Zotero.DB.valueQuery(sql, [this.id].concat(tagIDs));
|
return !!Zotero.DB.valueQuery(sql, [this.id].concat(tagIDs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all tags assigned to an item
|
||||||
|
*
|
||||||
|
* @return array Array of Zotero.Tag objects
|
||||||
|
*/
|
||||||
Zotero.Item.prototype.getTags = function() {
|
Zotero.Item.prototype.getTags = function() {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var sql = "SELECT tagID AS id, tag, tagType AS type FROM tags WHERE tagID IN "
|
var sql = "SELECT tagID, name FROM tags WHERE tagID IN "
|
||||||
+ "(SELECT tagID FROM itemTags WHERE itemID=" + this.id + ")";
|
+ "(SELECT tagID FROM itemTags WHERE itemID=?)";
|
||||||
|
var tags = Zotero.DB.query(sql, this.id);
|
||||||
var tags = Zotero.DB.query(sql);
|
|
||||||
if (!tags) {
|
if (!tags) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var collation = Zotero.getLocaleCollation();
|
var collation = Zotero.getLocaleCollation();
|
||||||
tags.sort(function(a, b) {
|
tags.sort(function(a, b) {
|
||||||
return collation.compareString(1, a.tag, b.tag);
|
return collation.compareString(1, a.name, b.name);
|
||||||
});
|
});
|
||||||
return tags;
|
|
||||||
|
var tagObjs = [];
|
||||||
|
for (var i=0; i<tags.length; i++) {
|
||||||
|
var tag = Zotero.Tags.get(tags[i].tagID, true);
|
||||||
|
tagObjs.push(tag);
|
||||||
|
}
|
||||||
|
return tagObjs;
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Item.prototype.getTagIDs = function() {
|
Zotero.Item.prototype.getTagIDs = function() {
|
||||||
var sql = "SELECT tagID FROM itemTags WHERE itemID=" + this.id;
|
var sql = "SELECT tagID FROM itemTags WHERE itemID=?";
|
||||||
return Zotero.DB.columnQuery(sql);
|
return Zotero.DB.columnQuery(sql, this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Item.prototype.replaceTag = function(oldTagID, newTag) {
|
Zotero.Item.prototype.replaceTag = function(oldTagID, newTag) {
|
||||||
|
@ -2537,16 +2528,20 @@ Zotero.Item.prototype.replaceTag = function(oldTagID, newTag) {
|
||||||
|
|
||||||
Zotero.Item.prototype.removeTag = function(tagID) {
|
Zotero.Item.prototype.removeTag = function(tagID) {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
throw ('Cannot remove tag on unsaved item');
|
throw ('Cannot remove tag on unsaved item in Zotero.Item.removeTag()');
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.DB.beginTransaction();
|
if (!tagID) {
|
||||||
var sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=?";
|
throw ('tagID not provided in Zotero.Item.removeTag()');
|
||||||
Zotero.DB.query(sql, [this.id, { int: tagID }]);
|
}
|
||||||
Zotero.Tags.purge();
|
|
||||||
Zotero.DB.commitTransaction();
|
var tag = Zotero.Tags.get(tagID);
|
||||||
Zotero.Notifier.trigger('modify', 'item', this.id);
|
if (!tag) {
|
||||||
Zotero.Notifier.trigger('remove', 'item-tag', this.id + '-' + tagID);
|
throw ('Cannot remove invalid tag ' + tagID + ' in Zotero.Item.removeTag()');
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.removeItem(this.id);
|
||||||
|
tag.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Item.prototype.removeAllTags = function() {
|
Zotero.Item.prototype.removeAllTags = function() {
|
||||||
|
@ -3188,10 +3183,14 @@ Zotero.Item.prototype.toArray = function (mode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arr.tags = this.getTags();
|
|
||||||
if (!arr.tags) {
|
|
||||||
arr.tags = [];
|
arr.tags = [];
|
||||||
|
var tags = this.getTags();
|
||||||
|
if (tags) {
|
||||||
|
for (var i=0; i<tags.length; i++) {
|
||||||
|
arr.tags.push(tags[i].serialize());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
arr.related = this.getSeeAlso();
|
arr.related = this.getSeeAlso();
|
||||||
if (!arr.related) {
|
if (!arr.related) {
|
||||||
arr.related = [];
|
arr.related = [];
|
||||||
|
@ -3312,10 +3311,14 @@ Zotero.Item.prototype.serialize = function(mode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arr.tags = this.getTags();
|
|
||||||
if (!arr.tags) {
|
|
||||||
arr.tags = [];
|
arr.tags = [];
|
||||||
|
var tags = this.getTags();
|
||||||
|
if (tags) {
|
||||||
|
for (var i=0; i<tags.length; i++) {
|
||||||
|
arr.tags.push(tags[i].serialize());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
arr.related = this.getSeeAlso();
|
arr.related = this.getSeeAlso();
|
||||||
if (!arr.related) {
|
if (!arr.related) {
|
||||||
arr.related = [];
|
arr.related = [];
|
||||||
|
|
544
chrome/content/zotero/xpcom/data/tag.js
Normal file
544
chrome/content/zotero/xpcom/data/tag.js
Normal file
|
@ -0,0 +1,544 @@
|
||||||
|
/*
|
||||||
|
***** 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.Tag = function(tagID) {
|
||||||
|
this._tagID = tagID ? tagID : null;
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.Tag.prototype._init = function () {
|
||||||
|
// Public members for access by public methods -- do not access directly
|
||||||
|
this._name = null;
|
||||||
|
this._type = null;
|
||||||
|
this._dateModified = null;
|
||||||
|
this._key = null;
|
||||||
|
|
||||||
|
this._loaded = false;
|
||||||
|
this._changed = false;
|
||||||
|
this._previousData = false;
|
||||||
|
|
||||||
|
this._linkedItemsLoaded = false;
|
||||||
|
this._linkedItems = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype.__defineGetter__('id', function () { return this._tagID; });
|
||||||
|
|
||||||
|
Zotero.Tag.prototype.__defineSetter__('tagID', function (val) { this._set('tagID', val); });
|
||||||
|
Zotero.Tag.prototype.__defineGetter__('name', function () { return this._get('name'); });
|
||||||
|
Zotero.Tag.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
|
||||||
|
Zotero.Tag.prototype.__defineGetter__('type', function () { return this._get('type'); });
|
||||||
|
Zotero.Tag.prototype.__defineSetter__('type', function (val) { this._set('type', val); });
|
||||||
|
Zotero.Tag.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
|
||||||
|
Zotero.Tag.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
|
||||||
|
Zotero.Tag.prototype.__defineGetter__('key', function () { return this._get('key'); });
|
||||||
|
Zotero.Tag.prototype.__defineSetter__('key', function (val) { this._set('key', val); });
|
||||||
|
|
||||||
|
Zotero.Tag.prototype.__defineSetter__('linkedItems', function (arr) { this._setLinkedItems(arr); });
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype._get = function (field) {
|
||||||
|
if (this.id && !this._loaded) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
return this['_' + field];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype._set = function (field, val) {
|
||||||
|
switch (field) {
|
||||||
|
case 'id': // set using constructor
|
||||||
|
//case 'tagID': // set using constructor
|
||||||
|
throw ("Invalid field '" + field + "' in Zotero.Tag.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if tag exists in the database
|
||||||
|
*
|
||||||
|
* @return bool TRUE if the tag exists, FALSE if not
|
||||||
|
*/
|
||||||
|
Zotero.Tag.prototype.exists = function() {
|
||||||
|
if (!this.id) {
|
||||||
|
throw ('tagID not set in Zotero.Tag.exists()');
|
||||||
|
}
|
||||||
|
|
||||||
|
var sql = "SELECT COUNT(*) FROM tags WHERE tagID=?";
|
||||||
|
return !!Zotero.DB.valueQuery(sql, this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build tag from database
|
||||||
|
*/
|
||||||
|
Zotero.Tag.prototype.load = function() {
|
||||||
|
Zotero.debug("Loading data for tag " + this.id + " in Zotero.Tag.load()");
|
||||||
|
|
||||||
|
if (!this.id) {
|
||||||
|
throw ("tagID not set in Zotero.Tag.load()");
|
||||||
|
}
|
||||||
|
|
||||||
|
var sql = "SELECT name, type, dateModified, key FROM tags WHERE tagID=?";
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns items linked to this tag
|
||||||
|
*
|
||||||
|
* @param bool asIDs Return as itemIDs
|
||||||
|
* @return array Array of Zotero.Item instances or itemIDs,
|
||||||
|
* or FALSE if none
|
||||||
|
*/
|
||||||
|
Zotero.Tag.prototype.getLinkedItems = function (asIDs) {
|
||||||
|
if (!this._linkedItemsLoaded) {
|
||||||
|
this._loadLinkedItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._linkedItems.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return itemIDs
|
||||||
|
if (asIDs) {
|
||||||
|
var ids = [];
|
||||||
|
for each(var item in this._linkedItems) {
|
||||||
|
ids.push(item.id);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return Zotero.Item objects
|
||||||
|
var objs = [];
|
||||||
|
for each(var item in this._linkedItems) {
|
||||||
|
objs.push(item);
|
||||||
|
}
|
||||||
|
return objs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype._setLinkedItems = function (itemIDs) {
|
||||||
|
if (!this._linkedItemsLoaded) {
|
||||||
|
this._loadLinkedItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIDs.constructor.name != 'Array') {
|
||||||
|
throw ('ids must be an array in Zotero.Tag._setLinkedItems()');
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentIDs = this.getLinkedItems(true);
|
||||||
|
if (!currentIDs) {
|
||||||
|
currentIDs = [];
|
||||||
|
}
|
||||||
|
var oldIDs = []; // children being kept
|
||||||
|
var newIDs = []; // new children
|
||||||
|
|
||||||
|
if (itemIDs.length == 0) {
|
||||||
|
if (currentIDs.length == 0) {
|
||||||
|
Zotero.debug('No linked items added', 4);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (var i in itemIDs) {
|
||||||
|
var id = parseInt(itemIDs[i]);
|
||||||
|
if (isNaN(id)) {
|
||||||
|
throw ("Invalid itemID '" + itemIDs[i]
|
||||||
|
+ "' in Zotero.Tag._setLinkedItems()");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIDs.indexOf(id) != -1) {
|
||||||
|
Zotero.debug("Item " + itemIDs[i]
|
||||||
|
+ " is already linked to tag " + this.id);
|
||||||
|
oldIDs.push(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newIDs.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as changed if new or removed ids
|
||||||
|
if (newIDs.length > 0 || oldIDs.length != currentIDs.length) {
|
||||||
|
this._prepFieldChange('linkedItems');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Zotero.debug('Linked items not changed in Zotero.Tag._setLinkedItems()', 4);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
newIDs = oldIDs.concat(newIDs);
|
||||||
|
|
||||||
|
var items = Zotero.Items.get(itemIDs);
|
||||||
|
this._linkedItems = items ? items : [];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype.addItem = function (itemID) {
|
||||||
|
var current = this.getLinkedItems(true);
|
||||||
|
if (current && current.indexOf(itemID) != -1) {
|
||||||
|
Zotero.debug("Item " + itemID + " already has tag "
|
||||||
|
+ this.id + " in Zotero.Tag.addItem()");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._prepFieldChange('linkedItems');
|
||||||
|
var item = Zotero.Items.get(itemID);
|
||||||
|
if (!item) {
|
||||||
|
throw ("Can't link invalid item " + itemID + " to tag " + this.id
|
||||||
|
+ " in Zotero.Tag.addItem()");
|
||||||
|
}
|
||||||
|
this._linkedItems.push(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype.removeItem = function (itemID) {
|
||||||
|
var current = this.getLinkedItems(true);
|
||||||
|
if (current) {
|
||||||
|
var index = current.indexOf(itemID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!current || index == -1) {
|
||||||
|
Zotero.debug("Item " + itemID + " doesn't have tag "
|
||||||
|
+ this.id + " in Zotero.Tag.removeItem()");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._prepFieldChange('linkedItems');
|
||||||
|
this._linkedItems.splice(index, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype.save = function () {
|
||||||
|
// Default to manual tag
|
||||||
|
if (!this.type) {
|
||||||
|
this.type = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.type != 0 && this.type != 1) {
|
||||||
|
throw ('Invalid tag type ' + this.type + ' in Zotero.Tag.save()');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.name) {
|
||||||
|
throw ('Tag name is empty in Zotero.Tag.save()');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._changed) {
|
||||||
|
Zotero.debug("Tag " + this.id + " has not changed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
// ID change
|
||||||
|
if (this._changed.tagID) {
|
||||||
|
var oldID = this._previousData.primary.tagID;
|
||||||
|
var params = [this.id, oldID];
|
||||||
|
|
||||||
|
Zotero.debug("Changing tagID " + oldID + " to " + this.id);
|
||||||
|
|
||||||
|
var row = Zotero.DB.rowQuery("SELECT * FROM tags WHERE tagID=?", oldID);
|
||||||
|
|
||||||
|
// Set type on old row to -1, since there's a UNIQUE on name/type
|
||||||
|
Zotero.DB.query("UPDATE tags SET type=-1 WHERE tagID=?", oldID);
|
||||||
|
|
||||||
|
// Add a new row so we can update the old rows despite FK checks
|
||||||
|
// Use temp key due to UNIQUE constraint on key column
|
||||||
|
Zotero.DB.query("INSERT INTO tags VALUES (?, ?, ?, ?, ?)",
|
||||||
|
[this.id, row.name, row.type, row.dateModified, 'TEMPKEY']);
|
||||||
|
|
||||||
|
Zotero.DB.query("UPDATE itemTags SET tagID=? WHERE tagID=?", params);
|
||||||
|
|
||||||
|
Zotero.DB.query("DELETE FROM tags WHERE tagID=?", oldID);
|
||||||
|
|
||||||
|
Zotero.DB.query("UPDATE tags SET key=? WHERE tagID=?", [row.key, this.id]);
|
||||||
|
|
||||||
|
Zotero.Tags.unload([{ oldID: { name: row.name, type: row.type } }]);
|
||||||
|
Zotero.Notifier.trigger('id-change', 'tag', oldID + '-' + this.id);
|
||||||
|
|
||||||
|
// update caches
|
||||||
|
}
|
||||||
|
|
||||||
|
var isNew = !this.id || !this.exists();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// how to know if date modified changed (in server code too?)
|
||||||
|
|
||||||
|
var tagID = this.id ? this.id : Zotero.ID.get('tags');
|
||||||
|
|
||||||
|
Zotero.debug("Saving tag " + this.id);
|
||||||
|
|
||||||
|
var key = this.key ? this.key : this._generateKey();
|
||||||
|
|
||||||
|
var columns = [
|
||||||
|
'tagID', 'name', 'type', 'dateModified', 'key'
|
||||||
|
];
|
||||||
|
var placeholders = ['?', '?', '?', '?', '?'];
|
||||||
|
var sqlValues = [
|
||||||
|
tagID ? { int: tagID } : null,
|
||||||
|
{ string: this.name },
|
||||||
|
{ int: this.type },
|
||||||
|
// If date modified hasn't changed, use current timestamp
|
||||||
|
this._changed.dateModified ?
|
||||||
|
this.dateModified : Zotero.DB.transactionDateTime,
|
||||||
|
key
|
||||||
|
];
|
||||||
|
|
||||||
|
var sql = "REPLACE INTO tags (" + columns.join(', ') + ") VALUES ("
|
||||||
|
+ placeholders.join(', ') + ")";
|
||||||
|
var insertID = Zotero.DB.query(sql, sqlValues);
|
||||||
|
if (!tagID) {
|
||||||
|
tagID = insertID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linked items
|
||||||
|
if (this._changed.linkedItems) {
|
||||||
|
var removed = [];
|
||||||
|
var newids = [];
|
||||||
|
var currentIDs = this.getLinkedItems(true);
|
||||||
|
if (!currentIDs) {
|
||||||
|
currentIDs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._previousData.linkedItems) {
|
||||||
|
for each(var id in this._previousData.linkedItems) {
|
||||||
|
if (currentIDs.indexOf(id) == -1) {
|
||||||
|
removed.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for each(var id in currentIDs) {
|
||||||
|
if (this._previousData.linkedItems &&
|
||||||
|
this._previousData.linkedItems.indexOf(id) != -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
newids.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed.length) {
|
||||||
|
var sql = "DELETE FROM itemTags WHERE tagID=? "
|
||||||
|
+ "AND itemID IN ("
|
||||||
|
+ removed.map(function () '?').join()
|
||||||
|
+ ")";
|
||||||
|
Zotero.DB.query(sql, [tagID].concat(removed));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newids.length) {
|
||||||
|
var sql = "INSERT INTO itemTags (itemID, tagID) VALUES (?,?)";
|
||||||
|
var insertStatement = Zotero.DB.getStatement(sql);
|
||||||
|
|
||||||
|
for each(var itemID in newids) {
|
||||||
|
insertStatement.bindInt32Parameter(0, itemID);
|
||||||
|
insertStatement.bindInt32Parameter(1, tagID);
|
||||||
|
|
||||||
|
try {
|
||||||
|
insertStatement.execute();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Zotero.Notifier.trigger('add', 'tag-item', this.id + '-' + itemID);
|
||||||
|
|
||||||
|
// TODO: notify linked items of name changes?
|
||||||
|
// Zotero.Notifier.trigger('modify', 'item', itemIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.DB.rollbackTransaction();
|
||||||
|
throw (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If successful, set values in object
|
||||||
|
if (!this.id) {
|
||||||
|
this._tagID = tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.key) {
|
||||||
|
this._key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.Tags.reload(this.id);
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
Zotero.Notifier.trigger('add', 'tag', this.id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Zotero.Notifier.trigger('modify', 'tag', this.id, this._previousData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype.serialize = function () {
|
||||||
|
var obj = {
|
||||||
|
primary: {
|
||||||
|
tagID: this.id,
|
||||||
|
dateModified: this.dateModified,
|
||||||
|
key: this.key
|
||||||
|
},
|
||||||
|
name: this.name,
|
||||||
|
type: this.type,
|
||||||
|
linkedItems: this.getLinkedItems(true),
|
||||||
|
};
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove tag from all linked items
|
||||||
|
*
|
||||||
|
* Tags.erase() should be used externally instead of this
|
||||||
|
*
|
||||||
|
* Actual deletion of tag occurs in Zotero.Tags.purge(),
|
||||||
|
* which is called by Tags.erase()
|
||||||
|
*/
|
||||||
|
Zotero.Tag.prototype.erase = function () {
|
||||||
|
Zotero.debug('Deleting tag ' + this.id);
|
||||||
|
|
||||||
|
if (!this.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkedItems = [];
|
||||||
|
var linkedItemsNotifierData = {};
|
||||||
|
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
var deletedTagNotifierData = {};
|
||||||
|
deletedTagNotifierData[this.id] = { old: this.serialize() };
|
||||||
|
|
||||||
|
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
|
||||||
|
var linkedItemIDs = Zotero.DB.columnQuery(sql, this.id);
|
||||||
|
|
||||||
|
if (!linkedItemIDs) {
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sql = "DELETE FROM itemTags WHERE tagID=?";
|
||||||
|
Zotero.DB.query(sql, this.id);
|
||||||
|
|
||||||
|
var itemTags = [];
|
||||||
|
for each(var itemID in linkedItemIDs) {
|
||||||
|
var item = Zotero.Items.get(itemID)
|
||||||
|
if (!item) {
|
||||||
|
throw ('Linked item not found in Zotero.Tag.erase()');
|
||||||
|
}
|
||||||
|
linkedItems.push(itemID);
|
||||||
|
linkedItemsNotifierData[itemID] = { old: item.serialize() };
|
||||||
|
|
||||||
|
itemTags.push(itemID + '-' + this.id);
|
||||||
|
}
|
||||||
|
Zotero.Notifier.trigger('remove', 'item-tag', itemTags);
|
||||||
|
|
||||||
|
// Send notification of linked items
|
||||||
|
if (linkedItems.length) {
|
||||||
|
Zotero.Notifier.trigger('modify', 'item', linkedItems, linkedItemsNotifierData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.Notifier.trigger('delete', 'tag', this.id, deletedTagNotifierData);
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype._loadLinkedItems = function() {
|
||||||
|
if (!this._loaded) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
|
||||||
|
var ids = Zotero.DB.columnQuery(sql, this.id);
|
||||||
|
|
||||||
|
this._linkedItems = [];
|
||||||
|
|
||||||
|
if (ids) {
|
||||||
|
for each(var id in ids) {
|
||||||
|
this._linkedItems.push(Zotero.Items.get(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._linkedItemsLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.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 tag exists
|
||||||
|
if (this.id && this.exists() && !this._previousData) {
|
||||||
|
this._previousData = this.serialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Zotero.Tag.prototype._generateKey = function () {
|
||||||
|
return Zotero.ID.getKey();
|
||||||
|
}
|
||||||
|
|
|
@ -33,37 +33,37 @@ Zotero.Tags = new function() {
|
||||||
this.getID = getID;
|
this.getID = getID;
|
||||||
this.getIDs = getIDs;
|
this.getIDs = getIDs;
|
||||||
this.getTypes = getTypes;
|
this.getTypes = getTypes;
|
||||||
|
this.getUpdated = getUpdated;
|
||||||
this.getAll = getAll;
|
this.getAll = getAll;
|
||||||
this.getAllWithinSearch = getAllWithinSearch;
|
this.getAllWithinSearch = getAllWithinSearch;
|
||||||
this.getTagItems = getTagItems;
|
this.getTagItems = getTagItems;
|
||||||
this.search = search;
|
this.search = search;
|
||||||
this.add = add;
|
|
||||||
this.rename = rename;
|
this.rename = rename;
|
||||||
this.remove = remove;
|
this.reload = reload;
|
||||||
|
this.erase = erase;
|
||||||
this.purge = purge;
|
this.purge = purge;
|
||||||
this.toArray = toArray;
|
this.unload = unload;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns a tag and type for a given tagID
|
* Returns a tag and type for a given tagID
|
||||||
*/
|
*/
|
||||||
function get(tagID) {
|
function get(tagID, skipCheck) {
|
||||||
if (_tagsByID[tagID]) {
|
if (_tagsByID[tagID]) {
|
||||||
return _tagsByID[tagID];
|
return _tagsByID[tagID];
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = 'SELECT tag, tagType FROM tags WHERE tagID=?';
|
if (!skipCheck) {
|
||||||
var result = Zotero.DB.rowQuery(sql, tagID);
|
var sql = 'SELECT COUNT(*) FROM tags WHERE tagID=?';
|
||||||
|
var result = Zotero.DB.valueQuery(sql, tagID);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_tagsByID[tagID] = {
|
_tagsByID[tagID] = new Zotero.Tag(tagID);
|
||||||
tag: result.tag,
|
return _tagsByID[tagID];
|
||||||
type: result.tagType
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,31 +72,31 @@ Zotero.Tags = new function() {
|
||||||
*/
|
*/
|
||||||
function getName(tagID) {
|
function getName(tagID) {
|
||||||
if (_tagsByID[tagID]) {
|
if (_tagsByID[tagID]) {
|
||||||
return _tagsByID[tagID].tag;
|
return _tagsByID[tagID].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tag = this.get(tagID);
|
var tag = this.get(tagID);
|
||||||
|
|
||||||
return _tagsByID[tagID] ? _tagsByID[tagID].tag : false;
|
return _tagsByID[tagID] ? _tagsByID[tagID].name : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the tagID matching given tag and type
|
* Returns the tagID matching given tag and type
|
||||||
*/
|
*/
|
||||||
function getID(tag, type) {
|
function getID(name, type) {
|
||||||
if (_tags[type] && _tags[type]['_' + tag]) {
|
if (_tags[type] && _tags[type]['_' + name]) {
|
||||||
return _tags[type]['_' + tag];
|
return _tags[type]['_' + name];
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = 'SELECT tagID FROM tags WHERE tag=? AND tagType=?';
|
var sql = 'SELECT tagID FROM tags WHERE name=? AND type=?';
|
||||||
var tagID = Zotero.DB.valueQuery(sql, [tag, type]);
|
var tagID = Zotero.DB.valueQuery(sql, [name, type]);
|
||||||
|
|
||||||
if (tagID) {
|
if (tagID) {
|
||||||
if (!_tags[type]) {
|
if (!_tags[type]) {
|
||||||
_tags[type] = [];
|
_tags[type] = [];
|
||||||
}
|
}
|
||||||
_tags[type]['_' + tag] = tagID;
|
_tags[type]['_' + name] = tagID;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tagID;
|
return tagID;
|
||||||
|
@ -106,30 +106,40 @@ Zotero.Tags = new function() {
|
||||||
/*
|
/*
|
||||||
* Returns all tagIDs for this tag (of all types)
|
* Returns all tagIDs for this tag (of all types)
|
||||||
*/
|
*/
|
||||||
function getIDs(tag) {
|
function getIDs(name) {
|
||||||
var sql = 'SELECT tagID FROM tags WHERE tag=?';
|
var sql = 'SELECT tagID FROM tags WHERE name=?';
|
||||||
return Zotero.DB.columnQuery(sql, [tag]);
|
return Zotero.DB.columnQuery(sql, [name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns an array of tagTypes for tags matching given tag
|
* Returns an array of tag types for tags matching given tag
|
||||||
*/
|
*/
|
||||||
function getTypes(tag) {
|
function getTypes(name) {
|
||||||
var sql = 'SELECT tagType FROM tags WHERE tag=?';
|
var sql = 'SELECT type FROM tags WHERE name=?';
|
||||||
return Zotero.DB.columnQuery(sql, [tag]);
|
return Zotero.DB.columnQuery(sql, [name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getUpdated(date) {
|
||||||
|
var sql = "SELECT tagID FROM tags";
|
||||||
|
if (date) {
|
||||||
|
sql += " WHERE dateModified>?";
|
||||||
|
return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
|
||||||
|
}
|
||||||
|
return Zotero.DB.columnQuery(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all tags indexed by tagID
|
* Get all tags indexed by tagID
|
||||||
*
|
*
|
||||||
* _types_ is an optional array of tagTypes to fetch
|
* _types_ is an optional array of tag types to fetch
|
||||||
*/
|
*/
|
||||||
function getAll(types) {
|
function getAll(types) {
|
||||||
var sql = "SELECT tagID, tag, tagType FROM tags ";
|
var sql = "SELECT tagID, name FROM tags ";
|
||||||
if (types) {
|
if (types) {
|
||||||
sql += "WHERE tagType IN (" + types.join() + ") ";
|
sql += "WHERE type IN (" + types.join() + ") ";
|
||||||
}
|
}
|
||||||
var tags = Zotero.DB.query(sql);
|
var tags = Zotero.DB.query(sql);
|
||||||
if (!tags) {
|
if (!tags) {
|
||||||
|
@ -138,15 +148,13 @@ Zotero.Tags = new function() {
|
||||||
|
|
||||||
var collation = Zotero.getLocaleCollation();
|
var collation = Zotero.getLocaleCollation();
|
||||||
tags.sort(function(a, b) {
|
tags.sort(function(a, b) {
|
||||||
return collation.compareString(1, a.tag, b.tag);
|
return collation.compareString(1, a.name, b.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
var indexed = {};
|
var indexed = {};
|
||||||
for (var i=0; i<tags.length; i++) {
|
for (var i=0; i<tags.length; i++) {
|
||||||
indexed[tags[i].tagID] = {
|
var tag = this.get(tags[i].tagID, true);
|
||||||
tag: tags[i].tag,
|
indexed[tags[i].tagID] = tag;
|
||||||
type: tags[i].tagType
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return indexed;
|
return indexed;
|
||||||
}
|
}
|
||||||
|
@ -155,7 +163,7 @@ Zotero.Tags = new function() {
|
||||||
/*
|
/*
|
||||||
* Get all tags within the items of a Zotero.Search object
|
* Get all tags within the items of a Zotero.Search object
|
||||||
*
|
*
|
||||||
* _types_ is an optional array of tagTypes to fetch
|
* _types_ is an optional array of tag types to fetch
|
||||||
*/
|
*/
|
||||||
function getAllWithinSearch(search, types) {
|
function getAllWithinSearch(search, types) {
|
||||||
// Save search results to temporary table
|
// Save search results to temporary table
|
||||||
|
@ -176,11 +184,11 @@ Zotero.Tags = new function() {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = "SELECT DISTINCT tagID, tag, tagType FROM itemTags "
|
var sql = "SELECT DISTINCT tagID, name, type FROM itemTags "
|
||||||
+ "NATURAL JOIN tags WHERE itemID IN "
|
+ "NATURAL JOIN tags WHERE itemID IN "
|
||||||
+ "(SELECT itemID FROM " + tmpTable + ") ";
|
+ "(SELECT itemID FROM " + tmpTable + ") ";
|
||||||
if (types) {
|
if (types) {
|
||||||
sql += "AND tagType IN (" + types.join() + ") ";
|
sql += "AND type IN (" + types.join() + ") ";
|
||||||
}
|
}
|
||||||
var tags = Zotero.DB.query(sql);
|
var tags = Zotero.DB.query(sql);
|
||||||
|
|
||||||
|
@ -192,15 +200,13 @@ Zotero.Tags = new function() {
|
||||||
|
|
||||||
var collation = Zotero.getLocaleCollation();
|
var collation = Zotero.getLocaleCollation();
|
||||||
tags.sort(function(a, b) {
|
tags.sort(function(a, b) {
|
||||||
return collation.compareString(1, a.tag, b.tag);
|
return collation.compareString(1, a.name, b.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
var indexed = {};
|
var indexed = {};
|
||||||
for (var i=0; i<tags.length; i++) {
|
for (var i=0; i<tags.length; i++) {
|
||||||
indexed[tags[i].tagID] = {
|
var tag = this.get(tags[i].tagID, true);
|
||||||
tag: tags[i].tag,
|
indexed[tags[i].tagID] = tag;
|
||||||
type: tags[i].tagType
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return indexed;
|
return indexed;
|
||||||
}
|
}
|
||||||
|
@ -213,76 +219,49 @@ Zotero.Tags = new function() {
|
||||||
|
|
||||||
|
|
||||||
function search(str) {
|
function search(str) {
|
||||||
var sql = 'SELECT tagID, tag, tagType FROM tags';
|
var sql = 'SELECT tagID, name, type FROM tags';
|
||||||
if (str) {
|
if (str) {
|
||||||
sql += ' WHERE tag LIKE ?';
|
sql += ' WHERE name LIKE ?';
|
||||||
}
|
}
|
||||||
sql += ' ORDER BY tag COLLATE NOCASE';
|
|
||||||
var tags = Zotero.DB.query(sql, str ? '%' + str + '%' : undefined);
|
var tags = Zotero.DB.query(sql, str ? '%' + str + '%' : undefined);
|
||||||
|
|
||||||
|
if (!tags) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var collation = Zotero.getLocaleCollation();
|
||||||
|
tags.sort(function(a, b) {
|
||||||
|
return collation.compareString(1, a.name, b.name);
|
||||||
|
});
|
||||||
|
|
||||||
var indexed = {};
|
var indexed = {};
|
||||||
for each(var tag in tags) {
|
for (var i=0; i<tags.length; i++) {
|
||||||
indexed[tag.tagID] = {
|
var tag = this.get(tags[i].tagID, true);
|
||||||
tag: tag.tag,
|
indexed[tags[i].tagID] = tag;
|
||||||
type: tag.tagType
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return indexed;
|
return indexed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
function rename(tagID, name) {
|
||||||
* 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.debug('Renaming tag', 4);
|
||||||
|
|
||||||
Zotero.DB.beginTransaction();
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
var tagObj = this.get(tagID);
|
var tagObj = this.get(tagID);
|
||||||
var oldName = tagObj.tag;
|
var oldName = tagObj.name;
|
||||||
var oldType = tagObj.type;
|
var oldType = tagObj.type;
|
||||||
var notifierData = {};
|
var notifierData = {};
|
||||||
notifierData[this.id] = { old: this.toArray() };
|
notifierData[tagID] = { old: tag.serialize() };
|
||||||
|
|
||||||
if (oldName == tag) {
|
if (oldName == name) {
|
||||||
// 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();
|
Zotero.DB.commitTransaction();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the new tag already exists
|
// Check if the new tag already exists
|
||||||
var sql = "SELECT tagID FROM tags WHERE tag=? AND tagType=0";
|
var sql = "SELECT tagID FROM tags WHERE name=? AND type=0";
|
||||||
var existingTagID = Zotero.DB.valueQuery(sql, tag);
|
var existingTagID = Zotero.DB.valueQuery(sql, name);
|
||||||
if (existingTagID) {
|
if (existingTagID) {
|
||||||
var itemIDs = this.getTagItems(tagID);
|
var itemIDs = this.getTagItems(tagID);
|
||||||
var existingItemIDs = this.getTagItems(existingTagID);
|
var existingItemIDs = this.getTagItems(existingTagID);
|
||||||
|
@ -316,54 +295,44 @@ Zotero.Tags = new function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Zotero.Notifier.trigger('add', 'item-tag', itemTags);
|
Zotero.Notifier.trigger('add', 'item-tag', itemTags);
|
||||||
|
// TODO: notify linked items?
|
||||||
|
//Zotero.Notifier.trigger('modify', 'item', itemIDs);
|
||||||
|
|
||||||
Zotero.Notifier.trigger('modify', 'item', itemIDs);
|
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0 == user tag -- we set all renamed tags to 0
|
tagObj.name = name;
|
||||||
var sql = "UPDATE tags SET tag=?, tagType=0 WHERE tagID=?";
|
// Set all renamed tags to manual
|
||||||
Zotero.DB.query(sql, [{string: tag}, tagID]);
|
tagObj.type = 0;
|
||||||
|
tagObj.save();
|
||||||
var itemIDs = this.getTagItems(tagID);
|
|
||||||
|
|
||||||
if (_tags[oldType]) {
|
|
||||||
delete _tags[oldType]['_' + oldName];
|
|
||||||
}
|
|
||||||
delete _tagsByID[tagID];
|
|
||||||
|
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
Zotero.Notifier.trigger('modify', 'item', itemIDs);
|
|
||||||
Zotero.Notifier.trigger('modify', 'tag', tagID, notifierData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function remove(tagID) {
|
function reload(ids) {
|
||||||
|
this.unload(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function erase(ids) {
|
||||||
|
ids = Zotero.flattenArguments(ids);
|
||||||
|
|
||||||
|
var erasedTags = {};
|
||||||
|
|
||||||
Zotero.DB.beginTransaction();
|
Zotero.DB.beginTransaction();
|
||||||
|
for each(var id in ids) {
|
||||||
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
|
var tag = this.get(id);
|
||||||
var itemIDs = Zotero.DB.columnQuery(sql, tagID);
|
if (tag) {
|
||||||
|
erasedTags[id] = tag.serialize();
|
||||||
if (!itemIDs) {
|
tag.erase();
|
||||||
Zotero.DB.commitTransaction();
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = "DELETE FROM itemTags WHERE tagID=?";
|
this.unload(ids);
|
||||||
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();
|
Zotero.DB.commitTransaction();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -373,47 +342,72 @@ Zotero.Tags = new function() {
|
||||||
* Returns removed tagIDs on success
|
* Returns removed tagIDs on success
|
||||||
*/
|
*/
|
||||||
function purge() {
|
function purge() {
|
||||||
|
Zotero.UnresponsiveScriptIndicator.disable();
|
||||||
|
try {
|
||||||
Zotero.DB.beginTransaction();
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
var sql = 'SELECT tagID, tag, tagType FROM tags WHERE tagID '
|
var sql = "CREATE TEMPORARY TABLE tagDelete AS "
|
||||||
+ 'NOT IN (SELECT tagID FROM itemTags);';
|
+ "SELECT tagID FROM tags WHERE tagID "
|
||||||
var toDelete = Zotero.DB.query(sql);
|
+ "NOT IN (SELECT tagID FROM itemTags);";
|
||||||
|
Zotero.DB.query(sql);
|
||||||
|
|
||||||
|
sql = "CREATE INDEX tagDelete_tagID ON tagDelete(tagID)";
|
||||||
|
Zotero.DB.query(sql);
|
||||||
|
|
||||||
|
sql = "SELECT * FROM tagDelete";
|
||||||
|
var toDelete = Zotero.DB.columnQuery(sql);
|
||||||
|
|
||||||
if (!toDelete) {
|
if (!toDelete) {
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.rollbackTransaction();
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var purged = [];
|
|
||||||
var notifierData = {};
|
var notifierData = {};
|
||||||
|
|
||||||
// Clear tag entries in internal array
|
for each(var tagID in toDelete) {
|
||||||
for each(var tag in toDelete) {
|
var tag = Zotero.Tags.get(tagID);
|
||||||
notifierData[tag.tagID] = { old: Zotero.Tags.toArray(tag.tagID) }
|
Zotero.debug(tag);
|
||||||
|
notifierData[tagID] = { old: tag.serialize() }
|
||||||
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 '
|
this.unload(toDelete);
|
||||||
+ '(SELECT tagID FROM itemTags);';
|
|
||||||
var result = Zotero.DB.query(sql);
|
sql = "DELETE FROM tags WHERE tagID IN "
|
||||||
|
+ "(SELECT tagID FROM tagDelete);";
|
||||||
|
Zotero.DB.query(sql);
|
||||||
|
|
||||||
|
sql = "DROP TABLE tagDelete";
|
||||||
|
Zotero.DB.query(sql);
|
||||||
|
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
Zotero.Notifier.trigger('delete', 'tag', purged, notifierData);
|
Zotero.Notifier.trigger('delete', 'tag', toDelete, notifierData);
|
||||||
|
}
|
||||||
return toDelete;
|
catch (e) {
|
||||||
|
Zotero.DB.rollbackTransaction();
|
||||||
|
throw (e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Zotero.UnresponsiveScriptIndicator.enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function toArray(tagID) {
|
/**
|
||||||
var obj = this.get(tagID);
|
* Unload tags from caches
|
||||||
obj.id = tagID;
|
*
|
||||||
return obj;
|
* @param int|array ids One or more tagIDs
|
||||||
|
*/
|
||||||
|
function unload() {
|
||||||
|
var ids = Zotero.flattenArguments(arguments);
|
||||||
|
|
||||||
|
for each(var id in ids) {
|
||||||
|
var tag = _tagsByID[id];
|
||||||
|
delete _tagsByID[id];
|
||||||
|
if (tag && _tags[tag.type]) {
|
||||||
|
delete _tags[tag.type]['_' + tag.name];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ Zotero.ID = new function () {
|
||||||
case 'creatorData':
|
case 'creatorData':
|
||||||
case 'collections':
|
case 'collections':
|
||||||
case 'savedSearches':
|
case 'savedSearches':
|
||||||
|
case 'tags':
|
||||||
var id = _getNextAvailable(table, skip);
|
var id = _getNextAvailable(table, skip);
|
||||||
if (!id && notNull) {
|
if (!id && notNull) {
|
||||||
return _getNext(table, skip);
|
return _getNext(table, skip);
|
||||||
|
@ -57,7 +58,6 @@ Zotero.ID = new function () {
|
||||||
//
|
//
|
||||||
// TODO: use autoincrement instead where available in 1.5
|
// TODO: use autoincrement instead where available in 1.5
|
||||||
case 'itemDataValues':
|
case 'itemDataValues':
|
||||||
case 'tags':
|
|
||||||
var id = _getNextAvailable(table, skip);
|
var id = _getNextAvailable(table, skip);
|
||||||
if (!id) {
|
if (!id) {
|
||||||
// If we can't find an empty id quickly, just use MAX() + 1
|
// If we can't find an empty id quickly, just use MAX() + 1
|
||||||
|
|
|
@ -1447,6 +1447,27 @@ Zotero.Schema = new function(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statement.reset();
|
statement.reset();
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
var tags = Zotero.DB.query("SELECT * FROM tags");
|
||||||
|
Zotero.DB.query("DROP TABLE tags");
|
||||||
|
Zotero.DB.query("CREATE TABLE tags (\n tagID INTEGER PRIMARY KEY,\n name TEXT,\n type INT,\n dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,\n key TEXT NOT NULL UNIQUE,\n UNIQUE (name, type)\n)");
|
||||||
|
var statement = Zotero.DB.getStatement("INSERT INTO tags (tagID, name, type, key) VALUES (?,?,?,?)");
|
||||||
|
for (var j=0, len=searches.length; j<len; j++) {
|
||||||
|
statement.bindInt32Parameter(0, tags[j].tagID);
|
||||||
|
statement.bindUTF8StringParameter(1, tags[j].tag);
|
||||||
|
statement.bindInt32Parameter(2, tags[j].tagType);
|
||||||
|
var key = Zotero.ID.getKey();
|
||||||
|
statement.bindStringParameter(3, key);
|
||||||
|
|
||||||
|
try {
|
||||||
|
statement.execute();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw (Zotero.DB.getLastErrorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statement.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1765,7 +1765,7 @@ Zotero.SearchConditions = new function(){
|
||||||
doesNotContain: true
|
doesNotContain: true
|
||||||
},
|
},
|
||||||
table: 'itemTags',
|
table: 'itemTags',
|
||||||
field: 'tag'
|
field: 'name'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,7 +27,12 @@ Zotero.Sync = new function() {
|
||||||
search: {
|
search: {
|
||||||
singular: 'Search',
|
singular: 'Search',
|
||||||
plural: 'Searches'
|
plural: 'Searches'
|
||||||
|
},
|
||||||
|
tag: {
|
||||||
|
singular: 'Tag',
|
||||||
|
plural: 'Tags'
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1068,6 +1073,8 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
this.xmlToCreator = xmlToCreator;
|
this.xmlToCreator = xmlToCreator;
|
||||||
this.searchToXML = searchToXML;
|
this.searchToXML = searchToXML;
|
||||||
this.xmlToSearch = xmlToSearch;
|
this.xmlToSearch = xmlToSearch;
|
||||||
|
this.tagToXML = tagToXML;
|
||||||
|
this.xmlToTag = xmlToTag;
|
||||||
|
|
||||||
var _noMergeTypes = ['search'];
|
var _noMergeTypes = ['search'];
|
||||||
|
|
||||||
|
@ -1208,7 +1215,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
// Update id in local updates array
|
// Update id in local updates array
|
||||||
var index = uploadIDs.updated[types].indexOf(oldID);
|
var index = uploadIDs.updated[types].indexOf(oldID);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
_error("Local " + type + " " + oldID + " not in "
|
throw ("Local " + type + " " + oldID + " not in "
|
||||||
+ "update array when changing id");
|
+ "update array when changing id");
|
||||||
}
|
}
|
||||||
uploadIDs.updated[types][index] = newID;
|
uploadIDs.updated[types][index] = newID;
|
||||||
|
@ -1256,7 +1263,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
|
|
||||||
if (type != 'item') {
|
if (type != 'item') {
|
||||||
alert('Delete reconciliation unimplemented for ' + types);
|
alert('Delete reconciliation unimplemented for ' + types);
|
||||||
_error('Delete reconciliation unimplemented for ' + types);
|
throw ('Delete reconciliation unimplemented for ' + types);
|
||||||
}
|
}
|
||||||
|
|
||||||
var remoteObj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode);
|
var remoteObj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode);
|
||||||
|
@ -1653,7 +1660,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (skipPrimary) {
|
else if (skipPrimary) {
|
||||||
_error("Cannot use skipPrimary with existing item in "
|
throw ("Cannot use skipPrimary with existing item in "
|
||||||
+ "Zotero.Sync.Server.Data.xmlToItem()");
|
+ "Zotero.Sync.Server.Data.xmlToItem()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1699,7 +1706,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
for each(var creator in xmlItem.creator) {
|
for each(var creator in xmlItem.creator) {
|
||||||
var pos = parseInt(creator.@index);
|
var pos = parseInt(creator.@index);
|
||||||
if (pos != i) {
|
if (pos != i) {
|
||||||
_error('No creator in position ' + i);
|
throw ('No creator in position ' + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.setCreator(
|
item.setCreator(
|
||||||
|
@ -1799,7 +1806,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (skipPrimary) {
|
else if (skipPrimary) {
|
||||||
_error("Cannot use skipPrimary with existing collection in "
|
throw ("Cannot use skipPrimary with existing collection in "
|
||||||
+ "Zotero.Sync.Server.Data.xmlToCollection()");
|
+ "Zotero.Sync.Server.Data.xmlToCollection()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1877,7 +1884,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (skipPrimary) {
|
else if (skipPrimary) {
|
||||||
_error("Cannot use skipPrimary with existing creator in "
|
throw ("Cannot use skipPrimary with existing creator in "
|
||||||
+ "Zotero.Sync.Server.Data.xmlToCreator()");
|
+ "Zotero.Sync.Server.Data.xmlToCreator()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1960,7 +1967,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (skipPrimary) {
|
else if (skipPrimary) {
|
||||||
_error("Cannot use new id with existing search in "
|
throw ("Cannot use new id with existing search in "
|
||||||
+ "Zotero.Sync.Server.Data.xmlToSearch()");
|
+ "Zotero.Sync.Server.Data.xmlToSearch()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2010,4 +2017,63 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
|
|
||||||
return search;
|
return search;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function tagToXML(tag) {
|
||||||
|
var xml = <tag/>;
|
||||||
|
|
||||||
|
xml.@id = tag.id;
|
||||||
|
xml.@name = tag.name;
|
||||||
|
if (tag.type) {
|
||||||
|
xml.@type = tag.type;
|
||||||
|
}
|
||||||
|
xml.@dateModified = tag.dateModified;
|
||||||
|
xml.@key = tag.key;
|
||||||
|
var linkedItems = tag.getLinkedItems(true);
|
||||||
|
if (linkedItems) {
|
||||||
|
xml.items = linkedItems.join(' ');
|
||||||
|
}
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert E4X <tag> object into an unsaved Zotero.Tag
|
||||||
|
*
|
||||||
|
* @param object xmlTag E4X XML node with tag data
|
||||||
|
* @param object tag (Optional) Existing Zotero.Tag to update
|
||||||
|
* @param bool skipPrimary (Optional) Ignore passed primary fields
|
||||||
|
*/
|
||||||
|
function xmlToTag(xmlTag, tag, skipPrimary) {
|
||||||
|
if (!tag) {
|
||||||
|
if (skipPrimary) {
|
||||||
|
tag = new Zotero.Tag;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tag = new Zotero.Tag(parseInt(xmlTag.@id));
|
||||||
|
/*
|
||||||
|
if (tag.exists()) {
|
||||||
|
throw ("Tag specified in XML node already exists "
|
||||||
|
+ "in Zotero.Sync.Server.Data.xmlToTag()");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (skipPrimary) {
|
||||||
|
throw ("Cannot use new id with existing tag in "
|
||||||
|
+ "Zotero.Sync.Server.Data.xmlToTag()");
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.name = xmlTag.@name.toString();
|
||||||
|
tag.type = parseInt(xmlTag.@type);
|
||||||
|
if (!skipPrimary) {
|
||||||
|
tag.dateModified = xmlTag.@dateModified.toString();
|
||||||
|
tag.key = xmlTag.@key.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var str = xmlTag.items ? xmlTag.items.toString() : false;
|
||||||
|
tag.linkedItems = str ? str.split(' ') : [];
|
||||||
|
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,15 +134,21 @@ ZoteroAutoComplete.prototype.startSearch = function(searchString, searchParam,
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'tag':
|
case 'tag':
|
||||||
var sql = "SELECT tag FROM tags WHERE tag LIKE ?";
|
var sql = "SELECT name FROM tags WHERE name LIKE ?";
|
||||||
var sqlParams = [searchString + '%'];
|
var sqlParams = [searchString + '%'];
|
||||||
if (extra){
|
if (extra){
|
||||||
sql += " AND tagID NOT IN (SELECT tagID FROM itemTags WHERE "
|
sql += " AND tagID NOT IN (SELECT tagID FROM itemTags WHERE "
|
||||||
+ "itemID = ?)";
|
+ "itemID = ?)";
|
||||||
sqlParams.push(extra);
|
sqlParams.push(extra);
|
||||||
}
|
}
|
||||||
sql += " ORDER BY tag";
|
|
||||||
var results = this._zotero.DB.columnQuery(sql, sqlParams);
|
var results = this._zotero.DB.columnQuery(sql, sqlParams);
|
||||||
|
if (results) {
|
||||||
|
var collation = Zotero.getLocaleCollation();
|
||||||
|
results.sort(function(a, b) {
|
||||||
|
return collation.compareString(1, a, b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'creator':
|
case 'creator':
|
||||||
|
|
|
@ -14,13 +14,14 @@ var ZoteroWrapped = this;
|
||||||
* Include the core objects to be stored within XPCOM
|
* Include the core objects to be stored within XPCOM
|
||||||
*********************************************************************/
|
*********************************************************************/
|
||||||
|
|
||||||
var xpcomFiles = [ 'zotero',
|
var xpcomFiles = ['zotero',
|
||||||
'annotate', 'attachments', 'cite', 'cite_compat', 'collectionTreeView',
|
'annotate', 'attachments', 'cite', 'cite_compat', 'collectionTreeView',
|
||||||
'dataServer', 'data_access', 'data/item', 'data/items', 'data/collection', 'data/collections',
|
'dataServer', 'data_access', 'data/item', 'data/items', 'data/collection',
|
||||||
'data/cachedTypes', 'data/creator', 'data/creators', 'data/itemFields',
|
'data/collections', 'data/cachedTypes', 'data/creator', 'data/creators',
|
||||||
'data/notes', 'data/tags', 'db', 'file', 'fulltext', 'id', 'ingester', 'integration',
|
'data/itemFields', 'data/notes', 'data/tag', 'data/tags', 'db', 'file',
|
||||||
'itemTreeView', 'mime', 'notifier', 'progressWindow', 'quickCopy', 'report',
|
'fulltext', 'id', 'ingester', 'integration', 'itemTreeView', 'mime',
|
||||||
'schema', 'search', 'sync', 'timeline', 'translate', 'utilities', 'zeroconf'];
|
'notifier', 'progressWindow', 'quickCopy', 'report', 'schema', 'search',
|
||||||
|
'sync', 'timeline', 'translate', 'utilities', 'zeroconf'];
|
||||||
|
|
||||||
for (var i=0; i<xpcomFiles.length; i++) {
|
for (var i=0; i<xpcomFiles.length; i++) {
|
||||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||||
|
|
|
@ -1249,3 +1249,4 @@ INSERT INTO "syncObjectTypes" VALUES(1, 'collection');
|
||||||
INSERT INTO "syncObjectTypes" VALUES(2, 'creator');
|
INSERT INTO "syncObjectTypes" VALUES(2, 'creator');
|
||||||
INSERT INTO "syncObjectTypes" VALUES(3, 'item');
|
INSERT INTO "syncObjectTypes" VALUES(3, 'item');
|
||||||
INSERT INTO "syncObjectTypes" VALUES(4, 'search');
|
INSERT INTO "syncObjectTypes" VALUES(4, 'search');
|
||||||
|
INSERT INTO "syncObjectTypes" VALUES(5, 'tag');
|
||||||
|
|
|
@ -71,9 +71,11 @@ CREATE INDEX itemAttachments_mimeType ON itemAttachments(mimeType);
|
||||||
-- Individual entries for each tag
|
-- Individual entries for each tag
|
||||||
CREATE TABLE tags (
|
CREATE TABLE tags (
|
||||||
tagID INTEGER PRIMARY KEY,
|
tagID INTEGER PRIMARY KEY,
|
||||||
tag TEXT,
|
name TEXT,
|
||||||
tagType INT,
|
type INT,
|
||||||
UNIQUE (tag, tagType)
|
dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
key TEXT NOT NULL UNIQUE,
|
||||||
|
UNIQUE (name, type)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Associates items with keywords
|
-- Associates items with keywords
|
||||||
|
|
Loading…
Reference in New Issue
Block a user