zotero-web-library
Version:
Web library from zotero.org
215 lines (190 loc) • 6.19 kB
JavaScript
'use strict';
var log = require('libzotero/lib/Log').Logger('zotero-web-library:Tags');
var React = require('react');
var LoadingSpinner = require('./LoadingSpinner.js');
var TagRow = React.createClass({
displayName: 'TagRow',
getDefaultProps: function getDefaultProps() {
return {
showAutomatic: false
};
},
handleClick: function handleClick(evt) {
evt.stopPropagation();
evt.preventDefault();
var tag = this.props.tag;
Z.state.toggleTag(tag.apiObj.tag);
Z.state.clearUrlVars(['tag', 'collectionKey']);
Z.state.pushState();
},
render: function render() {
var tag = this.props.tag;
var title = tag.apiObj.tag;
if (tag.apiObj.meta.numItems) {
title += ' (' + tag.apiObj.meta.numItems + ')';
}
var newUrl = '';
var tagStyle = {};
if (tag.color) {
tagStyle = {
color: tag.color,
fontWeight: 'bold'
};
}
//render nothing for automatic tags user doesn't want displayed
if (this.props.showAutomatic == false && tag.apiObj.meta.type != 0) {
return null;
}
return React.createElement(
'li',
{ className: 'tag-row' },
React.createElement(
'a',
{ onClick: this.handleClick, className: 'tag-link', title: title, style: tagStyle, href: newUrl },
Zotero.format.trimString(tag.apiObj.tag, 12)
)
);
}
});
var TagList = React.createClass({
displayName: 'TagList',
getDefaultProps: function getDefaultProps() {
return {
tags: [],
showAutomatic: false,
id: ''
};
},
render: function render() {
var showAutomatic = this.props.showAutomatic;
var tagRowNodes = this.props.tags.map(function (tag, ind) {
return React.createElement(TagRow, { key: tag.apiObj.tag, tag: tag, showAutomatic: showAutomatic });
});
return React.createElement(
'ul',
{ id: this.props.id },
tagRowNodes
);
}
});
var Tags = React.createClass({
displayName: 'Tags',
getDefaultProps: function getDefaultProps() {
return {};
},
getInitialState: function getInitialState() {
return {
tags: new Zotero.Tags(),
tagColors: null,
selectedTags: [],
tagFilter: '',
showAutomatic: false,
loading: false
};
},
componentWillMount: function componentWillMount(evt) {
var reactInstance = this;
var library = this.props.library;
var tagColors = library.preferences.getPref('tagColors');
reactInstance.setState({ tagColors: tagColors });
library.listen('tagsDirty', reactInstance.syncTags, {});
library.listen('cachedDataLoaded', reactInstance.syncTags, {});
library.listen('tagsChanged libraryTagsUpdated selectedTagsChanged', function (evt) {
reactInstance.setState({ tags: library.tags });
}, {});
},
handleFilterChanged: function handleFilterChanged(evt) {
this.setState({ tagFilter: evt.target.value });
},
getSelectedTagsArray: function getSelectedTagsArray() {
var selectedTags = Zotero.state.getUrlVar('tag');
if (!Array.isArray(selectedTags)) {
if (selectedTags) {
selectedTags = [selectedTags];
} else {
selectedTags = [];
}
}
return selectedTags;
},
syncTags: function syncTags(evt) {
log.debug('Tags.syncTags', 3);
var reactInstance = this;
if (this.state.loading) {
return;
}
var library = this.props.library;
//clear tags if we're explicitly not using cached tags
if (evt.data && evt.data.checkCached === false) {
library.tags.clear();
}
reactInstance.setState({ tags: library.tags, loading: true });
//cached tags are preloaded with library if the cache is enabled
//this function shouldn't be triggered until that has already been done
var loadingPromise = library.loadUpdatedTags().then(function () {
reactInstance.setState({ tags: library.tags, loading: false });
return;
}, function (error) {
log.error('syncTags failed. showing local data and clearing loading div');
reactInstance.setState({ tags: library.tags, loading: false });
Zotero.ui.jsNotificationMessage('There was an error loading tags. Some tags may not have been updated.', 'notice');
});
return;
},
render: function render() {
var reactInstance = this;
var tags = this.state.tags;
var selectedTagStrings = reactInstance.getSelectedTagsArray();
var tagColors = this.state.tagColors;
if (tagColors === null) {
tagColors = [];
}
var matchAnyFilter = this.state.tagFilter;
var plainTagsList = tags.plainTagsList(tags.tagsArray);
var matchedTagStrings = Z.utils.matchAnyAutocomplete(matchAnyFilter, plainTagsList);
var tagColorStrings = [];
var coloredTags = [];
tagColors.forEach(function (tagColor, index) {
tagColorStrings.push(tagColor.name.toLowerCase());
var coloredTag = tags.getTag(tagColor.name);
if (coloredTag) {
coloredTag.color = tagColor.color;
coloredTags.push(coloredTag);
}
});
var filteredTags = [];
var selectedTags = [];
//always show selected tags, even if they don't pass the filter
selectedTagStrings.forEach(function (tagString) {
var t = tags.getTag(tagString);
if (t) {
selectedTags.push(t);
}
});
//add to filteredTags if passes filter, and is not already selected or colored
matchedTagStrings.forEach(function (matchedString) {
var t = tags.getTag(matchedString);
if (t !== null && t.apiObj.meta.numItems > 0) {
//we have the actual tag object, and it has items
//add to filteredTags if it is not already in colored or selected lists
if (selectedTagStrings.indexOf(matchedString) == -1 && tagColorStrings.indexOf(matchedString) == -1) {
filteredTags.push(t);
}
}
});
return React.createElement(
'div',
{ id: 'tags-list-div', className: 'tags-list-div' },
React.createElement(
'div',
null,
React.createElement('input', { type: 'text', id: 'tag-filter-input', className: 'tag-filter-input form-control', placeholder: 'Filter Tags', onChange: this.handleFilterChanged }),
React.createElement(LoadingSpinner, { loading: this.state.loading }),
React.createElement(TagList, { tags: selectedTags, id: 'selected-tags-list' }),
React.createElement(TagList, { tags: coloredTags, id: 'colored-tags-list' }),
React.createElement(TagList, { tags: filteredTags, id: 'tags-list' })
)
);
}
});
module.exports = Tags;