- 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">
|
||||
<binding id="tags-box">
|
||||
<implementation>
|
||||
<field name="itemRef"/>
|
||||
<property name="item" onget="return this.itemRef;">
|
||||
<field name="clickHandler"/>
|
||||
|
||||
<!-- Modes are predefined settings groups for particular tasks -->
|
||||
<field name="_mode">"view"</field>
|
||||
<property name="mode" onget="return this._mode;">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
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.itemRef = val;
|
||||
this._item = val;
|
||||
this.reload();
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<property name="count"/>
|
||||
|
||||
<property name="summary">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
|
@ -50,7 +83,7 @@
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -60,10 +93,13 @@
|
|||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
|
||||
|
||||
<method name="reload">
|
||||
<body>
|
||||
<![CDATA[
|
||||
//Zotero.debug('Reloading tags');
|
||||
Zotero.debug('Reloading tags');
|
||||
|
||||
var rows = this.id('tagRows');
|
||||
while(rows.hasChildNodes())
|
||||
|
@ -88,6 +124,8 @@
|
|||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="addDynamicRow">
|
||||
<parameter name="tagObj"/>
|
||||
<parameter name="tabindex"/>
|
||||
|
@ -95,11 +133,11 @@
|
|||
<![CDATA[
|
||||
if (tagObj) {
|
||||
var tagID = tagObj.id;
|
||||
var tag = tagObj.tag;
|
||||
var name = tagObj.name;
|
||||
var type = tagObj.type;
|
||||
}
|
||||
if (!tag) {
|
||||
tag = '';
|
||||
if (!name) {
|
||||
name = '';
|
||||
}
|
||||
|
||||
if (!tabindex)
|
||||
|
@ -128,7 +166,7 @@
|
|||
// DEBUG: Why won't just this.nextSibling.blur() work?
|
||||
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");
|
||||
remove.setAttribute('value','-');
|
||||
|
@ -159,6 +197,284 @@
|
|||
]]>
|
||||
</body>
|
||||
</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">
|
||||
<body>
|
||||
<![CDATA[
|
||||
|
@ -167,18 +483,21 @@
|
|||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="add">
|
||||
<parameter name="value"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (value)
|
||||
{
|
||||
if (value) {
|
||||
return this.item.addTag(value);
|
||||
}
|
||||
return false;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="replace">
|
||||
<parameter name="oldTagID"/>
|
||||
<parameter name="newTag"/>
|
||||
|
@ -196,6 +515,8 @@
|
|||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="remove">
|
||||
<parameter name="id"/>
|
||||
<body>
|
||||
|
@ -204,6 +525,8 @@
|
|||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="updateCount">
|
||||
<parameter name="count"/>
|
||||
<body>
|
||||
|
@ -235,14 +558,8 @@
|
|||
]]>
|
||||
</body>
|
||||
</method>
|
||||
<method name="id">
|
||||
<parameter name="id"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="fixPopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
|
@ -262,6 +579,8 @@
|
|||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="closePopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
|
@ -271,10 +590,78 @@
|
|||
]]>
|
||||
</body>
|
||||
</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>
|
||||
<![CDATA[
|
||||
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];
|
||||
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
|
|
@ -181,7 +181,7 @@
|
|||
for (var tagID in this._tags) {
|
||||
// If the last tag was the same, add this tagID and tagType to it
|
||||
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('tagType', tagsToggleBox.lastChild.getAttribute('tagType') + '-' + this._tags[tagID].type);
|
||||
continue;
|
||||
|
@ -190,7 +190,7 @@
|
|||
var label = document.createElement('label');
|
||||
label.setAttribute('onclick', "this.parentNode.parentNode.parentNode.handleTagClick(event, this)");
|
||||
label.className = 'zotero-clicky';
|
||||
label.setAttribute('value', this._tags[tagID].tag);
|
||||
label.setAttribute('value', this._tags[tagID].name);
|
||||
label.setAttribute('tagID', tagID);
|
||||
label.setAttribute('tagType', this._tags[tagID].type);
|
||||
label.setAttribute('context', 'tag-menu');
|
||||
|
|
|
@ -120,16 +120,15 @@ var ZoteroItemPane = new function() {
|
|||
|
||||
// Info pane
|
||||
if (index == 0) {
|
||||
var itembox = document.getElementById('zotero-editpane-item-box');
|
||||
// Hack to allow read-only mode in right pane -- probably a better
|
||||
// way to allow access to this
|
||||
if (mode) {
|
||||
itembox.mode = mode;
|
||||
_itemBox.mode = mode;
|
||||
}
|
||||
else {
|
||||
itembox.mode = 'edit';
|
||||
_itemBox.mode = 'edit';
|
||||
}
|
||||
itembox.item = _itemBeingEdited;
|
||||
_itemBox.item = _itemBeingEdited;
|
||||
}
|
||||
|
||||
// Notes pane
|
||||
|
@ -147,7 +146,9 @@ var ZoteroItemPane = new function() {
|
|||
icon.setAttribute('src','chrome://zotero/skin/treeitem-note.png');
|
||||
|
||||
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('crop','end');
|
||||
|
||||
|
@ -236,6 +237,13 @@ var ZoteroItemPane = new function() {
|
|||
// Tags pane
|
||||
else if(index == 3)
|
||||
{
|
||||
if (mode) {
|
||||
_tagsBox.mode = mode;
|
||||
}
|
||||
else {
|
||||
_tagsBox.mode = 'edit';
|
||||
}
|
||||
|
||||
var focusMode = 'tags';
|
||||
var focusBox = _tagsBox;
|
||||
_tagsBox.item = _itemBeingEdited;
|
||||
|
@ -269,27 +277,6 @@ var ZoteroItemPane = new function() {
|
|||
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()
|
||||
{
|
||||
var c = _notesList.childNodes.length;
|
||||
|
|
|
@ -28,45 +28,49 @@
|
|||
<overlay
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script src="include.js"/>
|
||||
<script src="itemPane.js"/>
|
||||
|
||||
<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"/>
|
||||
<button label="&zotero.item.add;" oncommand="ZoteroItemPane.addNote();"/>
|
||||
</hbox>
|
||||
<grid flex="1">
|
||||
<columns>
|
||||
<column flex="1"/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows id="zotero-editpane-dynamic-notes" flex="1"/>
|
||||
</grid>
|
||||
</vbox>
|
||||
<vbox flex="1">
|
||||
<hbox align="center">
|
||||
<label id="zotero-editpane-attachments-label"/>
|
||||
<button id="zotero-tb-item-attachments-add" type="menu" label="&zotero.item.add;">
|
||||
<menupopup>
|
||||
<menuitem class="menuitem-iconic" id="zotero-tb-item-attachments-link" label="&zotero.toolbar.attachment.linked;" oncommand="ZoteroItemPane.addAttachmentFromDialog(true);"/>
|
||||
<menuitem class="menuitem-iconic" id="zotero-tb-item-attachments-file" label="&zotero.toolbar.attachment.add;" oncommand="ZoteroItemPane.addAttachmentFromDialog();"/>
|
||||
<menuitem class="menuitem-iconic" id="zotero-tb-item-attachments-web-link" label="&zotero.toolbar.attachment.weblink;" oncommand="ZoteroItemPane.addAttachmentFromPage(true);"/>
|
||||
<menuitem class="menuitem-iconic" id="zotero-tb-item-attachments-snapshot" label="&zotero.toolbar.attachment.snapshot;" oncommand="ZoteroItemPane.addAttachmentFromPage();"/>
|
||||
</menupopup>
|
||||
</button>
|
||||
</hbox>
|
||||
<grid flex="1">
|
||||
<columns>
|
||||
<column flex="1"/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows id="zotero-editpane-dynamic-attachments" flex="1"/>
|
||||
</grid>
|
||||
</vbox>
|
||||
<tagsbox id="zotero-editpane-tags" flex="1"/>
|
||||
<seealsobox id="zotero-editpane-related" flex="1"/>
|
||||
<vbox flex="1">
|
||||
<hbox align="center">
|
||||
<label id="zotero-editpane-notes-label"/>
|
||||
<button label="&zotero.item.add;" oncommand="ZoteroItemPane.addNote();"/>
|
||||
</hbox>
|
||||
<grid flex="1">
|
||||
<columns>
|
||||
<column flex="1"/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows id="zotero-editpane-dynamic-notes" flex="1"/>
|
||||
</grid>
|
||||
</vbox>
|
||||
|
||||
<vbox flex="1">
|
||||
<hbox align="center">
|
||||
<label id="zotero-editpane-attachments-label"/>
|
||||
<button id="zotero-tb-item-attachments-add" type="menu" label="&zotero.item.add;">
|
||||
<menupopup>
|
||||
<menuitem class="menuitem-iconic" id="zotero-tb-item-attachments-link" label="&zotero.toolbar.attachment.linked;" oncommand="ZoteroItemPane.addAttachmentFromDialog(true);"/>
|
||||
<menuitem class="menuitem-iconic" id="zotero-tb-item-attachments-file" label="&zotero.toolbar.attachment.add;" oncommand="ZoteroItemPane.addAttachmentFromDialog();"/>
|
||||
<menuitem class="menuitem-iconic" id="zotero-tb-item-attachments-web-link" label="&zotero.toolbar.attachment.weblink;" oncommand="ZoteroItemPane.addAttachmentFromPage(true);"/>
|
||||
<menuitem class="menuitem-iconic" id="zotero-tb-item-attachments-snapshot" label="&zotero.toolbar.attachment.snapshot;" oncommand="ZoteroItemPane.addAttachmentFromPage();"/>
|
||||
</menupopup>
|
||||
</button>
|
||||
</hbox>
|
||||
<grid flex="1">
|
||||
<columns>
|
||||
<column flex="1"/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows id="zotero-editpane-dynamic-attachments" flex="1"/>
|
||||
</grid>
|
||||
</vbox>
|
||||
|
||||
<tagsbox id="zotero-editpane-tags" flex="1"/>
|
||||
|
||||
<seealsobox id="zotero-editpane-related" flex="1"/>
|
||||
</deck>
|
||||
</overlay>
|
|
@ -785,8 +785,7 @@ var ZoteroPane = new function()
|
|||
{
|
||||
var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex);
|
||||
|
||||
if(item.ref.isNote())
|
||||
{
|
||||
if(item.ref.isNote()) {
|
||||
var noteEditor = document.getElementById('zotero-note-editor');
|
||||
if (this.itemsView.readOnly) {
|
||||
noteEditor.mode = 'view';
|
||||
|
@ -817,8 +816,8 @@ var ZoteroPane = new function()
|
|||
}
|
||||
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
|
||||
// version of createValueElement() from itemPane.js
|
||||
// (ideally in an XBL binding)
|
||||
|
@ -956,6 +955,8 @@ var ZoteroPane = new function()
|
|||
|
||||
document.getElementById('zotero-item-pane-content').selectedIndex = 3;
|
||||
}
|
||||
|
||||
// Regular item
|
||||
else
|
||||
{
|
||||
ZoteroItemPane.viewItem(item.ref, this.itemsView.readOnly ? 'view' : false);
|
||||
|
|
|
@ -551,11 +551,14 @@ Zotero.Collection.prototype.addItems = function(itemIDs) {
|
|||
* 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;
|
||||
var childItems = this.getChildItems(true);
|
||||
if (childItems) {
|
||||
var index = childItems.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();
|
||||
|
|
|
@ -353,6 +353,7 @@ Zotero.Creator.prototype.erase = function () {
|
|||
|
||||
Zotero.debug("Deleting creator " + this.id);
|
||||
|
||||
// TODO: notifier
|
||||
var changedItems = [];
|
||||
var changedItemsNotifierData = {};
|
||||
|
||||
|
|
|
@ -50,14 +50,14 @@ Zotero.Creators = new function() {
|
|||
return _creatorsByID[creatorID];
|
||||
}
|
||||
|
||||
var sql = 'SELECT * FROM creators WHERE creatorID=?';
|
||||
var result = Zotero.DB.rowQuery(sql, creatorID);
|
||||
var sql = 'SELECT COUNT(*) FROM creators WHERE creatorID=?';
|
||||
var result = Zotero.DB.valueQuery(sql, creatorID);
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_creatorsByID[creatorID] = new Zotero.Creator(result.creatorID);
|
||||
_creatorsByID[creatorID] = new Zotero.Creator(creatorID);
|
||||
return _creatorsByID[creatorID];
|
||||
}
|
||||
|
||||
|
|
|
@ -2364,12 +2364,12 @@ Zotero.Item.prototype.getBestSnapshot = function() {
|
|||
//
|
||||
// save() is not required for tag functions
|
||||
//
|
||||
Zotero.Item.prototype.addTag = function(tag, type) {
|
||||
Zotero.Item.prototype.addTag = function(name, type) {
|
||||
if (!this.id) {
|
||||
throw ('Cannot add tag to unsaved item in Item.addTag()');
|
||||
}
|
||||
|
||||
if (!tag) {
|
||||
if (!name) {
|
||||
Zotero.debug('Not saving empty tag in Item.addTag()', 2);
|
||||
return false;
|
||||
}
|
||||
|
@ -2378,18 +2378,13 @@ Zotero.Item.prototype.addTag = function(tag, type) {
|
|||
type = 0;
|
||||
}
|
||||
|
||||
if (type !=0 && type !=1) {
|
||||
throw ('Invalid tag type in Item.addTag()');
|
||||
}
|
||||
|
||||
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 existing automatic and adding identical user, remove automatic
|
||||
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
|
||||
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) {
|
||||
var tagID = Zotero.Tags.add(tag, type);
|
||||
var tag = new Zotero.Tag;
|
||||
tag.name = name;
|
||||
tag.type = type;
|
||||
var tagID = tag.save();
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -2433,38 +2432,20 @@ Zotero.Item.prototype.addTags = function (tags, type) {
|
|||
|
||||
Zotero.Item.prototype.addTagByID = function(tagID) {
|
||||
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) {
|
||||
Zotero.debug('Not saving nonexistent tag in Item.addTagByID()', 2);
|
||||
return false;
|
||||
throw ('tagID not provided in Zotero.Item.addTagByID()');
|
||||
}
|
||||
|
||||
var sql = "SELECT COUNT(*) FROM tags WHERE tagID = ?";
|
||||
var count = !!Zotero.DB.valueQuery(sql, tagID);
|
||||
|
||||
if (!count) {
|
||||
throw ('Cannot add invalid tag id ' + tagID + ' in Item.addTagByID()');
|
||||
var tag = Zotero.Tags.get(tagID);
|
||||
if (!tag) {
|
||||
throw ('Cannot add invalid tag ' + tagID + ' in Zotero.Item.addTagByID()');
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
// 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;
|
||||
tag.addItem(this.id);
|
||||
tag.save();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all tags assigned to an item
|
||||
*
|
||||
* @return array Array of Zotero.Tag objects
|
||||
*/
|
||||
Zotero.Item.prototype.getTags = function() {
|
||||
if (!this.id) {
|
||||
return false;
|
||||
}
|
||||
var sql = "SELECT tagID AS id, tag, tagType AS type FROM tags WHERE tagID IN "
|
||||
+ "(SELECT tagID FROM itemTags WHERE itemID=" + this.id + ")";
|
||||
|
||||
var tags = Zotero.DB.query(sql);
|
||||
var sql = "SELECT tagID, name FROM tags WHERE tagID IN "
|
||||
+ "(SELECT tagID FROM itemTags WHERE itemID=?)";
|
||||
var tags = Zotero.DB.query(sql, this.id);
|
||||
if (!tags) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
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() {
|
||||
var sql = "SELECT tagID FROM itemTags WHERE itemID=" + this.id;
|
||||
return Zotero.DB.columnQuery(sql);
|
||||
var sql = "SELECT tagID FROM itemTags WHERE itemID=?";
|
||||
return Zotero.DB.columnQuery(sql, this.id);
|
||||
}
|
||||
|
||||
Zotero.Item.prototype.replaceTag = function(oldTagID, newTag) {
|
||||
|
@ -2537,16 +2528,20 @@ Zotero.Item.prototype.replaceTag = function(oldTagID, newTag) {
|
|||
|
||||
Zotero.Item.prototype.removeTag = function(tagID) {
|
||||
if (!this.id) {
|
||||
throw ('Cannot remove tag on unsaved item');
|
||||
throw ('Cannot remove tag on unsaved item in Zotero.Item.removeTag()');
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
var sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=?";
|
||||
Zotero.DB.query(sql, [this.id, { int: tagID }]);
|
||||
Zotero.Tags.purge();
|
||||
Zotero.DB.commitTransaction();
|
||||
Zotero.Notifier.trigger('modify', 'item', this.id);
|
||||
Zotero.Notifier.trigger('remove', 'item-tag', this.id + '-' + tagID);
|
||||
if (!tagID) {
|
||||
throw ('tagID not provided in Zotero.Item.removeTag()');
|
||||
}
|
||||
|
||||
var tag = Zotero.Tags.get(tagID);
|
||||
if (!tag) {
|
||||
throw ('Cannot remove invalid tag ' + tagID + ' in Zotero.Item.removeTag()');
|
||||
}
|
||||
|
||||
tag.removeItem(this.id);
|
||||
tag.save();
|
||||
}
|
||||
|
||||
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();
|
||||
if (!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();
|
||||
if (!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.getIDs = getIDs;
|
||||
this.getTypes = getTypes;
|
||||
this.getUpdated = getUpdated;
|
||||
this.getAll = getAll;
|
||||
this.getAllWithinSearch = getAllWithinSearch;
|
||||
this.getTagItems = getTagItems;
|
||||
this.search = search;
|
||||
this.add = add;
|
||||
this.rename = rename;
|
||||
this.remove = remove;
|
||||
this.reload = reload;
|
||||
this.erase = erase;
|
||||
this.purge = purge;
|
||||
this.toArray = toArray;
|
||||
this.unload = unload;
|
||||
|
||||
|
||||
/*
|
||||
* Returns a tag and type for a given tagID
|
||||
*/
|
||||
function get(tagID) {
|
||||
function get(tagID, skipCheck) {
|
||||
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;
|
||||
if (!skipCheck) {
|
||||
var sql = 'SELECT COUNT(*) FROM tags WHERE tagID=?';
|
||||
var result = Zotero.DB.valueQuery(sql, tagID);
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_tagsByID[tagID] = {
|
||||
tag: result.tag,
|
||||
type: result.tagType
|
||||
};
|
||||
return result;
|
||||
_tagsByID[tagID] = new Zotero.Tag(tagID);
|
||||
return _tagsByID[tagID];
|
||||
}
|
||||
|
||||
|
||||
|
@ -72,31 +72,31 @@ Zotero.Tags = new function() {
|
|||
*/
|
||||
function getName(tagID) {
|
||||
if (_tagsByID[tagID]) {
|
||||
return _tagsByID[tagID].tag;
|
||||
return _tagsByID[tagID].name;
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
function getID(tag, type) {
|
||||
if (_tags[type] && _tags[type]['_' + tag]) {
|
||||
return _tags[type]['_' + tag];
|
||||
function getID(name, type) {
|
||||
if (_tags[type] && _tags[type]['_' + name]) {
|
||||
return _tags[type]['_' + name];
|
||||
}
|
||||
|
||||
var sql = 'SELECT tagID FROM tags WHERE tag=? AND tagType=?';
|
||||
var tagID = Zotero.DB.valueQuery(sql, [tag, type]);
|
||||
var sql = 'SELECT tagID FROM tags WHERE name=? AND type=?';
|
||||
var tagID = Zotero.DB.valueQuery(sql, [name, type]);
|
||||
|
||||
if (tagID) {
|
||||
if (!_tags[type]) {
|
||||
_tags[type] = [];
|
||||
}
|
||||
_tags[type]['_' + tag] = tagID;
|
||||
_tags[type]['_' + name] = tagID;
|
||||
}
|
||||
|
||||
return tagID;
|
||||
|
@ -106,30 +106,40 @@ Zotero.Tags = new function() {
|
|||
/*
|
||||
* 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]);
|
||||
function getIDs(name) {
|
||||
var sql = 'SELECT tagID FROM tags WHERE name=?';
|
||||
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) {
|
||||
var sql = 'SELECT tagType FROM tags WHERE tag=?';
|
||||
return Zotero.DB.columnQuery(sql, [tag]);
|
||||
function getTypes(name) {
|
||||
var sql = 'SELECT type FROM tags WHERE name=?';
|
||||
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
|
||||
*
|
||||
* _types_ is an optional array of tagTypes to fetch
|
||||
* _types_ is an optional array of tag types to fetch
|
||||
*/
|
||||
function getAll(types) {
|
||||
var sql = "SELECT tagID, tag, tagType FROM tags ";
|
||||
var sql = "SELECT tagID, name FROM tags ";
|
||||
if (types) {
|
||||
sql += "WHERE tagType IN (" + types.join() + ") ";
|
||||
sql += "WHERE type IN (" + types.join() + ") ";
|
||||
}
|
||||
var tags = Zotero.DB.query(sql);
|
||||
if (!tags) {
|
||||
|
@ -138,15 +148,13 @@ Zotero.Tags = new function() {
|
|||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
tags.sort(function(a, b) {
|
||||
return collation.compareString(1, a.tag, b.tag);
|
||||
return collation.compareString(1, a.name, b.name);
|
||||
});
|
||||
|
||||
var indexed = {};
|
||||
for (var i=0; i<tags.length; i++) {
|
||||
indexed[tags[i].tagID] = {
|
||||
tag: tags[i].tag,
|
||||
type: tags[i].tagType
|
||||
};
|
||||
var tag = this.get(tags[i].tagID, true);
|
||||
indexed[tags[i].tagID] = tag;
|
||||
}
|
||||
return indexed;
|
||||
}
|
||||
|
@ -155,7 +163,7 @@ Zotero.Tags = new function() {
|
|||
/*
|
||||
* 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) {
|
||||
// Save search results to temporary table
|
||||
|
@ -176,11 +184,11 @@ Zotero.Tags = new function() {
|
|||
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 "
|
||||
+ "(SELECT itemID FROM " + tmpTable + ") ";
|
||||
if (types) {
|
||||
sql += "AND tagType IN (" + types.join() + ") ";
|
||||
sql += "AND type IN (" + types.join() + ") ";
|
||||
}
|
||||
var tags = Zotero.DB.query(sql);
|
||||
|
||||
|
@ -192,15 +200,13 @@ Zotero.Tags = new function() {
|
|||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
tags.sort(function(a, b) {
|
||||
return collation.compareString(1, a.tag, b.tag);
|
||||
return collation.compareString(1, a.name, b.name);
|
||||
});
|
||||
|
||||
var indexed = {};
|
||||
for (var i=0; i<tags.length; i++) {
|
||||
indexed[tags[i].tagID] = {
|
||||
tag: tags[i].tag,
|
||||
type: tags[i].tagType
|
||||
};
|
||||
var tag = this.get(tags[i].tagID, true);
|
||||
indexed[tags[i].tagID] = tag;
|
||||
}
|
||||
return indexed;
|
||||
}
|
||||
|
@ -213,76 +219,49 @@ Zotero.Tags = new function() {
|
|||
|
||||
|
||||
function search(str) {
|
||||
var sql = 'SELECT tagID, tag, tagType FROM tags';
|
||||
var sql = 'SELECT tagID, name, type FROM tags';
|
||||
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);
|
||||
|
||||
if (!tags) {
|
||||
return {};
|
||||
}
|
||||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
tags.sort(function(a, b) {
|
||||
return collation.compareString(1, a.name, b.name);
|
||||
});
|
||||
|
||||
var indexed = {};
|
||||
for each(var tag in tags) {
|
||||
indexed[tag.tagID] = {
|
||||
tag: tag.tag,
|
||||
type: tag.tagType
|
||||
};
|
||||
for (var i=0; i<tags.length; i++) {
|
||||
var tag = this.get(tags[i].tagID, true);
|
||||
indexed[tags[i].tagID] = tag;
|
||||
}
|
||||
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) {
|
||||
function rename(tagID, name) {
|
||||
Zotero.debug('Renaming tag', 4);
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var tagObj = this.get(tagID);
|
||||
var oldName = tagObj.tag;
|
||||
var oldName = tagObj.name;
|
||||
var oldType = tagObj.type;
|
||||
var notifierData = {};
|
||||
notifierData[this.id] = { old: this.toArray() };
|
||||
notifierData[tagID] = { old: tag.serialize() };
|
||||
|
||||
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);
|
||||
}
|
||||
if (oldName == name) {
|
||||
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);
|
||||
var sql = "SELECT tagID FROM tags WHERE name=? AND type=0";
|
||||
var existingTagID = Zotero.DB.valueQuery(sql, name);
|
||||
if (existingTagID) {
|
||||
var itemIDs = this.getTagItems(tagID);
|
||||
var existingItemIDs = this.getTagItems(existingTagID);
|
||||
|
@ -316,54 +295,44 @@ Zotero.Tags = new function() {
|
|||
}
|
||||
}
|
||||
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();
|
||||
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];
|
||||
tagObj.name = name;
|
||||
// Set all renamed tags to manual
|
||||
tagObj.type = 0;
|
||||
tagObj.save();
|
||||
|
||||
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();
|
||||
|
||||
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
|
||||
var itemIDs = Zotero.DB.columnQuery(sql, tagID);
|
||||
|
||||
if (!itemIDs) {
|
||||
Zotero.DB.commitTransaction();
|
||||
return;
|
||||
for each(var id in ids) {
|
||||
var tag = this.get(id);
|
||||
if (tag) {
|
||||
erasedTags[id] = tag.serialize();
|
||||
tag.erase();
|
||||
}
|
||||
}
|
||||
|
||||
var sql = "DELETE FROM itemTags WHERE tagID=?";
|
||||
Zotero.DB.query(sql, tagID);
|
||||
this.unload(ids);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -373,47 +342,72 @@ Zotero.Tags = new function() {
|
|||
* 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) }
|
||||
Zotero.UnresponsiveScriptIndicator.disable();
|
||||
try {
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
purged.push(tag.tagID);
|
||||
if (_tags[tag.tagType]) {
|
||||
delete _tags[tag.tagType]['_' + tag.tag];
|
||||
var sql = "CREATE TEMPORARY TABLE tagDelete AS "
|
||||
+ "SELECT tagID FROM tags WHERE tagID "
|
||||
+ "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) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
return;
|
||||
}
|
||||
delete _tagsByID[tag.tagID];
|
||||
|
||||
var notifierData = {};
|
||||
|
||||
for each(var tagID in toDelete) {
|
||||
var tag = Zotero.Tags.get(tagID);
|
||||
Zotero.debug(tag);
|
||||
notifierData[tagID] = { old: tag.serialize() }
|
||||
}
|
||||
|
||||
this.unload(toDelete);
|
||||
|
||||
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.Notifier.trigger('delete', 'tag', toDelete, notifierData);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
throw (e);
|
||||
}
|
||||
finally {
|
||||
Zotero.UnresponsiveScriptIndicator.enable();
|
||||
}
|
||||
|
||||
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;
|
||||
/**
|
||||
* Unload tags from caches
|
||||
*
|
||||
* @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 'collections':
|
||||
case 'savedSearches':
|
||||
case 'tags':
|
||||
var id = _getNextAvailable(table, skip);
|
||||
if (!id && notNull) {
|
||||
return _getNext(table, skip);
|
||||
|
@ -57,7 +58,6 @@ Zotero.ID = new function () {
|
|||
//
|
||||
// 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
|
||||
|
|
|
@ -1447,6 +1447,27 @@ Zotero.Schema = new function(){
|
|||
}
|
||||
}
|
||||
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
|
||||
},
|
||||
table: 'itemTags',
|
||||
field: 'tag'
|
||||
field: 'name'
|
||||
},
|
||||
|
||||
{
|
||||
|
|
|
@ -27,7 +27,12 @@ Zotero.Sync = new function() {
|
|||
search: {
|
||||
singular: 'Search',
|
||||
plural: 'Searches'
|
||||
},
|
||||
tag: {
|
||||
singular: 'Tag',
|
||||
plural: 'Tags'
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -1068,6 +1073,8 @@ Zotero.Sync.Server.Data = new function() {
|
|||
this.xmlToCreator = xmlToCreator;
|
||||
this.searchToXML = searchToXML;
|
||||
this.xmlToSearch = xmlToSearch;
|
||||
this.tagToXML = tagToXML;
|
||||
this.xmlToTag = xmlToTag;
|
||||
|
||||
var _noMergeTypes = ['search'];
|
||||
|
||||
|
@ -1208,7 +1215,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
// Update id in local updates array
|
||||
var index = uploadIDs.updated[types].indexOf(oldID);
|
||||
if (index == -1) {
|
||||
_error("Local " + type + " " + oldID + " not in "
|
||||
throw ("Local " + type + " " + oldID + " not in "
|
||||
+ "update array when changing id");
|
||||
}
|
||||
uploadIDs.updated[types][index] = newID;
|
||||
|
@ -1256,7 +1263,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
if (type != 'item') {
|
||||
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);
|
||||
|
@ -1653,7 +1660,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
else if (skipPrimary) {
|
||||
_error("Cannot use skipPrimary with existing item in "
|
||||
throw ("Cannot use skipPrimary with existing item in "
|
||||
+ "Zotero.Sync.Server.Data.xmlToItem()");
|
||||
}
|
||||
|
||||
|
@ -1699,7 +1706,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
for each(var creator in xmlItem.creator) {
|
||||
var pos = parseInt(creator.@index);
|
||||
if (pos != i) {
|
||||
_error('No creator in position ' + i);
|
||||
throw ('No creator in position ' + i);
|
||||
}
|
||||
|
||||
item.setCreator(
|
||||
|
@ -1799,7 +1806,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
else if (skipPrimary) {
|
||||
_error("Cannot use skipPrimary with existing collection in "
|
||||
throw ("Cannot use skipPrimary with existing collection in "
|
||||
+ "Zotero.Sync.Server.Data.xmlToCollection()");
|
||||
}
|
||||
|
||||
|
@ -1877,7 +1884,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
else if (skipPrimary) {
|
||||
_error("Cannot use skipPrimary with existing creator in "
|
||||
throw ("Cannot use skipPrimary with existing creator in "
|
||||
+ "Zotero.Sync.Server.Data.xmlToCreator()");
|
||||
}
|
||||
|
||||
|
@ -1960,7 +1967,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
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()");
|
||||
}
|
||||
|
||||
|
@ -2010,4 +2017,63 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
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;
|
||||
|
||||
case 'tag':
|
||||
var sql = "SELECT tag FROM tags WHERE tag LIKE ?";
|
||||
var sql = "SELECT name FROM tags WHERE name LIKE ?";
|
||||
var sqlParams = [searchString + '%'];
|
||||
if (extra){
|
||||
sql += " AND tagID NOT IN (SELECT tagID FROM itemTags WHERE "
|
||||
+ "itemID = ?)";
|
||||
sqlParams.push(extra);
|
||||
}
|
||||
sql += " ORDER BY tag";
|
||||
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;
|
||||
|
||||
case 'creator':
|
||||
|
|
|
@ -14,13 +14,14 @@ var ZoteroWrapped = this;
|
|||
* Include the core objects to be stored within XPCOM
|
||||
*********************************************************************/
|
||||
|
||||
var xpcomFiles = [ 'zotero',
|
||||
var xpcomFiles = ['zotero',
|
||||
'annotate', 'attachments', 'cite', 'cite_compat', 'collectionTreeView',
|
||||
'dataServer', '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', 'zeroconf'];
|
||||
'dataServer', 'data_access', 'data/item', 'data/items', 'data/collection',
|
||||
'data/collections', 'data/cachedTypes', 'data/creator', 'data/creators',
|
||||
'data/itemFields', 'data/notes', 'data/tag', 'data/tags', 'db', 'file',
|
||||
'fulltext', 'id', 'ingester', 'integration', 'itemTreeView', 'mime',
|
||||
'notifier', 'progressWindow', 'quickCopy', 'report', 'schema', 'search',
|
||||
'sync', 'timeline', 'translate', 'utilities', 'zeroconf'];
|
||||
|
||||
for (var i=0; i<xpcomFiles.length; i++) {
|
||||
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(3, 'item');
|
||||
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
|
||||
CREATE TABLE tags (
|
||||
tagID INTEGER PRIMARY KEY,
|
||||
tag TEXT,
|
||||
tagType INT,
|
||||
UNIQUE (tag, tagType)
|
||||
name TEXT,
|
||||
type INT,
|
||||
dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
key TEXT NOT NULL UNIQUE,
|
||||
UNIQUE (name, type)
|
||||
);
|
||||
|
||||
-- Associates items with keywords
|
||||
|
|
Loading…
Reference in New Issue
Block a user