Tags overhaul [DB reupgrade]
- Simplified schema - Tags are now added without reloading entire tag selector - On my system, adding 400 tags to an item (separately, with the tag selector updating each time) went from 59 seconds to 42. (Given that it takes only 13 seconds with the tag selector closed, though, there's clearly more work to be done.) - Tag selector now uses HTML flexbox (in identical fashion, for now, but with the possibility of fancier changes later, and with streamlined logic thanks to the flexbox 'order' property) - Various async fixes - Tests
This commit is contained in:
parent
b602cc4bd2
commit
33dedd1753
24 changed files with 951 additions and 689 deletions
|
@ -117,7 +117,9 @@
|
||||||
this.mode = this.getAttribute('mode');
|
this.mode = this.getAttribute('mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item-tag', 'setting']);
|
this._notifierID = Zotero.Notifier.registerObserver(
|
||||||
|
this, ['item-tag', 'setting'], 'tagsbox'
|
||||||
|
);
|
||||||
]]>
|
]]>
|
||||||
</constructor>
|
</constructor>
|
||||||
|
|
||||||
|
@ -134,22 +136,23 @@
|
||||||
<parameter name="type"/>
|
<parameter name="type"/>
|
||||||
<parameter name="ids"/>
|
<parameter name="ids"/>
|
||||||
<parameter name="extraData"/>
|
<parameter name="extraData"/>
|
||||||
<body>
|
<body><![CDATA[
|
||||||
<![CDATA[
|
return Zotero.spawn(function* () {
|
||||||
if (type == 'setting') {
|
if (type == 'setting') {
|
||||||
if (ids.some(function (val) val.split("/")[1] == 'tagColors') && this.item) {
|
if (ids.some(function (val) val.split("/")[1] == 'tagColors') && this.item) {
|
||||||
this.reload();
|
return this.reload();
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else if (type == 'item-tag') {
|
else if (type == 'item-tag') {
|
||||||
let itemID, tagName;
|
let itemID, tagID;
|
||||||
|
|
||||||
for (let i=0; i<ids.length; i++) {
|
for (let i=0; i<ids.length; i++) {
|
||||||
[itemID, tagName] = ids[i].match(/^([0-9]+)-(.+)/).slice(1);
|
[itemID, tagID] = ids[i].split('-').map(x => parseInt(x));
|
||||||
if (!this.item || itemID != this.item.id) {
|
if (!this.item || itemID != this.item.id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let data = extraData[ids[i]];
|
||||||
|
let tagName = data.tag;
|
||||||
|
|
||||||
if (event == 'add') {
|
if (event == 'add') {
|
||||||
var newTabIndex = this.add(tagName);
|
var newTabIndex = this.add(tagName);
|
||||||
|
@ -168,9 +171,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (event == 'modify') {
|
else if (event == 'modify') {
|
||||||
Zotero.debug("EXTRA");
|
let oldTagName = data.old.tag;
|
||||||
Zotero.debug(extraData);
|
|
||||||
let oldTagName = extraData[tagName].old.tag;
|
|
||||||
this.remove(oldTagName);
|
this.remove(oldTagName);
|
||||||
this.add(tagName);
|
this.add(tagName);
|
||||||
}
|
}
|
||||||
|
@ -196,11 +197,11 @@
|
||||||
}
|
}
|
||||||
else if (type == 'tag') {
|
else if (type == 'tag') {
|
||||||
if (event == 'modify') {
|
if (event == 'modify') {
|
||||||
this.reload();
|
return this.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]]>
|
}.bind(this));
|
||||||
</body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
@ -235,6 +236,9 @@
|
||||||
|
|
||||||
this._reloading = false;
|
this._reloading = false;
|
||||||
this._focusField();
|
this._focusField();
|
||||||
|
|
||||||
|
var event = new Event('refresh');
|
||||||
|
this.dispatchEvent(event);
|
||||||
}, this);
|
}, this);
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
@ -388,13 +392,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag color
|
// Tag color
|
||||||
let color = this._tagColors[valueText];
|
var colorData = this._tagColors.get(valueText);
|
||||||
if (color) {
|
if (colorData) {
|
||||||
valueElement.setAttribute(
|
valueElement.style.color = colorData.color;
|
||||||
'style',
|
valueElement.style.fontWeight = 'bold';
|
||||||
'color:' + this._tagColors[valueText].color + '; '
|
|
||||||
+ 'font-weight: bold'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return valueElement;
|
return valueElement;
|
||||||
|
@ -522,12 +523,10 @@
|
||||||
yield this.blurHandler(target);
|
yield this.blurHandler(target);
|
||||||
|
|
||||||
if (focusField) {
|
if (focusField) {
|
||||||
Zotero.debug("FOCUSING FIELD");
|
|
||||||
this._focusField();
|
this._focusField();
|
||||||
}
|
}
|
||||||
// Return focus to items pane
|
// Return focus to items pane
|
||||||
else {
|
else {
|
||||||
Zotero.debug("FOCUSING ITEM PANE");
|
|
||||||
var tree = document.getElementById('zotero-items-tree');
|
var tree = document.getElementById('zotero-items-tree');
|
||||||
if (tree) {
|
if (tree) {
|
||||||
tree.focus();
|
tree.focus();
|
||||||
|
@ -757,8 +756,6 @@
|
||||||
<method name="add">
|
<method name="add">
|
||||||
<parameter name="tagName"/>
|
<parameter name="tagName"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
Zotero.debug("ADDING ROW WITH " + tagName);
|
|
||||||
|
|
||||||
var rowsElement = this.id('tagRows');
|
var rowsElement = this.id('tagRows');
|
||||||
var rows = rowsElement.childNodes;
|
var rows = rowsElement.childNodes;
|
||||||
|
|
||||||
|
@ -766,7 +763,6 @@
|
||||||
var row = false;
|
var row = false;
|
||||||
for (let i=0; i<rows.length; i++) {
|
for (let i=0; i<rows.length; i++) {
|
||||||
if (rows[i].getAttribute('tagName') === tagName) {
|
if (rows[i].getAttribute('tagName') === tagName) {
|
||||||
Zotero.debug("FOUND ROW with " + tagName);
|
|
||||||
return rows[i].getAttribute('ztabindex');
|
return rows[i].getAttribute('ztabindex');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -835,18 +831,12 @@
|
||||||
<method name="remove">
|
<method name="remove">
|
||||||
<parameter name="tagName"/>
|
<parameter name="tagName"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
Zotero.debug("REMOVING ROW WITH " + tagName);
|
|
||||||
|
|
||||||
var rowsElement = this.id('tagRows');
|
var rowsElement = this.id('tagRows');
|
||||||
var rows = rowsElement.childNodes;
|
var rows = rowsElement.childNodes;
|
||||||
var removed = false;
|
var removed = false;
|
||||||
var oldTabIndex = -1;
|
var oldTabIndex = -1;
|
||||||
for (var i=0; i<rows.length; i++) {
|
for (var i=0; i<rows.length; i++) {
|
||||||
let value = rows[i].getAttribute('tagName');
|
let value = rows[i].getAttribute('tagName');
|
||||||
Zotero.debug("-=-=");
|
|
||||||
Zotero.debug(value);
|
|
||||||
Zotero.debug(tagName);
|
|
||||||
Zotero.debug(value === tagName);
|
|
||||||
if (value === tagName) {
|
if (value === tagName) {
|
||||||
oldTabIndex = i + 1;
|
oldTabIndex = i + 1;
|
||||||
removed = true;
|
removed = true;
|
||||||
|
@ -1010,9 +1000,7 @@
|
||||||
<method name="scrollToTop">
|
<method name="scrollToTop">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
Zotero.debug('SCROLL TO TOP');
|
|
||||||
if (!this._activeScrollbox) {
|
if (!this._activeScrollbox) {
|
||||||
Zotero.debug('NO');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var sbo = this._activeScrollbox.boxObject;
|
var sbo = this._activeScrollbox.boxObject;
|
||||||
|
|
|
@ -44,8 +44,8 @@
|
||||||
<field name="_initialized">false</field>
|
<field name="_initialized">false</field>
|
||||||
<field name="_notifierID">false</field>
|
<field name="_notifierID">false</field>
|
||||||
<field name="_tags">null</field>
|
<field name="_tags">null</field>
|
||||||
|
<field name="_popupNode"/>
|
||||||
<field name="_dirty">null</field>
|
<field name="_dirty">null</field>
|
||||||
<field name="_emptyColored">null</field>
|
|
||||||
<field name="_emptyRegular">null</field>
|
<field name="_emptyRegular">null</field>
|
||||||
|
|
||||||
<!-- Modes are predefined settings groups for particular tasks -->
|
<!-- Modes are predefined settings groups for particular tasks -->
|
||||||
|
@ -173,7 +173,7 @@
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
this.selection = {};
|
this.selection = new Set();
|
||||||
this._notifierID = Zotero.Notifier.registerObserver(
|
this._notifierID = Zotero.Notifier.registerObserver(
|
||||||
this,
|
this,
|
||||||
['collection-item', 'item', 'item-tag', 'tag', 'setting'],
|
['collection-item', 'item', 'item-tag', 'tag', 'setting'],
|
||||||
|
@ -193,7 +193,7 @@
|
||||||
|
|
||||||
this._initialized = false;
|
this._initialized = false;
|
||||||
this.unregister();
|
this.unregister();
|
||||||
this.selection = {};
|
this.selection = new Set();
|
||||||
if (this.onchange) {
|
if (this.onchange) {
|
||||||
this.onchange();
|
this.onchange();
|
||||||
}
|
}
|
||||||
|
@ -217,10 +217,16 @@
|
||||||
<parameter name="fetch"/>
|
<parameter name="fetch"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
Zotero.spawn(function* () {
|
return Zotero.spawn(function* () {
|
||||||
Zotero.debug('Refreshing tags selector');
|
|
||||||
var t = new Date;
|
var t = new Date;
|
||||||
|
|
||||||
|
if (fetch || this._dirty) {
|
||||||
|
Zotero.debug('Reloading tags selector');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Zotero.debug('Refreshing tags selector');
|
||||||
|
}
|
||||||
|
|
||||||
if (!this._initialized) {
|
if (!this._initialized) {
|
||||||
this.init();
|
this.init();
|
||||||
fetch = true;
|
fetch = true;
|
||||||
|
@ -228,49 +234,26 @@
|
||||||
|
|
||||||
var emptyColored = true;
|
var emptyColored = true;
|
||||||
var emptyRegular = true;
|
var emptyRegular = true;
|
||||||
var tagsToggleBox = this.id('tags-toggle');
|
var tagsBox = this.id('tags-box');
|
||||||
|
|
||||||
var tagColors = yield Zotero.Tags.getColors(this.libraryID)
|
var tagColors = yield Zotero.Tags.getColors(this.libraryID)
|
||||||
.tap(() => Zotero.Promise.check(this.mode));
|
.tap(() => Zotero.Promise.check(this.mode));
|
||||||
|
|
||||||
|
// If new data, rebuild boxes
|
||||||
if (fetch || this._dirty) {
|
if (fetch || this._dirty) {
|
||||||
this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types)
|
this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types)
|
||||||
.tap(() => Zotero.Promise.check(this.mode));
|
.tap(() => Zotero.Promise.check(this.mode));
|
||||||
|
tagsBox.textContent = "";
|
||||||
// Remove children
|
|
||||||
tagsToggleBox.textContent = "";
|
|
||||||
|
|
||||||
// Sort by name
|
// Sort by name
|
||||||
var collation = Zotero.getLocaleCollation();
|
let collation = Zotero.getLocaleCollation();
|
||||||
var orderedTags = this._tags.concat();
|
this._tags.sort(function (a, b) {
|
||||||
orderedTags.sort(function(a, b) {
|
|
||||||
return collation.compareString(1, a.tag, b.tag);
|
return collation.compareString(1, a.tag, b.tag);
|
||||||
});
|
});
|
||||||
|
|
||||||
var tagColorsLowerCase = {};
|
let lastTag;
|
||||||
var colorTags = [];
|
for (let i = 0; i < this._tags.length; i++) {
|
||||||
for (let name in tagColors) {
|
let tagData = this._tags[i];
|
||||||
colorTags[tagColors[name].position] = name;
|
|
||||||
tagColorsLowerCase[name.toLowerCase()] = true;
|
|
||||||
}
|
|
||||||
var positions = Object.keys(colorTags);
|
|
||||||
for (let i=positions.length-1; i>=0; i--) {
|
|
||||||
let name = colorTags[positions[i]];
|
|
||||||
orderedTags.unshift({
|
|
||||||
tag: name,
|
|
||||||
type: 0,
|
|
||||||
hasColor: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastTag;
|
|
||||||
for (let i=0; i<orderedTags.length; i++) {
|
|
||||||
let tagData = orderedTags[i];
|
|
||||||
|
|
||||||
// Skip colored tags in the regular section,
|
|
||||||
// since we add them to the beginning above
|
|
||||||
if (!tagData.hasColor && tagColorsLowerCase[tagData.tag.toLowerCase()]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only show tags of different types once
|
// Only show tags of different types once
|
||||||
if (tagData.tag === lastTag) {
|
if (tagData.tag === lastTag) {
|
||||||
|
@ -278,108 +261,35 @@
|
||||||
}
|
}
|
||||||
lastTag = tagData.tag;
|
lastTag = tagData.tag;
|
||||||
|
|
||||||
let tagButton = this._makeClickableTag(tagData, this.editable);
|
let elem = this._insertClickableTag(tagsBox, tagData);
|
||||||
if (tagButton) {
|
let visible = this._updateClickableTag(
|
||||||
var self = this;
|
elem, tagData.tag, tagColors
|
||||||
tagButton.addEventListener('click', function(event) {
|
);
|
||||||
self.handleTagClick(event, this);
|
if (visible) {
|
||||||
});
|
emptyRegular = false;
|
||||||
if (this.editable) {
|
|
||||||
tagButton.addEventListener('dragover', this.dragObserver.onDragOver);
|
|
||||||
tagButton.addEventListener('dragexit', this.dragObserver.onDragExit);
|
|
||||||
tagButton.addEventListener('drop', this.dragObserver.onDrop, true);
|
|
||||||
}
|
|
||||||
tagsToggleBox.appendChild(tagButton);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._dirty = false;
|
this._dirty = false;
|
||||||
}
|
}
|
||||||
|
// Otherwise just update based on visibility
|
||||||
// Set attributes
|
else {
|
||||||
var colorTags = {};
|
elems = tagsBox.childNodes;
|
||||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
for (let i = 0; i < elems.length; i++) {
|
||||||
for (let i=0; i<labels.length; i++) {
|
let elem = elems[i];
|
||||||
let name = labels[i].value;
|
let visible = this._updateClickableTag(
|
||||||
let lcname = name.toLowerCase();
|
elem, elem.textContent, tagColors
|
||||||
|
|
||||||
let colorData = tagColors[name];
|
|
||||||
if (colorData) {
|
|
||||||
labels[i].setAttribute(
|
|
||||||
'style', 'color:' + colorData.color + '; ' + 'font-weight: bold'
|
|
||||||
);
|
);
|
||||||
}
|
if (visible) {
|
||||||
else {
|
|
||||||
labels[i].removeAttribute('style');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore selection
|
|
||||||
if (this.selection[name]){
|
|
||||||
labels[i].setAttribute('selected', 'true');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
labels[i].setAttribute('selected', 'false');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check tags against search
|
|
||||||
if (this._search) {
|
|
||||||
var inSearch = lcname.indexOf(this._search) != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check tags against scope
|
|
||||||
if (this._hasScope) {
|
|
||||||
var inScope = !!this._scope[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not in search, hide
|
|
||||||
if (this._search && !inSearch) {
|
|
||||||
labels[i].setAttribute('hidden', true);
|
|
||||||
}
|
|
||||||
else if (this.filterToScope) {
|
|
||||||
if (this._hasScope && inScope) {
|
|
||||||
labels[i].className = 'zotero-clicky';
|
|
||||||
labels[i].setAttribute('inScope', true);
|
|
||||||
labels[i].setAttribute('hidden', false);
|
|
||||||
emptyRegular = false;
|
emptyRegular = false;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
labels[i].className = '';
|
|
||||||
labels[i].setAttribute('hidden', true);
|
|
||||||
labels[i].setAttribute('inScope', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Display all
|
|
||||||
else {
|
|
||||||
if (this._hasScope && inScope) {
|
|
||||||
labels[i].className = 'zotero-clicky';
|
|
||||||
labels[i].setAttribute('inScope', true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
labels[i].className = '';
|
|
||||||
labels[i].setAttribute('inScope', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
labels[i].setAttribute('hidden', false);
|
|
||||||
emptyRegular = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always show colored tags at top, unless they
|
|
||||||
// don't match an active tag search
|
|
||||||
if (colorData && (!this._search || inSearch)) {
|
|
||||||
labels[i].setAttribute('hidden', false);
|
|
||||||
labels[i].setAttribute('hasColor', true);
|
|
||||||
emptyColored = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
labels[i].removeAttribute('hasColor');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//start tag cloud code
|
//start tag cloud code
|
||||||
|
|
||||||
var tagCloud = Zotero.Prefs.get('tagCloud');
|
var tagCloud = Zotero.Prefs.get('tagCloud');
|
||||||
|
if (false && tagCloud) {
|
||||||
if(tagCloud) {
|
var labels = tagsBox.getElementsByTagName('label');
|
||||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
|
||||||
|
|
||||||
//loop through displayed labels and find number of linked items
|
//loop through displayed labels and find number of linked items
|
||||||
var numlinked= [];
|
var numlinked= [];
|
||||||
|
@ -444,20 +354,15 @@
|
||||||
//end tag cloud code
|
//end tag cloud code
|
||||||
|
|
||||||
this.updateNumSelected();
|
this.updateNumSelected();
|
||||||
this._emptyColored = emptyColored;
|
var empty = this._emptyRegular = emptyRegular;
|
||||||
this._emptyRegular = emptyRegular;
|
// TODO: Show loading again when switching libraries/collections?
|
||||||
var empty = emptyColored && emptyRegular;
|
this.id('tags-deck').selectedIndex = empty ? 1 : 2;
|
||||||
this.id('tags-toggle').setAttribute('collapsed', empty);
|
|
||||||
this.id('no-tags-box').setAttribute('collapsed', !empty);
|
|
||||||
|
|
||||||
if (this.onRefresh) {
|
if (this.onRefresh) {
|
||||||
this.onRefresh();
|
this.onRefresh();
|
||||||
this.onRefresh = null;
|
this.onRefresh = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear "Loading tags…" after the first load
|
|
||||||
this.id('no-tags-deck').selectedIndex = 1;
|
|
||||||
|
|
||||||
Zotero.debug("Loaded tag selector in " + (new Date - t) + " ms");
|
Zotero.debug("Loaded tag selector in " + (new Date - t) + " ms");
|
||||||
|
|
||||||
var event = new Event('refresh');
|
var event = new Event('refresh');
|
||||||
|
@ -467,34 +372,56 @@
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<method name="insertSorted">
|
||||||
<method name="getVisible">
|
<parameter name="tagObjs"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
var tagsBox = this.id('tags-toggle');
|
return Zotero.spawn(function* () {
|
||||||
var labels = tagsBox.getElementsByTagName('label');
|
var tagColors = yield Zotero.Tags.getColors(this._libraryID);
|
||||||
var visible = [];
|
|
||||||
for (let i = 0; i < labels.length; i++){
|
var collation = Zotero.getLocaleCollation();
|
||||||
let label = labels[i];
|
tagObjs.sort(function (a, b) {
|
||||||
if (label.getAttribute('hidden') != 'true'
|
return collation.compareString(1, a.tag, b.tag);
|
||||||
&& label.getAttribute('inScope') == 'true') {
|
});
|
||||||
visible.push(label.value);
|
|
||||||
|
// Create tag elements in sorted order
|
||||||
|
var tagsBox = this.id('tags-box');
|
||||||
|
var tagElems = tagsBox.childNodes;
|
||||||
|
var j = 0;
|
||||||
|
loop:
|
||||||
|
for (let i = 0; i < tagObjs.length; i++) {
|
||||||
|
let tagObj = tagObjs[i];
|
||||||
|
while (j < tagElems.length) {
|
||||||
|
let elem = tagElems[j];
|
||||||
|
let comp = collation.compareString(
|
||||||
|
1, tagObj.tag, elem.textContent
|
||||||
|
);
|
||||||
|
// If tag already exists, update type if new one is lower
|
||||||
|
if (comp == 0) {
|
||||||
|
let tagType = elem.getAttribute('tagType');
|
||||||
|
if (parseInt(tagObj.type) < parseInt(tagType)) {
|
||||||
|
elem.setAttribute('tagType', tagObj.type);
|
||||||
}
|
}
|
||||||
|
continue loop;
|
||||||
}
|
}
|
||||||
return visible;
|
if (comp < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
this._insertClickableTag(tagsBox, tagObj, tagElems[j]);
|
||||||
|
this._updateClickableTag(
|
||||||
|
tagElems[j], tagElems[j].textContent, tagColors
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="getNumSelected">
|
<method name="getNumSelected">
|
||||||
<body>
|
<body><![CDATA[
|
||||||
<![CDATA[
|
return this.selection.size;
|
||||||
var count = 0;
|
]]></body>
|
||||||
for (var i in this.selection) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
]]>
|
|
||||||
</body>
|
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
@ -525,11 +452,12 @@
|
||||||
<parameter name="event"/>
|
<parameter name="event"/>
|
||||||
<parameter name="type"/>
|
<parameter name="type"/>
|
||||||
<parameter name="ids"/>
|
<parameter name="ids"/>
|
||||||
|
<parameter name="extraData"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
return Zotero.spawn(function* () {
|
return Zotero.spawn(function* () {
|
||||||
if (type == 'setting') {
|
if (type == 'setting') {
|
||||||
if (ids.some(function (val) val.split("/")[1] == 'tagColors')) {
|
if (ids.some(function (val) val.split("/")[1] == 'tagColors')) {
|
||||||
this.refresh(true);
|
yield this.refresh(true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -558,7 +486,7 @@
|
||||||
// TODO: necessary, or just use notifier value?
|
// TODO: necessary, or just use notifier value?
|
||||||
this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types);
|
this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types);
|
||||||
|
|
||||||
for (var tag in this.selection) {
|
for (let tag of this.selection) {
|
||||||
for each(var tag2 in this._tags) {
|
for each(var tag2 in this._tags) {
|
||||||
if (tag == tag2) {
|
if (tag == tag2) {
|
||||||
var found = true;
|
var found = true;
|
||||||
|
@ -566,14 +494,32 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
delete this.selection[tag];
|
this.selection.delete(tag);
|
||||||
selectionChanged = true;
|
selectionChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This could be more optimized to insert new/changed tags at the appropriate
|
if (event == 'add') {
|
||||||
// spot if we cared, but we probably don't
|
if (type == 'item-tag') {
|
||||||
|
let tagObjs = ids
|
||||||
|
// Get tag name and type
|
||||||
|
.map(x => extraData[x])
|
||||||
|
// Ignore tag adds for items not in the current library, if there is one
|
||||||
|
.filter(function (x) {
|
||||||
|
if (!this._libraryID) return true;
|
||||||
|
return x.libraryID == this._libraryID;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
if (tagObjs.length) {
|
||||||
|
yield this.insertSorted(tagObjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't add anything for item or collection-item; just update scope
|
||||||
|
|
||||||
|
return this.updateScope();
|
||||||
|
}
|
||||||
|
|
||||||
var t = this.id('tags-search').inputField;
|
var t = this.id('tags-search').inputField;
|
||||||
if (t.value) {
|
if (t.value) {
|
||||||
this.setSearch(t.value, true);
|
this.setSearch(t.value, true);
|
||||||
|
@ -610,34 +556,19 @@
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<!-- Not currently used -->
|
<method name="deselectAll">
|
||||||
<method name="selectVisible">
|
|
||||||
<body>
|
|
||||||
<![CDATA[
|
|
||||||
var tagsToggleBox = this.id('tags-toggle');
|
|
||||||
|
|
||||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
|
||||||
for (var i=0; i<labels.length; i++){
|
|
||||||
if (labels[i].getAttribute('hidden') != 'true'
|
|
||||||
&& labels[i].getAttribute('inScope') == 'true') {
|
|
||||||
labels[i].setAttribute('selected', 'true');
|
|
||||||
this.selection[labels[i].value] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]]>
|
|
||||||
</body>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
|
|
||||||
<method name="clearVisible">
|
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
var tagsToggleBox = this.id('tags-toggle');
|
if (!this.selection || !this.selection.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var labels = Zotero.Utilities.xpath(tagsToggleBox, 'label[@selected="true"]');
|
this.selection = new Set();
|
||||||
for (var i=0; i<labels.length; i++){
|
|
||||||
var label = labels[i];
|
var elems = this.id('tags-box').querySelectorAll("button[selected=true]");
|
||||||
label.setAttribute('selected', 'false');
|
for (let i = 0; i < elems.length; i++) {
|
||||||
delete this.selection[label.value];
|
let elem = elems[i];
|
||||||
|
elem.setAttribute('selected', false);
|
||||||
|
this.selection.delete(elem.textContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.onchange) {
|
if (this.onchange) {
|
||||||
|
@ -647,14 +578,6 @@
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="clearAll">
|
|
||||||
<body><![CDATA[
|
|
||||||
this.selection = {};
|
|
||||||
return this.clearVisible();
|
|
||||||
]]></body>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
|
|
||||||
<method name="handleKeyPress">
|
<method name="handleKeyPress">
|
||||||
<parameter name="clear"/>
|
<parameter name="clear"/>
|
||||||
<body>
|
<body>
|
||||||
|
@ -687,7 +610,7 @@
|
||||||
|
|
||||||
<method name="handleTagClick">
|
<method name="handleTagClick">
|
||||||
<parameter name="event"/>
|
<parameter name="event"/>
|
||||||
<parameter name="label"/>
|
<parameter name="elem"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
if (event.button != 0) {
|
if (event.button != 0) {
|
||||||
|
@ -695,19 +618,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore clicks on tags not in scope
|
// Ignore clicks on tags not in scope
|
||||||
if (label.getAttribute('inScope') == 'false') {
|
if (elem.getAttribute('inScope') == 'false') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deselect
|
// Deselect
|
||||||
if (label.getAttribute('selected')=='true'){
|
if (elem.getAttribute('selected')=='true'){
|
||||||
delete this.selection[label.value];
|
this.selection.delete(elem.textContent);
|
||||||
label.setAttribute('selected', 'false');
|
elem.setAttribute('selected', 'false');
|
||||||
}
|
}
|
||||||
// Select
|
// Select
|
||||||
else {
|
else {
|
||||||
this.selection[label.value] = true;
|
this.selection.add(elem.textContent);
|
||||||
label.setAttribute('selected', 'true');
|
elem.setAttribute('selected', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateNumSelected();
|
this.updateNumSelected();
|
||||||
|
@ -723,7 +646,7 @@
|
||||||
<method name="rename">
|
<method name="rename">
|
||||||
<parameter name="oldName"/>
|
<parameter name="oldName"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
Zotero.spawn(function* () {
|
return Zotero.spawn(function* () {
|
||||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||||
.getService(Components.interfaces.nsIPromptService);
|
.getService(Components.interfaces.nsIPromptService);
|
||||||
|
|
||||||
|
@ -737,13 +660,12 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selection[oldName]) {
|
if (this.selection.has(oldName)) {
|
||||||
var wasSelected = true;
|
var wasSelected = true;
|
||||||
delete this.selection[oldName];
|
this.selection.delete(oldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield Zotero.Tags.load(this.libraryID);
|
if (yield Zotero.Tags.getID(oldName)) {
|
||||||
if (Zotero.Tags.getID(this.libraryID, oldName)) {
|
|
||||||
yield Zotero.Tags.rename(this.libraryID, oldName, newName.value);
|
yield Zotero.Tags.rename(this.libraryID, oldName, newName.value);
|
||||||
}
|
}
|
||||||
// Colored tags don't need to exist, so in that case
|
// Colored tags don't need to exist, so in that case
|
||||||
|
@ -758,7 +680,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasSelected) {
|
if (wasSelected) {
|
||||||
this.selection[newName.value] = true;
|
this.selection.add(newName.value);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
]]>
|
]]>
|
||||||
|
@ -768,8 +690,8 @@
|
||||||
|
|
||||||
<method name="delete">
|
<method name="delete">
|
||||||
<parameter name="name"/>
|
<parameter name="name"/>
|
||||||
<body>
|
<body><![CDATA[
|
||||||
<![CDATA[
|
return Zotero.spawn(function* () {
|
||||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||||
.getService(Components.interfaces.nsIPromptService);
|
.getService(Components.interfaces.nsIPromptService);
|
||||||
|
|
||||||
|
@ -781,81 +703,190 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Zotero.DB.executeTransaction(function* () {
|
var tagID = yield Zotero.Tags.getID(name);
|
||||||
yield Zotero.Tags.load(this.libraryID);
|
|
||||||
var tagID = Zotero.Tags.getID(this.libraryID, name);
|
|
||||||
if (tagID) {
|
if (tagID) {
|
||||||
yield Zotero.Tags.erase(this.libraryID, tagID);
|
yield Zotero.Tags.removeFromLibrary(this.libraryID, tagID);
|
||||||
|
}
|
||||||
|
// If only a tag color setting, remove that
|
||||||
|
else {
|
||||||
|
yield Zotero.Tags.setColor(this.libraryID, name, false);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
]]></body>
|
||||||
// If only a tag color setting, remove that
|
|
||||||
if (!tagID) {
|
|
||||||
Zotero.Tags.setColor(this.libraryID, name, false);
|
|
||||||
}
|
|
||||||
]]>
|
|
||||||
</body>
|
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="getColor">
|
<method name="getColor">
|
||||||
<parameter name="tagIDs"/>
|
<parameter name="tagIDs"/>
|
||||||
<body>
|
<body><![CDATA[
|
||||||
<![CDATA[
|
return Zotero.spawn(function* () {
|
||||||
tagIDs = tagIDs.split('-');
|
tagIDs = tagIDs.split('-');
|
||||||
var name = Zotero.Tags.getName(this.libraryID, tagIDs[0]);
|
var name = yield Zotero.Tags.getName(tagIDs[0]);
|
||||||
return Zotero.Tags.getColor(this.libraryID, name)
|
var colorData = yield Zotero.Tags.getColor(this.libraryID, name);
|
||||||
.then(function (colorData) {
|
|
||||||
return colorData ? colorData.color : '#000000';
|
return colorData ? colorData.color : '#000000';
|
||||||
});
|
}.bind(this));
|
||||||
]]>
|
]]></body>
|
||||||
</body>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="_insertClickableTag">
|
||||||
|
<parameter name="tagsBox"/>
|
||||||
|
<parameter name="tagData"/>
|
||||||
|
<parameter name="insertBefore"/>
|
||||||
|
<body><![CDATA[
|
||||||
|
var button = this._makeClickableTag(tagData, this.editable);
|
||||||
|
if (insertBefore === undefined) {
|
||||||
|
tagsBox.appendChild(button);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tagsBox.insertBefore(button, insertBefore);
|
||||||
|
}
|
||||||
|
return button;
|
||||||
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="_makeClickableTag">
|
<method name="_makeClickableTag">
|
||||||
<parameter name="tagObj"/>
|
<parameter name="tagObj"/>
|
||||||
<parameter name="editable"/>
|
<parameter name="editable"/>
|
||||||
<body>
|
<body><![CDATA[
|
||||||
<![CDATA[
|
var elem = document.createElementNS('http://www.w3.org/1999/xhtml', 'button');
|
||||||
var tagName = tagObj.tag;
|
elem.textContent = tagObj.tag;
|
||||||
var tagType = tagObj.type;
|
if (tagObj.type) {
|
||||||
|
elem.setAttribute('tagType', tagObj.type);
|
||||||
var label = document.createElement('label');
|
|
||||||
|
|
||||||
label.setAttribute('value', tagName);
|
|
||||||
label.setAttribute('tagType', tagType);
|
|
||||||
if (editable) {
|
|
||||||
label.setAttribute('context', 'tag-menu');
|
|
||||||
}
|
}
|
||||||
return label;
|
var self = this;
|
||||||
]]>
|
elem.addEventListener('click', function(event) {
|
||||||
</body>
|
self.handleTagClick(event, this);
|
||||||
|
});
|
||||||
|
if (this.editable) {
|
||||||
|
elem.addEventListener('mousedown', function (event) {
|
||||||
|
if (event.button == 2) {
|
||||||
|
// Without the setTimeout, the popup gets immediately hidden
|
||||||
|
// for some reason
|
||||||
|
setTimeout(function () {
|
||||||
|
_popupNode = elem;
|
||||||
|
self.id('tag-menu').openPopup(
|
||||||
|
null,
|
||||||
|
'after_pointer',
|
||||||
|
event.clientX + 2,
|
||||||
|
event.clientY + 2,
|
||||||
|
true,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
elem.addEventListener('dragover', this.dragObserver.onDragOver);
|
||||||
|
elem.addEventListener('dragexit', this.dragObserver.onDragExit);
|
||||||
|
elem.addEventListener('drop', this.dragObserver.onDrop, true);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
]]></body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="_updateClickableTag">
|
||||||
|
<parameter name="elem"/>
|
||||||
|
<parameter name="name"/>
|
||||||
|
<parameter name="colors"/>
|
||||||
|
<body><![CDATA[
|
||||||
|
var visible = false;
|
||||||
|
|
||||||
|
var colorData = colors.get(name);
|
||||||
|
if (colorData) {
|
||||||
|
elem.style.color = colorData.color;
|
||||||
|
elem.style.fontWeight = 'bold';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elem.style.color = '';
|
||||||
|
elem.style.fontWeight = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore selection
|
||||||
|
elem.setAttribute('selected', this.selection.has(name));
|
||||||
|
|
||||||
|
// Check against tag search
|
||||||
|
if (this._search) {
|
||||||
|
var inSearch = name.toLowerCase().indexOf(this._search) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tags against scope
|
||||||
|
if (this._hasScope) {
|
||||||
|
var inScope = !!this._scope[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not in search, hide
|
||||||
|
if (this._search && !inSearch) {
|
||||||
|
elem.style.display = 'none';
|
||||||
|
}
|
||||||
|
// Only show tags matching current scope
|
||||||
|
// (i.e., associated with items in the current view)
|
||||||
|
else if (this.filterToScope) {
|
||||||
|
if (inScope) {
|
||||||
|
elem.className = 'zotero-clicky';
|
||||||
|
elem.setAttribute('inScope', true);
|
||||||
|
elem.style.display = '';
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elem.className = '';
|
||||||
|
elem.style.display = 'none';
|
||||||
|
elem.setAttribute('inScope', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Display all
|
||||||
|
else {
|
||||||
|
if (inScope) {
|
||||||
|
elem.className = 'zotero-clicky';
|
||||||
|
elem.setAttribute('inScope', true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elem.className = '';
|
||||||
|
elem.setAttribute('inScope', false);
|
||||||
|
}
|
||||||
|
elem.style.display = '';
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always show colored tags at top, unless they
|
||||||
|
// don't match an active tag search
|
||||||
|
if (colorData && (!this._search || inSearch)) {
|
||||||
|
elem.style.display = '';
|
||||||
|
elem.style.order = (Zotero.Tags.MAX_COLORED_TAGS * -1) + colorData.position - 1;
|
||||||
|
elem.setAttribute('hasColor', true);
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elem.style.order = 0;
|
||||||
|
elem.removeAttribute('hasColor', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return visible;
|
||||||
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="_openColorPickerWindow">
|
<method name="_openColorPickerWindow">
|
||||||
<parameter name="name"/>
|
<parameter name="name"/>
|
||||||
<body>
|
<body><![CDATA[
|
||||||
<![CDATA[
|
return Zotero.spawn(function* () {
|
||||||
var io = {
|
var io = {
|
||||||
libraryID: this.libraryID,
|
libraryID: this.libraryID,
|
||||||
name: name
|
name: name
|
||||||
};
|
};
|
||||||
|
|
||||||
var self = this;
|
var tagColors = yield Zotero.Tags.getColors(this.libraryID);
|
||||||
Zotero.Tags.getColors(this.libraryID)
|
if (tagColors.size >= Zotero.Tags.MAX_COLORED_TAGS && !tagColors.has(io.name)) {
|
||||||
.then(function (tagColors) {
|
|
||||||
if (Object.keys(tagColors).length >= Zotero.Tags.MAX_COLORED_TAGS && !tagColors[io.name]) {
|
|
||||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||||
.getService(Components.interfaces.nsIPromptService);
|
.getService(Components.interfaces.nsIPromptService);
|
||||||
ps.alert(null, "", Zotero.getString('pane.tagSelector.maxColoredTags', Zotero.Tags.MAX_COLORED_TAGS));
|
ps.alert(null, "", Zotero.getString('pane.tagSelector.maxColoredTags', Zotero.Tags.MAX_COLORED_TAGS));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opening a modal window directly from within this promise handler causes
|
io.tagColors = tagColors;
|
||||||
// the opened window to block on the first yielded promise until the window
|
|
||||||
// is closed.
|
|
||||||
setTimeout(function () {
|
|
||||||
window.openDialog(
|
window.openDialog(
|
||||||
'chrome://zotero/content/tagColorChooser.xul',
|
'chrome://zotero/content/tagColorChooser.xul',
|
||||||
"zotero-tagSelector-colorChooser",
|
"zotero-tagSelector-colorChooser",
|
||||||
|
@ -867,9 +898,8 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Tags.setColor(self.libraryID, io.name, io.color, io.position);
|
yield Zotero.Tags.setColor(this.libraryID, io.name, io.color, io.position);
|
||||||
}, 0);
|
}.bind(this));
|
||||||
});
|
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
@ -928,7 +958,7 @@
|
||||||
return Zotero.DB.executeTransaction(function* () {
|
return Zotero.DB.executeTransaction(function* () {
|
||||||
ids = ids.split(',');
|
ids = ids.split(',');
|
||||||
var items = Zotero.Items.get(ids);
|
var items = Zotero.Items.get(ids);
|
||||||
var value = node.getAttribute('value')
|
var value = node.textContent
|
||||||
|
|
||||||
for (let i=0; i<items.length; i++) {
|
for (let i=0; i<items.length; i++) {
|
||||||
let item = items[i];
|
let item = items[i];
|
||||||
|
@ -953,28 +983,33 @@
|
||||||
</implementation>
|
</implementation>
|
||||||
|
|
||||||
<content>
|
<content>
|
||||||
<groupbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1">
|
<groupbox flex="1"
|
||||||
<menupopup id="tag-menu">
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||||
|
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||||
|
<menupopup id="tag-menu"
|
||||||
|
onpopuphidden="_popupNode = null">
|
||||||
<menuitem label="&zotero.tagSelector.assignColor;"
|
<menuitem label="&zotero.tagSelector.assignColor;"
|
||||||
oncommand="_openColorPickerWindow(document.popupNode.getAttribute('value')); event.stopPropagation()"/>
|
oncommand="_openColorPickerWindow(_popupNode.textContent); event.stopPropagation()"/>
|
||||||
<menuitem label="&zotero.tagSelector.renameTag;"
|
<menuitem label="&zotero.tagSelector.renameTag;"
|
||||||
oncommand="document.getBindingParent(this).rename(document.popupNode.getAttribute('value')); event.stopPropagation()"/>
|
oncommand="document.getBindingParent(this).rename(_popupNode.textContent); event.stopPropagation()"/>
|
||||||
<menuitem label="&zotero.tagSelector.deleteTag;"
|
<menuitem label="&zotero.tagSelector.deleteTag;"
|
||||||
oncommand="document.getBindingParent(this).delete(document.popupNode.getAttribute('value')); event.stopPropagation()"/>
|
oncommand="document.getBindingParent(this).delete(_popupNode.textContent); event.stopPropagation()"/>
|
||||||
</menupopup>
|
</menupopup>
|
||||||
|
|
||||||
<vbox id="no-tags-box" align="center" pack="center" flex="1">
|
<deck id="tags-deck">
|
||||||
<deck id="no-tags-deck">
|
<box id="loading-box">
|
||||||
<label value="&zotero.tagSelector.loadingTags;"/>
|
<label value="&zotero.tagSelector.loadingTags;"/>
|
||||||
<label value="&zotero.tagSelector.noTagsToDisplay;"/>
|
</box>
|
||||||
</deck>
|
|
||||||
</vbox>
|
|
||||||
|
|
||||||
<vbox id="tags-toggle" flex="1"/>
|
<box id="no-tags-box">
|
||||||
|
<label value="&zotero.tagSelector.noTagsToDisplay;"/>
|
||||||
|
</box>
|
||||||
|
|
||||||
|
<html:div id="tags-box"/>
|
||||||
|
</deck>
|
||||||
|
|
||||||
<vbox id="tag-controls">
|
<vbox id="tag-controls">
|
||||||
<hbox>
|
<hbox>
|
||||||
<!-- TODO: &zotero.tagSelector.filter; is now unused -->
|
|
||||||
<textbox id="tags-search" flex="1" type="search" timeout="250" dir="reverse"
|
<textbox id="tags-search" flex="1" type="search" timeout="250" dir="reverse"
|
||||||
oncommand="document.getBindingParent(this).handleKeyPress(); event.stopPropagation()"
|
oncommand="document.getBindingParent(this).handleKeyPress(); event.stopPropagation()"
|
||||||
onkeypress="if (event.keyCode == event.DOM_VK_ESCAPE) { document.getBindingParent(this).handleKeyPress(true); }"/>
|
onkeypress="if (event.keyCode == event.DOM_VK_ESCAPE) { document.getBindingParent(this).handleKeyPress(true); }"/>
|
||||||
|
@ -989,14 +1024,16 @@
|
||||||
document.getElementById('display-all-tags').setAttribute('checked', !document.getBindingParent(this).filterToScope);">
|
document.getElementById('display-all-tags').setAttribute('checked', !document.getBindingParent(this).filterToScope);">
|
||||||
<menuitem id="num-selected" disabled="true"/>
|
<menuitem id="num-selected" disabled="true"/>
|
||||||
<menuitem id="deselect-all" label="&zotero.tagSelector.clearAll;"
|
<menuitem id="deselect-all" label="&zotero.tagSelector.clearAll;"
|
||||||
oncommand="document.getBindingParent(this).clearAll(); event.stopPropagation();"/>
|
oncommand="document.getBindingParent(this).deselectAll(); event.stopPropagation();"/>
|
||||||
<menuseparator/>
|
<menuseparator/>
|
||||||
<menuitem id="show-automatic" label="&zotero.tagSelector.showAutomatic;" type="checkbox"
|
<menuitem id="show-automatic" label="&zotero.tagSelector.showAutomatic;" type="checkbox"
|
||||||
oncommand="var ts = document.getBindingParent(this);
|
oncommand="var ts = document.getBindingParent(this);
|
||||||
ts._dirty = true;
|
ts._dirty = true;
|
||||||
var showAutomatic = this.getAttribute('checked') == 'true';
|
var showAutomatic = this.getAttribute('checked') == 'true';
|
||||||
ts.setAttribute('showAutomatic', showAutomatic);
|
ts.setAttribute('showAutomatic', showAutomatic);
|
||||||
this.setAttribute('checked', showAutomatic);"/>
|
this.setAttribute('checked', showAutomatic);
|
||||||
|
ts.refresh();
|
||||||
|
event.stopPropagation();"/>
|
||||||
<menuitem id="display-all-tags" label="&zotero.tagSelector.displayAllInLibrary;" type="checkbox"
|
<menuitem id="display-all-tags" label="&zotero.tagSelector.displayAllInLibrary;" type="checkbox"
|
||||||
oncommand="var displayAll = this.getAttribute('checked') == 'true';
|
oncommand="var displayAll = this.getAttribute('checked') == 'true';
|
||||||
this.setAttribute('checked', !displayAll);
|
this.setAttribute('checked', !displayAll);
|
||||||
|
|
|
@ -180,6 +180,7 @@ var Zotero_Long_Tag_Fixer = new function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old tags
|
// Remove old tags
|
||||||
|
// TODO: Update
|
||||||
Zotero.Tags.erase(oldTagIDs);
|
Zotero.Tags.erase(oldTagIDs);
|
||||||
Zotero.Tags.purge();
|
Zotero.Tags.purge();
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
|
|
|
@ -24,13 +24,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
var _io;
|
|
||||||
|
|
||||||
var Zotero_Tag_Color_Chooser = new function() {
|
var Zotero_Tag_Color_Chooser = new function() {
|
||||||
|
var _io;
|
||||||
|
|
||||||
this.init = function () {
|
this.init = function () {
|
||||||
var dialog = document.getElementById('tag-color-chooser');
|
var dialog = document.getElementById('tag-color-chooser');
|
||||||
|
|
||||||
return Zotero.spawn(function* () {
|
try {
|
||||||
// Set font size from pref
|
// Set font size from pref
|
||||||
Zotero.setFontSize(document.getElementById("tag-color-chooser-container"));
|
Zotero.setFontSize(document.getElementById("tag-color-chooser-container"));
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ var Zotero_Tag_Color_Chooser = new function() {
|
||||||
}
|
}
|
||||||
if (typeof _io.libraryID == 'undefined') throw new Error("libraryID not set");
|
if (typeof _io.libraryID == 'undefined') throw new Error("libraryID not set");
|
||||||
if (typeof _io.name == 'undefined' || _io.name === "") throw new Error("name not set");
|
if (typeof _io.name == 'undefined' || _io.name === "") throw new Error("name not set");
|
||||||
|
if (_io.tagColors === undefined) throw new Error("tagColors not provided");
|
||||||
|
|
||||||
window.sizeToContent();
|
window.sizeToContent();
|
||||||
|
|
||||||
|
@ -58,8 +60,8 @@ var Zotero_Tag_Color_Chooser = new function() {
|
||||||
var maxTags = document.getElementById('max-tags');
|
var maxTags = document.getElementById('max-tags');
|
||||||
maxTags.value = Zotero.getString('tagColorChooser.maxTags', Zotero.Tags.MAX_COLORED_TAGS);
|
maxTags.value = Zotero.getString('tagColorChooser.maxTags', Zotero.Tags.MAX_COLORED_TAGS);
|
||||||
|
|
||||||
var tagColors = yield Zotero.Tags.getColors(_io.libraryID);
|
var tagColors = _io.tagColors;
|
||||||
var colorData = tagColors[_io.name];
|
var colorData = tagColors.get(_io.name);
|
||||||
|
|
||||||
// Color
|
// Color
|
||||||
if (colorData) {
|
if (colorData) {
|
||||||
|
@ -68,11 +70,7 @@ var Zotero_Tag_Color_Chooser = new function() {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Get unused color at random
|
// Get unused color at random
|
||||||
var usedColors = [];
|
var usedColors = [for (x of tagColors.values()) x.color];
|
||||||
for (var i in tagColors) {
|
|
||||||
usedColors.push(tagColors[i].color);
|
|
||||||
}
|
|
||||||
|
|
||||||
var unusedColors = Zotero.Utilities.arrayDiff(
|
var unusedColors = Zotero.Utilities.arrayDiff(
|
||||||
colorPicker.colors, usedColors
|
colorPicker.colors, usedColors
|
||||||
);
|
);
|
||||||
|
@ -82,7 +80,7 @@ var Zotero_Tag_Color_Chooser = new function() {
|
||||||
}
|
}
|
||||||
colorPicker.setAttribute('disabled', 'false');
|
colorPicker.setAttribute('disabled', 'false');
|
||||||
|
|
||||||
var numColors = Object.keys(tagColors).length;
|
var numColors = tagColors.size;
|
||||||
var max = colorData ? numColors : numColors + 1;
|
var max = colorData ? numColors : numColors + 1;
|
||||||
|
|
||||||
// Position
|
// Position
|
||||||
|
@ -106,14 +104,13 @@ var Zotero_Tag_Color_Chooser = new function() {
|
||||||
|
|
||||||
this.onPositionChange();
|
this.onPositionChange();
|
||||||
window.sizeToContent();
|
window.sizeToContent();
|
||||||
}.bind(this))
|
}
|
||||||
.catch(function (e) {
|
catch (e) {
|
||||||
Zotero.debug(e, 1);
|
Zotero.logError(e);
|
||||||
Components.utils.reportError(e);
|
|
||||||
if (dialog.cancelDialog) {
|
if (dialog.cancelDialog) {
|
||||||
dialog.cancelDialog();
|
dialog.cancelDialog();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2398,12 +2398,10 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.tags){
|
if (this.tags){
|
||||||
for (var tag in this.tags){
|
for (let tag of this.tags) {
|
||||||
if (this.tags[tag]){
|
|
||||||
s2.addCondition('tag', 'is', tag);
|
s2.addCondition('tag', 'is', tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.CollectionTreeCache.lastTreeRow = this;
|
Zotero.CollectionTreeCache.lastTreeRow = this;
|
||||||
Zotero.CollectionTreeCache.lastSearch = s2;
|
Zotero.CollectionTreeCache.lastSearch = s2;
|
||||||
|
@ -2456,9 +2454,7 @@ Zotero.CollectionTreeRow.prototype.isSearchMode = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag filter
|
// Tag filter
|
||||||
if (this.tags) {
|
if (this.tags && this.tags.size) {
|
||||||
for (var i in this.tags) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -370,7 +370,7 @@ Zotero.DataObjects.prototype.getUnwrittenData = function (libraryID) {
|
||||||
Zotero.DataObjects.prototype.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) {
|
Zotero.DataObjects.prototype.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) {
|
||||||
ids = Zotero.flattenArguments(ids);
|
ids = Zotero.flattenArguments(ids);
|
||||||
|
|
||||||
Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '')
|
Zotero.debug('Reloading ' + (dataTypes ? '[' + dataTypes.join(', ') + '] for ' : '')
|
||||||
+ this._ZDO_objects + ' ' + ids);
|
+ this._ZDO_objects + ' ' + ids);
|
||||||
|
|
||||||
for (let i=0; i<ids.length; i++) {
|
for (let i=0; i<ids.length; i++) {
|
||||||
|
|
|
@ -1495,21 +1495,33 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
||||||
|
|
||||||
for (let i=0; i<toAdd.length; i++) {
|
for (let i=0; i<toAdd.length; i++) {
|
||||||
let tag = toAdd[i];
|
let tag = toAdd[i];
|
||||||
let tagID = yield Zotero.Tags.getIDFromName(this.libraryID, tag.tag, true);
|
let tagID = yield Zotero.Tags.getID(tag.tag, true);
|
||||||
|
let tagType = tag.type ? tag.type : 0;
|
||||||
// "OR REPLACE" allows changing type
|
// "OR REPLACE" allows changing type
|
||||||
let sql = "INSERT OR REPLACE INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)";
|
let sql = "INSERT OR REPLACE INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)";
|
||||||
yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
|
yield Zotero.DB.queryAsync(sql, [this.id, tagID, tagType]);
|
||||||
Zotero.Notifier.queue('add', 'item-tag', this.id + '-' + tag.tag);
|
|
||||||
|
let notifierData = {};
|
||||||
|
notifierData[this.id + '-' + tagID] = {
|
||||||
|
libraryID: this.libraryID,
|
||||||
|
tag: tag.tag,
|
||||||
|
type: tagType
|
||||||
|
};
|
||||||
|
Zotero.Notifier.queue('add', 'item-tag', this.id + '-' + tagID, notifierData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toRemove.length) {
|
if (toRemove.length) {
|
||||||
yield Zotero.Tags.load(this.libraryID);
|
|
||||||
for (let i=0; i<toRemove.length; i++) {
|
for (let i=0; i<toRemove.length; i++) {
|
||||||
let tag = toRemove[i];
|
let tag = toRemove[i];
|
||||||
let tagID = Zotero.Tags.getID(this.libraryID, tag.tag);
|
let tagID = yield Zotero.Tags.getID(tag.tag);
|
||||||
let sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=? AND type=?";
|
let sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=? AND type=?";
|
||||||
yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
|
yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
|
||||||
Zotero.Notifier.queue('remove', 'item-tag', this.id + '-' + tag.tag);
|
let notifierData = {};
|
||||||
|
notifierData[this.id + '-' + tagID] = {
|
||||||
|
libraryID: this.libraryID,
|
||||||
|
tag: tag.tag
|
||||||
|
};
|
||||||
|
Zotero.Notifier.queue('remove', 'item-tag', this.id + '-' + tagID, notifierData);
|
||||||
}
|
}
|
||||||
Zotero.Prefs.set('purge.tags', true);
|
Zotero.Prefs.set('purge.tags', true);
|
||||||
}
|
}
|
||||||
|
@ -3426,8 +3438,9 @@ Zotero.Item.prototype.getImageSrcWithTags = Zotero.Promise.coroutine(function* (
|
||||||
var colorData = [];
|
var colorData = [];
|
||||||
for (let i=0; i<tags.length; i++) {
|
for (let i=0; i<tags.length; i++) {
|
||||||
let tag = tags[i];
|
let tag = tags[i];
|
||||||
if (tagColors[tag.tag]) {
|
let data = tagColors.get(tag.tag);
|
||||||
colorData.push(tagColors[tag.tag]);
|
if (data) {
|
||||||
|
colorData.push(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!colorData.length) {
|
if (!colorData.length) {
|
||||||
|
|
|
@ -30,10 +30,6 @@
|
||||||
Zotero.Tags = new function() {
|
Zotero.Tags = new function() {
|
||||||
this.MAX_COLORED_TAGS = 6;
|
this.MAX_COLORED_TAGS = 6;
|
||||||
|
|
||||||
var _tagIDsByName = {};
|
|
||||||
var _tagNamesByID = {};
|
|
||||||
var _loaded = {};
|
|
||||||
|
|
||||||
var _libraryColors = {};
|
var _libraryColors = {};
|
||||||
var _libraryColorsByName = {};
|
var _libraryColorsByName = {};
|
||||||
var _itemsListImagePromises = {};
|
var _itemsListImagePromises = {};
|
||||||
|
@ -43,78 +39,43 @@ Zotero.Tags = new function() {
|
||||||
/**
|
/**
|
||||||
* Returns a tag for a given tagID
|
* Returns a tag for a given tagID
|
||||||
*
|
*
|
||||||
* @param {Number} libraryID
|
* @param {Integer} tagID
|
||||||
* @param {Number} tagID
|
* @return {Promise<String|false>} - A tag name, or false if tag with id not found
|
||||||
*/
|
*/
|
||||||
this.getName = function (libraryID, tagID) {
|
this.getName = function (tagID) {
|
||||||
if (!tagID) {
|
return Zotero.DB.valueQueryAsync("SELECT name FROM tags WHERE tagID=?", tagID);
|
||||||
throw new Error("tagID not provided");
|
|
||||||
}
|
}
|
||||||
if (_tagNamesByID[tagID]) {
|
|
||||||
return _tagNamesByID[tagID];
|
|
||||||
}
|
|
||||||
_requireLoad(libraryID);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the tagID matching a given tag
|
|
||||||
*
|
|
||||||
* @param {Number} libraryID
|
|
||||||
* @param {String} name
|
|
||||||
*/
|
|
||||||
this.getID = function (libraryID, name) {
|
|
||||||
if (_tagIDsByName[libraryID] && _tagIDsByName[libraryID]['_' + name]) {
|
|
||||||
return _tagIDsByName[libraryID]['_' + name];
|
|
||||||
}
|
|
||||||
_requireLoad(libraryID);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the tagID matching given fields, or creates a new tag and returns its id
|
* Returns the tagID matching given fields, or creates a new tag and returns its id
|
||||||
*
|
*
|
||||||
* @requireTransaction
|
|
||||||
* @param {Number} libraryID
|
|
||||||
* @param {String} name - Tag data in API JSON format
|
* @param {String} name - Tag data in API JSON format
|
||||||
* @param {Boolean} [create=false] - If no matching tag, create one
|
* @param {Boolean} [create=false] - If no matching tag, create one;
|
||||||
|
* requires a wrapping transaction
|
||||||
* @return {Promise<Integer>} tagID
|
* @return {Promise<Integer>} tagID
|
||||||
*/
|
*/
|
||||||
this.getIDFromName = Zotero.Promise.coroutine(function* (libraryID, name, create) {
|
this.getID = Zotero.Promise.coroutine(function* (name, create) {
|
||||||
|
if (create) {
|
||||||
Zotero.DB.requireTransaction();
|
Zotero.DB.requireTransaction();
|
||||||
|
}
|
||||||
data = this.cleanData({
|
data = this.cleanData({
|
||||||
tag: name
|
tag: name
|
||||||
});
|
});
|
||||||
var sql = "SELECT tagID FROM tags WHERE libraryID=? AND name=?";
|
var sql = "SELECT tagID FROM tags WHERE name=?";
|
||||||
var id = yield Zotero.DB.valueQueryAsync(sql, [libraryID, data.tag]);
|
var id = yield Zotero.DB.valueQueryAsync(sql, data.tag);
|
||||||
if (!id && create) {
|
if (!id && create) {
|
||||||
id = yield Zotero.ID.get('tags');
|
id = yield Zotero.ID.get('tags');
|
||||||
let sql = "INSERT INTO tags (tagID, libraryID, name) VALUES (?, ?, ?)";
|
let sql = "INSERT INTO tags (tagID, name) VALUES (?, ?)";
|
||||||
let insertID = yield Zotero.DB.queryAsync(sql, [id, libraryID, data.tag]);
|
let insertID = yield Zotero.DB.queryAsync(sql, [id, data.tag]);
|
||||||
if (!id) {
|
if (!id) {
|
||||||
id = insertID;
|
id = insertID;
|
||||||
}
|
}
|
||||||
_cacheTag(libraryID, id, data.tag);
|
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns an array of tag types for tags matching given tag
|
|
||||||
*/
|
|
||||||
this.getTypes = Zotero.Promise.method(function (name, libraryID) {
|
|
||||||
if (libraryID != parseInt(libraryID)) {
|
|
||||||
throw new Error("libraryID must be an integer");
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql = "SELECT type FROM tags WHERE libraryID=? AND name=?";
|
|
||||||
return Zotero.DB.columnQueryAsync(sql, [libraryID, name.trim()]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all tags indexed by tagID
|
* Get all tags indexed by tagID
|
||||||
*
|
*
|
||||||
|
@ -125,7 +86,7 @@ Zotero.Tags = new function() {
|
||||||
*/
|
*/
|
||||||
this.getAll = Zotero.Promise.coroutine(function* (libraryID, types) {
|
this.getAll = Zotero.Promise.coroutine(function* (libraryID, types) {
|
||||||
var sql = "SELECT DISTINCT name AS tag, type FROM tags "
|
var sql = "SELECT DISTINCT name AS tag, type FROM tags "
|
||||||
+ "JOIN itemTags USING (tagID) WHERE libraryID=?";
|
+ "JOIN itemTags USING (tagID) JOIN items USING (itemID) WHERE libraryID=?";
|
||||||
var params = [libraryID];
|
var params = [libraryID];
|
||||||
if (types) {
|
if (types) {
|
||||||
sql += " AND type IN (" + types.join() + ")";
|
sql += " AND type IN (" + types.join() + ")";
|
||||||
|
@ -177,14 +138,15 @@ Zotero.Tags = new function() {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the items associated with the given saved tag
|
* Get the items associated with the given tag
|
||||||
*
|
*
|
||||||
* @param {Number} tagID
|
* @param {Number} tagID
|
||||||
* @return {Promise<Number[]>} A promise for an array of itemIDs
|
* @return {Promise<Number[]>} A promise for an array of itemIDs
|
||||||
*/
|
*/
|
||||||
this.getTagItems = function (tagID) {
|
this.getTagItems = function (libraryID, tagID) {
|
||||||
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
|
var sql = "SELECT itemID FROM itemTags JOIN items USING (itemID) "
|
||||||
return Zotero.DB.columnQueryAsync(sql, tagID);
|
+ "WHERE tagID=? AND libraryID=?";
|
||||||
|
return Zotero.DB.columnQueryAsync(sql, [tagID, libraryID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -198,28 +160,6 @@ Zotero.Tags = new function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.load = Zotero.Promise.coroutine(function* (libraryID, reload) {
|
|
||||||
if (_loaded[libraryID] && !reload) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.debug("Loading tags in library " + libraryID);
|
|
||||||
|
|
||||||
var sql = 'SELECT tagID AS id, name FROM tags WHERE libraryID=?';
|
|
||||||
var tags = yield Zotero.DB.queryAsync(sql, libraryID);
|
|
||||||
|
|
||||||
_tagIDsByName[libraryID] = {}
|
|
||||||
|
|
||||||
for (var i=0; i<tags.length; i++) {
|
|
||||||
let tag = tags[i];
|
|
||||||
_tagIDsByName[libraryID]['_' + tag.name] = tag.id;
|
|
||||||
_tagNamesByID[tag.id] = tag.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
_loaded[libraryID] = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename a tag and update the tag colors setting accordingly if necessary
|
* Rename a tag and update the tag colors setting accordingly if necessary
|
||||||
*
|
*
|
||||||
|
@ -228,7 +168,7 @@ Zotero.Tags = new function() {
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
this.rename = Zotero.Promise.coroutine(function* (libraryID, oldName, newName) {
|
this.rename = Zotero.Promise.coroutine(function* (libraryID, oldName, newName) {
|
||||||
Zotero.debug("Renaming tag '" + oldName + "' to '" + newName + "'", 4);
|
Zotero.debug("Renaming tag '" + oldName + "' to '" + newName + "' in library " + libraryID);
|
||||||
|
|
||||||
oldName = oldName.trim();
|
oldName = oldName.trim();
|
||||||
newName = newName.trim();
|
newName = newName.trim();
|
||||||
|
@ -238,16 +178,15 @@ Zotero.Tags = new function() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield Zotero.Tags.load(libraryID);
|
var oldTagID = yield this.getID(oldName);
|
||||||
var oldTagID = this.getID(libraryID, oldName);
|
|
||||||
|
|
||||||
// We need to know if the old tag has a color assigned so that
|
// We need to know if the old tag has a color assigned so that
|
||||||
// we can assign it to the new name
|
// we can assign it to the new name
|
||||||
var oldColorData = yield this.getColor(libraryID, oldName);
|
var oldColorData = yield this.getColor(libraryID, oldName);
|
||||||
|
|
||||||
yield Zotero.DB.executeTransaction(function* () {
|
yield Zotero.DB.executeTransaction(function* () {
|
||||||
var oldItemIDs = yield this.getTagItems(oldTagID);
|
var oldItemIDs = yield this.getTagItems(libraryID, oldTagID);
|
||||||
var newTagID = yield this.getIDFromName(libraryID, newName, true);
|
var newTagID = yield this.getID(newName, true);
|
||||||
|
|
||||||
yield Zotero.Utilities.Internal.forEachChunkAsync(
|
yield Zotero.Utilities.Internal.forEachChunkAsync(
|
||||||
oldItemIDs,
|
oldItemIDs,
|
||||||
|
@ -269,19 +208,23 @@ Zotero.Tags = new function() {
|
||||||
);
|
);
|
||||||
|
|
||||||
var notifierData = {};
|
var notifierData = {};
|
||||||
notifierData[newName] = {
|
for (let i = 0; i < oldItemIDs.length; i++) {
|
||||||
|
notifierData[oldItemIDs[i] + '-' + newTagID] = {
|
||||||
|
tag: newName,
|
||||||
old: {
|
old: {
|
||||||
tag: oldName
|
tag: oldName
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Zotero.Notifier.queue(
|
Zotero.Notifier.queue(
|
||||||
'modify',
|
'modify',
|
||||||
'item-tag',
|
'item-tag',
|
||||||
oldItemIDs.map(function (itemID) itemID + '-' + newName),
|
oldItemIDs.map(itemID => itemID + '-' + newTagID),
|
||||||
notifierData
|
notifierData
|
||||||
);
|
);
|
||||||
|
|
||||||
yield this.purge(libraryID, oldTagID);
|
yield this.purge(oldTagID);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
if (oldColorData) {
|
if (oldColorData) {
|
||||||
|
@ -302,30 +245,48 @@ Zotero.Tags = new function() {
|
||||||
/**
|
/**
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
this.erase = Zotero.Promise.coroutine(function* (libraryID, tagIDs) {
|
this.removeFromLibrary = Zotero.Promise.coroutine(function* (libraryID, tagIDs) {
|
||||||
tagIDs = Zotero.flattenArguments(tagIDs);
|
tagIDs = Zotero.flattenArguments(tagIDs);
|
||||||
|
|
||||||
var deletedNames = [];
|
var deletedNames = [];
|
||||||
var oldItemIDs = [];
|
var oldItemIDs = [];
|
||||||
|
|
||||||
yield Zotero.DB.executeTransaction(function* () {
|
yield Zotero.DB.executeTransaction(function* () {
|
||||||
yield Zotero.Tags.load(libraryID);
|
var notifierPairs = [];
|
||||||
|
var notifierData = {};
|
||||||
for (let i=0; i<tagIDs.length; i++) {
|
for (let i=0; i<tagIDs.length; i++) {
|
||||||
let tagID = tagIDs[i];
|
let tagID = tagIDs[i];
|
||||||
let name = this.getName(libraryID, tagID);
|
let name = yield this.getName(tagID);
|
||||||
if (name === false) {
|
if (name === false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
deletedNames.push(name);
|
deletedNames.push(name);
|
||||||
oldItemIDs = oldItemIDs.concat(yield this.getTagItems(tagID));
|
|
||||||
|
|
||||||
// This causes a cascading delete from itemTags
|
// Since we're performing the DELETE query directly,
|
||||||
let sql = "DELETE FROM tags WHERE tagID=?";
|
// get the list of items that will need their tags reloaded,
|
||||||
yield Zotero.DB.queryAsync(sql, [tagID]);
|
// and generate data for item-tag notifications
|
||||||
|
let tagItems = yield this.getTagItems(libraryID, tagID);
|
||||||
|
for (let j = 0; j < tagItems.length; j++) {
|
||||||
|
let itemID = tagItems[i];
|
||||||
|
let pair = itemID + "-" + tagID;
|
||||||
|
notifierPairs.push(pair);
|
||||||
|
notifierData[pair] = {
|
||||||
|
libraryID: libraryID,
|
||||||
|
tag: name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
oldItemIDs = oldItemIDs.concat(tagItems);
|
||||||
|
}
|
||||||
|
if (oldItemIDs.length) {
|
||||||
|
Zotero.Notifier.queue('remove', 'item-tag', notifierPairs, notifierData);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield this.purge(libraryID, tagIDs);
|
var sql = "DELETE FROM itemTags WHERE tagID IN ("
|
||||||
|
+ tagIDs.map(x => '?').join(', ') + ") AND itemID IN "
|
||||||
|
+ "(SELECT itemID FROM items WHERE libraryID=?)";
|
||||||
|
yield Zotero.DB.queryAsync(sql, tagIDs.concat([libraryID]));
|
||||||
|
|
||||||
|
yield this.purge(tagIDs);
|
||||||
|
|
||||||
// Update internal timestamps on all items that had these tags
|
// Update internal timestamps on all items that had these tags
|
||||||
yield Zotero.Utilities.Internal.forEachChunkAsync(
|
yield Zotero.Utilities.Internal.forEachChunkAsync(
|
||||||
|
@ -334,11 +295,11 @@ Zotero.Tags = new function() {
|
||||||
function* (chunk) {
|
function* (chunk) {
|
||||||
let placeholders = chunk.map(function () '?').join(',');
|
let placeholders = chunk.map(function () '?').join(',');
|
||||||
|
|
||||||
sql = 'UPDATE items SET clientDateModified=? '
|
sql = 'UPDATE items SET synced=0, clientDateModified=? '
|
||||||
+ 'WHERE itemID IN (' + placeholders + ')'
|
+ 'WHERE itemID IN (' + placeholders + ')'
|
||||||
yield Zotero.DB.queryAsync(sql, [Zotero.DB.transactionDateTime].concat(chunk));
|
yield Zotero.DB.queryAsync(sql, [Zotero.DB.transactionDateTime].concat(chunk));
|
||||||
|
|
||||||
yield Zotero.Items.reload(oldItemIDs, ['tags']);
|
yield Zotero.Items.reload(oldItemIDs, ['primaryData', 'tags'], true);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
@ -355,7 +316,7 @@ Zotero.Tags = new function() {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete obsolete tags from database and clear internal cache entries
|
* Delete obsolete tags from database
|
||||||
*
|
*
|
||||||
* @param {Number} libraryID
|
* @param {Number} libraryID
|
||||||
* @param {Number|Number[]} [tagIDs] - tagID or array of tagIDs to purge
|
* @param {Number|Number[]} [tagIDs] - tagID or array of tagIDs to purge
|
||||||
|
@ -379,7 +340,7 @@ Zotero.Tags = new function() {
|
||||||
for (let i=0; i<tagIDs.length; i++) {
|
for (let i=0; i<tagIDs.length; i++) {
|
||||||
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO tagDelete VALUES (?)", tagIDs[i]);
|
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO tagDelete VALUES (?)", tagIDs[i]);
|
||||||
}
|
}
|
||||||
sql = "SELECT tagID AS id, libraryID, name FROM tagDelete JOIN tags USING (tagID) "
|
sql = "SELECT tagID AS id, name FROM tagDelete JOIN tags USING (tagID) "
|
||||||
+ "WHERE tagID NOT IN (SELECT tagID FROM itemTags)";
|
+ "WHERE tagID NOT IN (SELECT tagID FROM itemTags)";
|
||||||
var toDelete = yield Zotero.DB.queryAsync(sql);
|
var toDelete = yield Zotero.DB.queryAsync(sql);
|
||||||
}
|
}
|
||||||
|
@ -393,7 +354,7 @@ Zotero.Tags = new function() {
|
||||||
sql = "CREATE INDEX tagDelete_tagID ON tagDelete(tagID)";
|
sql = "CREATE INDEX tagDelete_tagID ON tagDelete(tagID)";
|
||||||
yield Zotero.DB.queryAsync(sql);
|
yield Zotero.DB.queryAsync(sql);
|
||||||
|
|
||||||
sql = "SELECT tagID AS id, libraryID, name FROM tagDelete JOIN tags USING (tagID)";
|
sql = "SELECT tagID AS id, name FROM tagDelete JOIN tags USING (tagID)";
|
||||||
var toDelete = yield Zotero.DB.queryAsync(sql);
|
var toDelete = yield Zotero.DB.queryAsync(sql);
|
||||||
|
|
||||||
if (!toDelete.length) {
|
if (!toDelete.length) {
|
||||||
|
@ -409,16 +370,9 @@ Zotero.Tags = new function() {
|
||||||
ids.push(row.id);
|
ids.push(row.id);
|
||||||
notifierData[row.id] = {
|
notifierData[row.id] = {
|
||||||
old: {
|
old: {
|
||||||
libraryID: row.libraryID,
|
|
||||||
tag: row.name
|
tag: row.name
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clear cached values
|
|
||||||
delete _tagNamesByID[row.id];
|
|
||||||
if (_tagIDsByName[row.libraryID]) {
|
|
||||||
delete _tagIDsByName[row.libraryID]['_' + row.name];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sql = "DELETE FROM tags WHERE tagID IN (SELECT tagID FROM tagDelete);";
|
sql = "DELETE FROM tags WHERE tagID IN (SELECT tagID FROM tagDelete);";
|
||||||
|
@ -445,8 +399,7 @@ Zotero.Tags = new function() {
|
||||||
this.getColor = function (libraryID, name) {
|
this.getColor = function (libraryID, name) {
|
||||||
return this.getColors(libraryID)
|
return this.getColors(libraryID)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return _libraryColorsByName[libraryID][name]
|
return _libraryColorsByName[libraryID].get(name) || false;
|
||||||
? _libraryColorsByName[libraryID][name] : false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,8 +421,10 @@ Zotero.Tags = new function() {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Get colored tags within a given library
|
||||||
|
*
|
||||||
* @param {Integer} libraryID
|
* @param {Integer} libraryID
|
||||||
* @return {Promise} A promise for an object with tag names as keys and
|
* @return {Promise<Map>} - A promise for a Map with tag names as keys and
|
||||||
* objects containing 'color' and 'position' as values
|
* objects containing 'color' and 'position' as values
|
||||||
*/
|
*/
|
||||||
this.getColors = Zotero.Promise.coroutine(function* (libraryID) {
|
this.getColors = Zotero.Promise.coroutine(function* (libraryID) {
|
||||||
|
@ -487,14 +442,14 @@ Zotero.Tags = new function() {
|
||||||
tagColors = tagColors || [];
|
tagColors = tagColors || [];
|
||||||
|
|
||||||
_libraryColors[libraryID] = tagColors;
|
_libraryColors[libraryID] = tagColors;
|
||||||
_libraryColorsByName[libraryID] = {};
|
_libraryColorsByName[libraryID] = new Map;
|
||||||
|
|
||||||
// Also create object keyed by name for quick checking for individual tag colors
|
// Also create object keyed by name for quick checking for individual tag colors
|
||||||
for (let i=0; i<tagColors.length; i++) {
|
for (let i=0; i<tagColors.length; i++) {
|
||||||
_libraryColorsByName[libraryID][tagColors[i].name] = {
|
_libraryColorsByName[libraryID].set(tagColors[i].name, {
|
||||||
color: tagColors[i].color,
|
color: tagColors[i].color,
|
||||||
position: i
|
position: i
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return _libraryColorsByName[libraryID];
|
return _libraryColorsByName[libraryID];
|
||||||
|
@ -511,7 +466,6 @@ Zotero.Tags = new function() {
|
||||||
throw new Error("libraryID must be an integer");
|
throw new Error("libraryID must be an integer");
|
||||||
}
|
}
|
||||||
|
|
||||||
yield this.load(libraryID);
|
|
||||||
yield this.getColors(libraryID);
|
yield this.getColors(libraryID);
|
||||||
|
|
||||||
var tagColors = _libraryColors[libraryID];
|
var tagColors = _libraryColors[libraryID];
|
||||||
|
@ -519,7 +473,7 @@ Zotero.Tags = new function() {
|
||||||
// Unset
|
// Unset
|
||||||
if (!color) {
|
if (!color) {
|
||||||
// Trying to clear color on tag that doesn't have one
|
// Trying to clear color on tag that doesn't have one
|
||||||
if (!_libraryColorsByName[libraryID][name]) {
|
if (!_libraryColorsByName[libraryID].has(name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,12 +561,13 @@ Zotero.Tags = new function() {
|
||||||
var tagNames = tagColors.concat(previousTagColors).map(function (val) val.name);
|
var tagNames = tagColors.concat(previousTagColors).map(function (val) val.name);
|
||||||
tagNames = Zotero.Utilities.arrayUnique(tagNames);
|
tagNames = Zotero.Utilities.arrayUnique(tagNames);
|
||||||
if (tagNames.length) {
|
if (tagNames.length) {
|
||||||
yield Zotero.Tags.load(libraryID);
|
|
||||||
for (let i=0; i<tagNames.length; i++) {
|
for (let i=0; i<tagNames.length; i++) {
|
||||||
let tagID = this.getID(libraryID, tagNames[i]);
|
let tagID = yield this.getID(tagNames[i]);
|
||||||
// Colored tags may not exist
|
// Colored tags may not exist
|
||||||
if (tagID) {
|
if (tagID) {
|
||||||
affectedItems = affectedItems.concat(yield this.getTagItems(tagID));
|
affectedItems = affectedItems.concat(
|
||||||
|
yield this.getTagItems(libraryID, tagID)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -629,8 +584,7 @@ Zotero.Tags = new function() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield this.load(libraryID);
|
var tagID = yield this.getID(tagName);
|
||||||
var tagID = this.getID(libraryID, tagName);
|
|
||||||
|
|
||||||
// If there's a color setting but no matching tag, don't throw
|
// If there's a color setting but no matching tag, don't throw
|
||||||
// an error (though ideally this wouldn't be possible).
|
// an error (though ideally this wouldn't be possible).
|
||||||
|
@ -854,40 +808,5 @@ Zotero.Tags = new function() {
|
||||||
}
|
}
|
||||||
return cleanedData;
|
return cleanedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear cache to reload
|
|
||||||
*/
|
|
||||||
this.reload = function () {
|
|
||||||
_tagNamesByID = {};
|
|
||||||
_tagIDsByName = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.getPrimaryDataSQL = function () {
|
|
||||||
// This should be the same as the query in Zotero.Tag.load(),
|
|
||||||
// just without a specific tagID
|
|
||||||
return "SELECT * FROM tags O WHERE 1";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function _requireLoad(libraryID) {
|
|
||||||
if (!_loaded[libraryID]) {
|
|
||||||
throw new Zotero.Exception.UnloadedDataException(
|
|
||||||
"Tag data has not been loaded for library " + libraryID,
|
|
||||||
"tags"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function _cacheTag(libraryID, tagID, name) {
|
|
||||||
_tagNamesByID[tagID] = name;
|
|
||||||
if (!_tagIDsByName[libraryID]) {
|
|
||||||
_tagIDsByName[libraryID] = {};
|
|
||||||
}
|
|
||||||
_tagIDsByName[libraryID]['_' + name] = tagID;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,30 +166,23 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
Zotero.Promise.try(function () {
|
Zotero.spawn(function* () {
|
||||||
if (coloredTagsRE.test(key)) {
|
if (coloredTagsRE.test(key)) {
|
||||||
let libraryID = self.collectionTreeRow.ref.libraryID;
|
let libraryID = self.collectionTreeRow.ref.libraryID;
|
||||||
let position = parseInt(key) - 1;
|
let position = parseInt(key) - 1;
|
||||||
return Zotero.Tags.getColorByPosition(libraryID, position)
|
let colorData = yield Zotero.Tags.getColorByPosition(libraryID, position);
|
||||||
.then(function (colorData) {
|
|
||||||
// If a color isn't assigned to this number or any
|
// If a color isn't assigned to this number or any
|
||||||
// other numbers, allow key navigation
|
// other numbers, allow key navigation
|
||||||
if (!colorData) {
|
if (!colorData) {
|
||||||
return Zotero.Tags.getColors(libraryID)
|
let colors = yield Zotero.Tags.getColors(libraryID);
|
||||||
.then(function (colors) {
|
return !colors.size;
|
||||||
return !Object.keys(colors).length;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = self.getSelectedItems();
|
var items = self.getSelectedItems();
|
||||||
return Zotero.Tags.toggleItemsListTags(libraryID, items, colorData.name)
|
yield Zotero.Tags.toggleItemsListTags(libraryID, items, colorData.name);
|
||||||
.then(function () {
|
return;
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
})
|
|
||||||
// We have to disable key navigation on the tree in order to
|
// We have to disable key navigation on the tree in order to
|
||||||
// keep it from acting on the 1-6 keys used for colored tags.
|
// keep it from acting on the 1-6 keys used for colored tags.
|
||||||
// To allow navigation with other keys, we temporarily enable
|
// To allow navigation with other keys, we temporarily enable
|
||||||
|
@ -197,11 +190,6 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
|
||||||
// that will trigger this listener again, we set a flag to
|
// that will trigger this listener again, we set a flag to
|
||||||
// ignore the event, and then clear the flag above when the
|
// ignore the event, and then clear the flag above when the
|
||||||
// event comes in. I see no way this could go wrong...
|
// event comes in. I see no way this could go wrong...
|
||||||
.then(function (resend) {
|
|
||||||
if (!resend) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tree.disableKeyNavigation = false;
|
tree.disableKeyNavigation = false;
|
||||||
self._skipKeyPress = true;
|
self._skipKeyPress = true;
|
||||||
var nsIDWU = Components.interfaces.nsIDOMWindowUtils;
|
var nsIDWU = Components.interfaces.nsIDOMWindowUtils;
|
||||||
|
@ -230,10 +218,8 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
|
||||||
tree.disableKeyNavigation = true;
|
tree.disableKeyNavigation = true;
|
||||||
})
|
})
|
||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
Zotero.debug(e, 1);
|
Zotero.logError(e);
|
||||||
Components.utils.reportError(e);
|
|
||||||
})
|
})
|
||||||
.done();
|
|
||||||
};
|
};
|
||||||
// Store listener so we can call removeEventListener() in ItemTreeView.unregister()
|
// Store listener so we can call removeEventListener() in ItemTreeView.unregister()
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
|
|
|
@ -2122,11 +2122,11 @@ Zotero.Schema = new function(){
|
||||||
yield Zotero.DB.queryAsync("CREATE INDEX savedSearches_synced ON savedSearches(synced)");
|
yield Zotero.DB.queryAsync("CREATE INDEX savedSearches_synced ON savedSearches(synced)");
|
||||||
|
|
||||||
yield Zotero.DB.queryAsync("ALTER TABLE tags RENAME TO tagsOld");
|
yield Zotero.DB.queryAsync("ALTER TABLE tags RENAME TO tagsOld");
|
||||||
yield Zotero.DB.queryAsync("CREATE TABLE tags (\n tagID INTEGER PRIMARY KEY,\n libraryID INT NOT NULL,\n name TEXT NOT NULL,\n UNIQUE (libraryID, name)\n)");
|
yield Zotero.DB.queryAsync("CREATE TABLE tags (\n tagID INTEGER PRIMARY KEY,\n name TEXT NOT NULL UNIQUE\n)");
|
||||||
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO tags SELECT tagID, IFNULL(libraryID, 1), name FROM tagsOld");
|
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO tags SELECT tagID, name FROM tagsOld");
|
||||||
yield Zotero.DB.queryAsync("ALTER TABLE itemTags RENAME TO itemTagsOld");
|
yield Zotero.DB.queryAsync("ALTER TABLE itemTags RENAME TO itemTagsOld");
|
||||||
yield Zotero.DB.queryAsync("CREATE TABLE itemTags (\n itemID INT NOT NULL,\n tagID INT NOT NULL,\n type INT NOT NULL,\n PRIMARY KEY (itemID, tagID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (tagID) REFERENCES tags(tagID) ON DELETE CASCADE\n)");
|
yield Zotero.DB.queryAsync("CREATE TABLE itemTags (\n itemID INT NOT NULL,\n tagID INT NOT NULL,\n type INT NOT NULL,\n PRIMARY KEY (itemID, tagID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (tagID) REFERENCES tags(tagID) ON DELETE CASCADE\n)");
|
||||||
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemTags SELECT itemID, T.tagID, TOld.type FROM itemTagsOld ITO JOIN tagsOld TOld USING (tagID) JOIN tags T ON (IFNULL(TOld.libraryID, 1)=T.libraryID AND TOld.name=T.name COLLATE BINARY)");
|
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemTags SELECT itemID, T.tagID, TOld.type FROM itemTagsOld ITO JOIN tagsOld TOld USING (tagID) JOIN tags T ON (TOld.name=T.name COLLATE BINARY)");
|
||||||
yield Zotero.DB.queryAsync("DROP INDEX IF EXISTS itemTags_tagID");
|
yield Zotero.DB.queryAsync("DROP INDEX IF EXISTS itemTags_tagID");
|
||||||
yield Zotero.DB.queryAsync("CREATE INDEX itemTags_tagID ON itemTags(tagID)");
|
yield Zotero.DB.queryAsync("CREATE INDEX itemTags_tagID ON itemTags(tagID)");
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,7 @@ Zotero.SyncedSettings = (function () {
|
||||||
if (currentValue === false) {
|
if (currentValue === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
currentValue = JSON.parse(currentValue);
|
||||||
|
|
||||||
var id = libraryID + '/' + setting;
|
var id = libraryID + '/' + setting;
|
||||||
|
|
||||||
|
|
|
@ -2092,7 +2092,6 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||||
|
|
||||||
this.reloadDataObjects = function () {
|
this.reloadDataObjects = function () {
|
||||||
return Zotero.Promise.all([
|
return Zotero.Promise.all([
|
||||||
Zotero.Tags.reloadAll(),
|
|
||||||
Zotero.Collections.reloadAll(),
|
Zotero.Collections.reloadAll(),
|
||||||
Zotero.Creators.reloadAll(),
|
Zotero.Creators.reloadAll(),
|
||||||
Zotero.Items.reloadAll()
|
Zotero.Items.reloadAll()
|
||||||
|
|
|
@ -1056,18 +1056,13 @@ var ZoteroPane = new function()
|
||||||
|
|
||||||
function getTagSelection() {
|
function getTagSelection() {
|
||||||
var tagSelector = document.getElementById('zotero-tag-selector');
|
var tagSelector = document.getElementById('zotero-tag-selector');
|
||||||
return tagSelector.selection ? tagSelector.selection : {};
|
return tagSelector.selection ? tagSelector.selection : new Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.clearTagSelection = Zotero.Promise.coroutine(function* () {
|
this.clearTagSelection = function () {
|
||||||
if (Zotero.Utilities.isEmpty(getTagSelection())) {
|
document.getElementById('zotero-tag-selector').deselectAll();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
var tagSelector = document.getElementById('zotero-tag-selector');
|
|
||||||
yield tagSelector.clearAll();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1142,8 +1137,8 @@ var ZoteroPane = new function()
|
||||||
|
|
||||||
// XBL functions might not yet be available
|
// XBL functions might not yet be available
|
||||||
var tagSelector = document.getElementById('zotero-tag-selector');
|
var tagSelector = document.getElementById('zotero-tag-selector');
|
||||||
if (tagSelector.clearAll) {
|
if (tagSelector.deselectAll) {
|
||||||
tagSelector.clearAll();
|
tagSelector.deselectAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not necessary with seltype="cell", which calls nsITreeView::isSelectable()
|
// Not necessary with seltype="cell", which calls nsITreeView::isSelectable()
|
||||||
|
|
|
@ -142,11 +142,8 @@
|
||||||
|
|
||||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "No tags to display">
|
<!ENTITY zotero.tagSelector.noTagsToDisplay "No tags to display">
|
||||||
<!ENTITY zotero.tagSelector.loadingTags "Loading tags…">
|
<!ENTITY zotero.tagSelector.loadingTags "Loading tags…">
|
||||||
<!ENTITY zotero.tagSelector.filter "Filter:">
|
|
||||||
<!ENTITY zotero.tagSelector.showAutomatic "Show Automatic">
|
<!ENTITY zotero.tagSelector.showAutomatic "Show Automatic">
|
||||||
<!ENTITY zotero.tagSelector.displayAllInLibrary "Display All Tags in This Library">
|
<!ENTITY zotero.tagSelector.displayAllInLibrary "Display All Tags in This Library">
|
||||||
<!ENTITY zotero.tagSelector.selectVisible "Select Visible">
|
|
||||||
<!ENTITY zotero.tagSelector.clearVisible "Deselect Visible">
|
|
||||||
<!ENTITY zotero.tagSelector.clearAll "Deselect All">
|
<!ENTITY zotero.tagSelector.clearAll "Deselect All">
|
||||||
<!ENTITY zotero.tagSelector.assignColor "Assign Color…">
|
<!ENTITY zotero.tagSelector.assignColor "Assign Color…">
|
||||||
<!ENTITY zotero.tagSelector.renameTag "Rename Tag…">
|
<!ENTITY zotero.tagSelector.renameTag "Rename Tag…">
|
||||||
|
|
|
@ -6,41 +6,51 @@ groupbox
|
||||||
padding: 1px 1px 0;
|
padding: 1px 1px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tags-toggle
|
#tags-deck {
|
||||||
{
|
-moz-box-flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tags-deck > box {
|
||||||
|
-moz-box-align: center;
|
||||||
|
-moz-box-pack: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tags-box {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
align-content: flex-start;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: block; /* allow labels to wrap instead of all being in one line */
|
|
||||||
background-color: -moz-field;
|
background-color: -moz-field;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkbox
|
#tags-box button {
|
||||||
{
|
|
||||||
margin: .75em 0 .4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tags-toggle label
|
|
||||||
{
|
|
||||||
margin: .15em .05em .15em .3em !important;
|
margin: .15em .05em .15em .3em !important;
|
||||||
padding: 0 .25em 0 .25em !important;
|
padding: 0 .25em 0 .25em !important;
|
||||||
-moz-user-focus: ignore;
|
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
border: 1px solid transparent; /* always include border so height is same as zotero-clicky */
|
border: 1px solid transparent; /* always include border so height is same as zotero-clicky */
|
||||||
|
-moz-appearance: none;
|
||||||
|
-moz-padding-start: 0 !important;
|
||||||
|
-moz-padding-end: 0 !important;
|
||||||
|
-moz-user-focus: ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Visible out-of-scope tags should be grey */
|
/* Visible out-of-scope tags should be grey */
|
||||||
#tags-toggle label[inScope=false]:not([hasColor=true])
|
#tags-box button[inScope=false]:not([hasColor=true]) {
|
||||||
{
|
|
||||||
color: #666 !important;
|
color: #666 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tags-toggle label[inScope=false][hasColor=true]
|
#tags-box button[inScope=false][hasColor=true] {
|
||||||
{
|
|
||||||
opacity: .6;
|
opacity: .6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tags-toggle label[draggedOver="true"]
|
#tags-box button[draggedOver="true"] {
|
||||||
{
|
|
||||||
color: white !important;
|
color: white !important;
|
||||||
background: #666;
|
background: #666;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,9 @@ ZoteroAutoComplete.prototype.startSearch = Zotero.Promise.coroutine(function* (s
|
||||||
case 'tag':
|
case 'tag':
|
||||||
var sql = "SELECT DISTINCT name AS val, NULL AS comment FROM tags WHERE name LIKE ?";
|
var sql = "SELECT DISTINCT name AS val, NULL AS comment FROM tags WHERE name LIKE ?";
|
||||||
var sqlParams = [searchString + '%'];
|
var sqlParams = [searchString + '%'];
|
||||||
if (typeof searchParams.libraryID != 'undefined') {
|
if (searchParams.libraryID !== undefined) {
|
||||||
sql += " AND libraryID=?";
|
sql += " AND tagID IN (SELECT tagID FROM itemTags JOIN items USING (itemID) "
|
||||||
|
+ "WHERE libraryID=?)";
|
||||||
sqlParams.push(searchParams.libraryID);
|
sqlParams.push(searchParams.libraryID);
|
||||||
}
|
}
|
||||||
if (searchParams.itemID) {
|
if (searchParams.itemID) {
|
||||||
|
|
|
@ -210,24 +210,6 @@ CREATE TRIGGER fku_itemNotes
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
|
||||||
-- itemTags libraryID
|
|
||||||
DROP TRIGGER IF EXISTS fki_itemTags_libraryID;
|
|
||||||
CREATE TRIGGER fki_itemTags_libraryID
|
|
||||||
BEFORE INSERT ON itemTags
|
|
||||||
FOR EACH ROW BEGIN
|
|
||||||
SELECT RAISE(ABORT, 'insert on table "itemTags" violates foreign key constraint "fki_itemTags_libraryID"')
|
|
||||||
WHERE (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
|
|
||||||
END;
|
|
||||||
|
|
||||||
DROP TRIGGER IF EXISTS fku_itemTags_libraryID;
|
|
||||||
CREATE TRIGGER fku_itemTags_libraryID
|
|
||||||
BEFORE UPDATE ON itemTags
|
|
||||||
FOR EACH ROW BEGIN
|
|
||||||
SELECT RAISE(ABORT, 'update on table "itemTags" violates foreign key constraint "fku_itemTags_libraryID"')
|
|
||||||
WHERE (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
|
|
||||||
END;
|
|
||||||
|
|
||||||
|
|
||||||
-- Make sure tags aren't empty
|
-- Make sure tags aren't empty
|
||||||
DROP TRIGGER IF EXISTS fki_tags;
|
DROP TRIGGER IF EXISTS fki_tags;
|
||||||
CREATE TRIGGER fki_tags
|
CREATE TRIGGER fki_tags
|
||||||
|
|
|
@ -115,9 +115,7 @@ CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState);
|
||||||
|
|
||||||
CREATE TABLE tags (
|
CREATE TABLE tags (
|
||||||
tagID INTEGER PRIMARY KEY,
|
tagID INTEGER PRIMARY KEY,
|
||||||
libraryID INT NOT NULL,
|
name TEXT NOT NULL UNIQUE
|
||||||
name TEXT NOT NULL,
|
|
||||||
UNIQUE (libraryID, name)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE itemRelations (
|
CREATE TABLE itemRelations (
|
||||||
|
|
|
@ -77,7 +77,7 @@ describe("Support Functions for Unit Testing", function() {
|
||||||
|
|
||||||
let tags = data.itemWithTags.tags;
|
let tags = data.itemWithTags.tags;
|
||||||
for (let i=0; i<tags.length; i++) {
|
for (let i=0; i<tags.length; i++) {
|
||||||
let tagID = Zotero.Tags.getID(zItem.libraryID, tags[i].tag);
|
let tagID = yield Zotero.Tags.getID(tags[i].tag);
|
||||||
assert.ok(tagID, '"' + tags[i].tag + '" tag was inserted into the database');
|
assert.ok(tagID, '"' + tags[i].tag + '" tag was inserted into the database');
|
||||||
assert.ok(zItem.hasTag(tags[i].tag), '"' + tags[i].tag + '" tag was assigned to item');
|
assert.ok(zItem.hasTag(tags[i].tag), '"' + tags[i].tag + '" tag was assigned to item');
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,40 @@
|
||||||
describe("Tag Selector", function () {
|
describe("Tag Selector", function () {
|
||||||
var win, doc, collectionsView;
|
var win, doc, collectionsView;
|
||||||
|
|
||||||
|
var clearTagColors = Zotero.Promise.coroutine(function* (libraryID) {
|
||||||
|
var tagColors = yield Zotero.Tags.getColors(libraryID);
|
||||||
|
for (let name of tagColors.keys()) {
|
||||||
|
yield Zotero.Tags.setColor(libraryID, name, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getColoredTags() {
|
||||||
|
var tagSelector = doc.getElementById('zotero-tag-selector');
|
||||||
|
var tagsBox = tagSelector.id('tags-box');
|
||||||
|
var elems = tagsBox.getElementsByTagName('button');
|
||||||
|
var names = [];
|
||||||
|
for (let i = 0; i < elems.length; i++) {
|
||||||
|
if (elems[i].style.order < 0) {
|
||||||
|
names.push(elems[i].textContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRegularTags() {
|
||||||
|
var tagSelector = doc.getElementById('zotero-tag-selector');
|
||||||
|
var tagsBox = tagSelector.id('tags-box');
|
||||||
|
var elems = tagsBox.getElementsByTagName('button');
|
||||||
|
var names = [];
|
||||||
|
for (let i = 0; i < elems.length; i++) {
|
||||||
|
if (elems[i].style.order >= 0 && elems[i].style.display != 'none') {
|
||||||
|
names.push(elems[i].textContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
before(function* () {
|
before(function* () {
|
||||||
win = yield loadZoteroPane();
|
win = yield loadZoteroPane();
|
||||||
doc = win.document;
|
doc = win.document;
|
||||||
|
@ -11,6 +45,10 @@ describe("Tag Selector", function () {
|
||||||
// Wait for things to settle
|
// Wait for things to settle
|
||||||
yield Zotero.Promise.delay(100);
|
yield Zotero.Promise.delay(100);
|
||||||
});
|
});
|
||||||
|
beforeEach(function* () {
|
||||||
|
var libraryID = Zotero.Libraries.userLibraryID;
|
||||||
|
yield clearTagColors(libraryID);
|
||||||
|
})
|
||||||
after(function () {
|
after(function () {
|
||||||
win.close();
|
win.close();
|
||||||
});
|
});
|
||||||
|
@ -27,7 +65,7 @@ describe("Tag Selector", function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("#notify()", function () {
|
describe("#notify()", function () {
|
||||||
it("should add a tag when added to an item in the current view", function* () {
|
it("should add a tag when added to an item in the library root", function* () {
|
||||||
var promise, tagSelector;
|
var promise, tagSelector;
|
||||||
|
|
||||||
if (collectionsView.selection.currentIndex != 0) {
|
if (collectionsView.selection.currentIndex != 0) {
|
||||||
|
@ -41,6 +79,10 @@ describe("Tag Selector", function () {
|
||||||
item.setTags([
|
item.setTags([
|
||||||
{
|
{
|
||||||
tag: 'A'
|
tag: 'A'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'B',
|
||||||
|
type: 1
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
promise = waitForTagSelector();
|
promise = waitForTagSelector();
|
||||||
|
@ -48,8 +90,11 @@ describe("Tag Selector", function () {
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
// Tag selector should have at least one tag
|
// Tag selector should have at least one tag
|
||||||
tagSelector = doc.getElementById('zotero-tag-selector');
|
assert.isAbove(getRegularTags().length, 1);
|
||||||
assert.isAbove(tagSelector.getVisible().length, 0);
|
});
|
||||||
|
|
||||||
|
it("should add a tag when an item is added in a collection", function* () {
|
||||||
|
var promise, tagSelector;
|
||||||
|
|
||||||
// Add collection
|
// Add collection
|
||||||
promise = waitForTagSelector();
|
promise = waitForTagSelector();
|
||||||
|
@ -57,14 +102,13 @@ describe("Tag Selector", function () {
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
// Tag selector should be empty in new collection
|
// Tag selector should be empty in new collection
|
||||||
tagSelector = doc.getElementById('zotero-tag-selector');
|
assert.equal(getRegularTags().length, 0);
|
||||||
assert.equal(tagSelector.getVisible().length, 0);
|
|
||||||
|
|
||||||
// Add item with tag to collection
|
// Add item with tag to collection
|
||||||
var item = createUnsavedDataObject('item');
|
var item = createUnsavedDataObject('item');
|
||||||
item.setTags([
|
item.setTags([
|
||||||
{
|
{
|
||||||
tag: 'B'
|
tag: 'C'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
item.setCollections([collection.id]);
|
item.setCollections([collection.id]);
|
||||||
|
@ -72,9 +116,80 @@ describe("Tag Selector", function () {
|
||||||
yield item.saveTx();
|
yield item.saveTx();
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
|
// Tag selector should show the new item's tag
|
||||||
|
assert.equal(getRegularTags().length, 1);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add a tag when an item is added to a collection", function* () {
|
||||||
|
var promise, tagSelector;
|
||||||
|
|
||||||
|
// Add collection
|
||||||
|
promise = waitForTagSelector();
|
||||||
|
var collection = yield createDataObject('collection');
|
||||||
|
yield promise;
|
||||||
|
|
||||||
|
// Tag selector should be empty in new collection
|
||||||
|
assert.equal(getRegularTags().length, 0);
|
||||||
|
|
||||||
|
// Add item with tag to library root
|
||||||
|
var item = createUnsavedDataObject('item');
|
||||||
|
item.setTags([
|
||||||
|
{
|
||||||
|
tag: 'C'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
promise = waitForTagSelector()
|
||||||
|
yield item.saveTx();
|
||||||
|
yield promise;
|
||||||
|
|
||||||
|
// Tag selector should still be empty in collection
|
||||||
|
assert.equal(getRegularTags().length, 0);
|
||||||
|
|
||||||
|
item.setCollections([collection.id]);
|
||||||
|
promise = waitForTagSelector();
|
||||||
|
yield item.saveTx();
|
||||||
|
yield promise;
|
||||||
|
|
||||||
// Tag selector should show the new item's tag
|
// Tag selector should show the new item's tag
|
||||||
tagSelector = doc.getElementById('zotero-tag-selector');
|
tagSelector = doc.getElementById('zotero-tag-selector');
|
||||||
assert.equal(tagSelector.getVisible().length, 1);
|
assert.equal(getRegularTags().length, 1);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shouldn't re-insert a new tag that matches an existing color", function* () {
|
||||||
|
var libraryID = Zotero.Libraries.userLibraryID;
|
||||||
|
|
||||||
|
/*// Remove all tags in library
|
||||||
|
var tags = yield Zotero.Tags.getAll(libraryID);
|
||||||
|
tags.forEach(function (tag) {
|
||||||
|
var tagID = yield Zotero.Tags.getID(tag);
|
||||||
|
yield Zotero.Tags.removeFromLibrary(libraryID, tagID);
|
||||||
|
});*/
|
||||||
|
|
||||||
|
// Add B and A as colored tags without any items
|
||||||
|
yield Zotero.Tags.setColor(libraryID, "B", '#990000');
|
||||||
|
yield Zotero.Tags.setColor(libraryID, "A", '#CC9933');
|
||||||
|
|
||||||
|
// Add A to an item to make it a real tag
|
||||||
|
var item = createUnsavedDataObject('item');
|
||||||
|
item.setTags([
|
||||||
|
{
|
||||||
|
tag: "A"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
var promise = waitForTagSelector();
|
||||||
|
yield item.saveTx();
|
||||||
|
yield promise;
|
||||||
|
|
||||||
|
var tagSelector = doc.getElementById('zotero-tag-selector');
|
||||||
|
var tagElems = tagSelector.id('tags-box').childNodes;
|
||||||
|
|
||||||
|
// Make sure the colored tags are still in the right position
|
||||||
|
var tags = new Map();
|
||||||
|
for (let i = 0; i < tagElems.length; i++) {
|
||||||
|
tags.set(tagElems[i].textContent, tagElems[i].style.order);
|
||||||
|
}
|
||||||
|
assert.isBelow(parseInt(tags.get("B")), 0);
|
||||||
|
assert.isBelow(parseInt(tags.get("B")), parseInt(tags.get("A")));
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should remove a tag when an item is removed from a collection", function* () {
|
it("should remove a tag when an item is removed from a collection", function* () {
|
||||||
|
@ -91,13 +206,12 @@ describe("Tag Selector", function () {
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
item.setCollections([collection.id]);
|
item.setCollections([collection.id]);
|
||||||
promise = waitForTagSelector()
|
promise = waitForTagSelector();
|
||||||
yield item.saveTx();
|
yield item.saveTx();
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
// Tag selector should show the new item's tag
|
// Tag selector should show the new item's tag
|
||||||
var tagSelector = doc.getElementById('zotero-tag-selector');
|
assert.equal(getRegularTags().length, 1);
|
||||||
assert.equal(tagSelector.getVisible().length, 1);
|
|
||||||
|
|
||||||
item.setCollections();
|
item.setCollections();
|
||||||
promise = waitForTagSelector();
|
promise = waitForTagSelector();
|
||||||
|
@ -105,8 +219,7 @@ describe("Tag Selector", function () {
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
// Tag selector shouldn't show the removed item's tag
|
// Tag selector shouldn't show the removed item's tag
|
||||||
tagSelector = doc.getElementById('zotero-tag-selector');
|
assert.equal(getRegularTags().length, 0);
|
||||||
assert.equal(tagSelector.getVisible().length, 0);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should remove a tag when an item in a collection is moved to the trash", function* () {
|
it("should remove a tag when an item in a collection is moved to the trash", function* () {
|
||||||
|
@ -128,8 +241,7 @@ describe("Tag Selector", function () {
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
// Tag selector should show the new item's tag
|
// Tag selector should show the new item's tag
|
||||||
var tagSelector = doc.getElementById('zotero-tag-selector');
|
assert.equal(getRegularTags().length, 1);
|
||||||
assert.equal(tagSelector.getVisible().length, 1);
|
|
||||||
|
|
||||||
// Move item to trash
|
// Move item to trash
|
||||||
item.deleted = true;
|
item.deleted = true;
|
||||||
|
@ -138,8 +250,96 @@ describe("Tag Selector", function () {
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
// Tag selector shouldn't show the deleted item's tag
|
// Tag selector shouldn't show the deleted item's tag
|
||||||
tagSelector = doc.getElementById('zotero-tag-selector');
|
assert.equal(getRegularTags().length, 0);
|
||||||
assert.equal(tagSelector.getVisible().length, 0);
|
})
|
||||||
|
|
||||||
|
it("should remove a tag when a tag is deleted for a library", function* () {
|
||||||
|
yield selectLibrary(win);
|
||||||
|
|
||||||
|
var item = createUnsavedDataObject('item');
|
||||||
|
item.setTags([
|
||||||
|
{
|
||||||
|
tag: 'A'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
var promise = waitForTagSelector();
|
||||||
|
yield item.saveTx();
|
||||||
|
yield promise;
|
||||||
|
|
||||||
|
// Tag selector should show the new item's tag
|
||||||
|
assert.include(getRegularTags(), "A");
|
||||||
|
|
||||||
|
// Remove tag from library
|
||||||
|
promise = waitForTagSelector();
|
||||||
|
var dialogPromise = waitForDialog();
|
||||||
|
var tagSelector = doc.getElementById('zotero-tag-selector');
|
||||||
|
yield tagSelector.delete("A");
|
||||||
|
yield promise;
|
||||||
|
|
||||||
|
// Tag selector shouldn't show the deleted item's tag
|
||||||
|
assert.notInclude(getRegularTags(), "A");
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("#rename()", function () {
|
||||||
|
it("should rename a tag and update the tag selector", function* () {
|
||||||
|
yield selectLibrary(win);
|
||||||
|
|
||||||
|
var tag = Zotero.Utilities.randomString();
|
||||||
|
var newTag = Zotero.Utilities.randomString();
|
||||||
|
var item = createUnsavedDataObject('item');
|
||||||
|
item.setTags([
|
||||||
|
{
|
||||||
|
tag: tag
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
var promise = waitForTagSelector();
|
||||||
|
yield item.saveTx();
|
||||||
|
yield promise;
|
||||||
|
|
||||||
|
var tagSelector = doc.getElementById('zotero-tag-selector');
|
||||||
|
promise = waitForTagSelector();
|
||||||
|
var promptPromise = waitForWindow("chrome://global/content/commonDialog.xul", function (dialog) {
|
||||||
|
dialog.document.getElementById('loginTextbox').value = newTag;
|
||||||
|
dialog.document.documentElement.acceptDialog();
|
||||||
|
})
|
||||||
|
yield tagSelector.rename(tag);
|
||||||
|
yield promise;
|
||||||
|
|
||||||
|
var tags = getRegularTags();
|
||||||
|
assert.include(tags, newTag);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#_openColorPickerWindow()", function () {
|
||||||
|
it("should assign a color to a tag", function* () {
|
||||||
|
yield selectLibrary(win);
|
||||||
|
var tag = "b " + Zotero.Utilities.randomString();
|
||||||
|
var item = createUnsavedDataObject('item');
|
||||||
|
item.setTags([
|
||||||
|
{
|
||||||
|
tag: "a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: tag
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
var promise = waitForTagSelector();
|
||||||
|
yield item.saveTx();
|
||||||
|
yield promise;
|
||||||
|
|
||||||
|
var tagSelector = doc.getElementById('zotero-tag-selector');
|
||||||
|
|
||||||
|
assert.include(getRegularTags(), "a");
|
||||||
|
|
||||||
|
var dialogPromise = waitForDialog(false, undefined, 'chrome://zotero/content/tagColorChooser.xul');
|
||||||
|
var tagSelectorPromise = waitForTagSelector();
|
||||||
|
yield tagSelector._openColorPickerWindow(tag);
|
||||||
|
yield dialogPromise;
|
||||||
|
yield tagSelectorPromise;
|
||||||
|
|
||||||
|
assert.include(getColoredTags(), tag);
|
||||||
|
assert.notInclude(getRegularTags(), tag);
|
||||||
|
})
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,7 @@ describe("Zotero.Tags", function () {
|
||||||
item.addTag(tagName);
|
item.addTag(tagName);
|
||||||
yield item.saveTx();
|
yield item.saveTx();
|
||||||
|
|
||||||
assert.typeOf(Zotero.Tags.getID(Zotero.Libraries.userLibraryID, tagName), "number");
|
assert.typeOf((yield Zotero.Tags.getID(tagName)), "number");
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -19,32 +19,49 @@ describe("Zotero.Tags", function () {
|
||||||
item.addTag(tagName);
|
item.addTag(tagName);
|
||||||
yield item.saveTx();
|
yield item.saveTx();
|
||||||
|
|
||||||
var tagID = Zotero.Tags.getID(Zotero.Libraries.userLibraryID, tagName);
|
var libraryID = Zotero.Libraries.userLibraryID;
|
||||||
assert.equal(Zotero.Tags.getName(Zotero.Libraries.userLibraryID, tagID), tagName);
|
var tagID = yield Zotero.Tags.getID(tagName);
|
||||||
|
assert.equal((yield Zotero.Tags.getName(tagID)), tagName);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#removeFromLibrary()", function () {
|
||||||
|
it("should reload tags of associated items", function* () {
|
||||||
|
var libraryID = Zotero.Libraries.userLibraryID;
|
||||||
|
|
||||||
|
var tagName = Zotero.Utilities.randomString();
|
||||||
|
var item = createUnsavedDataObject('item');
|
||||||
|
item.addTag(tagName);
|
||||||
|
yield item.saveTx();
|
||||||
|
assert.lengthOf(item.getTags(), 1);
|
||||||
|
|
||||||
|
var tagID = yield Zotero.Tags.getID(tagName);
|
||||||
|
yield Zotero.Tags.removeFromLibrary(libraryID, tagID);
|
||||||
|
assert.lengthOf(item.getTags(), 0);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#purge()", function () {
|
describe("#purge()", function () {
|
||||||
it("should remove orphaned tags", function* () {
|
it("should remove orphaned tags", function* () {
|
||||||
var libraryID = Zotero.Libraries.userLibraryID;
|
var libraryID = Zotero.Libraries.userLibraryID;
|
||||||
|
|
||||||
var tagName = Zotero.Utilities.randomString();
|
var tagName = Zotero.Utilities.randomString();
|
||||||
var item = createUnsavedDataObject('item');
|
var item = createUnsavedDataObject('item');
|
||||||
item.addTag(tagName);
|
item.addTag(tagName);
|
||||||
yield item.saveTx();
|
yield item.saveTx();
|
||||||
|
|
||||||
var tagID = Zotero.Tags.getID(libraryID, tagName);
|
var tagID = yield Zotero.Tags.getID(tagName);
|
||||||
assert.typeOf(tagID, "number");
|
assert.typeOf(tagID, "number");
|
||||||
|
|
||||||
yield item.eraseTx();
|
yield item.eraseTx();
|
||||||
|
|
||||||
assert.equal(Zotero.Tags.getName(libraryID, tagID), tagName);
|
assert.equal((yield Zotero.Tags.getName(tagID)), tagName);
|
||||||
|
|
||||||
yield Zotero.DB.executeTransaction(function* () {
|
yield Zotero.DB.executeTransaction(function* () {
|
||||||
yield Zotero.Tags.purge();
|
yield Zotero.Tags.purge();
|
||||||
});
|
});
|
||||||
|
|
||||||
yield Zotero.Tags.load(libraryID);
|
assert.isFalse(yield Zotero.Tags.getName(tagID));
|
||||||
assert.isFalse(Zotero.Tags.getName(libraryID, tagID));
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
125
test/tests/tagsboxTest.js
Normal file
125
test/tests/tagsboxTest.js
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("Item Tags Box", function () {
|
||||||
|
var win, doc, collectionsView;
|
||||||
|
|
||||||
|
before(function* () {
|
||||||
|
win = yield loadZoteroPane();
|
||||||
|
doc = win.document;
|
||||||
|
|
||||||
|
// Wait for things to settle
|
||||||
|
yield Zotero.Promise.delay(100);
|
||||||
|
});
|
||||||
|
after(function () {
|
||||||
|
win.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
function waitForTagsBox() {
|
||||||
|
var deferred = Zotero.Promise.defer();
|
||||||
|
var tagsbox = doc.getElementById('zotero-editpane-tags');
|
||||||
|
var onRefresh = function (event) {
|
||||||
|
tagsbox.removeEventListener('refresh', onRefresh);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
tagsbox.addEventListener('refresh', onRefresh);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("#notify()", function () {
|
||||||
|
it("should update an existing tag on rename", function* () {
|
||||||
|
var tag = Zotero.Utilities.randomString();
|
||||||
|
var newTag = Zotero.Utilities.randomString();
|
||||||
|
|
||||||
|
var tabbox = doc.getElementById('zotero-view-tabbox');
|
||||||
|
tabbox.selectedIndex = 0;
|
||||||
|
|
||||||
|
var item = createUnsavedDataObject('item');
|
||||||
|
item.setTags([
|
||||||
|
{
|
||||||
|
tag: tag
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
yield item.saveTx();
|
||||||
|
|
||||||
|
var tabbox = doc.getElementById('zotero-view-tabbox');
|
||||||
|
tabbox.selectedIndex = 2;
|
||||||
|
yield waitForTagsBox();
|
||||||
|
var tagsbox = doc.getElementById('zotero-editpane-tags');
|
||||||
|
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
||||||
|
assert.equal(rows.length, 1);
|
||||||
|
assert.equal(rows[0].textContent, tag);
|
||||||
|
|
||||||
|
yield Zotero.Tags.rename(Zotero.Libraries.userLibraryID, tag, newTag);
|
||||||
|
|
||||||
|
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
||||||
|
assert.equal(rows.length, 1);
|
||||||
|
assert.equal(rows[0].textContent, newTag);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update when a tag's color is removed", function* () {
|
||||||
|
var libraryID = Zotero.Libraries.userLibraryID;
|
||||||
|
|
||||||
|
var tag = Zotero.Utilities.randomString();
|
||||||
|
var tabbox = doc.getElementById('zotero-view-tabbox');
|
||||||
|
tabbox.selectedIndex = 0;
|
||||||
|
|
||||||
|
yield Zotero.Tags.setColor(libraryID, tag, "#990000");
|
||||||
|
var item = createUnsavedDataObject('item');
|
||||||
|
item.setTags([
|
||||||
|
{
|
||||||
|
tag: tag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "_A"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
yield item.saveTx();
|
||||||
|
|
||||||
|
var tabbox = doc.getElementById('zotero-view-tabbox');
|
||||||
|
tabbox.selectedIndex = 2;
|
||||||
|
yield waitForTagsBox();
|
||||||
|
var tagsbox = doc.getElementById('zotero-editpane-tags');
|
||||||
|
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
||||||
|
|
||||||
|
// Colored tags aren't sorted first, for now
|
||||||
|
assert.notOk(rows[0].getElementsByTagName('label')[0].style.color);
|
||||||
|
assert.ok(rows[1].getElementsByTagName('label')[0].style.color);
|
||||||
|
assert.equal(rows[0].textContent, "_A");
|
||||||
|
assert.equal(rows[1].textContent, tag);
|
||||||
|
|
||||||
|
yield Zotero.Tags.setColor(libraryID, tag, false);
|
||||||
|
|
||||||
|
assert.notOk(rows[1].getElementsByTagName('label')[0].style.color);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update when a tag is removed from the library", function* () {
|
||||||
|
var tag = Zotero.Utilities.randomString();
|
||||||
|
|
||||||
|
var tabbox = doc.getElementById('zotero-view-tabbox');
|
||||||
|
tabbox.selectedIndex = 0;
|
||||||
|
|
||||||
|
var item = createUnsavedDataObject('item');
|
||||||
|
item.setTags([
|
||||||
|
{
|
||||||
|
tag: tag
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
yield item.saveTx();
|
||||||
|
|
||||||
|
var tabbox = doc.getElementById('zotero-view-tabbox');
|
||||||
|
tabbox.selectedIndex = 2;
|
||||||
|
yield waitForTagsBox();
|
||||||
|
var tagsbox = doc.getElementById('zotero-editpane-tags');
|
||||||
|
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
||||||
|
assert.equal(rows.length, 1);
|
||||||
|
assert.equal(rows[0].textContent, tag);
|
||||||
|
|
||||||
|
yield Zotero.Tags.removeFromLibrary(
|
||||||
|
Zotero.Libraries.userLibraryID, (yield Zotero.Tags.getID(tag))
|
||||||
|
);
|
||||||
|
|
||||||
|
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
||||||
|
assert.equal(rows.length, 0);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue