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._notifierID = Zotero.Notifier.registerObserver(this, ['item-tag', 'setting']);
|
||||
this._notifierID = Zotero.Notifier.registerObserver(
|
||||
this, ['item-tag', 'setting'], 'tagsbox'
|
||||
);
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
|
@ -134,73 +136,72 @@
|
|||
<parameter name="type"/>
|
||||
<parameter name="ids"/>
|
||||
<parameter name="extraData"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (type == 'setting') {
|
||||
if (ids.some(function (val) val.split("/")[1] == 'tagColors') && this.item) {
|
||||
this.reload();
|
||||
<body><![CDATA[
|
||||
return Zotero.spawn(function* () {
|
||||
if (type == 'setting') {
|
||||
if (ids.some(function (val) val.split("/")[1] == 'tagColors') && this.item) {
|
||||
return this.reload();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (type == 'item-tag') {
|
||||
let itemID, tagName;
|
||||
|
||||
for (let i=0; i<ids.length; i++) {
|
||||
[itemID, tagName] = ids[i].match(/^([0-9]+)-(.+)/).slice(1);
|
||||
if (!this.item || itemID != this.item.id) {
|
||||
continue;
|
||||
else if (type == 'item-tag') {
|
||||
let itemID, tagID;
|
||||
|
||||
for (let i=0; i<ids.length; i++) {
|
||||
[itemID, tagID] = ids[i].split('-').map(x => parseInt(x));
|
||||
if (!this.item || itemID != this.item.id) {
|
||||
continue;
|
||||
}
|
||||
let data = extraData[ids[i]];
|
||||
let tagName = data.tag;
|
||||
|
||||
if (event == 'add') {
|
||||
var newTabIndex = this.add(tagName);
|
||||
if (newTabIndex == -1) {
|
||||
return;
|
||||
}
|
||||
if (this._tabDirection == -1) {
|
||||
if (this._lastTabIndex > newTabIndex) {
|
||||
this._lastTabIndex++;
|
||||
}
|
||||
}
|
||||
else if (this._tabDirection == 1) {
|
||||
if (this._lastTabIndex > newTabIndex) {
|
||||
this._lastTabIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event == 'modify') {
|
||||
let oldTagName = data.old.tag;
|
||||
this.remove(oldTagName);
|
||||
this.add(tagName);
|
||||
}
|
||||
else if (event == 'remove') {
|
||||
var oldTabIndex = this.remove(tagName);
|
||||
if (oldTabIndex == -1) {
|
||||
return;
|
||||
}
|
||||
if (this._tabDirection == -1) {
|
||||
if (this._lastTabIndex > oldTabIndex) {
|
||||
this._lastTabIndex--;
|
||||
}
|
||||
}
|
||||
else if (this._tabDirection == 1) {
|
||||
if (this._lastTabIndex >= oldTabIndex) {
|
||||
this._lastTabIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event == 'add') {
|
||||
var newTabIndex = this.add(tagName);
|
||||
if (newTabIndex == -1) {
|
||||
return;
|
||||
}
|
||||
if (this._tabDirection == -1) {
|
||||
if (this._lastTabIndex > newTabIndex) {
|
||||
this._lastTabIndex++;
|
||||
}
|
||||
}
|
||||
else if (this._tabDirection == 1) {
|
||||
if (this._lastTabIndex > newTabIndex) {
|
||||
this._lastTabIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event == 'modify') {
|
||||
Zotero.debug("EXTRA");
|
||||
Zotero.debug(extraData);
|
||||
let oldTagName = extraData[tagName].old.tag;
|
||||
this.remove(oldTagName);
|
||||
this.add(tagName);
|
||||
}
|
||||
else if (event == 'remove') {
|
||||
var oldTabIndex = this.remove(tagName);
|
||||
if (oldTabIndex == -1) {
|
||||
return;
|
||||
}
|
||||
if (this._tabDirection == -1) {
|
||||
if (this._lastTabIndex > oldTabIndex) {
|
||||
this._lastTabIndex--;
|
||||
}
|
||||
}
|
||||
else if (this._tabDirection == 1) {
|
||||
if (this._lastTabIndex >= oldTabIndex) {
|
||||
this._lastTabIndex--;
|
||||
}
|
||||
}
|
||||
this.updateCount();
|
||||
}
|
||||
else if (type == 'tag') {
|
||||
if (event == 'modify') {
|
||||
return this.reload();
|
||||
}
|
||||
}
|
||||
|
||||
this.updateCount();
|
||||
}
|
||||
else if (type == 'tag') {
|
||||
if (event == 'modify') {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
}.bind(this));
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
||||
|
@ -235,6 +236,9 @@
|
|||
|
||||
this._reloading = false;
|
||||
this._focusField();
|
||||
|
||||
var event = new Event('refresh');
|
||||
this.dispatchEvent(event);
|
||||
}, this);
|
||||
]]></body>
|
||||
</method>
|
||||
|
@ -388,13 +392,10 @@
|
|||
}
|
||||
|
||||
// Tag color
|
||||
let color = this._tagColors[valueText];
|
||||
if (color) {
|
||||
valueElement.setAttribute(
|
||||
'style',
|
||||
'color:' + this._tagColors[valueText].color + '; '
|
||||
+ 'font-weight: bold'
|
||||
);
|
||||
var colorData = this._tagColors.get(valueText);
|
||||
if (colorData) {
|
||||
valueElement.style.color = colorData.color;
|
||||
valueElement.style.fontWeight = 'bold';
|
||||
}
|
||||
|
||||
return valueElement;
|
||||
|
@ -522,12 +523,10 @@
|
|||
yield this.blurHandler(target);
|
||||
|
||||
if (focusField) {
|
||||
Zotero.debug("FOCUSING FIELD");
|
||||
this._focusField();
|
||||
}
|
||||
// Return focus to items pane
|
||||
else {
|
||||
Zotero.debug("FOCUSING ITEM PANE");
|
||||
var tree = document.getElementById('zotero-items-tree');
|
||||
if (tree) {
|
||||
tree.focus();
|
||||
|
@ -757,8 +756,6 @@
|
|||
<method name="add">
|
||||
<parameter name="tagName"/>
|
||||
<body><![CDATA[
|
||||
Zotero.debug("ADDING ROW WITH " + tagName);
|
||||
|
||||
var rowsElement = this.id('tagRows');
|
||||
var rows = rowsElement.childNodes;
|
||||
|
||||
|
@ -766,7 +763,6 @@
|
|||
var row = false;
|
||||
for (let i=0; i<rows.length; i++) {
|
||||
if (rows[i].getAttribute('tagName') === tagName) {
|
||||
Zotero.debug("FOUND ROW with " + tagName);
|
||||
return rows[i].getAttribute('ztabindex');
|
||||
}
|
||||
}
|
||||
|
@ -835,18 +831,12 @@
|
|||
<method name="remove">
|
||||
<parameter name="tagName"/>
|
||||
<body><![CDATA[
|
||||
Zotero.debug("REMOVING ROW WITH " + tagName);
|
||||
|
||||
var rowsElement = this.id('tagRows');
|
||||
var rows = rowsElement.childNodes;
|
||||
var removed = false;
|
||||
var oldTabIndex = -1;
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
let value = rows[i].getAttribute('tagName');
|
||||
Zotero.debug("-=-=");
|
||||
Zotero.debug(value);
|
||||
Zotero.debug(tagName);
|
||||
Zotero.debug(value === tagName);
|
||||
if (value === tagName) {
|
||||
oldTabIndex = i + 1;
|
||||
removed = true;
|
||||
|
@ -1010,9 +1000,7 @@
|
|||
<method name="scrollToTop">
|
||||
<body>
|
||||
<![CDATA[
|
||||
Zotero.debug('SCROLL TO TOP');
|
||||
if (!this._activeScrollbox) {
|
||||
Zotero.debug('NO');
|
||||
return;
|
||||
}
|
||||
var sbo = this._activeScrollbox.boxObject;
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
<field name="_initialized">false</field>
|
||||
<field name="_notifierID">false</field>
|
||||
<field name="_tags">null</field>
|
||||
<field name="_popupNode"/>
|
||||
<field name="_dirty">null</field>
|
||||
<field name="_emptyColored">null</field>
|
||||
<field name="_emptyRegular">null</field>
|
||||
|
||||
<!-- Modes are predefined settings groups for particular tasks -->
|
||||
|
@ -173,7 +173,7 @@
|
|||
<body>
|
||||
<![CDATA[
|
||||
this._initialized = true;
|
||||
this.selection = {};
|
||||
this.selection = new Set();
|
||||
this._notifierID = Zotero.Notifier.registerObserver(
|
||||
this,
|
||||
['collection-item', 'item', 'item-tag', 'tag', 'setting'],
|
||||
|
@ -193,7 +193,7 @@
|
|||
|
||||
this._initialized = false;
|
||||
this.unregister();
|
||||
this.selection = {};
|
||||
this.selection = new Set();
|
||||
if (this.onchange) {
|
||||
this.onchange();
|
||||
}
|
||||
|
@ -217,10 +217,16 @@
|
|||
<parameter name="fetch"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
Zotero.spawn(function* () {
|
||||
Zotero.debug('Refreshing tags selector');
|
||||
return Zotero.spawn(function* () {
|
||||
var t = new Date;
|
||||
|
||||
if (fetch || this._dirty) {
|
||||
Zotero.debug('Reloading tags selector');
|
||||
}
|
||||
else {
|
||||
Zotero.debug('Refreshing tags selector');
|
||||
}
|
||||
|
||||
if (!this._initialized) {
|
||||
this.init();
|
||||
fetch = true;
|
||||
|
@ -228,49 +234,26 @@
|
|||
|
||||
var emptyColored = true;
|
||||
var emptyRegular = true;
|
||||
var tagsToggleBox = this.id('tags-toggle');
|
||||
var tagsBox = this.id('tags-box');
|
||||
|
||||
var tagColors = yield Zotero.Tags.getColors(this.libraryID)
|
||||
.tap(() => Zotero.Promise.check(this.mode));
|
||||
|
||||
// If new data, rebuild boxes
|
||||
if (fetch || this._dirty) {
|
||||
this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types)
|
||||
.tap(() => Zotero.Promise.check(this.mode));
|
||||
|
||||
// Remove children
|
||||
tagsToggleBox.textContent = "";
|
||||
tagsBox.textContent = "";
|
||||
|
||||
// Sort by name
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
var orderedTags = this._tags.concat();
|
||||
orderedTags.sort(function(a, b) {
|
||||
let collation = Zotero.getLocaleCollation();
|
||||
this._tags.sort(function (a, b) {
|
||||
return collation.compareString(1, a.tag, b.tag);
|
||||
});
|
||||
|
||||
var tagColorsLowerCase = {};
|
||||
var colorTags = [];
|
||||
for (let name in tagColors) {
|
||||
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;
|
||||
}
|
||||
let lastTag;
|
||||
for (let i = 0; i < this._tags.length; i++) {
|
||||
let tagData = this._tags[i];
|
||||
|
||||
// Only show tags of different types once
|
||||
if (tagData.tag === lastTag) {
|
||||
|
@ -278,108 +261,35 @@
|
|||
}
|
||||
lastTag = tagData.tag;
|
||||
|
||||
let tagButton = this._makeClickableTag(tagData, this.editable);
|
||||
if (tagButton) {
|
||||
var self = this;
|
||||
tagButton.addEventListener('click', function(event) {
|
||||
self.handleTagClick(event, this);
|
||||
});
|
||||
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);
|
||||
let elem = this._insertClickableTag(tagsBox, tagData);
|
||||
let visible = this._updateClickableTag(
|
||||
elem, tagData.tag, tagColors
|
||||
);
|
||||
if (visible) {
|
||||
emptyRegular = false;
|
||||
}
|
||||
}
|
||||
this._dirty = false;
|
||||
}
|
||||
|
||||
// Set attributes
|
||||
var colorTags = {};
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
for (let i=0; i<labels.length; i++) {
|
||||
let name = labels[i].value;
|
||||
let lcname = name.toLowerCase();
|
||||
|
||||
let colorData = tagColors[name];
|
||||
if (colorData) {
|
||||
labels[i].setAttribute(
|
||||
'style', 'color:' + colorData.color + '; ' + 'font-weight: bold'
|
||||
// Otherwise just update based on visibility
|
||||
else {
|
||||
elems = tagsBox.childNodes;
|
||||
for (let i = 0; i < elems.length; i++) {
|
||||
let elem = elems[i];
|
||||
let visible = this._updateClickableTag(
|
||||
elem, elem.textContent, tagColors
|
||||
);
|
||||
}
|
||||
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);
|
||||
if (visible) {
|
||||
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
|
||||
|
||||
var tagCloud = Zotero.Prefs.get('tagCloud');
|
||||
|
||||
if(tagCloud) {
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
if (false && tagCloud) {
|
||||
var labels = tagsBox.getElementsByTagName('label');
|
||||
|
||||
//loop through displayed labels and find number of linked items
|
||||
var numlinked= [];
|
||||
|
@ -444,20 +354,15 @@
|
|||
//end tag cloud code
|
||||
|
||||
this.updateNumSelected();
|
||||
this._emptyColored = emptyColored;
|
||||
this._emptyRegular = emptyRegular;
|
||||
var empty = emptyColored && emptyRegular;
|
||||
this.id('tags-toggle').setAttribute('collapsed', empty);
|
||||
this.id('no-tags-box').setAttribute('collapsed', !empty);
|
||||
var empty = this._emptyRegular = emptyRegular;
|
||||
// TODO: Show loading again when switching libraries/collections?
|
||||
this.id('tags-deck').selectedIndex = empty ? 1 : 2;
|
||||
|
||||
if (this.onRefresh) {
|
||||
this.onRefresh();
|
||||
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");
|
||||
|
||||
var event = new Event('refresh');
|
||||
|
@ -467,34 +372,56 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="getVisible">
|
||||
<method name="insertSorted">
|
||||
<parameter name="tagObjs"/>
|
||||
<body><![CDATA[
|
||||
var tagsBox = this.id('tags-toggle');
|
||||
var labels = tagsBox.getElementsByTagName('label');
|
||||
var visible = [];
|
||||
for (let i = 0; i < labels.length; i++){
|
||||
let label = labels[i];
|
||||
if (label.getAttribute('hidden') != 'true'
|
||||
&& label.getAttribute('inScope') == 'true') {
|
||||
visible.push(label.value);
|
||||
return Zotero.spawn(function* () {
|
||||
var tagColors = yield Zotero.Tags.getColors(this._libraryID);
|
||||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
tagObjs.sort(function (a, b) {
|
||||
return collation.compareString(1, a.tag, b.tag);
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (comp < 0) {
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
this._insertClickableTag(tagsBox, tagObj, tagElems[j]);
|
||||
this._updateClickableTag(
|
||||
tagElems[j], tagElems[j].textContent, tagColors
|
||||
);
|
||||
}
|
||||
}
|
||||
return visible;
|
||||
}, this);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="getNumSelected">
|
||||
<body>
|
||||
<![CDATA[
|
||||
var count = 0;
|
||||
for (var i in this.selection) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
]]>
|
||||
</body>
|
||||
<body><![CDATA[
|
||||
return this.selection.size;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
||||
|
@ -525,11 +452,12 @@
|
|||
<parameter name="event"/>
|
||||
<parameter name="type"/>
|
||||
<parameter name="ids"/>
|
||||
<parameter name="extraData"/>
|
||||
<body><![CDATA[
|
||||
return Zotero.spawn(function* () {
|
||||
if (type == 'setting') {
|
||||
if (ids.some(function (val) val.split("/")[1] == 'tagColors')) {
|
||||
this.refresh(true);
|
||||
yield this.refresh(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -558,7 +486,7 @@
|
|||
// TODO: necessary, or just use notifier value?
|
||||
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) {
|
||||
if (tag == tag2) {
|
||||
var found = true;
|
||||
|
@ -566,14 +494,32 @@
|
|||
}
|
||||
}
|
||||
if (!found) {
|
||||
delete this.selection[tag];
|
||||
this.selection.delete(tag);
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This could be more optimized to insert new/changed tags at the appropriate
|
||||
// spot if we cared, but we probably don't
|
||||
if (event == 'add') {
|
||||
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;
|
||||
if (t.value) {
|
||||
this.setSearch(t.value, true);
|
||||
|
@ -610,34 +556,19 @@
|
|||
</method>
|
||||
|
||||
|
||||
<!-- Not currently used -->
|
||||
<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">
|
||||
<method name="deselectAll">
|
||||
<body><![CDATA[
|
||||
var tagsToggleBox = this.id('tags-toggle');
|
||||
if (!this.selection || !this.selection.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
var labels = Zotero.Utilities.xpath(tagsToggleBox, 'label[@selected="true"]');
|
||||
for (var i=0; i<labels.length; i++){
|
||||
var label = labels[i];
|
||||
label.setAttribute('selected', 'false');
|
||||
delete this.selection[label.value];
|
||||
this.selection = new Set();
|
||||
|
||||
var elems = this.id('tags-box').querySelectorAll("button[selected=true]");
|
||||
for (let i = 0; i < elems.length; i++) {
|
||||
let elem = elems[i];
|
||||
elem.setAttribute('selected', false);
|
||||
this.selection.delete(elem.textContent);
|
||||
}
|
||||
|
||||
if (this.onchange) {
|
||||
|
@ -647,14 +578,6 @@
|
|||
</method>
|
||||
|
||||
|
||||
<method name="clearAll">
|
||||
<body><![CDATA[
|
||||
this.selection = {};
|
||||
return this.clearVisible();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="handleKeyPress">
|
||||
<parameter name="clear"/>
|
||||
<body>
|
||||
|
@ -687,7 +610,7 @@
|
|||
|
||||
<method name="handleTagClick">
|
||||
<parameter name="event"/>
|
||||
<parameter name="label"/>
|
||||
<parameter name="elem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (event.button != 0) {
|
||||
|
@ -695,19 +618,19 @@
|
|||
}
|
||||
|
||||
// Ignore clicks on tags not in scope
|
||||
if (label.getAttribute('inScope') == 'false') {
|
||||
if (elem.getAttribute('inScope') == 'false') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deselect
|
||||
if (label.getAttribute('selected')=='true'){
|
||||
delete this.selection[label.value];
|
||||
label.setAttribute('selected', 'false');
|
||||
if (elem.getAttribute('selected')=='true'){
|
||||
this.selection.delete(elem.textContent);
|
||||
elem.setAttribute('selected', 'false');
|
||||
}
|
||||
// Select
|
||||
else {
|
||||
this.selection[label.value] = true;
|
||||
label.setAttribute('selected', 'true');
|
||||
this.selection.add(elem.textContent);
|
||||
elem.setAttribute('selected', 'true');
|
||||
}
|
||||
|
||||
this.updateNumSelected();
|
||||
|
@ -723,7 +646,7 @@
|
|||
<method name="rename">
|
||||
<parameter name="oldName"/>
|
||||
<body><![CDATA[
|
||||
Zotero.spawn(function* () {
|
||||
return Zotero.spawn(function* () {
|
||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
|
||||
|
@ -737,13 +660,12 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.selection[oldName]) {
|
||||
if (this.selection.has(oldName)) {
|
||||
var wasSelected = true;
|
||||
delete this.selection[oldName];
|
||||
this.selection.delete(oldName);
|
||||
}
|
||||
|
||||
yield Zotero.Tags.load(this.libraryID);
|
||||
if (Zotero.Tags.getID(this.libraryID, oldName)) {
|
||||
if (yield Zotero.Tags.getID(oldName)) {
|
||||
yield Zotero.Tags.rename(this.libraryID, oldName, newName.value);
|
||||
}
|
||||
// Colored tags don't need to exist, so in that case
|
||||
|
@ -758,7 +680,7 @@
|
|||
}
|
||||
|
||||
if (wasSelected) {
|
||||
this.selection[newName.value] = true;
|
||||
this.selection.add(newName.value);
|
||||
}
|
||||
}.bind(this));
|
||||
]]>
|
||||
|
@ -768,108 +690,216 @@
|
|||
|
||||
<method name="delete">
|
||||
<parameter name="name"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
|
||||
var confirmed = promptService.confirm(window,
|
||||
Zotero.getString('pane.tagSelector.delete.title'),
|
||||
Zotero.getString('pane.tagSelector.delete.message'));
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Zotero.DB.executeTransaction(function* () {
|
||||
yield Zotero.Tags.load(this.libraryID);
|
||||
var tagID = Zotero.Tags.getID(this.libraryID, name);
|
||||
<body><![CDATA[
|
||||
return Zotero.spawn(function* () {
|
||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
|
||||
var confirmed = promptService.confirm(window,
|
||||
Zotero.getString('pane.tagSelector.delete.title'),
|
||||
Zotero.getString('pane.tagSelector.delete.message'));
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tagID = yield Zotero.Tags.getID(name);
|
||||
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));
|
||||
|
||||
// If only a tag color setting, remove that
|
||||
if (!tagID) {
|
||||
Zotero.Tags.setColor(this.libraryID, name, false);
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="getColor">
|
||||
<parameter name="tagIDs"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
tagIDs = tagIDs.split('-');
|
||||
var name = Zotero.Tags.getName(this.libraryID, tagIDs[0]);
|
||||
return Zotero.Tags.getColor(this.libraryID, name)
|
||||
.then(function (colorData) {
|
||||
<body><![CDATA[
|
||||
return Zotero.spawn(function* () {
|
||||
tagIDs = tagIDs.split('-');
|
||||
var name = yield Zotero.Tags.getName(tagIDs[0]);
|
||||
var colorData = yield Zotero.Tags.getColor(this.libraryID, name);
|
||||
return colorData ? colorData.color : '#000000';
|
||||
});
|
||||
]]>
|
||||
</body>
|
||||
}.bind(this));
|
||||
]]></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 name="_makeClickableTag">
|
||||
<parameter name="tagObj"/>
|
||||
<parameter name="editable"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var tagName = tagObj.tag;
|
||||
var tagType = tagObj.type;
|
||||
|
||||
var label = document.createElement('label');
|
||||
|
||||
label.setAttribute('value', tagName);
|
||||
label.setAttribute('tagType', tagType);
|
||||
if (editable) {
|
||||
label.setAttribute('context', 'tag-menu');
|
||||
<body><![CDATA[
|
||||
var elem = document.createElementNS('http://www.w3.org/1999/xhtml', 'button');
|
||||
elem.textContent = tagObj.tag;
|
||||
if (tagObj.type) {
|
||||
elem.setAttribute('tagType', tagObj.type);
|
||||
}
|
||||
return label;
|
||||
]]>
|
||||
</body>
|
||||
var self = this;
|
||||
elem.addEventListener('click', function(event) {
|
||||
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 name="_openColorPickerWindow">
|
||||
<parameter name="name"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var io = {
|
||||
libraryID: this.libraryID,
|
||||
name: name
|
||||
};
|
||||
|
||||
var self = this;
|
||||
Zotero.Tags.getColors(this.libraryID)
|
||||
.then(function (tagColors) {
|
||||
if (Object.keys(tagColors).length >= Zotero.Tags.MAX_COLORED_TAGS && !tagColors[io.name]) {
|
||||
<body><![CDATA[
|
||||
return Zotero.spawn(function* () {
|
||||
var io = {
|
||||
libraryID: this.libraryID,
|
||||
name: name
|
||||
};
|
||||
|
||||
var tagColors = yield Zotero.Tags.getColors(this.libraryID);
|
||||
if (tagColors.size >= Zotero.Tags.MAX_COLORED_TAGS && !tagColors.has(io.name)) {
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
ps.alert(null, "", Zotero.getString('pane.tagSelector.maxColoredTags', Zotero.Tags.MAX_COLORED_TAGS));
|
||||
return;
|
||||
}
|
||||
|
||||
// Opening a modal window directly from within this promise handler causes
|
||||
// the opened window to block on the first yielded promise until the window
|
||||
// is closed.
|
||||
setTimeout(function () {
|
||||
window.openDialog(
|
||||
'chrome://zotero/content/tagColorChooser.xul',
|
||||
"zotero-tagSelector-colorChooser",
|
||||
"chrome,modal,centerscreen", io
|
||||
);
|
||||
|
||||
// Dialog cancel
|
||||
if (typeof io.color == 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.Tags.setColor(self.libraryID, io.name, io.color, io.position);
|
||||
}, 0);
|
||||
});
|
||||
io.tagColors = tagColors;
|
||||
|
||||
window.openDialog(
|
||||
'chrome://zotero/content/tagColorChooser.xul',
|
||||
"zotero-tagSelector-colorChooser",
|
||||
"chrome,modal,centerscreen", io
|
||||
);
|
||||
|
||||
// Dialog cancel
|
||||
if (typeof io.color == 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
yield Zotero.Tags.setColor(this.libraryID, io.name, io.color, io.position);
|
||||
}.bind(this));
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -928,7 +958,7 @@
|
|||
return Zotero.DB.executeTransaction(function* () {
|
||||
ids = ids.split(',');
|
||||
var items = Zotero.Items.get(ids);
|
||||
var value = node.getAttribute('value')
|
||||
var value = node.textContent
|
||||
|
||||
for (let i=0; i<items.length; i++) {
|
||||
let item = items[i];
|
||||
|
@ -953,28 +983,33 @@
|
|||
</implementation>
|
||||
|
||||
<content>
|
||||
<groupbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1">
|
||||
<menupopup id="tag-menu">
|
||||
<groupbox flex="1"
|
||||
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;"
|
||||
oncommand="_openColorPickerWindow(document.popupNode.getAttribute('value')); event.stopPropagation()"/>
|
||||
oncommand="_openColorPickerWindow(_popupNode.textContent); event.stopPropagation()"/>
|
||||
<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;"
|
||||
oncommand="document.getBindingParent(this).delete(document.popupNode.getAttribute('value')); event.stopPropagation()"/>
|
||||
oncommand="document.getBindingParent(this).delete(_popupNode.textContent); event.stopPropagation()"/>
|
||||
</menupopup>
|
||||
|
||||
<vbox id="no-tags-box" align="center" pack="center" flex="1">
|
||||
<deck id="no-tags-deck">
|
||||
<deck id="tags-deck">
|
||||
<box id="loading-box">
|
||||
<label value="&zotero.tagSelector.loadingTags;"/>
|
||||
</box>
|
||||
|
||||
<box id="no-tags-box">
|
||||
<label value="&zotero.tagSelector.noTagsToDisplay;"/>
|
||||
</deck>
|
||||
</vbox>
|
||||
|
||||
<vbox id="tags-toggle" flex="1"/>
|
||||
</box>
|
||||
|
||||
<html:div id="tags-box"/>
|
||||
</deck>
|
||||
|
||||
<vbox id="tag-controls">
|
||||
<hbox>
|
||||
<!-- TODO: &zotero.tagSelector.filter; is now unused -->
|
||||
<textbox id="tags-search" flex="1" type="search" timeout="250" dir="reverse"
|
||||
oncommand="document.getBindingParent(this).handleKeyPress(); event.stopPropagation()"
|
||||
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);">
|
||||
<menuitem id="num-selected" disabled="true"/>
|
||||
<menuitem id="deselect-all" label="&zotero.tagSelector.clearAll;"
|
||||
oncommand="document.getBindingParent(this).clearAll(); event.stopPropagation();"/>
|
||||
oncommand="document.getBindingParent(this).deselectAll(); event.stopPropagation();"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="show-automatic" label="&zotero.tagSelector.showAutomatic;" type="checkbox"
|
||||
oncommand="var ts = document.getBindingParent(this);
|
||||
ts._dirty = true;
|
||||
var showAutomatic = this.getAttribute('checked') == 'true';
|
||||
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"
|
||||
oncommand="var displayAll = this.getAttribute('checked') == 'true';
|
||||
this.setAttribute('checked', !displayAll);
|
||||
|
|
|
@ -180,6 +180,7 @@ var Zotero_Long_Tag_Fixer = new function () {
|
|||
}
|
||||
|
||||
// Remove old tags
|
||||
// TODO: Update
|
||||
Zotero.Tags.erase(oldTagIDs);
|
||||
Zotero.Tags.purge();
|
||||
Zotero.DB.commitTransaction();
|
||||
|
|
|
@ -24,13 +24,14 @@
|
|||
*/
|
||||
|
||||
"use strict";
|
||||
var _io;
|
||||
|
||||
var Zotero_Tag_Color_Chooser = new function() {
|
||||
var _io;
|
||||
|
||||
this.init = function () {
|
||||
var dialog = document.getElementById('tag-color-chooser');
|
||||
|
||||
return Zotero.spawn(function* () {
|
||||
try {
|
||||
// Set font size from pref
|
||||
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.name == 'undefined' || _io.name === "") throw new Error("name not set");
|
||||
if (_io.tagColors === undefined) throw new Error("tagColors not provided");
|
||||
|
||||
window.sizeToContent();
|
||||
|
||||
|
@ -58,8 +60,8 @@ var Zotero_Tag_Color_Chooser = new function() {
|
|||
var maxTags = document.getElementById('max-tags');
|
||||
maxTags.value = Zotero.getString('tagColorChooser.maxTags', Zotero.Tags.MAX_COLORED_TAGS);
|
||||
|
||||
var tagColors = yield Zotero.Tags.getColors(_io.libraryID);
|
||||
var colorData = tagColors[_io.name];
|
||||
var tagColors = _io.tagColors;
|
||||
var colorData = tagColors.get(_io.name);
|
||||
|
||||
// Color
|
||||
if (colorData) {
|
||||
|
@ -68,11 +70,7 @@ var Zotero_Tag_Color_Chooser = new function() {
|
|||
}
|
||||
else {
|
||||
// Get unused color at random
|
||||
var usedColors = [];
|
||||
for (var i in tagColors) {
|
||||
usedColors.push(tagColors[i].color);
|
||||
}
|
||||
|
||||
var usedColors = [for (x of tagColors.values()) x.color];
|
||||
var unusedColors = Zotero.Utilities.arrayDiff(
|
||||
colorPicker.colors, usedColors
|
||||
);
|
||||
|
@ -82,7 +80,7 @@ var Zotero_Tag_Color_Chooser = new function() {
|
|||
}
|
||||
colorPicker.setAttribute('disabled', 'false');
|
||||
|
||||
var numColors = Object.keys(tagColors).length;
|
||||
var numColors = tagColors.size;
|
||||
var max = colorData ? numColors : numColors + 1;
|
||||
|
||||
// Position
|
||||
|
@ -106,14 +104,13 @@ var Zotero_Tag_Color_Chooser = new function() {
|
|||
|
||||
this.onPositionChange();
|
||||
window.sizeToContent();
|
||||
}.bind(this))
|
||||
.catch(function (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
if (dialog.cancelDialog) {
|
||||
dialog.cancelDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -2398,10 +2398,8 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
|
|||
}
|
||||
|
||||
if (this.tags){
|
||||
for (var tag in this.tags){
|
||||
if (this.tags[tag]){
|
||||
s2.addCondition('tag', 'is', tag);
|
||||
}
|
||||
for (let tag of this.tags) {
|
||||
s2.addCondition('tag', 'is', tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2456,9 +2454,7 @@ Zotero.CollectionTreeRow.prototype.isSearchMode = function() {
|
|||
}
|
||||
|
||||
// Tag filter
|
||||
if (this.tags) {
|
||||
for (var i in this.tags) {
|
||||
return true;
|
||||
}
|
||||
if (this.tags && this.tags.size) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -370,7 +370,7 @@ Zotero.DataObjects.prototype.getUnwrittenData = function (libraryID) {
|
|||
Zotero.DataObjects.prototype.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) {
|
||||
ids = Zotero.flattenArguments(ids);
|
||||
|
||||
Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '')
|
||||
Zotero.debug('Reloading ' + (dataTypes ? '[' + dataTypes.join(', ') + '] for ' : '')
|
||||
+ this._ZDO_objects + ' ' + ids);
|
||||
|
||||
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++) {
|
||||
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
|
||||
let sql = "INSERT OR REPLACE INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)";
|
||||
yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
|
||||
Zotero.Notifier.queue('add', 'item-tag', this.id + '-' + tag.tag);
|
||||
yield Zotero.DB.queryAsync(sql, [this.id, tagID, tagType]);
|
||||
|
||||
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) {
|
||||
yield Zotero.Tags.load(this.libraryID);
|
||||
for (let i=0; i<toRemove.length; 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=?";
|
||||
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);
|
||||
}
|
||||
|
@ -3426,8 +3438,9 @@ Zotero.Item.prototype.getImageSrcWithTags = Zotero.Promise.coroutine(function* (
|
|||
var colorData = [];
|
||||
for (let i=0; i<tags.length; i++) {
|
||||
let tag = tags[i];
|
||||
if (tagColors[tag.tag]) {
|
||||
colorData.push(tagColors[tag.tag]);
|
||||
let data = tagColors.get(tag.tag);
|
||||
if (data) {
|
||||
colorData.push(data);
|
||||
}
|
||||
}
|
||||
if (!colorData.length) {
|
||||
|
|
|
@ -30,10 +30,6 @@
|
|||
Zotero.Tags = new function() {
|
||||
this.MAX_COLORED_TAGS = 6;
|
||||
|
||||
var _tagIDsByName = {};
|
||||
var _tagNamesByID = {};
|
||||
var _loaded = {};
|
||||
|
||||
var _libraryColors = {};
|
||||
var _libraryColorsByName = {};
|
||||
var _itemsListImagePromises = {};
|
||||
|
@ -43,78 +39,43 @@ Zotero.Tags = new function() {
|
|||
/**
|
||||
* Returns a tag for a given tagID
|
||||
*
|
||||
* @param {Number} libraryID
|
||||
* @param {Number} tagID
|
||||
* @param {Integer} tagID
|
||||
* @return {Promise<String|false>} - A tag name, or false if tag with id not found
|
||||
*/
|
||||
this.getName = function (libraryID, tagID) {
|
||||
if (!tagID) {
|
||||
throw new Error("tagID not provided");
|
||||
}
|
||||
if (_tagNamesByID[tagID]) {
|
||||
return _tagNamesByID[tagID];
|
||||
}
|
||||
_requireLoad(libraryID);
|
||||
return false;
|
||||
this.getName = function (tagID) {
|
||||
return Zotero.DB.valueQueryAsync("SELECT name FROM tags WHERE tagID=?", tagID);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @requireTransaction
|
||||
* @param {Number} libraryID
|
||||
* @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
|
||||
*/
|
||||
this.getIDFromName = Zotero.Promise.coroutine(function* (libraryID, name, create) {
|
||||
Zotero.DB.requireTransaction();
|
||||
this.getID = Zotero.Promise.coroutine(function* (name, create) {
|
||||
if (create) {
|
||||
Zotero.DB.requireTransaction();
|
||||
}
|
||||
data = this.cleanData({
|
||||
tag: name
|
||||
});
|
||||
var sql = "SELECT tagID FROM tags WHERE libraryID=? AND name=?";
|
||||
var id = yield Zotero.DB.valueQueryAsync(sql, [libraryID, data.tag]);
|
||||
var sql = "SELECT tagID FROM tags WHERE name=?";
|
||||
var id = yield Zotero.DB.valueQueryAsync(sql, data.tag);
|
||||
if (!id && create) {
|
||||
id = yield Zotero.ID.get('tags');
|
||||
let sql = "INSERT INTO tags (tagID, libraryID, name) VALUES (?, ?, ?)";
|
||||
let insertID = yield Zotero.DB.queryAsync(sql, [id, libraryID, data.tag]);
|
||||
let sql = "INSERT INTO tags (tagID, name) VALUES (?, ?)";
|
||||
let insertID = yield Zotero.DB.queryAsync(sql, [id, data.tag]);
|
||||
if (!id) {
|
||||
id = insertID;
|
||||
}
|
||||
_cacheTag(libraryID, id, data.tag);
|
||||
}
|
||||
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
|
||||
*
|
||||
|
@ -125,7 +86,7 @@ Zotero.Tags = new function() {
|
|||
*/
|
||||
this.getAll = Zotero.Promise.coroutine(function* (libraryID, types) {
|
||||
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];
|
||||
if (types) {
|
||||
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
|
||||
* @return {Promise<Number[]>} A promise for an array of itemIDs
|
||||
*/
|
||||
this.getTagItems = function (tagID) {
|
||||
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
|
||||
return Zotero.DB.columnQueryAsync(sql, tagID);
|
||||
this.getTagItems = function (libraryID, tagID) {
|
||||
var sql = "SELECT itemID FROM itemTags JOIN items USING (itemID) "
|
||||
+ "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
|
||||
*
|
||||
|
@ -228,7 +168,7 @@ Zotero.Tags = new function() {
|
|||
* @return {Promise}
|
||||
*/
|
||||
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();
|
||||
newName = newName.trim();
|
||||
|
@ -238,16 +178,15 @@ Zotero.Tags = new function() {
|
|||
return;
|
||||
}
|
||||
|
||||
yield Zotero.Tags.load(libraryID);
|
||||
var oldTagID = this.getID(libraryID, oldName);
|
||||
var oldTagID = yield this.getID(oldName);
|
||||
|
||||
// We need to know if the old tag has a color assigned so that
|
||||
// we can assign it to the new name
|
||||
var oldColorData = yield this.getColor(libraryID, oldName);
|
||||
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
var oldItemIDs = yield this.getTagItems(oldTagID);
|
||||
var newTagID = yield this.getIDFromName(libraryID, newName, true);
|
||||
var oldItemIDs = yield this.getTagItems(libraryID, oldTagID);
|
||||
var newTagID = yield this.getID(newName, true);
|
||||
|
||||
yield Zotero.Utilities.Internal.forEachChunkAsync(
|
||||
oldItemIDs,
|
||||
|
@ -269,19 +208,23 @@ Zotero.Tags = new function() {
|
|||
);
|
||||
|
||||
var notifierData = {};
|
||||
notifierData[newName] = {
|
||||
old: {
|
||||
tag: oldName
|
||||
for (let i = 0; i < oldItemIDs.length; i++) {
|
||||
notifierData[oldItemIDs[i] + '-' + newTagID] = {
|
||||
tag: newName,
|
||||
old: {
|
||||
tag: oldName
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Zotero.Notifier.queue(
|
||||
'modify',
|
||||
'item-tag',
|
||||
oldItemIDs.map(function (itemID) itemID + '-' + newName),
|
||||
oldItemIDs.map(itemID => itemID + '-' + newTagID),
|
||||
notifierData
|
||||
);
|
||||
|
||||
yield this.purge(libraryID, oldTagID);
|
||||
yield this.purge(oldTagID);
|
||||
}.bind(this));
|
||||
|
||||
if (oldColorData) {
|
||||
|
@ -302,30 +245,48 @@ Zotero.Tags = new function() {
|
|||
/**
|
||||
* @return {Promise}
|
||||
*/
|
||||
this.erase = Zotero.Promise.coroutine(function* (libraryID, tagIDs) {
|
||||
this.removeFromLibrary = Zotero.Promise.coroutine(function* (libraryID, tagIDs) {
|
||||
tagIDs = Zotero.flattenArguments(tagIDs);
|
||||
|
||||
var deletedNames = [];
|
||||
var oldItemIDs = [];
|
||||
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
yield Zotero.Tags.load(libraryID);
|
||||
|
||||
var notifierPairs = [];
|
||||
var notifierData = {};
|
||||
for (let i=0; i<tagIDs.length; i++) {
|
||||
let tagID = tagIDs[i];
|
||||
let name = this.getName(libraryID, tagID);
|
||||
let name = yield this.getName(tagID);
|
||||
if (name === false) {
|
||||
continue;
|
||||
}
|
||||
deletedNames.push(name);
|
||||
oldItemIDs = oldItemIDs.concat(yield this.getTagItems(tagID));
|
||||
|
||||
// This causes a cascading delete from itemTags
|
||||
let sql = "DELETE FROM tags WHERE tagID=?";
|
||||
yield Zotero.DB.queryAsync(sql, [tagID]);
|
||||
// Since we're performing the DELETE query directly,
|
||||
// get the list of items that will need their tags reloaded,
|
||||
// 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
|
||||
yield Zotero.Utilities.Internal.forEachChunkAsync(
|
||||
|
@ -334,11 +295,11 @@ Zotero.Tags = new function() {
|
|||
function* (chunk) {
|
||||
let placeholders = chunk.map(function () '?').join(',');
|
||||
|
||||
sql = 'UPDATE items SET clientDateModified=? '
|
||||
sql = 'UPDATE items SET synced=0, clientDateModified=? '
|
||||
+ 'WHERE itemID IN (' + placeholders + ')'
|
||||
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));
|
||||
|
@ -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|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++) {
|
||||
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)";
|
||||
var toDelete = yield Zotero.DB.queryAsync(sql);
|
||||
}
|
||||
|
@ -393,7 +354,7 @@ Zotero.Tags = new function() {
|
|||
sql = "CREATE INDEX tagDelete_tagID ON tagDelete(tagID)";
|
||||
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);
|
||||
|
||||
if (!toDelete.length) {
|
||||
|
@ -409,16 +370,9 @@ Zotero.Tags = new function() {
|
|||
ids.push(row.id);
|
||||
notifierData[row.id] = {
|
||||
old: {
|
||||
libraryID: row.libraryID,
|
||||
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);";
|
||||
|
@ -445,8 +399,7 @@ Zotero.Tags = new function() {
|
|||
this.getColor = function (libraryID, name) {
|
||||
return this.getColors(libraryID)
|
||||
.then(function () {
|
||||
return _libraryColorsByName[libraryID][name]
|
||||
? _libraryColorsByName[libraryID][name] : false;
|
||||
return _libraryColorsByName[libraryID].get(name) || false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -468,9 +421,11 @@ Zotero.Tags = new function() {
|
|||
|
||||
|
||||
/**
|
||||
* Get colored tags within a given library
|
||||
*
|
||||
* @param {Integer} libraryID
|
||||
* @return {Promise} A promise for an object with tag names as keys and
|
||||
* objects containing 'color' and 'position' as values
|
||||
* @return {Promise<Map>} - A promise for a Map with tag names as keys and
|
||||
* objects containing 'color' and 'position' as values
|
||||
*/
|
||||
this.getColors = Zotero.Promise.coroutine(function* (libraryID) {
|
||||
if (_libraryColorsByName[libraryID]) {
|
||||
|
@ -487,14 +442,14 @@ Zotero.Tags = new function() {
|
|||
tagColors = tagColors || [];
|
||||
|
||||
_libraryColors[libraryID] = tagColors;
|
||||
_libraryColorsByName[libraryID] = {};
|
||||
_libraryColorsByName[libraryID] = new Map;
|
||||
|
||||
// Also create object keyed by name for quick checking for individual tag colors
|
||||
for (let i=0; i<tagColors.length; i++) {
|
||||
_libraryColorsByName[libraryID][tagColors[i].name] = {
|
||||
_libraryColorsByName[libraryID].set(tagColors[i].name, {
|
||||
color: tagColors[i].color,
|
||||
position: i
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return _libraryColorsByName[libraryID];
|
||||
|
@ -511,7 +466,6 @@ Zotero.Tags = new function() {
|
|||
throw new Error("libraryID must be an integer");
|
||||
}
|
||||
|
||||
yield this.load(libraryID);
|
||||
yield this.getColors(libraryID);
|
||||
|
||||
var tagColors = _libraryColors[libraryID];
|
||||
|
@ -519,7 +473,7 @@ Zotero.Tags = new function() {
|
|||
// Unset
|
||||
if (!color) {
|
||||
// Trying to clear color on tag that doesn't have one
|
||||
if (!_libraryColorsByName[libraryID][name]) {
|
||||
if (!_libraryColorsByName[libraryID].has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -607,12 +561,13 @@ Zotero.Tags = new function() {
|
|||
var tagNames = tagColors.concat(previousTagColors).map(function (val) val.name);
|
||||
tagNames = Zotero.Utilities.arrayUnique(tagNames);
|
||||
if (tagNames.length) {
|
||||
yield Zotero.Tags.load(libraryID);
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
yield this.load(libraryID);
|
||||
var tagID = this.getID(libraryID, tagName);
|
||||
var tagID = yield this.getID(tagName);
|
||||
|
||||
// If there's a color setting but no matching tag, don't throw
|
||||
// an error (though ideally this wouldn't be possible).
|
||||
|
@ -854,40 +808,5 @@ Zotero.Tags = new function() {
|
|||
}
|
||||
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,42 +166,30 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
|
|||
|
||||
event.preventDefault();
|
||||
|
||||
Zotero.Promise.try(function () {
|
||||
Zotero.spawn(function* () {
|
||||
if (coloredTagsRE.test(key)) {
|
||||
let libraryID = self.collectionTreeRow.ref.libraryID;
|
||||
let position = parseInt(key) - 1;
|
||||
return Zotero.Tags.getColorByPosition(libraryID, position)
|
||||
.then(function (colorData) {
|
||||
// If a color isn't assigned to this number or any
|
||||
// other numbers, allow key navigation
|
||||
if (!colorData) {
|
||||
return Zotero.Tags.getColors(libraryID)
|
||||
.then(function (colors) {
|
||||
return !Object.keys(colors).length;
|
||||
});
|
||||
}
|
||||
|
||||
var items = self.getSelectedItems();
|
||||
return Zotero.Tags.toggleItemsListTags(libraryID, items, colorData.name)
|
||||
.then(function () {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
return true;
|
||||
})
|
||||
// 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.
|
||||
// To allow navigation with other keys, we temporarily enable
|
||||
// key navigation and recreate the keyboard event. Since
|
||||
// that will trigger this listener again, we set a flag to
|
||||
// ignore the event, and then clear the flag above when the
|
||||
// event comes in. I see no way this could go wrong...
|
||||
.then(function (resend) {
|
||||
if (!resend) {
|
||||
let colorData = yield Zotero.Tags.getColorByPosition(libraryID, position);
|
||||
// If a color isn't assigned to this number or any
|
||||
// other numbers, allow key navigation
|
||||
if (!colorData) {
|
||||
let colors = yield Zotero.Tags.getColors(libraryID);
|
||||
return !colors.size;
|
||||
}
|
||||
|
||||
var items = self.getSelectedItems();
|
||||
yield Zotero.Tags.toggleItemsListTags(libraryID, items, colorData.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// To allow navigation with other keys, we temporarily enable
|
||||
// key navigation and recreate the keyboard event. Since
|
||||
// that will trigger this listener again, we set a flag to
|
||||
// ignore the event, and then clear the flag above when the
|
||||
// event comes in. I see no way this could go wrong...
|
||||
tree.disableKeyNavigation = false;
|
||||
self._skipKeyPress = true;
|
||||
var nsIDWU = Components.interfaces.nsIDOMWindowUtils;
|
||||
|
@ -230,10 +218,8 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
|
|||
tree.disableKeyNavigation = true;
|
||||
})
|
||||
.catch(function (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
Zotero.logError(e);
|
||||
})
|
||||
.done();
|
||||
};
|
||||
// Store listener so we can call removeEventListener() in ItemTreeView.unregister()
|
||||
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("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("INSERT OR IGNORE INTO tags SELECT tagID, IFNULL(libraryID, 1), name FROM tagsOld");
|
||||
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, name FROM tagsOld");
|
||||
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("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("CREATE INDEX itemTags_tagID ON itemTags(tagID)");
|
||||
|
||||
|
|
|
@ -108,6 +108,7 @@ Zotero.SyncedSettings = (function () {
|
|||
if (currentValue === false) {
|
||||
return false;
|
||||
}
|
||||
currentValue = JSON.parse(currentValue);
|
||||
|
||||
var id = libraryID + '/' + setting;
|
||||
|
||||
|
|
|
@ -2092,7 +2092,6 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
|
||||
this.reloadDataObjects = function () {
|
||||
return Zotero.Promise.all([
|
||||
Zotero.Tags.reloadAll(),
|
||||
Zotero.Collections.reloadAll(),
|
||||
Zotero.Creators.reloadAll(),
|
||||
Zotero.Items.reloadAll()
|
||||
|
|
|
@ -1054,20 +1054,15 @@ var ZoteroPane = new function()
|
|||
}
|
||||
|
||||
|
||||
function getTagSelection () {
|
||||
function getTagSelection() {
|
||||
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* () {
|
||||
if (Zotero.Utilities.isEmpty(getTagSelection())) {
|
||||
return false;
|
||||
}
|
||||
var tagSelector = document.getElementById('zotero-tag-selector');
|
||||
yield tagSelector.clearAll();
|
||||
return true;
|
||||
});
|
||||
this.clearTagSelection = function () {
|
||||
document.getElementById('zotero-tag-selector').deselectAll();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
@ -1142,8 +1137,8 @@ var ZoteroPane = new function()
|
|||
|
||||
// XBL functions might not yet be available
|
||||
var tagSelector = document.getElementById('zotero-tag-selector');
|
||||
if (tagSelector.clearAll) {
|
||||
tagSelector.clearAll();
|
||||
if (tagSelector.deselectAll) {
|
||||
tagSelector.deselectAll();
|
||||
}
|
||||
|
||||
// Not necessary with seltype="cell", which calls nsITreeView::isSelectable()
|
||||
|
|
|
@ -142,11 +142,8 @@
|
|||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "No tags to display">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "Loading tags…">
|
||||
<!ENTITY zotero.tagSelector.filter "Filter:">
|
||||
<!ENTITY zotero.tagSelector.showAutomatic "Show Automatic">
|
||||
<!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.assignColor "Assign Color…">
|
||||
<!ENTITY zotero.tagSelector.renameTag "Rename Tag…">
|
||||
|
|
|
@ -6,41 +6,51 @@ groupbox
|
|||
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-y: auto;
|
||||
display: block; /* allow labels to wrap instead of all being in one line */
|
||||
background-color: -moz-field;
|
||||
}
|
||||
|
||||
checkbox
|
||||
{
|
||||
margin: .75em 0 .4em;
|
||||
}
|
||||
|
||||
#tags-toggle label
|
||||
{
|
||||
#tags-box button {
|
||||
margin: .15em .05em .15em .3em !important;
|
||||
padding: 0 .25em 0 .25em !important;
|
||||
-moz-user-focus: ignore;
|
||||
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 */
|
||||
-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 */
|
||||
#tags-toggle label[inScope=false]:not([hasColor=true])
|
||||
{
|
||||
#tags-box button[inScope=false]:not([hasColor=true]) {
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
#tags-toggle label[inScope=false][hasColor=true]
|
||||
{
|
||||
#tags-box button[inScope=false][hasColor=true] {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
#tags-toggle label[draggedOver="true"]
|
||||
{
|
||||
#tags-box button[draggedOver="true"] {
|
||||
color: white !important;
|
||||
background: #666;
|
||||
}
|
||||
|
|
|
@ -73,8 +73,9 @@ ZoteroAutoComplete.prototype.startSearch = Zotero.Promise.coroutine(function* (s
|
|||
case 'tag':
|
||||
var sql = "SELECT DISTINCT name AS val, NULL AS comment FROM tags WHERE name LIKE ?";
|
||||
var sqlParams = [searchString + '%'];
|
||||
if (typeof searchParams.libraryID != 'undefined') {
|
||||
sql += " AND libraryID=?";
|
||||
if (searchParams.libraryID !== undefined) {
|
||||
sql += " AND tagID IN (SELECT tagID FROM itemTags JOIN items USING (itemID) "
|
||||
+ "WHERE libraryID=?)";
|
||||
sqlParams.push(searchParams.libraryID);
|
||||
}
|
||||
if (searchParams.itemID) {
|
||||
|
|
|
@ -210,24 +210,6 @@ CREATE TRIGGER fku_itemNotes
|
|||
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
|
||||
DROP TRIGGER IF EXISTS fki_tags;
|
||||
CREATE TRIGGER fki_tags
|
||||
|
|
|
@ -115,9 +115,7 @@ CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState);
|
|||
|
||||
CREATE TABLE tags (
|
||||
tagID INTEGER PRIMARY KEY,
|
||||
libraryID INT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
UNIQUE (libraryID, name)
|
||||
name TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE itemRelations (
|
||||
|
|
|
@ -77,7 +77,7 @@ describe("Support Functions for Unit Testing", function() {
|
|||
|
||||
let tags = data.itemWithTags.tags;
|
||||
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(zItem.hasTag(tags[i].tag), '"' + tags[i].tag + '" tag was assigned to item');
|
||||
}
|
||||
|
|
|
@ -3,6 +3,40 @@
|
|||
describe("Tag Selector", function () {
|
||||
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* () {
|
||||
win = yield loadZoteroPane();
|
||||
doc = win.document;
|
||||
|
@ -11,6 +45,10 @@ describe("Tag Selector", function () {
|
|||
// Wait for things to settle
|
||||
yield Zotero.Promise.delay(100);
|
||||
});
|
||||
beforeEach(function* () {
|
||||
var libraryID = Zotero.Libraries.userLibraryID;
|
||||
yield clearTagColors(libraryID);
|
||||
})
|
||||
after(function () {
|
||||
win.close();
|
||||
});
|
||||
|
@ -27,7 +65,7 @@ describe("Tag Selector", 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;
|
||||
|
||||
if (collectionsView.selection.currentIndex != 0) {
|
||||
|
@ -41,6 +79,10 @@ describe("Tag Selector", function () {
|
|||
item.setTags([
|
||||
{
|
||||
tag: 'A'
|
||||
},
|
||||
{
|
||||
tag: 'B',
|
||||
type: 1
|
||||
}
|
||||
]);
|
||||
promise = waitForTagSelector();
|
||||
|
@ -48,8 +90,11 @@ describe("Tag Selector", function () {
|
|||
yield promise;
|
||||
|
||||
// Tag selector should have at least one tag
|
||||
tagSelector = doc.getElementById('zotero-tag-selector');
|
||||
assert.isAbove(tagSelector.getVisible().length, 0);
|
||||
assert.isAbove(getRegularTags().length, 1);
|
||||
});
|
||||
|
||||
it("should add a tag when an item is added in a collection", function* () {
|
||||
var promise, tagSelector;
|
||||
|
||||
// Add collection
|
||||
promise = waitForTagSelector();
|
||||
|
@ -57,14 +102,13 @@ describe("Tag Selector", function () {
|
|||
yield promise;
|
||||
|
||||
// Tag selector should be empty in new collection
|
||||
tagSelector = doc.getElementById('zotero-tag-selector');
|
||||
assert.equal(tagSelector.getVisible().length, 0);
|
||||
assert.equal(getRegularTags().length, 0);
|
||||
|
||||
// Add item with tag to collection
|
||||
var item = createUnsavedDataObject('item');
|
||||
item.setTags([
|
||||
{
|
||||
tag: 'B'
|
||||
tag: 'C'
|
||||
}
|
||||
]);
|
||||
item.setCollections([collection.id]);
|
||||
|
@ -72,9 +116,80 @@ describe("Tag Selector", function () {
|
|||
yield item.saveTx();
|
||||
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
|
||||
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* () {
|
||||
|
@ -91,13 +206,12 @@ describe("Tag Selector", function () {
|
|||
}
|
||||
]);
|
||||
item.setCollections([collection.id]);
|
||||
promise = waitForTagSelector()
|
||||
promise = waitForTagSelector();
|
||||
yield item.saveTx();
|
||||
yield promise;
|
||||
|
||||
// Tag selector should show the new item's tag
|
||||
var tagSelector = doc.getElementById('zotero-tag-selector');
|
||||
assert.equal(tagSelector.getVisible().length, 1);
|
||||
assert.equal(getRegularTags().length, 1);
|
||||
|
||||
item.setCollections();
|
||||
promise = waitForTagSelector();
|
||||
|
@ -105,8 +219,7 @@ describe("Tag Selector", function () {
|
|||
yield promise;
|
||||
|
||||
// Tag selector shouldn't show the removed item's tag
|
||||
tagSelector = doc.getElementById('zotero-tag-selector');
|
||||
assert.equal(tagSelector.getVisible().length, 0);
|
||||
assert.equal(getRegularTags().length, 0);
|
||||
})
|
||||
|
||||
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;
|
||||
|
||||
// Tag selector should show the new item's tag
|
||||
var tagSelector = doc.getElementById('zotero-tag-selector');
|
||||
assert.equal(tagSelector.getVisible().length, 1);
|
||||
assert.equal(getRegularTags().length, 1);
|
||||
|
||||
// Move item to trash
|
||||
item.deleted = true;
|
||||
|
@ -138,8 +250,96 @@ describe("Tag Selector", function () {
|
|||
yield promise;
|
||||
|
||||
// Tag selector shouldn't show the deleted item's tag
|
||||
tagSelector = doc.getElementById('zotero-tag-selector');
|
||||
assert.equal(tagSelector.getVisible().length, 0);
|
||||
assert.equal(getRegularTags().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);
|
||||
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);
|
||||
yield item.saveTx();
|
||||
|
||||
var tagID = Zotero.Tags.getID(Zotero.Libraries.userLibraryID, tagName);
|
||||
assert.equal(Zotero.Tags.getName(Zotero.Libraries.userLibraryID, tagID), tagName);
|
||||
var libraryID = Zotero.Libraries.userLibraryID;
|
||||
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 () {
|
||||
it("should remove orphaned tags", function* () {
|
||||
var libraryID = Zotero.Libraries.userLibraryID;
|
||||
|
||||
var tagName = Zotero.Utilities.randomString();
|
||||
var item = createUnsavedDataObject('item');
|
||||
item.addTag(tagName);
|
||||
yield item.saveTx();
|
||||
|
||||
var tagID = Zotero.Tags.getID(libraryID, tagName);
|
||||
var tagID = yield Zotero.Tags.getID(tagName);
|
||||
assert.typeOf(tagID, "number");
|
||||
|
||||
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.Tags.purge();
|
||||
});
|
||||
|
||||
yield Zotero.Tags.load(libraryID);
|
||||
assert.isFalse(Zotero.Tags.getName(libraryID, tagID));
|
||||
assert.isFalse(yield Zotero.Tags.getName(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