Addresses #502, Special handling for automatic tags

- Automatic tags now appear in orange; tooltip says either "User-added tag" or "Automatically added tag"
- New menu in tag selector to toggle automatic tags
- User and automatic tags are combined in tag selector, so renaming/deleting a tag will affect both user and automatic, regardless of view mode
- Editing a tag makes it a user tag, as does adding an identical user tag to an item (rather than creating a second one)
- ingester/export will need to be adjusted to add automatic tags

Changed:

Item.addTag(tag) => addTag(tag, type)
Item.getTags() - now returns 'id', 'tag', 'type'
Item.toArray() - tags now include 'type' property (from Item.getTags())
Tags.getID(tag) => getID(tag, type)
Tags.getAll() => getAll([types]) - types is an optional array of tagTypes to fetch; now returns objects with 'tag' and 'type' properties
Tags.getAllWithinSearch(search) => Tags.getAllWithinSearch(search, [types]) - now returns object with 'tag'/'type'

Added:

Tags.get(tagID) - returns object with 'tag' and 'type' properties
Tags.getIDs(tag) - returns all tagIDs for this tag (of all types)
Tags.getType(tag) - returns array of tag types matching given tag

For type property, 0 == user, 1 == automatic
This commit is contained in:
Dan Stillman 2007-01-22 22:32:52 +00:00
parent 2f94234a36
commit a3126da160
9 changed files with 350 additions and 85 deletions

View File

@ -50,7 +50,7 @@
{
for(var i = 0; i < tags.length; i++)
{
r = r + tags[i] + ", ";
r = r + tags[i].tag + ", ";
}
r = r.substr(0,r.length-2);
}
@ -73,8 +73,7 @@
if(tags)
{
for(var i = 0; i < tags.length; i++)
{
for (var i=0; i<tags.length; i++) {
this.addDynamicRow(tags[i], i+1);
}
this.updateCount(tags.length);
@ -90,12 +89,18 @@
</body>
</method>
<method name="addDynamicRow">
<parameter name="tag"/>
<parameter name="tagObj"/>
<parameter name="tabindex"/>
<body>
<![CDATA[
var id = tag ? Zotero.Tags.getID(tag) : null;
tag = tag ? tag : '';
if (tagObj) {
var tagID = tagObj.id;
var tag = tagObj.tag;
var type = tagObj.type;
}
if (!tag) {
tag = '';
}
if (!tabindex)
{
@ -109,8 +114,17 @@
}
}
var icon= document.createElement("image");
icon.setAttribute('src','chrome://zotero/skin/tag.png');
var icon = document.createElement("image");
var iconFile = 'tag';
if (type == 0) {
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.user'));
}
else if (type == 1) {
iconFile += '-automatic';
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.automatic'));
}
icon.setAttribute('src', 'chrome://zotero/skin/' + iconFile + '.png');
// DEBUG: Why won't just this.nextSibling.blur() work?
icon.setAttribute('onclick','if (this.nextSibling.inputField){ this.nextSibling.inputField.blur() }');
@ -119,10 +133,10 @@
var remove = document.createElement("label");
remove.setAttribute('value','-');
remove.setAttribute('class','zotero-clicky');
if (id)
if (tagID)
{
remove.setAttribute('ztabindex', -1);
remove.setAttribute('onclick',"this.parentNode.parentNode.parentNode.parentNode.parentNode.remove('"+id+"');");
remove.setAttribute('onclick',"this.parentNode.parentNode.parentNode.parentNode.parentNode.remove('"+ tagID +"');");
}
else
{
@ -133,9 +147,11 @@
row.appendChild(icon);
row.appendChild(label);
row.appendChild(remove);
if (id)
if (tagID)
{
row.setAttribute('id','tag-'+id);
row.setAttribute('id', 'tag-' + tagID);
row.setAttribute('tagType', type);
}
this.id('tagRows').appendChild(row);
@ -246,6 +262,15 @@
]]>
</body>
</method>
<method name="closePopup">
<body>
<![CDATA[
if (this.parentNode.getAttribute('showing')=='true') {
this.parentNode.setAttribute('showing', false);
}
]]>
</body>
</method>
<method name="getScrollBox">
<body>
<![CDATA[

View File

@ -39,6 +39,18 @@
<field name="_dirty">null</field>
<field name="_empty">null</field>
<field name="selection"/>
<property name="showAutomatic" onget="return this.getAttribute('showAutomatic')"/>
<property name="_types">
<getter>
<![CDATA[
var types = [0];
if (this.showAutomatic == 'true') {
types.push(1);
}
return types;
]]>
</getter>
</property>
<field name="_hasFilter">false</field>
<field name="_filter">null</field>
@ -99,6 +111,7 @@
<constructor>
<![CDATA[
this.setAttribute('filterToScope', true);
this.id('show-automatic').setAttribute('checked', this.getAttribute('showAutomatic'));
this.dragObserver = new this._dragObserverConstructor;
]]>
</constructor>
@ -156,7 +169,7 @@
var tagsToggleBox = this.id('tags-toggle');
if (fetch || this._dirty) {
this._tags = Zotero.Tags.getAll();
this._tags = Zotero.Tags.getAll(this._types);
// Remove children
while (tagsToggleBox.hasChildNodes()){
@ -165,11 +178,21 @@
// Regenerate list
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) {
Zotero.debug(this._tags[tagID].tag);
tagsToggleBox.lastChild.setAttribute('tagID', tagsToggleBox.lastChild.getAttribute('tagID') + '-' + tagID);
tagsToggleBox.lastChild.setAttribute('tagType', tagsToggleBox.lastChild.getAttribute('tagType') + '-' + this._tags[tagID].type);
continue;
}
var label = document.createElement('label');
label.setAttribute('onclick', "this.parentNode.parentNode.parentNode.handleTagClick(this)");
label.className = 'zotero-clicky';
label.setAttribute('value', this._tags[tagID]);
label.setAttribute('value', this._tags[tagID].tag);
label.setAttribute('tagID', tagID);
label.setAttribute('tagType', this._tags[tagID].type);
label.setAttribute('context', 'tag-menu');
label.setAttribute('ondragover', 'nsDragAndDrop.dragOver(event, this.parentNode.parentNode.parentNode.dragObserver)');
label.setAttribute('ondragexit', 'nsDragAndDrop.dragExit(event, this.parentNode.parentNode.parentNode.dragObserver)');
@ -183,7 +206,7 @@
// Set attributes
var labels = tagsToggleBox.getElementsByTagName('label');
for (var i=0; i<labels.length; i++){
var tagID = labels[i].getAttribute('tagID');
var tagIDs = labels[i].getAttribute('tagID').split('-');
// Restore selection
if (this.selection[labels[i].value]){
@ -193,13 +216,35 @@
labels[i].setAttribute('selected', 'false');
}
// Check tags against filter
if (this._hasFilter) {
var inFilter = false;
for each(var tagID in tagIDs) {
if (this._filter[tagID]) {
inFilter = true;
break;
}
}
}
// Check tags against scope
if (this._hasScope) {
var inScope = false;
for each(var tagID in tagIDs) {
if (this._scope[tagID]) {
inScope = true;
break;
}
}
}
// If not in filter, hide
if (this._hasFilter && !this._filter[tagID]) {
if (this._hasFilter && !inFilter) {
//Zotero.debug(1);
labels[i].setAttribute('hidden', true);
}
else if (this.filterToScope) {
if (this._hasScope && this.scope[tagID]) {
if (this._hasScope && inScope) {
//Zotero.debug(2);
labels[i].setAttribute('inScope', true);
labels[i].setAttribute('hidden', false);
@ -213,7 +258,7 @@
}
// Display all
else {
if (this._hasScope && this.scope[tagID]) {
if (this._hasScope && inScope) {
//Zotero.debug(4);
labels[i].setAttribute('inScope', true);
}
@ -294,7 +339,7 @@
<![CDATA[
// If a selected tag no longer exists, deselect it
if (event == 'delete') {
this._tags = Zotero.Tags.getAll();
this._tags = Zotero.Tags.getAll(this._types);
for (var tag in this.selection) {
for each(var tag2 in this._tags) {
@ -430,10 +475,15 @@
<method name="rename">
<parameter name="tagID"/>
<parameter name="tagIDs"/>
<body>
<![CDATA[
var oldName = Zotero.Tags.getName(tagID);
tagIDs = tagIDs.split('-');
// Convert to ints
for (var i=0; i<tagIDs.length; i++) {
tagIDs[i] = parseInt(tagIDs[i]);
}
var oldName = Zotero.Tags.getName(tagIDs[0]);
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
@ -444,9 +494,21 @@
Zotero.getString('pane.tagSelector.rename.message'),
newName, '', {});
if (result && newName.value)
{
Zotero.Tags.rename(tagID, newName.value);
if (result && newName.value) {
Zotero.DB.beginTransaction();
// Add other ids with same tag
var ids = Zotero.Tags.getIDs(oldName);
for each(var id in ids) {
if (tagIDs.indexOf(id) == -1) {
tagIDs.push(id);
}
}
for each(var tagID in tagIDs) {
Zotero.Tags.rename(tagID, newName.value);
}
Zotero.DB.commitTransaction();
}
]]>
</body>
@ -454,9 +516,12 @@
<method name="delete">
<parameter name="tagID"/>
<parameter name="tagIDs"/>
<body>
<![CDATA[
tagIDs = tagIDs.split('-');
var oldName = Zotero.Tags.getName(tagIDs[0]);
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
@ -465,7 +530,21 @@
Zotero.getString('pane.tagSelector.delete.message'));
if (confirmed) {
Zotero.Tags.remove(tagID);
Zotero.DB.beginTransaction();
// Add other ids with same tag
var ids = Zotero.Tags.getIDs(oldName);
for each(var id in ids) {
if (tagIDs.indexOf(id) == -1) {
tagIDs.push(id);
}
}
for each(var tagID in tagIDs) {
Zotero.Tags.remove(tagID);
}
Zotero.DB.commitTransaction()
}
]]>
</body>
@ -572,6 +651,13 @@
<xul:toolbarbutton id="search-cancel"
oncommand="this.parentNode.focus(); this.parentNode.parentNode.parentNode.parentNode.handleKeyPress(true)" hidden="true"/>
</xul:textbox>
<xul:toolbarbutton id="view-settings-menu" tooltiptext="&zotero.toolbar.actions.label;"
image="chrome://zotero/skin/tag-selector-menu.png" type="menu">
<xul:menupopup id="view-settings-popup">
<xul:menuitem id="show-automatic" label="Show automatic" autocheck="true" type="checkbox"
oncommand="var ts = this.parentNode.parentNode.parentNode.parentNode.parentNode; ts._dirty = true; ts.setAttribute('showAutomatic', this.getAttribute('checked') == 'true')"/>
</xul:menupopup>
</xul:toolbarbutton>
</xul:hbox>
<xul:hbox>

View File

@ -1153,7 +1153,7 @@ var ZoteroItemPane = new function()
// (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);
var existing = Zotero.Tags.getID(value, 0);
if (existing && id != existing)
{
_tabDirection = false;
@ -1170,9 +1170,20 @@ var ZoteroItemPane = new function()
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) {
_lastTabIndex--;
}
var id = tagsbox.add(value);
// DEBUG: why does this need to continue if added?
}
}
@ -1183,8 +1194,14 @@ var ZoteroItemPane = new function()
else
{
// Just remove the row
var row = rows.removeChild(row);
//
// If there's an open popup, this throws NODE CANNOT BE FOUND
try {
var row = rows.removeChild(row);
}
catch (e) {}
tagsbox.fixPopup();
_tabDirection = false;
return;
}

View File

@ -136,7 +136,8 @@
</tree>
</vbox>
<splitter id="zotero-tags-splitter" onmouseup="ZoteroPane.updateTagSelectorSize()"/>
<zoterotagselector id="zotero-tag-selector" persist="height,collapsed" oncommand="ZoteroPane.updateTagFilter()"/>
<zoterotagselector id="zotero-tag-selector" persist="height,collapsed,showAutomaticsvn st"
oncommand="ZoteroPane.updateTagFilter()"/>
</vbox>
<splitter id="zotero-tree-splitter" resizebefore="closest" resizeafter="closest"/>

View File

@ -1491,7 +1491,7 @@ Zotero.Item.prototype.getAttachmentLinkMode = function(){
throw ("getAttachmentLinkMode() can only be called on items of type 'attachment'");
}
if (this._fileLinkMode!==null){
if (this._fileLinkMode !==null && this._fileLinkMode !==false){
return this._fileLinkMode;
}
@ -1573,7 +1573,7 @@ Zotero.Item.prototype.getBestSnapshot = function(){
//
// save() is not required for tag functions
//
Zotero.Item.prototype.addTag = function(tag){
Zotero.Item.prototype.addTag = function(tag, type){
if (!this.getID()){
throw ('Cannot add tag to unsaved item in Item.addTag()');
}
@ -1587,11 +1587,34 @@ Zotero.Item.prototype.addTag = function(tag){
return false;
}
Zotero.DB.beginTransaction();
var tagID = Zotero.Tags.getID(tag);
if (!tagID){
var tagID = Zotero.Tags.add(tag);
if (!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);
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));
}
// If existing user and adding automatic, skip
else if (type == 1 && existingTypes.indexOf(0) != -1) {
Zotero.debug('Identical user tag already exists -- skipping automatic tag add');
return false;
}
}
if (!tagID) {
var tagID = Zotero.Tags.add(tag, type);
}
try {
var result = this.addTagByID(tagID);
Zotero.DB.commitTransaction();
@ -1651,10 +1674,10 @@ Zotero.Item.prototype.hasTag = function(tagID) {
}
Zotero.Item.prototype.getTags = function(){
var sql = "SELECT tag FROM tags WHERE tagID IN "
var sql = "SELECT tagID AS id, tag, tagType AS type FROM tags WHERE tagID IN "
+ "(SELECT tagID FROM itemTags WHERE itemID=" + this.getID() + ") "
+ "ORDER BY tag COLLATE NOCASE";
return Zotero.DB.columnQuery(sql);
return Zotero.DB.query(sql);
}
Zotero.Item.prototype.getTagIDs = function(){
@ -3141,11 +3164,14 @@ Zotero.Creators = new function(){
* Same structure as Zotero.Creators -- make changes in both places if possible
*/
Zotero.Tags = new function(){
var _tags = new Array; // indexed by tag text
var _tagsByID = new Array; // indexed by tagID
var _tags = []; // indexed by tag text
var _tagsByID = []; // indexed by tagID
this.get = get;
this.getName = getName;
this.getID = getID;
this.getIDs = getIDs;
this.getTypes = getTypes;
this.getAll = getAll;
this.getAllWithinSearch = getAllWithinSearch;
this.getTagItems = getTagItems;
@ -3156,53 +3182,100 @@ Zotero.Tags = new function(){
this.purge = purge;
/*
* Returns a tag for a given tagID
* Returns a tag and type for a given tagID
*/
function getName(tagID){
function get(tagID) {
if (_tagsByID[tagID]){
return _tagsByID[tagID];
}
var sql = 'SELECT tag FROM tags WHERE tagID=' + tagID;
var result = Zotero.DB.valueQuery(sql);
var sql = 'SELECT tag, tagType FROM tags WHERE tagID=?';
var result = Zotero.DB.rowQuery(sql, tagID);
if (!result){
return false;
}
_tagsByID[tagID] = result;
_tagsByID[tagID] = {
tag: result.tag,
type: result.tagType
};
return result;
}
/*
* Returns the tagID matching given tag
* Returns a tag for a given tagID
*/
function getID(tag){
if (_tags[tag]){
return _tags[tag];
function getName(tagID) {
if (_tagsByID[tagID]){
return _tagsByID[tagID].tag;
}
var sql = 'SELECT tagID FROM tags WHERE tag=?';
var tagID = Zotero.DB.valueQuery(sql, [{string:tag}]);
var tag = this.get(tagID);
if (tagID){
_tags[tag] = tagID;
return _tagsByID[tagID] ? _tagsByID[tagID].tag : false;
}
/*
* Returns the tagID matching given tag and type
*/
function getID(tag, type) {
if (_tags[type] && _tags[type][tag]){
return _tags[type][tag];
}
var sql = 'SELECT tagID FROM tags WHERE tag=? AND tagType=?';
var tagID = Zotero.DB.valueQuery(sql, [tag, type]);
if (tagID) {
if (!_tags[type]) {
_tags[type] = [];
}
_tags[type][tag] = tagID;
}
return tagID;
}
/*
* Returns all tagIDs for this tag (of all types)
*/
function getIDs(tag) {
var sql = 'SELECT tagID FROM tags WHERE tag=?';
return Zotero.DB.columnQuery(sql, [tag]);
}
/*
* Returns an array of tagTypes for tags matching given tag
*/
function getTypes(tag) {
var sql = 'SELECT tagType FROM tags WHERE tag=?';
return Zotero.DB.columnQuery(sql, [tag]);
}
/**
* Get all tags indexed by tagID
*
* _types_ is an optional array of tagTypes to fetch
*/
function getAll(){
var sql = 'SELECT tagID, tag FROM tags ORDER BY tag COLLATE NOCASE';
function getAll(types) {
var sql = "SELECT tagID, tag, tagType FROM tags ";
if (types) {
sql += "WHERE tagType IN (" + types.join() + ") ";
}
sql += "ORDER BY tag COLLATE NOCASE";
var tags = Zotero.DB.query(sql);
var indexed = {};
for (var i in tags){
indexed[tags[i]['tagID']] = tags[i]['tag'];
for each(var tag in tags) {
indexed[tag.tagID] = {
tag: tag.tag,
type: tag.tagType
};
}
return indexed;
}
@ -3210,8 +3283,10 @@ Zotero.Tags = new function(){
/*
* Get all tags within the items of a Zotero.Search object
*
* _types_ is an optional array of tagTypes to fetch
*/
function getAllWithinSearch(search) {
function getAllWithinSearch(search, types) {
// If search has post-search filters (e.g. fulltext content), run it
// and just include the ids
//
@ -3220,19 +3295,30 @@ Zotero.Tags = new function(){
// -- should probably use a temporary table instead
if (search.hasPostSearchFilter()) {
var ids = search.search();
var sql = "SELECT DISTINCT tagID, tag FROM itemTags NATURAL JOIN tags "
+ "WHERE itemID IN (" + ids.join() + ") ORDER BY tag COLLATE NOCASE";
var sql = "SELECT DISTINCT tagID, tag, tagType FROM itemTags "
+ "NATURAL JOIN tags WHERE itemID IN (" + ids.join() + ") ";
if (types) {
sql += "AND tagType IN (" + types.join() + ") ";
}
sql += "ORDER BY tag COLLATE NOCASE";
var tags = Zotero.DB.query(sql);
}
else {
var sql = "SELECT DISTINCT tagID, tag FROM itemTags NATURAL JOIN tags "
+ "WHERE itemID IN (" + search.getSQL() + ") ORDER BY tag COLLATE NOCASE";
var sql = "SELECT DISTINCT tagID, tag, tagType FROM itemTags "
+ "NATURAL JOIN tags WHERE itemID IN (" + search.getSQL() + ") ";
if (types) {
sql += "AND tagType IN (" + types.join() + ") ";
}
sql += "ORDER BY tag COLLATE NOCASE";
var tags = Zotero.DB.query(sql, search.getSQLParams());
}
var indexed = {};
for (var i in tags){
indexed[tags[i]['tagID']] = tags[i]['tag'];
for each(var tag in tags) {
indexed[tag.tagID] = {
tag: tag.tag,
type: tag.tagType
};
}
return indexed;
}
@ -3245,12 +3331,15 @@ Zotero.Tags = new function(){
function search(str){
var sql = 'SELECT tagID, tag FROM tags WHERE tag LIKE ? '
var sql = 'SELECT tagID, tag, tagType FROM tags WHERE tag LIKE ? '
+ 'ORDER BY tag COLLATE NOCASE';
var tags = Zotero.DB.query(sql, '%' + str + '%');
var indexed = {};
for (var i in tags){
indexed[tags[i]['tagID']] = tags[i]['tag'];
for each(var tag in tags) {
indexed[tag.tagID] = {
tag: tag.tag,
type: tag.tagType
};
}
return indexed;
}
@ -3261,14 +3350,22 @@ Zotero.Tags = new function(){
*
* Returns new tagID
*/
function add(tag){
Zotero.debug('Adding new tag', 4);
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 sql = 'INSERT INTO tags VALUES (?,?,?)';
var rnd = Zotero.getRandomID('tags', 'tagID');
Zotero.DB.query(sql, [{int: rnd}, {string: tag}]);
Zotero.DB.query(sql, [{int: rnd}, {string: tag}, {int: type}]);
Zotero.DB.commitTransaction();
Zotero.Notifier.trigger('add', 'tag', rnd);
@ -3281,8 +3378,21 @@ Zotero.Tags = new function(){
Zotero.DB.beginTransaction();
var tagObj = this.get(tagID);
var oldName = tagObj.tag;
var oldType = tagObj.type;
if (oldName == tag) {
if (oldType != 0) {
var sql = "UPDATE tags SET tagType=0 WHERE tagID=?";
Zotero.DB.query(sql, tagID);
}
Zotero.DB.commitTransaction();
return;
}
// Check if the new tag already exists
var sql = "SELECT tagID FROM tags WHERE tag=?";
var sql = "SELECT tagID FROM tags WHERE tag=? AND tagType=0";
var existingTagID = Zotero.DB.valueQuery(sql, tag);
if (existingTagID) {
var itemIDs = this.getTagItems(tagID);
@ -3296,7 +3406,9 @@ Zotero.Tags = new function(){
// Manual purge of old tag
var sql = "DELETE FROM tags WHERE tagID=?";
Zotero.DB.query(sql, tagID);
delete _tags[_tagsByID[tagID]];
if (_tags[oldType]) {
delete _tags[oldType][oldName];
}
delete _tagsByID[tagID];
Zotero.Notifier.trigger('delete', 'tag', tagID);
@ -3321,12 +3433,15 @@ Zotero.Tags = new function(){
return;
}
var sql = "UPDATE tags SET tag=? WHERE tagID=?";
// 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}, {int: tagID}]);
var itemIDs = this.getTagItems(tagID);
delete _tags[_tagsByID[tagID]];
if (_tags[oldType]) {
delete _tags[oldType][oldName];
}
delete _tagsByID[tagID];
Zotero.DB.commitTransaction();
@ -3343,6 +3458,7 @@ Zotero.Tags = new function(){
var itemIDs = Zotero.DB.columnQuery(sql, tagID);
if (!itemIDs) {
Zotero.DB.commitTransaction();
return;
}
@ -3368,26 +3484,35 @@ Zotero.Tags = new function(){
* Returns removed tagIDs on success
*/
function purge(){
var sql = 'SELECT tagID FROM tags WHERE tagID NOT IN '
+ '(SELECT tagID FROM itemTags);';
var toDelete = Zotero.DB.columnQuery(sql);
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 = [];
// Clear tag entries in internal array
for (var i=0; i<toDelete.length; i++){
var tag = this.getName(toDelete[i]);
delete _tags[tag];
delete _tagsByID[toDelete[i]];
for each(var tag in toDelete) {
purged.push(tag.tagID);
if (_tags[tag.tagType]) {
delete _tags[tag.tagType][tag.tag];
}
delete _tagsByID[tag.tagID];
}
sql = 'DELETE FROM tags WHERE tagID NOT IN '
+ '(SELECT tagID FROM itemTags);';
var result = Zotero.DB.query(sql);
Zotero.Notifier.trigger('delete', 'tag', toDelete);
Zotero.DB.commitTransaction();
Zotero.Notifier.trigger('delete', 'tag', purged);
return toDelete;
}

View File

@ -701,6 +701,15 @@ Zotero.Schema = new function(){
if (i==15) {
Zotero.DB.query("DROP TABLE IF EXISTS annotations");
}
if (i==16) {
Zotero.DB.query("CREATE TABLE tagsTemp (tagID INT, tag TEXT, PRIMARY KEY (tagID))");
Zotero.DB.query("INSERT INTO tagsTemp SELECT * FROM tags");
Zotero.DB.query("DROP TABLE tags");
Zotero.DB.query("CREATE TABLE tags (\n tagID INT,\n tag TEXT,\n tagType INT,\n PRIMARY KEY (tagID),\n UNIQUE (tag, tagType)\n);");
Zotero.DB.query("INSERT INTO tags SELECT tagID, tag, 0 FROM tagsTemp");
Zotero.DB.query("DROP TABLE tagsTemp");
}
}
_updateSchema('userdata');

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

View File

@ -1,4 +1,4 @@
-- 15
-- 16
-- This file creates tables containing user-specific data -- any changes
-- to existing tables made here must be mirrored in transition steps in
@ -106,8 +106,10 @@ CREATE INDEX IF NOT EXISTS itemAttachments_mimeType ON itemAttachments(mimeType)
-- Individual entries for each tag
CREATE TABLE IF NOT EXISTS tags (
tagID INT,
tag TEXT UNIQUE,
PRIMARY KEY (tagID)
tag TEXT,
tagType INT,
PRIMARY KEY (tagID),
UNIQUE (tag, tagType)
);
-- Associates items with keywords