diff --git a/.babelrc b/.babelrc
index c11d720d86..2965585ef6 100644
--- a/.babelrc
+++ b/.babelrc
@@ -18,6 +18,7 @@
"syntax-jsx",
"transform-react-jsx",
"transform-react-display-name",
+ "transform-class-properties",
[
"transform-es2015-modules-commonjs",
{
diff --git a/chrome/content/zotero/bindings/tagselector.xml b/chrome/content/zotero/bindings/tagselector.xml
deleted file mode 100644
index 9db67dc840..0000000000
--- a/chrome/content/zotero/bindings/tagselector.xml
+++ /dev/null
@@ -1,1213 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- false
- false
- null
-
- null
- null
-
-
- "view"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- null
-
-
-
-
-
-
-
-
- false
- null
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Zotero.Promise.check(this.mode));
-
- let { div, emptyRegular } = this.createTagsList(this._tags);
-
- this._emptyRegular = emptyRegular;
- tagsBox.innerHTML = "";
- tagsBox.appendChild(div);
- this._dirty = false;
- this._tagsDiv = null;
- }
- // Otherwise just update based on visibility
- else {
- // If only a few tags, regenerate buttons from scratch
- if (this.filterToScope && this._scope.size <= 100) {
- // If full set is currently displayed, store it for later
- if (!this._tagsDiv) {
- this._tagsDiv = tagsBox.firstChild;
- }
-
- let tags = [];
- this._scope.forEach(function (types, name) {
- tags.push(...types.map(type => {
- return {
- tag: name,
- type
- };
- }));
- });
- let { div, emptyRegular } = this.createTagsList(tags);
- tagsBox.replaceChild(div, tagsBox.firstChild);
- this._emptyRegular = emptyRegular;
- }
- // Otherwise swap in the stored buttons from the last full run,
- // after updating their visibility
- else {
- let oldDiv = tagsBox.removeChild(tagsBox.firstChild);
- if (!this._tagsDiv) {
- this._tagsDiv = oldDiv;
- }
-
- let elems = this._tagsDiv.childNodes;
- let tagColors = Zotero.Tags.getColors(this.libraryID);
- for (let i = 0; i < elems.length; i++) {
- let elem = elems[i];
- let visible = this._updateClickableTag(
- elem, elem.textContent, tagColors
- );
- if (visible) {
- this._emptyRegular = false;
- }
- }
- tagsBox.appendChild(this._tagsDiv);
- }
- }
-
- //start tag cloud code
-
- var tagCloud = Zotero.Prefs.get('tagCloud');
- if (false && tagCloud) {
- var labels = tagsBox.getElementsByTagName('label');
-
- //loop through displayed labels and find number of linked items
- var numlinked= [];
- for (var i=0; i
-
-
-
-
-
- tag.tag));
- [...new Set(tagColors.keys())].filter(x => !regularTags.has(x)).forEach((x) => {
- tags.push(Zotero.Tags.cleanData({ tag: x }));
- });
-
- // Sort by name
- var t = new Date();
- Zotero.debug("Sorting tags");
- var collation = Zotero.getLocaleCollation();
- tags.sort(function (a, b) {
- return collation.compareString(1, a.tag, b.tag);
- });
- Zotero.debug(`Sorted tags in ${new Date() - t} ms`);
-
- var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
- var emptyRegular = true;
- var lastTag;
- for (let i = 0; i < tags.length; i++) {
- let tagData = tags[i];
-
- // Only show tags of different types once
- if (tagData.tag === lastTag) {
- continue;
- }
- lastTag = tagData.tag;
-
- let elem = this._insertClickableTag(div, tagData);
- let visible = this._updateClickableTag(
- elem, tagData.tag, tagColors
- );
- if (visible) {
- emptyRegular = false;
- }
- }
- return { div, emptyRegular };
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- val.split("/")[1] == 'tagColors')) {
- yield this.refresh(true);
- }
- return;
- }
-
- // Ignore anything other than deletes in duplicates view
- if (this.collectionTreeRow.isDuplicates()) {
- switch (event) {
- case 'delete':
- case 'trash':
- break;
-
- default:
- return;
- }
- }
-
- // Ignore item events other than 'trash'
- if (type == 'item' && event != 'trash') {
- return;
- }
-
- var selectionChanged = false;
-
- // If a selected tag no longer exists, deselect it
- if (event == 'delete' || event == 'trash' || event == 'modify') {
- // TODO: necessary, or just use notifier value?
- this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types);
-
- for (let tag of this.selection) {
- for (let tag2 of this._tags) {
- if (tag == tag2) {
- var found = true;
- break;
- }
- }
- if (!found) {
- this.selection.delete(tag);
- selectionChanged = true;
- }
- }
- }
-
- 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) {
- this.insertSorted(this.id('tags-box').firstChild, tagObjs);
- // If full set isn't currently displayed, update it too
- if (this._tagsDiv) {
- this.insertSorted(this._tagsDiv, 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);
- }
- else {
- this.setSearch(false, true);
- }
- this._dirty = true;
-
- // This is a hack, but set this to run after the refresh,
- // since _emptyRegular isn't set until then
- this.onRefresh = function () {
- // If no regular tags visible after a delete, deselect all.
- // This is necessary so that a selected tag that's removed
- // from its last item doesn't cause all regular tags to
- // disappear without anything being visibly selected.
- if ((event == 'remove' || event == 'delete') &&
- this._emptyRegular && this.getNumSelected()) {
- Zotero.debug('No tags visible after delete -- deselecting all');
- return this.clearAll();
- }
- }.bind(this);
-
- // If the selection changed, update the items list
- if (selectionChanged && this.onchange) {
- return this.onchange();
- }
-
- // Otherwise, just update the tag selector
- return this.updateScope();
- }, this);
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- Zotero.updateZoteroPaneProgressMeter(
- Math.round(progress / progressMax * 100)
- );
- }
- );
- }
- finally {
- Zotero.hideZoteroPaneOverlays();
- }
- }
- }.bind(this))();
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- = 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;
- }
-
- 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));
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/chrome/content/zotero/components/button.jsx b/chrome/content/zotero/components/button.jsx
new file mode 100644
index 0000000000..93af1b1dd1
--- /dev/null
+++ b/chrome/content/zotero/components/button.jsx
@@ -0,0 +1,131 @@
+'use strict'
+
+const React = require('react')
+const { PureComponent, createElement: create } = React
+const { injectIntl, intlShape } = require('react-intl')
+const { IconDownChevron } = require('./icons')
+const cx = require('classnames')
+const {
+ bool, element, func, node, number, oneOf, string
+} = require('prop-types')
+
+
+const ButtonGroup = ({ children }) => (
+
{children}
+)
+
+ButtonGroup.propTypes = {
+ children: node
+}
+
+class Button extends PureComponent {
+ componentDidMount() {
+ if (!Zotero.isNode && this.title) {
+ // Workaround for XUL tooltips
+ this.container.setAttribute('tooltiptext', this.title);
+ }
+ }
+
+ get classes() {
+ return ['btn', this.props.className, `btn-${this.props.size}`, {
+ 'btn-icon': this.props.icon != null,
+ 'active': this.props.isActive,
+ 'btn-flat': this.props.isFlat,
+ 'btn-menu': this.props.isMenu,
+ 'disabled': this.props.isDisabled,
+ }]
+ }
+
+ get node() {
+ return 'button'
+ }
+
+ get text() {
+ const { intl, text } = this.props
+
+ return text ?
+ intl.formatMessage({ id: text }) :
+ null
+ }
+
+ get title() {
+ const { intl, title } = this.props
+
+ return title ?
+ intl.formatMessage({ id: title }) :
+ null
+ }
+
+ get menuMarker() {
+ if (!Zotero.isNode && Zotero.isLinux) {
+ return this.props.isMenu &&
+ }
+ return this.props.isMenu &&
+ }
+
+ get attributes() {
+ const attr = {
+ className: cx(...this.classes),
+ disabled: !this.props.noFocus && this.props.isDisabled,
+ onBlur: this.handleBlur,
+ onFocus: this.props.onFocus,
+ ref: this.setContainer,
+ title: this.title
+ }
+
+ if (!this.props.isDisabled) {
+ attr.onMouseDown = this.handleMouseDown
+ attr.onClick = this.handleClick
+ }
+
+ return attr
+ }
+
+ setContainer = (container) => {
+ this.container = container
+ }
+
+ handleClick = (event) => {
+ event.preventDefault()
+
+ if (!this.props.isDisabled && this.props.onClick) {
+ this.props.onClick(event)
+ }
+ }
+
+ handleMouseDown = (event) => {
+ event.preventDefault()
+
+ if (!this.props.isDisabled && this.props.onMouseDown) {
+ this.props.onMouseDown(event)
+ }
+ }
+
+ render() {
+ return create(this.node, this.attributes, this.props.icon, this.text, this.menuMarker)
+ }
+
+ static propTypes = {
+ className: string,
+ icon: element,
+ intl: intlShape.isRequired,
+ isActive: bool,
+ isDisabled: bool,
+ isMenu: bool,
+ size: oneOf(['sm', 'md', 'lg']),
+ title: string,
+ text: string,
+ onClick: func,
+ onMouseDown: func
+ }
+
+ static defaultProps = {
+ size: 'md'
+ }
+}
+
+
+module.exports = {
+ ButtonGroup,
+ Button: injectIntl(Button)
+}
diff --git a/chrome/content/zotero/components/form/input.jsx b/chrome/content/zotero/components/form/input.jsx
new file mode 100644
index 0000000000..d70b131b56
--- /dev/null
+++ b/chrome/content/zotero/components/form/input.jsx
@@ -0,0 +1,155 @@
+/* eslint-disable react/no-deprecated */
+'use strict';
+
+const React = require('react');
+const PropTypes = require('prop-types');
+const cx = require('classnames');
+const { noop } = () => {};
+
+class Input extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ value: props.value
+ };
+ }
+
+ cancel(event = null) {
+ this.props.onCancel && this.props.onCancel(this.hasChanged, event);
+ this.hasBeenCancelled = true;
+ this.input.blur();
+ }
+
+ commit(event = null) {
+ this.props.onCommit && this.props.onCommit(this.state.value, this.hasChanged, event);
+ this.hasBeenCommitted = true;
+ }
+
+ focus() {
+ if(this.input != null) {
+ this.input.focus();
+ this.props.selectOnFocus && this.input.select();
+ }
+ }
+
+ componentWillReceiveProps({ value }) {
+ if (value !== this.props.value) {
+ this.setState({ value });
+ }
+ }
+
+ handleChange({ target }) {
+ this.setState({ value: target.value });
+ this.props.onChange && this.props.onChange(target.value);
+ }
+
+ handleBlur(event) {
+ const shouldCancel = this.props.onBlur && this.props.onBlur(event);
+ if (this.hasBeenCancelled || this.hasBeenCommitted) { return; }
+ shouldCancel ? this.cancel(event) : this.commit(event);
+ }
+
+ handleFocus(event) {
+ this.props.selectOnFocus && event.target.select();
+ this.props.onFocus && this.props.onFocus(event);
+ }
+
+ handleKeyDown(event) {
+ switch (event.key) {
+ case 'Escape':
+ this.cancel(event);
+ break;
+ case 'Enter':
+ this.commit(event);
+ break;
+ default:
+ return;
+ }
+ }
+
+ get hasChanged() {
+ return this.state.value !== this.props.value;
+ }
+
+ render() {
+ this.hasBeenCancelled = false;
+ this.hasBeenCommitted = false;
+ const extraProps = Object.keys(this.props).reduce((aggr, key) => {
+ if(key.match(/^(aria-|data-).*/)) {
+ aggr[key] = this.props[key];
+ }
+ return aggr;
+ }, {});
+ const input = this.input = input }
+ required={ this.props.isRequired }
+ size={ this.props.size }
+ spellCheck={ this.props.spellCheck }
+ step={ this.props.step }
+ tabIndex={ this.props.tabIndex }
+ type={ this.props.type }
+ value={ this.state.value }
+ { ...extraProps }
+ />;
+ return input;
+ }
+
+ static defaultProps = {
+ className: 'form-control',
+ onBlur: noop,
+ onCancel: noop,
+ onChange: noop,
+ onCommit: noop,
+ onFocus: noop,
+ tabIndex: -1,
+ type: 'text',
+ value: '',
+ };
+
+ static propTypes = {
+ autoFocus: PropTypes.bool,
+ className: PropTypes.string,
+ form: PropTypes.string,
+ id: PropTypes.string,
+ inputMode: PropTypes.string,
+ isDisabled: PropTypes.bool,
+ isReadOnly: PropTypes.bool,
+ isRequired: PropTypes.bool,
+ max: PropTypes.number,
+ maxLength: PropTypes.number,
+ min: PropTypes.number,
+ minLength: PropTypes.number,
+ name: PropTypes.string,
+ onBlur: PropTypes.func,
+ onCancel: PropTypes.func,
+ onChange: PropTypes.func,
+ onCommit: PropTypes.func,
+ onFocus: PropTypes.func,
+ placeholder: PropTypes.string,
+ selectOnFocus: PropTypes.bool,
+ spellCheck: PropTypes.bool,
+ step: PropTypes.number,
+ tabIndex: PropTypes.number,
+ type: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired,
+ };
+}
+
+module.exports = Input;
diff --git a/chrome/content/zotero/components/icons.jsx b/chrome/content/zotero/components/icons.jsx
new file mode 100644
index 0000000000..5a6bc2f31e
--- /dev/null
+++ b/chrome/content/zotero/components/icons.jsx
@@ -0,0 +1,57 @@
+'use strict';
+
+const React = require('react')
+const { PureComponent } = React
+const { element, string } = require('prop-types')
+const cx = require('classnames')
+
+const Icon = ({ children, className, name }) => (
+
+ {children}
+
+)
+
+Icon.propTypes = {
+ children: element.isRequired,
+ className: string,
+ name: string.isRequired
+}
+
+module.exports = { Icon }
+
+
+function i(name, svgOrSrc, hasDPI=true) {
+ const icon = class extends PureComponent {
+ render() {
+ const { className } = this.props
+
+ if (typeof svgOrSrc == 'string') {
+ if (hasDPI && window.devicePixelRatio >= 1.25) {
+ let parts = svgOrSrc.split('.');
+ parts[parts.length-2] = parts[parts.length-2] + '@2x';
+ svgOrSrc = parts.join('.')
+ }
+ return
+ }
+
+ return (
+ {svgOrImg}
+ )
+ }
+ }
+
+ icon.propTypes = {
+ className: string
+ }
+
+ icon.displayName = `Icon${name}`
+
+ module.exports[icon.displayName] = icon
+}
+
+/* eslint-disable max-len */
+
+
+i('TagSelectorMenu', "chrome://zotero/skin/tag-selector-menu.png")
+i('DownChevron', "chrome://zotero/skin/searchbar-dropmarker.png")
+
diff --git a/chrome/content/zotero/components/tag-selector.jsx b/chrome/content/zotero/components/tag-selector.jsx
new file mode 100644
index 0000000000..4d2bd11f74
--- /dev/null
+++ b/chrome/content/zotero/components/tag-selector.jsx
@@ -0,0 +1,68 @@
+'use strict';
+
+const React = require('react');
+const PropTypes = require('prop-types');
+const TagList = require('./tag-selector/tag-list');
+const Input = require('./form/input');
+const { Button } = require('./button');
+const { IconTagSelectorMenu } = require('./icons');
+
+class TagSelector extends React.Component {
+ render() {
+ return (
+
+
+
+ this.focusTextbox = ref && ref.focus}
+ value={this.props.searchString}
+ onChange={this.props.onSearch}
+ className="tag-selector-filter"
+ size="1"
+ />
+ }
+ title="zotero.toolbar.actions.label"
+ className="tag-selector-actions"
+ isMenu
+ onClick={ev => this.props.onSettings(ev)}
+ />
+
+
+ );
+ }
+}
+
+TagSelector.propTypes = {
+ tags: PropTypes.arrayOf(PropTypes.shape({
+ name: PropTypes.string,
+ selected: PropTypes.bool,
+ color: PropTypes.string,
+ disabled: PropTypes.bool
+ })),
+ dragObserver: PropTypes.shape({
+ onDragOver: PropTypes.func,
+ onDragExit: PropTypes.func,
+ onDrop: PropTypes.func
+ }),
+ searchString: PropTypes.string,
+ shouldFocus: PropTypes.bool,
+ onSelect: PropTypes.func,
+ onTagContext: PropTypes.func,
+ onSearch: PropTypes.func,
+ onSettings: PropTypes.func,
+ loaded: PropTypes.bool,
+};
+
+TagSelector.defaultProps = {
+ tags: [],
+ searchString: '',
+ shouldFocus: false,
+ onSelect: () => Promise.resolve(),
+ onTagContext: () => Promise.resolve(),
+ onSearch: () => Promise.resolve(),
+ onSettings: () => Promise.resolve()
+};
+
+module.exports = TagSelector;
diff --git a/chrome/content/zotero/components/tag-selector/tag-list.jsx b/chrome/content/zotero/components/tag-selector/tag-list.jsx
new file mode 100644
index 0000000000..ba140c85eb
--- /dev/null
+++ b/chrome/content/zotero/components/tag-selector/tag-list.jsx
@@ -0,0 +1,77 @@
+const React = require('react');
+const { FormattedMessage } = require('react-intl');
+const PropTypes = require('prop-types');
+const cx = require('classnames');
+
+class TagList extends React.PureComponent {
+ renderTag(index) {
+ const { tags } = this.props;
+ const tag = index < tags.length ?
+ tags[index] : {
+ tag: "",
+ };
+ const { onDragOver, onDragExit, onDrop } = this.props.dragObserver;
+
+ const className = cx('tag-selector-item', 'zotero-clicky', {
+ selected: tag.selected,
+ colored: tag.color,
+ disabled: tag.disabled
+ });
+
+ let props = {
+ className,
+ onClick: ev => !tag.disabled && this.props.onSelect(tag.name, ev),
+ onContextMenu: ev => this.props.onTagContext(tag, ev),
+ onDragOver,
+ onDragExit,
+ onDrop
+ };
+
+ if (tag.color) {
+ props['style'] = {
+ color: tag.color,
+ };
+ }
+
+
+ return (
+
+ {tag.name}
+
+ );
+ }
+
+ render() {
+ const totalTagCount = this.props.tags.length;
+ var tagList = (
+
+ {
+ [...Array(totalTagCount).keys()].map(index => this.renderTag(index))
+ }
+
+ );
+ if (!this.props.loaded) {
+ tagList = (
+
+
+
+ );
+ } else if (totalTagCount == 0) {
+ tagList = (
+
+
+
+ );
+ }
+ return (
+ { this.container = ref }}>
+ {tagList}
+
+ )
+
+ }
+}
+
+module.exports = TagList;
diff --git a/chrome/content/zotero/containers/containers.js b/chrome/content/zotero/containers/containers.js
new file mode 100644
index 0000000000..e0b8e39dd7
--- /dev/null
+++ b/chrome/content/zotero/containers/containers.js
@@ -0,0 +1,59 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2018 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+'use strict';
+
+const { defineMessages } = require('react-intl');
+
+ZoteroPane.Containers = {
+ async init() {
+ await this.initIntlStrings();
+ },
+
+ loadPane() {
+ var tagSelector = document.getElementById('zotero-tag-selector');
+ ZoteroPane.tagSelector = Zotero.TagSelector.init(tagSelector, {
+ onSelection: ZoteroPane.updateTagFilter.bind(ZoteroPane)
+ });
+ },
+
+ async initIntlStrings() {
+ this.intlMessages = {};
+ const intlFiles = ['zotero.dtd'];
+ for (let intlFile of intlFiles) {
+ let localeXML = await Zotero.File.getContentsFromURLAsync(`chrome://zotero/locale/${intlFile}`);
+ let regexp = /
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chrome/content/zotero/containers/tagSelector.jsx b/chrome/content/zotero/containers/tagSelector.jsx
new file mode 100644
index 0000000000..858f8ed1fb
--- /dev/null
+++ b/chrome/content/zotero/containers/tagSelector.jsx
@@ -0,0 +1,412 @@
+/* global Zotero: false */
+'use strict';
+
+(function() {
+
+const React = require('react');
+const ReactDOM = require('react-dom');
+const { IntlProvider } = require('react-intl');
+const TagSelector = require('components/tag-selector.js');
+const noop = Promise.resolve();
+const defaults = {
+ tagColors: new Map(),
+ tags: [],
+ showAutomatic: Zotero.Prefs.get('tagSelector.showAutomatic'),
+ searchString: '',
+ inScope: new Set(),
+ loaded: false
+};
+const { Cc, Ci } = require('chrome');
+
+Zotero.TagSelector = class TagSelectorContainer extends React.Component {
+ constructor(props) {
+ super(props);
+ this._notifierID = Zotero.Notifier.registerObserver(
+ this,
+ ['collection-item', 'item', 'item-tag', 'tag', 'setting'],
+ 'tagSelector'
+ );
+ this.displayAllTags = Zotero.Prefs.get('tagSelector.displayAllTags');
+ this.selectedTags = new Set();
+ this.state = defaults;
+ }
+
+ // Update trigger #1 (triggered by ZoteroPane)
+ async onItemViewChanged({collectionTreeRow, libraryID, tagsInScope}) {
+ this.collectionTreeRow = collectionTreeRow || this.collectionTreeRow;
+
+ let newState = {loaded: true};
+
+ if (!this.state.tagColors.length && libraryID && this.libraryID != libraryID) {
+ newState.tagColors = Zotero.Tags.getColors(libraryID);
+ }
+ this.libraryID = libraryID;
+
+ newState.tags = await this.getTags(tagsInScope,
+ this.state.tagColors.length ? this.state.tagColors : newState.tagColors);
+ this.setState(newState);
+ }
+
+ // Update trigger #2
+ async notify(event, type, ids, extraData) {
+ if (type === 'setting') {
+ if (ids.some(val => val.split('/')[1] == 'tagColors')) {
+ let tagColors = Zotero.Tags.getColors(this.libraryID);
+ this.state.tagColors = tagColors;
+ this.setState({tagColors, tags: await this.getTags(null, tagColors)});
+ }
+ return;
+ }
+
+ // Ignore anything other than deletes in duplicates view
+ if (this.collectionTreeRow.isDuplicates()) {
+ switch (event) {
+ case 'delete':
+ case 'trash':
+ break;
+
+ default:
+ return;
+ }
+ }
+
+ // Ignore item events other than 'trash'
+ if (type == 'item' && (event == 'trash')) {
+ return this.setState({tags: await this.getTags()});
+ }
+
+ // If a selected tag no longer exists, deselect it
+ if (type == 'item-tag') {
+ if (event == 'delete' || event == 'trash' || event == 'modify') {
+ for (let tag of this.selectedTags) {
+ if (tag == extraData[ids[0]].old.tag) {
+ this.selectedTags.delete(tag);
+ }
+ }
+ }
+ return this.setState({tags: await this.getTags()});
+ }
+
+ this.setState({tags: await this.getTags()});
+ }
+
+ async getTags(tagsInScope, tagColors) {
+ if (!tagsInScope) {
+ tagsInScope = await this.collectionTreeRow.getChildTags();
+ }
+ this.inScope = new Set(tagsInScope.map(t => t.tag));
+ let tags;
+ if (this.displayAllTags) {
+ tags = await Zotero.Tags.getAll(this.libraryID, [0, 1]);
+ } else {
+ tags = tagsInScope
+ }
+
+ tagColors = tagColors || this.state.tagColors;
+
+ // Add colored tags that aren't already real tags
+ let regularTags = new Set(tags.map(tag => tag.tag));
+ let coloredTags = Array.from(tagColors.keys());
+
+ coloredTags.filter(ct => !regularTags.has(ct)).forEach(x =>
+ tags.push(Zotero.Tags.cleanData({ tag: x }))
+ );
+
+ // Sort by name
+ tags.sort(function (a, b) {
+ let aColored = tagColors.has(a.tag),
+ bColored = tagColors.has(b.tag);
+ if (aColored && !bColored) return -1;
+ if (!aColored && bColored) return 1;
+ return Zotero.getLocaleCollation().compareString(1, a.tag, b.tag);
+ });
+
+ return tags;
+ }
+
+ render() {
+ let tags = this.state.tags;
+ let tagMap = new Map();
+ if (!this.showAutomatic) {
+ tags = tags.filter(t => t.type != 1)
+ } else {
+ // Ensure no duplicates from auto and manual tags
+ tags.forEach(t => !tagMap.has() && t.type != 1 && tagMap.set(t.tag, t));
+ tags = Array.from(tagMap.values());
+ }
+ if (this.state.searchString) {
+ tags = tags.filter(tag => !!tag.tag.match(new RegExp(this.state.searchString, 'i')));
+ }
+ tags = tags.map(t => {
+ let name = t.tag;
+ return {
+ name,
+ selected: this.selectedTags.has(name),
+ color: this.state.tagColors.has(name) ? this.state.tagColors.get(name).color : '',
+ disabled: !this.inScope.has(name)
+ }
+ });
+ return this.focusTextbox = ref && ref.focusTextbox}
+ searchString={this.state.searchString}
+ shouldFocus={this.state.shouldFocus}
+ dragObserver={this.dragObserver}
+ onSelect={this.state.viewOnly ? () => {} : this.handleTagSelected}
+ onTagContext={this.handleTagContext}
+ onSearch={this.handleSearch}
+ onSettings={this.handleSettings}
+ loaded={this.state.loaded}
+ />;
+ }
+
+ setMode(mode) {
+ this.state.viewOnly != (mode == 'view') && this.setState({viewOnly: mode == 'view'});
+ }
+
+ unregister() {
+ ReactDOM.unmountComponentAtNode(this.domEl);
+ if (this._notifierID) {
+ Zotero.Notifier.unregisterObserver(this._notifierID);
+ }
+ }
+
+ uninit() {
+ this.setState({searchString: ''});
+ this.selectedTags = new Set();
+ }
+
+ handleTagContext = (tag, ev) => {
+ let tagContextMenu = document.getElementById('tag-menu');
+ ev.preventDefault();
+ tagContextMenu.openPopup(null, null, ev.clientX+2, ev.clientY+2);
+ this.contextTag = tag;
+ }
+
+ handleSettings = (ev) => {
+ let settingsContextMenu = document.getElementById('tag-selector-view-settings-menu');
+ ev.preventDefault();
+ settingsContextMenu.openPopup(ev.target, 'end_before', 0, 0, true);
+ }
+
+ handleTagSelected = (tag) => {
+ let selectedTags = this.selectedTags;
+ if(selectedTags.has(tag)) {
+ selectedTags.delete(tag);
+ } else {
+ selectedTags.add(tag);
+ }
+
+ if (typeof(this.props.onSelection) === 'function') {
+ this.props.onSelection(selectedTags);
+ }
+ }
+
+ handleSearch = Zotero.Utilities.debounce((searchString) => {
+ this.setState({searchString});
+ })
+
+ dragObserver = {
+ onDragOver: function(event) {
+ if (!event.dataTransfer.getData('zotero/item')) {
+ return;
+ }
+
+ var elem = event.target;
+
+ // Ignore drops not on tags
+ if (elem.localName != 'li') {
+ return;
+ }
+
+ // Store the event, because drop event does not have shiftKey attribute set
+ Zotero.DragDrop.currentEvent = event;
+ elem.classList.add('dragged-over');
+ event.preventDefault();
+ event.dataTransfer.dropEffect = "copy";
+ },
+ onDragExit: function (event) {
+ Zotero.DragDrop.currentEvent = null;
+ event.target.classList.remove('dragged-over');
+ },
+ onDrop: async function(event) {
+ var elem = event.target;
+
+ // Ignore drops not on tags
+ if (elem.localName != 'li') {
+ return;
+ }
+
+ elem.classList.remove('dragged-over');
+
+ var dt = event.dataTransfer;
+ var ids = dt.getData('zotero/item');
+ if (!ids) {
+ return;
+ }
+
+ return Zotero.DB.executeTransaction(function* () {
+ ids = ids.split(',');
+ var items = Zotero.Items.get(ids);
+ var value = elem.textContent;
+
+ for (let i=0; i= Zotero.Tags.MAX_COLORED_TAGS && !tagColors.has(io.name)) {
+ var ps = Cc['@mozilla.org/embedcomp/prompt-service;1']
+ .getService(Ci.nsIPromptService);
+ ps.alert(null, '', Zotero.getString('pane.tagSelector.maxColoredTags', Zotero.Tags.MAX_COLORED_TAGS));
+ return;
+ }
+
+ 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;
+ }
+
+ await Zotero.Tags.setColor(this.libraryID, io.name, io.color, io.position);
+ }
+
+ async openRenamePrompt() {
+ var promptService = Cc['@mozilla.org/embedcomp/prompt-service;1']
+ .getService(Ci.nsIPromptService);
+
+ var newName = { value: this.contextTag.name };
+ var result = promptService.prompt(window,
+ Zotero.getString('pane.tagSelector.rename.title'),
+ Zotero.getString('pane.tagSelector.rename.message'),
+ newName, '', {});
+
+ if (!result || !newName.value || this.contextTag.name == newName.value) {
+ return;
+ }
+
+ let selectedTags = this.selectedTags;
+ if (selectedTags.has(this.contextTag.name)) {
+ var wasSelected = true;
+ selectedTags.delete(this.contextTag.name);
+ }
+
+ if (Zotero.Tags.getID(this.contextTag.name)) {
+ await Zotero.Tags.rename(this.libraryID, this.contextTag.name, newName.value);
+ }
+ // Colored tags don't need to exist, so in that case
+ // just rename the color setting
+ else {
+ let color = Zotero.Tags.getColor(this.libraryID, this.contextTag.name);
+ if (!color) {
+ throw new Error("Can't rename missing tag");
+ }
+ await Zotero.Tags.setColor(this.libraryID, this.contextTag.name, false);
+ await Zotero.Tags.setColor(this.libraryID, newName.value, color.color);
+ }
+
+ if (wasSelected) {
+ selectedTags.add(newName.value);
+ }
+ this.setState({tags: await this.getTags()})
+ }
+
+ async openDeletePrompt() {
+ var promptService = Cc['@mozilla.org/embedcomp/prompt-service;1']
+ .getService(Ci.nsIPromptService);
+
+ var confirmed = promptService.confirm(window,
+ Zotero.getString('pane.tagSelector.delete.title'),
+ Zotero.getString('pane.tagSelector.delete.message'));
+
+ if (!confirmed) {
+ return;
+ }
+
+ var tagID = Zotero.Tags.getID(this.contextTag.name);
+
+ if (tagID) {
+ await Zotero.Tags.removeFromLibrary(this.libraryID, tagID);
+ }
+ // If only a tag color setting, remove that
+ else {
+ await Zotero.Tags.setColor(this.libraryID, this.contextTag.name, false);
+ }
+
+ this.setState({tags: await this.getTags()});
+ }
+
+ async toggleDisplayAllTags(newValue) {
+ newValue = typeof(newValue) === 'undefined' ? !this.displayAllTags : newValue;
+ Zotero.Prefs.set('tagSelector.displayAllTags', newValue);
+ this.displayAllTags = newValue;
+ this.setState({tags: await this.getTags()});
+ }
+
+ toggleShowAutomatic(newValue) {
+ newValue = typeof(newValue) === 'undefined' ? !this.showAutomatic : newValue;
+ Zotero.Prefs.set('tagSelector.showAutomatic', newValue);
+ this.setState({showAutomatic: newValue});
+ }
+
+ deselectAll() {
+ this.selectedTags = new Set();
+ if('onSelection' in this.props && typeof(this.props.onSelection) === 'function') {
+ this.props.onSelection(this.selectedTags);
+ }
+ }
+
+ get label() {
+ let count = this.selectedTags.size;
+ let mod = count === 1 ? 'singular' : count === 0 ? 'none' : 'plural';
+
+ return Zotero.getString('pane.tagSelector.numSelected.' + mod, [count]);
+ }
+
+ get showAutomatic() {
+ return this.state.showAutomatic;
+ }
+
+ static init(domEl, opts) {
+ var ref;
+ let elem = (
+
+ ref = c } {...opts} />
+
+ );
+ ReactDOM.render(elem, domEl);
+ ref.domEl = domEl;
+ return ref;
+ }
+}
+})();
diff --git a/chrome/content/zotero/containers/tagSelector.xul b/chrome/content/zotero/containers/tagSelector.xul
new file mode 100644
index 0000000000..5022281054
--- /dev/null
+++ b/chrome/content/zotero/containers/tagSelector.xul
@@ -0,0 +1,58 @@
+
+
+ %globalDTD;
+ %zoteroDTD;
+]>
+
+
+
+
+
\ No newline at end of file
diff --git a/chrome/content/zotero/include.js b/chrome/content/zotero/include.js
index 45e4fd4973..fcb0858de6 100644
--- a/chrome/content/zotero/include.js
+++ b/chrome/content/zotero/include.js
@@ -7,4 +7,9 @@ var Zotero = Components.classes['@zotero.org/Zotero;1']
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
-Components.utils.import('resource://zotero/require.js');
\ No newline at end of file
+// Components.utils.import('resource://zotero/require.js');
+// Not using Cu.import here since we don't want the require module to be cached
+// for includes within ZoteroPane or other code, where we want the window instance available to modules.
+Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Components.interfaces.mozIJSSubScriptLoader)
+ .loadSubScript('resource://zotero/require.js');
diff --git a/chrome/content/zotero/standalone/standalone.js b/chrome/content/zotero/standalone/standalone.js
index 1b34a3acd6..d81330de88 100644
--- a/chrome/content/zotero/standalone/standalone.js
+++ b/chrome/content/zotero/standalone/standalone.js
@@ -47,7 +47,7 @@ const ZoteroStandalone = new function() {
}
return Zotero.initializationPromise;
})
- .then(function () {
+ .then(async function () {
if (Zotero.Prefs.get('devtools.errorconsole.enabled', true)) {
document.getElementById('menu_errorConsole').hidden = false;
}
@@ -60,6 +60,7 @@ const ZoteroStandalone = new function() {
ZoteroStandalone.DebugOutput.init();
Zotero.hideZoteroPaneOverlays();
+ await ZoteroPane.Containers.init();
ZoteroPane.init();
ZoteroPane.makeVisible();
diff --git a/chrome/content/zotero/standalone/standalone.xul b/chrome/content/zotero/standalone/standalone.xul
index dd381dc7f6..ff548bee21 100644
--- a/chrome/content/zotero/standalone/standalone.xul
+++ b/chrome/content/zotero/standalone/standalone.xul
@@ -46,7 +46,8 @@
windowtype="navigator:browser"
title="&brandShortName;"
width="1000" height="600"
- persist="screenX screenY width height sizemode">
+ persist="screenX screenY width height sizemode">
+
@@ -287,4 +288,7 @@
+
+
+
diff --git a/chrome/content/zotero/xpcom/connector/server_connector.js b/chrome/content/zotero/xpcom/connector/server_connector.js
index dd891125ed..c80293b25a 100644
--- a/chrome/content/zotero/xpcom/connector/server_connector.js
+++ b/chrome/content/zotero/xpcom/connector/server_connector.js
@@ -742,9 +742,10 @@ Zotero.Server.Connector.SaveItems.prototype = {
session,
targetID,
requestData,
- function (topLevelItems) {
+ function (jsonItems, items) {
+ session.addItems(items);
// Only return the properties the connector needs
- topLevelItems = topLevelItems.map((item) => {
+ jsonItems = jsonItems.map((item) => {
let o = {
id: item.id,
title: item.title,
@@ -764,7 +765,7 @@ Zotero.Server.Connector.SaveItems.prototype = {
};
return o;
});
- resolve([201, "application/json", JSON.stringify({items: topLevelItems})]);
+ resolve([201, "application/json", JSON.stringify({items: jsonItems})]);
}
)
// Add items to session once all attachments have been saved
diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js
index eedd85b76d..704033b71d 100644
--- a/chrome/content/zotero/xpcom/itemTreeView.js
+++ b/chrome/content/zotero/xpcom/itemTreeView.js
@@ -1850,7 +1850,7 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
// Clear the quick search and tag selection and try again (once)
if (!noRecurse && this.window.ZoteroPane) {
let cleared1 = yield this.window.ZoteroPane.clearQuicksearch();
- let cleared2 = this.window.ZoteroPane.clearTagSelection();
+ let cleared2 = this.window.ZoteroPane.tagSelector.clearTagSelection();
if (cleared1 || cleared2) {
return this.selectItem(id, expand, true);
}
diff --git a/chrome/content/zotero/xpcom/translation/translate_item.js b/chrome/content/zotero/xpcom/translation/translate_item.js
index b4336c0600..b1cd971849 100644
--- a/chrome/content/zotero/xpcom/translation/translate_item.js
+++ b/chrome/content/zotero/xpcom/translation/translate_item.js
@@ -179,7 +179,7 @@ Zotero.Translate.ItemSaver.prototype = {
}.bind(this));
if (itemsDoneCallback) {
- itemsDoneCallback(items.map(item => jsonByItem.get(item)));
+ itemsDoneCallback(items.map(item => jsonByItem.get(item)), items);
}
// Save standalone attachments
diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js
index f1353e5408..a00e3fcda3 100644
--- a/chrome/content/zotero/xpcom/utilities.js
+++ b/chrome/content/zotero/xpcom/utilities.js
@@ -144,6 +144,25 @@ var CSL_TYPE_MAPPINGS = {
* @class Functions for text manipulation and other miscellaneous purposes
*/
Zotero.Utilities = {
+ /**
+ * Returns a function which will execute `fn` with provided arguments after `delay` milliseconds and not more
+ * than once, if called multiple times. See
+ * http://stackoverflow.com/questions/24004791/can-someone-explain-the-debounce-function-in-javascript
+ * @param fn {Function} function to debounce
+ * @param delay {Integer} number of miliseconds to delay the function execution
+ * @returns {Function}
+ */
+ debounce: function(fn, delay=500) {
+ var timer = null;
+ return function () {
+ let args = arguments;
+ clearTimeout(timer);
+ timer = setTimeout(function () {
+ fn.apply(this, args);
+ }.bind(this), delay);
+ };
+ },
+
/**
* Fixes author name capitalization.
* Currently for all uppercase names only
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index 1b597f2f6d..f66afa2c46 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -58,8 +58,6 @@ var ZoteroPane = new function()
this.document = document;
- const COLLECTIONS_HEIGHT = 32; // minimum height of the collections pane and toolbar
-
var self = this,
_loaded = false, _madeVisible = false,
titlebarcolorState, titleState, observerService,
@@ -145,6 +143,8 @@ var ZoteroPane = new function()
Zotero.hiDPI = window.devicePixelRatio > 1;
Zotero.hiDPISuffix = Zotero.hiDPI ? "@2x" : "";
+ ZoteroPane_Local.Containers.loadPane();
+
ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
// Add a default progress window
@@ -171,11 +171,6 @@ var ZoteroPane = new function()
itemsTree.addEventListener("mousedown", ZoteroPane_Local.onTreeMouseDown, true);
itemsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
- var tagSelector = document.getElementById('zotero-tag-selector');
- tagSelector.onchange = function () {
- return ZoteroPane_Local.updateTagFilter();
- };
-
Zotero.Keys.windowInit(document);
if (Zotero.restoreFromServer) {
@@ -351,8 +346,7 @@ var ZoteroPane = new function()
this.serializePersist();
}
- var tagSelector = document.getElementById('zotero-tag-selector');
- tagSelector.unregister();
+ ZoteroPane_Local.Containers.destroy();
if(this.collectionsView) this.collectionsView.unregister();
if(this.itemsView) this.itemsView.unregister();
@@ -395,7 +389,6 @@ var ZoteroPane = new function()
this.unserializePersist();
this.updateLayout();
this.updateToolbarPosition();
- this.updateTagSelectorSize();
// restore saved row selection (for tab switching)
// TODO: Remove now that no tab mode?
@@ -1100,100 +1093,25 @@ var ZoteroPane = new function()
var showing = tagSelector.getAttribute('collapsed') == 'true';
tagSelector.setAttribute('collapsed', !showing);
- this.updateTagSelectorSize();
// If showing, set scope to items in current view
// and focus filter textbox
if (showing) {
yield this.setTagScope();
- tagSelector.focusTextbox();
+ ZoteroPane_Local.tagSelector.focusTextbox();
}
// If hiding, clear selection
else {
- tagSelector.uninit();
+ ZoteroPane_Local.tagSelector.uninit();
}
});
-
- this.updateTagSelectorSize = function () {
- //Zotero.debug('Updating tag selector size');
- var zoteroPane = document.getElementById('zotero-pane-stack');
- var splitter = document.getElementById('zotero-tags-splitter');
- var tagSelector = document.getElementById('zotero-tag-selector');
-
- // Nothing should be bigger than appcontent's height
- var max = document.getElementById('appcontent').boxObject.height
- - splitter.boxObject.height;
-
- // Shrink tag selector to appcontent's height
- var maxTS = max - COLLECTIONS_HEIGHT;
- if (parseInt(tagSelector.getAttribute("height")) > maxTS) {
- //Zotero.debug("Limiting tag selector height to appcontent");
- tagSelector.setAttribute('height', maxTS);
- }
-
- var height = tagSelector.boxObject.height;
-
-
- /*Zotero.debug("tagSelector.boxObject.height: " + tagSelector.boxObject.height);
- Zotero.debug("tagSelector.getAttribute('height'): " + tagSelector.getAttribute('height'));
- Zotero.debug("zoteroPane.boxObject.height: " + zoteroPane.boxObject.height);
- Zotero.debug("zoteroPane.getAttribute('height'): " + zoteroPane.getAttribute('height'));*/
-
-
- // Don't let the Z-pane jump back down to its previous height
- // (if shrinking or hiding the tag selector let it clear the min-height)
- if (zoteroPane.getAttribute('height') < zoteroPane.boxObject.height) {
- //Zotero.debug("Setting Zotero pane height attribute to " + zoteroPane.boxObject.height);
- zoteroPane.setAttribute('height', zoteroPane.boxObject.height);
- }
-
- if (tagSelector.getAttribute('collapsed') == 'true') {
- // 32px is the default Z pane min-height in overlay.css
- height = 32;
- }
- else {
- // tS.boxObject.height doesn't exist at startup, so get from attribute
- if (!height) {
- height = parseInt(tagSelector.getAttribute('height'));
- }
- // 121px seems to be enough room for the toolbar and collections
- // tree at minimum height
- height = height + COLLECTIONS_HEIGHT;
- }
-
- //Zotero.debug('Setting Zotero pane minheight to ' + height);
- zoteroPane.setAttribute('minheight', height);
-
- if (this.isShowing() && !this.isFullScreen()) {
- zoteroPane.setAttribute('savedHeight', zoteroPane.boxObject.height);
- }
-
- // Fix bug whereby resizing the Z pane downward after resizing
- // the tag selector up and then down sometimes caused the Z pane to
- // stay at a fixed size and get pushed below the bottom
- tagSelector.height++;
- tagSelector.height--;
- }
-
-
- function getTagSelection() {
- var tagSelector = document.getElementById('zotero-tag-selector');
- return tagSelector.selection ? tagSelector.selection : new Set();
- }
-
-
- this.clearTagSelection = function () {
- document.getElementById('zotero-tag-selector').deselectAll();
- }
-
-
/*
* Sets the tag filter on the items view
*/
this.updateTagFilter = Zotero.Promise.coroutine(function* () {
if (this.itemsView) {
- yield this.itemsView.setFilter('tags', getTagSelection());
+ yield this.itemsView.setFilter('tags', ZoteroPane_Local.tagSelector.getTagSelection());
}
});
@@ -1212,23 +1130,23 @@ var ZoteroPane = new function()
*
* Passed to the items tree to trigger on changes
*/
- this.setTagScope = Zotero.Promise.coroutine(function* () {
- var collectionTreeRow = this.getCollectionTreeRow();
- var tagSelector = document.getElementById('zotero-tag-selector');
- if (this.tagSelectorShown()) {
+ this.setTagScope = async function () {
+ var collectionTreeRow = self.getCollectionTreeRow();
+ if (self.tagSelectorShown()) {
Zotero.debug('Updating tag selector with current tags');
if (collectionTreeRow.editable) {
- tagSelector.mode = 'edit';
+ ZoteroPane_Local.tagSelector.setMode('edit');
}
else {
- tagSelector.mode = 'view';
+ ZoteroPane_Local.tagSelector.setMode('view');
}
- tagSelector.collectionTreeRow = collectionTreeRow;
- tagSelector.updateScope = () => this.setTagScope();
- tagSelector.libraryID = collectionTreeRow.ref.libraryID;
- tagSelector.scope = yield collectionTreeRow.getChildTags();
+ ZoteroPane_Local.tagSelector.onItemViewChanged({
+ collectionTreeRow,
+ libraryID: collectionTreeRow.ref.libraryID,
+ tagsInScope: await collectionTreeRow.getChildTags()
+ });
}
- });
+ };
this.onCollectionSelected = function () {
@@ -1280,7 +1198,7 @@ var ZoteroPane = new function()
}*/
collectionTreeRow.setSearch('');
- collectionTreeRow.setTags(getTagSelection());
+ collectionTreeRow.setTags(ZoteroPane_Local.tagSelector.getTagSelection());
this._updateToolbarIconsForRow(collectionTreeRow);
@@ -1294,17 +1212,7 @@ var ZoteroPane = new function()
ZoteroPane_Local.displayErrorMessage();
};
this.itemsView.onRefresh.addListener(() => this.setTagScope());
- if (this.tagSelectorShown()) {
- let tagSelector = document.getElementById('zotero-tag-selector')
- let handler = function () {
- tagSelector.removeEventListener('refresh', handler);
- Zotero.uiIsReady();
- };
- tagSelector.addEventListener('refresh', handler);
- }
- else {
- this.itemsView.onLoad.addListener(() => Zotero.uiIsReady());
- }
+ this.itemsView.onLoad.addListener(() => Zotero.uiIsReady());
// If item data not yet loaded for library, load it now.
// Other data types are loaded at startup
@@ -4910,8 +4818,10 @@ var ZoteroPane = new function()
var itemsToolbar = document.getElementById("zotero-items-toolbar");
var itemPane = document.getElementById("zotero-item-pane");
var itemToolbar = document.getElementById("zotero-item-toolbar");
+ var tagSelector = document.getElementById("zotero-tag-selector");
collectionsToolbar.style.width = collectionsPane.boxObject.width + 'px';
+ tagSelector.style.maxWidth = collectionsPane.boxObject.width + 'px';
if (stackedLayout || itemPane.collapsed) {
// The itemsToolbar and itemToolbar share the same space, and it seems best to use some flex attribute from right (because there might be other icons appearing or vanishing).
diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul
index adff5bfeae..590fa490d2 100644
--- a/chrome/content/zotero/zoteroPane.xul
+++ b/chrome/content/zotero/zoteroPane.xul
@@ -27,6 +27,7 @@
+
%globalDTD;
@@ -35,7 +36,8 @@
]>
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
@@ -304,7 +306,7 @@
ondragover="return ZoteroPane_Local.collectionsView.onDragOver(event)"
ondrop="return ZoteroPane_Local.collectionsView.onDrop(event)"/>
-
@@ -314,7 +316,9 @@
TODO: deal with this some other way?
-->
-
+
+
+
=0.10.0"
@@ -3047,15 +2994,10 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true
},
- "jquery": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
- "integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI="
- },
"js-base64": {
- "version": "2.4.5",
- "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz",
- "integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ==",
+ "version": "2.4.9",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz",
+ "integrity": "sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ==",
"dev": true
},
"js-tokens": {
@@ -3067,8 +3009,7 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
- "dev": true,
- "optional": true
+ "dev": true
},
"jsesc": {
"version": "1.3.0",
@@ -3082,6 +3023,12 @@
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
"dev": true
},
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
"json-stable-stringify": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz",
@@ -3130,12 +3077,6 @@
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
"dev": true
},
- "jsonpointer": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
- "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
- "dev": true
- },
"jspath": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/jspath/-/jspath-0.4.0.tgz",
@@ -3143,23 +3084,15 @@
"dev": true
},
"jsprim": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
- "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"dev": true,
"requires": {
"assert-plus": "1.0.0",
- "extsprintf": "1.0.2",
+ "extsprintf": "1.3.0",
"json-schema": "0.2.3",
- "verror": "1.3.6"
- },
- "dependencies": {
- "assert-plus": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
- "dev": true
- }
+ "verror": "1.10.0"
}
},
"jsx-ast-utils": {
@@ -3223,25 +3156,6 @@
"astw": "^2.0.0"
}
},
- "libzotero": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/libzotero/-/libzotero-0.1.13.tgz",
- "integrity": "sha1-QQhFtsHa9jmSZwIfnLw0t8SBuzI=",
- "requires": {
- "es6-promise": "^3.1.2",
- "node-fetch": "^1.6.0",
- "spark-md5": "^2.0.2",
- "striptags": "^2.1.1",
- "whatwg-fetch": "^1.0.0"
- },
- "dependencies": {
- "whatwg-fetch": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-1.1.1.tgz",
- "integrity": "sha1-rDydOfMgxtzlM5lp0FTvQ90zMxk="
- }
- }
- },
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@@ -3256,14 +3170,10 @@
}
},
"lodash": {
- "version": "4.17.4",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
- "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
- },
- "lodash-es": {
- "version": "4.17.4",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz",
- "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc="
+ "version": "4.17.11",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
+ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
+ "dev": true
},
"lodash._baseassign": {
"version": "3.2.0",
@@ -3287,11 +3197,6 @@
"integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=",
"dev": true
},
- "lodash._basefor": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz",
- "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI="
- },
"lodash._getnative": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
@@ -3336,22 +3241,14 @@
"lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
- "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo="
+ "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
+ "dev": true
},
"lodash.isarray": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
- "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U="
- },
- "lodash.isplainobject": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz",
- "integrity": "sha1-moI4rhayAEMpYM1zRlEtASP79MU=",
- "requires": {
- "lodash._basefor": "^3.0.0",
- "lodash.isarguments": "^3.0.0",
- "lodash.keysin": "^3.0.0"
- }
+ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
+ "dev": true
},
"lodash.keys": {
"version": "3.1.2",
@@ -3364,15 +3261,6 @@
"lodash.isarray": "^3.0.0"
}
},
- "lodash.keysin": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz",
- "integrity": "sha1-IsRJPrvtsUJ5YqVLRFssinZ/tH8=",
- "requires": {
- "lodash.isarguments": "^3.0.0",
- "lodash.isarray": "^3.0.0"
- }
- },
"lodash.memoize": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
@@ -3410,13 +3298,13 @@
}
},
"lru-cache": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
- "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==",
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.4.tgz",
+ "integrity": "sha512-EPstzZ23znHUVLKj+lcXO1KvZkrlw+ZirdwvOmnAnA/1PB4ggyXJ77LRkCqkff+ShQ+cqoxCxLQOh4cKITO5iA==",
"dev": true,
"requires": {
"pseudomap": "^1.0.2",
- "yallist": "^2.1.2"
+ "yallist": "^3.0.2"
}
},
"map-obj": {
@@ -3425,6 +3313,12 @@
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
"dev": true
},
+ "math-random": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz",
+ "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=",
+ "dev": true
+ },
"md5.js": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
@@ -3505,18 +3399,18 @@
}
},
"mime-db": {
- "version": "1.27.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
- "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=",
+ "version": "1.37.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
+ "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==",
"dev": true
},
"mime-types": {
- "version": "2.1.15",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
- "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
+ "version": "2.1.21",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
+ "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
"dev": true,
"requires": {
- "mime-db": "~1.27.0"
+ "mime-db": "~1.37.0"
}
},
"minimalistic-assert": {
@@ -3575,6 +3469,15 @@
"supports-color": "3.1.2"
},
"dependencies": {
+ "debug": {
+ "version": "2.6.8",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+ "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
"glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
@@ -3645,8 +3548,7 @@
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
- "dev": true,
- "optional": true
+ "dev": true
},
"nise": {
"version": "1.3.2",
@@ -3665,15 +3567,16 @@
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz",
"integrity": "sha512-j8XsFGCLw79vWXkZtMSmmLaOk9z5SQ9bV/tkbZVCqvgwzrjAGq66igobLofHtF63NvMTp2WjytpsNTGKa+XRIQ==",
+ "dev": true,
"requires": {
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
}
},
"node-gyp": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.7.0.tgz",
- "integrity": "sha512-qDQE/Ft9xXP6zphwx4sD0t+VhwV7yFaloMpfbL2QnnDZcyaiakWlLdtFGGQfTAwpFHdpbRhRxVhIHN1OKAjgbg==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
+ "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
"dev": true,
"requires": {
"fstream": "^1.0.0",
@@ -3683,17 +3586,25 @@
"nopt": "2 || 3",
"npmlog": "0 || 1 || 2 || 3 || 4",
"osenv": "0",
- "request": ">=2.9.0 <2.82.0",
+ "request": "^2.87.0",
"rimraf": "2",
"semver": "~5.3.0",
"tar": "^2.0.0",
"which": "1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.3.0",
+ "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+ "dev": true
+ }
}
},
"node-sass": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.0.tgz",
- "integrity": "sha512-QFHfrZl6lqRU3csypwviz2XLgGNOoWQbo2GOvtsfQqOfL4cy1BtWnhx/XUeAO9LT3ahBzSRXcEO6DdvAH9DzSg==",
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz",
+ "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==",
"dev": true,
"requires": {
"async-foreach": "^0.1.3",
@@ -3709,78 +3620,12 @@
"meow": "^3.7.0",
"mkdirp": "^0.5.1",
"nan": "^2.10.0",
- "node-gyp": "^3.3.1",
+ "node-gyp": "^3.8.0",
"npmlog": "^4.0.0",
- "request": "~2.79.0",
+ "request": "^2.88.0",
"sass-graph": "^2.2.4",
"stdout-stream": "^1.4.0",
"true-case-path": "^1.0.2"
- },
- "dependencies": {
- "caseless": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
- "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
- "dev": true
- },
- "har-validator": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
- "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
- "dev": true,
- "requires": {
- "chalk": "^1.1.1",
- "commander": "^2.9.0",
- "is-my-json-valid": "^2.12.4",
- "pinkie-promise": "^2.0.0"
- }
- },
- "nan": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
- "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
- "dev": true
- },
- "qs": {
- "version": "6.3.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz",
- "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=",
- "dev": true
- },
- "request": {
- "version": "2.79.0",
- "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
- "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
- "dev": true,
- "requires": {
- "aws-sign2": "~0.6.0",
- "aws4": "^1.2.1",
- "caseless": "~0.11.0",
- "combined-stream": "~1.0.5",
- "extend": "~3.0.0",
- "forever-agent": "~0.6.1",
- "form-data": "~2.1.1",
- "har-validator": "~2.0.6",
- "hawk": "~3.1.3",
- "http-signature": "~1.1.0",
- "is-typedarray": "~1.0.0",
- "isstream": "~0.1.2",
- "json-stringify-safe": "~5.0.1",
- "mime-types": "~2.1.7",
- "oauth-sign": "~0.8.1",
- "qs": "~6.3.0",
- "stringstream": "~0.0.4",
- "tough-cookie": "~2.3.0",
- "tunnel-agent": "~0.4.1",
- "uuid": "^3.0.0"
- }
- },
- "tunnel-agent": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
- "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
- "dev": true
- }
}
},
"nopt": {
@@ -3814,9 +3659,9 @@
}
},
"npmlog": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz",
- "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"requires": {
"are-we-there-yet": "~1.1.2",
@@ -3832,9 +3677,9 @@
"dev": true
},
"oauth-sign": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
- "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true
},
"object-assign": {
@@ -3895,9 +3740,9 @@
"dev": true
},
"osenv": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
- "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"dev": true,
"requires": {
"os-homedir": "^1.0.0",
@@ -4034,9 +3879,9 @@
}
},
"performance-now": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
- "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
"pify": {
@@ -4088,17 +3933,18 @@
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "dev": true,
"requires": {
"asap": "~2.0.3"
}
},
"prop-types": {
- "version": "15.5.10",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz",
- "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=",
+ "version": "15.6.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
+ "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
"requires": {
- "fbjs": "^0.8.9",
- "loose-envify": "^1.3.1"
+ "loose-envify": "^1.3.1",
+ "object-assign": "^4.1.1"
}
},
"pseudomap": {
@@ -4107,6 +3953,12 @@
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
},
+ "psl": {
+ "version": "1.1.29",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
+ "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==",
+ "dev": true
+ },
"public-encrypt": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz",
@@ -4127,19 +3979,11 @@
"dev": true
},
"qs": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
- "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
},
- "query-string": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-3.0.3.tgz",
- "integrity": "sha1-ri4UtNBQcdTpuetIc8NbDc1C5jg=",
- "requires": {
- "strict-uri-encode": "^1.0.0"
- }
- },
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
@@ -4152,47 +3996,6 @@
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
"dev": true
},
- "randomatic": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
- "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
- "dev": true,
- "requires": {
- "is-number": "^3.0.0",
- "kind-of": "^4.0.0"
- },
- "dependencies": {
- "is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "kind-of": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
- "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
"randombytes": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz",
@@ -4213,61 +4016,37 @@
}
},
"react": {
- "version": "15.6.2",
- "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz",
- "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=",
+ "version": "16.6.3",
+ "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz",
+ "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==",
"requires": {
- "create-react-class": "^15.6.0",
- "fbjs": "^0.8.9",
"loose-envify": "^1.1.0",
- "object-assign": "^4.1.0",
- "prop-types": "^15.5.10"
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2",
+ "scheduler": "^0.11.2"
}
},
"react-dom": {
- "version": "15.6.2",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz",
- "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=",
+ "version": "16.6.3",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz",
+ "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==",
"requires": {
- "fbjs": "^0.8.9",
"loose-envify": "^1.1.0",
- "object-assign": "^4.1.0",
- "prop-types": "^15.5.10"
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2",
+ "scheduler": "^0.11.2"
}
},
- "react-redux": {
- "version": "4.4.8",
- "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-4.4.8.tgz",
- "integrity": "sha1-57wd0QDotk6WrIIS2xEyObni4I8=",
+ "react-intl": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.7.2.tgz",
+ "integrity": "sha512-3dcNGLqEw2FKkX+1L2WYLgjP0MVJkvWuVd1uLcnwifIQe8JQvnd9Bss4hb4Gvg/YhBIRcs4LM6C2bAgyklucjw==",
"requires": {
- "create-react-class": "^15.5.1",
- "hoist-non-react-statics": "^1.0.3",
- "invariant": "^2.0.0",
- "lodash": "^4.2.0",
- "loose-envify": "^1.1.0",
- "prop-types": "^15.5.4"
- }
- },
- "react-router": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-2.8.1.tgz",
- "integrity": "sha1-c+lJH2zrMW0Pd5gpCBhj43juTtc=",
- "requires": {
- "history": "^2.1.2",
- "hoist-non-react-statics": "^1.2.0",
- "invariant": "^2.2.1",
- "loose-envify": "^1.2.0",
- "warning": "^3.0.0"
- },
- "dependencies": {
- "warning": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
- "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
- "requires": {
- "loose-envify": "^1.0.0"
- }
- }
+ "hoist-non-react-statics": "^2.5.5",
+ "intl-format-cache": "^2.0.5",
+ "intl-messageformat": "^2.1.0",
+ "intl-relativeformat": "^2.1.0",
+ "invariant": "^2.1.1"
}
},
"read-only-stream": {
@@ -4337,46 +4116,6 @@
"strip-indent": "^1.0.1"
}
},
- "redux": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.0.tgz",
- "integrity": "sha512-GHjaOkEQtQnnuLoYPFkRKHIqs1i1tdTlisu/xUHfk2juzCobSy4STxs4Lz5bPkc07Owb6BeGKx/r76c9IVTkOw==",
- "requires": {
- "lodash": "^4.2.1",
- "lodash-es": "^4.2.1",
- "loose-envify": "^1.1.0",
- "symbol-observable": "^1.0.3"
- }
- },
- "redux-logger": {
- "version": "2.10.2",
- "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-2.10.2.tgz",
- "integrity": "sha1-PFpfCm8yV3wd6t9mVfJX+CxsOTc=",
- "requires": {
- "deep-diff": "0.3.4"
- }
- },
- "redux-promise": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/redux-promise/-/redux-promise-0.5.3.tgz",
- "integrity": "sha1-6X5snTvzdurLebq+bZBtogES1tg=",
- "requires": {
- "flux-standard-action": "^0.6.1"
- }
- },
- "redux-router": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/redux-router/-/redux-router-2.1.2.tgz",
- "integrity": "sha1-A0ckutCPQbr3VNtNIuLYWK22TxE=",
- "requires": {
- "deep-equal": "^1.0.1"
- }
- },
- "redux-thunk": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz",
- "integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU="
- },
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
@@ -4421,33 +4160,39 @@
}
},
"request": {
- "version": "2.81.0",
- "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
- "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
+ "version": "2.88.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+ "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"dev": true,
"requires": {
- "aws-sign2": "~0.6.0",
- "aws4": "^1.2.1",
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
"caseless": "~0.12.0",
- "combined-stream": "~1.0.5",
- "extend": "~3.0.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
"forever-agent": "~0.6.1",
- "form-data": "~2.1.1",
- "har-validator": "~4.2.1",
- "hawk": "~3.1.3",
- "http-signature": "~1.1.0",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.0",
+ "http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
- "mime-types": "~2.1.7",
- "oauth-sign": "~0.8.1",
- "performance-now": "^0.2.0",
- "qs": "~6.4.0",
- "safe-buffer": "^5.0.1",
- "stringstream": "~0.0.4",
- "tough-cookie": "~2.3.0",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.4.3",
"tunnel-agent": "^0.6.0",
- "uuid": "^3.0.0"
+ "uuid": "^3.3.2"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ }
}
},
"require-directory": {
@@ -4472,9 +4217,9 @@
}
},
"rimraf": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
- "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=",
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"dev": true,
"requires": {
"glob": "^7.0.5"
@@ -4496,6 +4241,12 @@
"integrity": "sha512-aSLEDudu6OoRr/2rU609gRmnYboRLxgDG1z9o2Q0os7236FwvcqIOO8r8U5JUEwivZOhDaKlFO4SbPTJYyBEyQ==",
"dev": true
},
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
"samsam": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz",
@@ -4514,6 +4265,15 @@
"yargs": "^7.0.0"
}
},
+ "scheduler": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz",
+ "integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
"scss-tokenizer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
@@ -4536,9 +4296,9 @@
}
},
"semver": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
- "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
"dev": true
},
"set-blocking": {
@@ -4556,7 +4316,8 @@
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
- "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+ "dev": true
},
"sha.js": {
"version": "2.4.9",
@@ -4640,15 +4401,6 @@
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
"dev": true
},
- "sntp": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
- "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
- "dev": true,
- "requires": {
- "hoek": "2.x.x"
- }
- },
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -4664,15 +4416,10 @@
"source-map": "^0.5.6"
}
},
- "spark-md5": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-2.0.2.tgz",
- "integrity": "sha1-N7djhHdjrn56zvLKUjPQHmSaeLc="
- },
"spdx-correct": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
- "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz",
+ "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==",
"dev": true,
"requires": {
"spdx-expression-parse": "^3.0.0",
@@ -4680,9 +4427,9 @@
}
},
"spdx-exceptions": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz",
- "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+ "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
"dev": true
},
"spdx-expression-parse": {
@@ -4696,15 +4443,15 @@
}
},
"spdx-license-ids": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz",
- "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz",
+ "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg==",
"dev": true
},
"sshpk": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
- "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
+ "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==",
"dev": true,
"requires": {
"asn1": "~0.2.3",
@@ -4714,21 +4461,14 @@
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
- },
- "dependencies": {
- "assert-plus": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
- "dev": true
- }
}
},
"stdout-stream": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz",
- "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
+ "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
"dev": true,
"requires": {
"readable-stream": "^2.0.1"
@@ -4777,11 +4517,6 @@
"readable-stream": "^2.0.2"
}
},
- "strict-uri-encode": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
- "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
- },
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
@@ -4810,12 +4545,6 @@
}
}
},
- "stringstream": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
- "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
- "dev": true
- },
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
@@ -4843,11 +4572,6 @@
"get-stdin": "^4.0.1"
}
},
- "striptags": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/striptags/-/striptags-2.2.1.tgz",
- "integrity": "sha1-TEULcI1BuL85zyTEn/I0/Gqr/TI="
- },
"subarg": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
@@ -4871,11 +4595,6 @@
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
},
- "symbol-observable": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz",
- "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0="
- },
"syntax-error": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.3.0.tgz",
@@ -4940,11 +4659,12 @@
"dev": true
},
"tough-cookie": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
- "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+ "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"dev": true,
"requires": {
+ "psl": "^1.1.24",
"punycode": "^1.4.1"
}
},
@@ -4961,27 +4681,12 @@
"dev": true
},
"true-case-path": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz",
- "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
+ "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
"dev": true,
"requires": {
- "glob": "^6.0.4"
- },
- "dependencies": {
- "glob": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
- "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
- "dev": true,
- "requires": {
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "2 || 3",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- }
+ "glob": "^7.1.2"
}
},
"tty-browserify": {
@@ -5003,8 +4708,7 @@
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
- "dev": true,
- "optional": true
+ "dev": true
},
"type-detect": {
"version": "4.0.3",
@@ -5021,7 +4725,8 @@
"ua-parser-js": {
"version": "0.7.12",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.12.tgz",
- "integrity": "sha1-BMgamb3V3FImPqKdJMa/jUgYpLs="
+ "integrity": "sha1-BMgamb3V3FImPqKdJMa/jUgYpLs=",
+ "dev": true
},
"umd": {
"version": "3.0.1",
@@ -5035,6 +4740,23 @@
"integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=",
"dev": true
},
+ "uri-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ }
+ }
+ },
"url": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
@@ -5077,15 +4799,15 @@
"dev": true
},
"uuid": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
- "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"dev": true
},
"validate-npm-package-license": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz",
- "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"requires": {
"spdx-correct": "^3.0.0",
@@ -5093,12 +4815,14 @@
}
},
"verror": {
- "version": "1.3.6",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
- "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"dev": true,
"requires": {
- "extsprintf": "1.0.2"
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
}
},
"vm-browserify": {
@@ -5110,23 +4834,11 @@
"indexof": "0.0.1"
}
},
- "w3c-xmlhttprequest": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/w3c-xmlhttprequest/-/w3c-xmlhttprequest-2.1.2.tgz",
- "integrity": "sha1-8LyJN1YoKOLCubQDZkhqE1H+QaA="
- },
- "warning": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/warning/-/warning-2.1.0.tgz",
- "integrity": "sha1-ISINnGOvx3qMkhEeARr3Bc4MaQE=",
- "requires": {
- "loose-envify": "^1.0.0"
- }
- },
"whatwg-fetch": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
- "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
+ "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=",
+ "dev": true
},
"which": {
"version": "1.3.1",
@@ -5144,12 +4856,12 @@
"dev": true
},
"wide-align": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
- "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"dev": true,
"requires": {
- "string-width": "^1.0.2"
+ "string-width": "^1.0.2 || 2"
}
},
"wrap-ansi": {
@@ -5181,9 +4893,9 @@
"dev": true
},
"yallist": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
- "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"dev": true
},
"yargs": {
@@ -5231,28 +4943,6 @@
"dev": true
}
}
- },
- "zotero-web-library": {
- "version": "0.9.4-alpha",
- "resolved": "https://registry.npmjs.org/zotero-web-library/-/zotero-web-library-0.9.4-alpha.tgz",
- "integrity": "sha1-AbLMXXqmsDEbhNZeLsc6llBdqS4=",
- "requires": {
- "bootstrap-sass": "^3.3.6",
- "floatthead": "1.4.0",
- "history": "<3.0.0",
- "jquery": "^2.2.4",
- "libzotero": "^0.1.8",
- "react": "^15.3.2",
- "react-dom": "^15.3.2",
- "react-redux": "^4.4.5",
- "react-router": "<3.0.0",
- "redux": "^3.6.0",
- "redux-logger": "^2.6.1",
- "redux-promise": "^0.5.3",
- "redux-router": "^2.1.2",
- "redux-thunk": "^2.1.0",
- "w3c-xmlhttprequest": "^2.1.0"
- }
}
}
}
diff --git a/package.json b/package.json
index e78efebf7b..0163ffdd7a 100644
--- a/package.json
+++ b/package.json
@@ -16,21 +16,23 @@
"license": "",
"dependencies": {
"bluebird": "^3.5.1",
- "react": "^15.6.2",
- "react-dom": "^15.6.2",
- "zotero-web-library": "^0.9.4-alpha"
+ "classnames": "^2.2.6",
+ "prop-types": "^15.6.2",
+ "react": "^16.6.3",
+ "react-dom": "^16.6.3",
+ "react-intl": "^2.7.2"
},
"devDependencies": {
- "babel-core": "^6.26.0",
+ "babel-core": "^6.26.3",
"babel-plugin-syntax-async-generators": "^6.13.0",
- "babel-plugin-syntax-class-properties": "^6.13.0",
"babel-plugin-syntax-decorators": "^6.13.0",
"babel-plugin-syntax-do-expressions": "^6.13.0",
"babel-plugin-syntax-export-extensions": "^6.13.0",
"babel-plugin-syntax-flow": "^6.13.0",
"babel-plugin-syntax-jsx": "^6.13.0",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
- "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
+ "babel-plugin-transform-class-properties": "^6.24.1",
+ "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"babel-preset-react": "^6.16.0",
"browserify": "^14.5.0",
"chai": "^4.1.2",
@@ -44,7 +46,7 @@
"jspath": "^0.4.0",
"mocha": "^3.5.3",
"multimatch": "^2.1.0",
- "node-sass": "^4.9.0",
+ "node-sass": "^4.11.0",
"sinon": "^4.5.0",
"universalify": "^0.1.1"
}
diff --git a/resource/classnames.js b/resource/classnames.js
new file mode 120000
index 0000000000..c3ed92cdf3
--- /dev/null
+++ b/resource/classnames.js
@@ -0,0 +1 @@
+../node_modules/classnames/index.js
\ No newline at end of file
diff --git a/resource/concurrentCaller.js b/resource/concurrentCaller.js
index 87c3d12d1e..5500b8c6c4 100644
--- a/resource/concurrentCaller.js
+++ b/resource/concurrentCaller.js
@@ -26,7 +26,12 @@
EXPORTED_SYMBOLS = ["ConcurrentCaller"];
if (!(typeof process === 'object' && process + '' === '[object process]')) {
- Components.utils.import('resource://zotero/require.js');
+ // Components.utils.import('resource://zotero/require.js');
+ // Not using Cu.import here since we don't want the require module to be cached
+ // for includes within ZoteroPane or other code where we want the window instance available to modules.
+ Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Components.interfaces.mozIJSSubScriptLoader)
+ .loadSubScript('resource://zotero/require.js');
var Promise = require('resource://zotero/bluebird.js');
} else {
Promise = require('bluebird');
diff --git a/resource/prop-types.js b/resource/prop-types.js
new file mode 120000
index 0000000000..f73a9a9c3c
--- /dev/null
+++ b/resource/prop-types.js
@@ -0,0 +1 @@
+../node_modules/prop-types/prop-types.js
\ No newline at end of file
diff --git a/resource/react-dom.js b/resource/react-dom.js
index 7b95c1e019..dcca22f2ff 120000
--- a/resource/react-dom.js
+++ b/resource/react-dom.js
@@ -1 +1 @@
-../node_modules/react-dom/dist/react-dom.js
\ No newline at end of file
+../node_modules/react-dom/umd/react-dom.development.js
\ No newline at end of file
diff --git a/resource/react-intl.js b/resource/react-intl.js
new file mode 120000
index 0000000000..ef64c43adc
--- /dev/null
+++ b/resource/react-intl.js
@@ -0,0 +1 @@
+../node_modules/react-intl/dist/react-intl.js
\ No newline at end of file
diff --git a/resource/react.js b/resource/react.js
index 3382a7499b..1af854d8eb 120000
--- a/resource/react.js
+++ b/resource/react.js
@@ -1 +1 @@
-../node_modules/react/dist/react.js
\ No newline at end of file
+../node_modules/react/umd/react.development.js
\ No newline at end of file
diff --git a/resource/require.js b/resource/require.js
index 85b4784e06..103b16783d 100644
--- a/resource/require.js
+++ b/resource/require.js
@@ -1,77 +1,102 @@
'use strict';
-var EXPORTED_SYMBOLS = ['require'];
-
var require = (function() {
+ var win, Zotero;
var { Loader, Require, Module } = Components.utils.import('resource://gre/modules/commonjs/toolkit/loader.js');
var requirer = Module('/', '/');
var _runningTimers = {};
- var window = {};
+ if (typeof window != 'undefined') {
+ win = window;
+ } else {
+ win = {};
- window.setTimeout = function (func, ms) {
- var id = Math.floor(Math.random() * (1000000000000 - 1)) + 1
- var useMethodjit = Components.utils.methodjit;
- var timer = Components.classes["@mozilla.org/timer;1"]
- .createInstance(Components.interfaces.nsITimer);
- timer.initWithCallback({"notify":function() {
- // Remove timer from object so it can be garbage collected
- delete _runningTimers[id];
-
- // Execute callback function
- try {
- func();
- } catch(err) {
- // Rethrow errors that occur so that they appear in the error
- // console with the appropriate name and line numbers. While the
- // the errors appear without this, the line numbers get eaten.
- var scriptError = Components.classes["@mozilla.org/scripterror;1"]
- .createInstance(Components.interfaces.nsIScriptError);
- scriptError.init(
- err.message || err.toString(),
- err.fileName || err.filename || null,
- null,
- err.lineNumber || null,
- null,
- scriptError.errorFlag,
- 'component javascript'
- );
- Components.classes["@mozilla.org/consoleservice;1"]
- .getService(Components.interfaces.nsIConsoleService)
- .logMessage(scriptError);
- typeof Zotero !== 'undefined' && Zotero.debug(err.stack, 1);
+ win.setTimeout = function (func, ms) {
+ var id = Math.floor(Math.random() * (1000000000000 - 1)) + 1
+ var useMethodjit = Components.utils.methodjit;
+ var timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback({"notify":function() {
+ // Remove timer from object so it can be garbage collected
+ delete _runningTimers[id];
+
+ // Execute callback function
+ try {
+ func();
+ } catch(err) {
+ // Rethrow errors that occur so that they appear in the error
+ // console with the appropriate name and line numbers. While the
+ // the errors appear without this, the line numbers get eaten.
+ var scriptError = Components.classes["@mozilla.org/scripterror;1"]
+ .createInstance(Components.interfaces.nsIScriptError);
+ scriptError.init(
+ err.message || err.toString(),
+ err.fileName || err.filename || null,
+ null,
+ err.lineNumber || null,
+ null,
+ scriptError.errorFlag,
+ 'component javascript'
+ );
+ Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService)
+ .logMessage(scriptError);
+ typeof Zotero !== 'undefined' && Zotero.debug(err.stack, 1);
+ }
+ }}, ms, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+ _runningTimers[id] = timer;
+ return id;
+ };
+
+ win.clearTimeout = function (id) {
+ var timer = _runningTimers[id];
+ if (timer) {
+ timer.cancel();
}
- }}, ms, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
- _runningTimers[id] = timer;
- return id;
- };
+ delete _runningTimers[id];
+ };
+
+ win.debug = function (msg) {
+ dump(msg + "\n\n");
+ };
+ }
- window.clearTimeout = function (id) {
- var timer = _runningTimers[id];
- if (timer) {
- timer.cancel();
+ function getZotero() {
+ if (typeof Zotero === 'undefined') {
+ try {
+ Zotero = Components.classes["@zotero.org/Zotero;1"]
+ .getService(Components.interfaces.nsISupports).wrappedJSObject;
+ } catch (e) {}
}
- delete _runningTimers[id];
- };
+ return Zotero || {};
+ }
- window.debug = function (msg) {
- dump(msg + "\n\n");
+ var cons;
+ if (typeof win.console !== 'undefined') {
+ cons = console;
+ }
+ if (!cons) {
+ cons = {};
+ for (let key of ['log', 'warn', 'error']) {
+ cons[key] = text => {getZotero(); typeof Zotero !== 'undefined' && false && Zotero.debug(`console.${key}: ${text}`)};
+ }
+ }
+ let globals = {
+ window: win,
+ document: typeof win.document !== 'undefined' && win.document || {},
+ console: cons,
+ navigator: typeof win.navigator !== 'undefined' && win.navigator || {},
+ setTimeout: win.setTimeout,
+ clearTimeout: win.clearTimeout,
};
-
+ Object.defineProperty(globals, 'Zotero', { get: getZotero });
var loader = Loader({
id: 'zotero/require',
paths: {
'': 'resource://zotero/',
+ 'components/': 'chrome://zotero/content/components/'
},
- globals: {
- document: typeof document !== 'undefined' && document || {},
- console: typeof console !== 'undefined' && console || {},
- navigator: typeof navigator !== 'undefined' && navigator || {},
- window,
- setTimeout: window.setTimeout,
- clearTimeout: window.clearTimeout,
- Zotero: typeof Zotero !== 'undefined' && Zotero || {}
- }
+ globals
});
return Require(loader, requirer);
-})();
\ No newline at end of file
+})();
diff --git a/resource/web-library b/resource/web-library
deleted file mode 120000
index 2b824f9b31..0000000000
--- a/resource/web-library
+++ /dev/null
@@ -1 +0,0 @@
-../node_modules/zotero-web-library/lib
\ No newline at end of file
diff --git a/scripts/babel-worker.js b/scripts/babel-worker.js
index 19754ffb95..219776f5d1 100644
--- a/scripts/babel-worker.js
+++ b/scripts/babel-worker.js
@@ -12,7 +12,7 @@ const cluster = require('cluster');
async function babelWorker(ev) {
const t1 = Date.now();
const sourcefile = ev.file;
- const outfile = path.join('build', sourcefile);
+ const outfile = path.join('build', sourcefile.replace('.jsx', '.js'));
const postError = (error) => {
process.send({
sourcefile,
@@ -26,9 +26,15 @@ async function babelWorker(ev) {
try {
let contents = await fs.readFile(sourcefile, 'utf8');
- if (sourcefile === 'resource/react-dom.js') {
+ if (sourcefile === 'resource/react.js') {
// patch react
- transformed = contents.replace(/ownerDocument\.createElement\((.*?)\)/gi, 'ownerDocument.createElementNS(DOMNamespaces.html, $1)');
+ transformed = contents.replace('instanceof Error', '.constructor.name == "Error"')
+ } else if (sourcefile === 'resource/react-dom.js') {
+ // and react-dom
+ transformed = contents.replace(/ ownerDocument\.createElement\((.*?)\)/gi, 'ownerDocument.createElementNS(HTML_NAMESPACE, $1)')
+ .replace('element instanceof win.HTMLIFrameElement',
+ 'typeof element != "undefined" && element.tagName.toLowerCase() == "iframe"')
+ .replace("isInputEventSupported = false", 'isInputEventSupported = true');
} else if ('ignore' in options && options.ignore.some(ignoreGlob => multimatch(sourcefile, ignoreGlob).length)) {
transformed = contents;
isSkipped = true;
diff --git a/scripts/build.js b/scripts/build.js
index 0386ac08dc..07fe74dbf7 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -6,7 +6,7 @@ const getJS = require('./js');
const getSass = require('./sass');
const getSymlinks = require('./symlinks');
const { formatDirsForMatcher, getSignatures, writeSignatures, cleanUp, onSuccess, onError} = require('./utils');
-const { dirs, symlinkDirs, copyDirs, symlinkFiles, jsFiles, ignoreMask } = require('./config');
+const { dirs, symlinkDirs, copyDirs, symlinkFiles, jsFiles, scssFiles, ignoreMask } = require('./config');
if (require.main === module) {
(async () => {
@@ -16,6 +16,7 @@ if (require.main === module) {
const symlinks = symlinkFiles
.concat(dirs.map(d => `${d}/**`))
.concat([`!${formatDirsForMatcher(dirs)}/**/*.js`])
+ .concat([`!${formatDirsForMatcher(dirs)}/**/*.jsx`])
.concat([`!${formatDirsForMatcher(copyDirs)}/**`])
const signatures = await getSignatures();
@@ -23,7 +24,7 @@ if (require.main === module) {
getBrowserify(signatures),
getCopy(copyDirs.map(d => `${d}/**`), { ignore: ignoreMask }, signatures),
getJS(jsFiles, { ignore: ignoreMask }, signatures),
- getSass('scss/*.scss', { root: 'scss', ignore: ignoreMask }, signatures),
+ getSass(scssFiles, { ignore: ignoreMask }, signatures),
getSymlinks(symlinks, { nodir: true, ignore: ignoreMask }, signatures),
getSymlinks(symlinkDirs, { ignore: ignoreMask }, signatures),
cleanUp(signatures)
diff --git a/scripts/config.js b/scripts/config.js
index a00979e1eb..3687bbc8fa 100644
--- a/scripts/config.js
+++ b/scripts/config.js
@@ -4,7 +4,6 @@ const dirs = [
'components',
'defaults',
'resource',
- 'resource/web-library',
'test',
'test/resource/chai',
'test/resource/chai-as-promised',
@@ -49,13 +48,20 @@ const browserifyConfigs = [
];
// exclude mask used for js, copy, symlink and sass tasks
-const ignoreMask = ['**/#*'];
+const ignoreMask = ['**/#*', '**/_*.scss'];
const jsFiles = [
`{${dirs.join(',')}}/**/*.js`,
- `!{${symlinkDirs.concat(copyDirs).join(',')}}/**/*.js`
+ `{${dirs.join(',')}}/**/*.jsx`,
+ `!{${symlinkDirs.concat(copyDirs).join(',')}}/**/*.js`,
+ `!{${symlinkDirs.concat(copyDirs).join(',')}}/**/*.jsx`
+];
+
+const scssFiles = [
+ 'scss/**/*.scss',
+ 'chrome/skin/default/zotero/**/*.scss'
];
module.exports = {
- dirs, symlinkDirs, copyDirs, symlinkFiles, browserifyConfigs, jsFiles, ignoreMask
+ dirs, symlinkDirs, copyDirs, symlinkFiles, browserifyConfigs, jsFiles, scssFiles, ignoreMask
};
diff --git a/scripts/js.js b/scripts/js.js
index 3fc752efa4..d3f4b35725 100644
--- a/scripts/js.js
+++ b/scripts/js.js
@@ -27,7 +27,7 @@ async function getJS(source, options, signatures) {
var f;
while ((f = matchingJSFiles.pop()) != null) {
const newFileSignature = await getFileSignature(f);
- const dest = path.join('build', f);
+ const dest = path.join('build', f.replace('.jsx', '.js'));
f = path.normalize(f);
if (f in signatures) {
if (compareSignatures(newFileSignature, signatures[f])) {
diff --git a/scripts/sass.js b/scripts/sass.js
index fbb02fba32..060892130f 100644
--- a/scripts/sass.js
+++ b/scripts/sass.js
@@ -11,7 +11,7 @@ const sassRender = universalify.fromCallback(sass.render);
const ROOT = path.resolve(__dirname, '..');
-async function getSass(source, options, signatures) {
+async function getSass(source, options, signatures={}) {
const t1 = Date.now();
const files = await globby(source, Object.assign({ cwd: ROOT }, options ));
const totalCount = files.length;
@@ -20,13 +20,22 @@ async function getSass(source, options, signatures) {
while ((f = files.pop()) != null) {
let newFileSignature = await getFileSignature(f);
- const dest = path.join.apply(this, ['build', 'chrome', 'skin', 'default', 'zotero', 'components', getPathRelativeTo(f, 'scss')]);
+ let destFile = getPathRelativeTo(f, 'scss');
+ destFile = path.join(path.dirname(destFile), path.basename(destFile, '.scss') + '.css');
+ let dest = path.join.apply(this, ['build', 'chrome', 'skin', 'default', 'zotero', destFile]);
+ if (['win', 'mac', 'unix'].some(platform => f.endsWith(`-${platform}.scss`))) {
+ let platform = f.slice(f.lastIndexOf('-')+1, f.lastIndexOf('.'));
+ destFile = destFile.slice(0, destFile.lastIndexOf('-'))
+ + destFile.slice(destFile.lastIndexOf('-')+1+platform.length);
+ dest = path.join.apply(this, ['build', 'chrome', 'content', 'zotero-platform', platform, destFile]);
+ }
if (f in signatures) {
if (compareSignatures(newFileSignature, signatures[f])) {
try {
await fs.access(dest, fs.constants.F_OK);
- continue;
+ // TODO: Doesn't recompile on partial scss file changes, so temporarily disabled
+ // continue;
} catch (_) {
// file does not exists in build, fallback to browserifing
}
@@ -34,10 +43,14 @@ async function getSass(source, options, signatures) {
}
try {
const sass = await sassRender({
- file: f
+ file: f,
+ outFile: dest,
+ sourceMap: true,
+ outputStyle: 'compressed'
});
- await fs.outputFile(dest, sass);
+ await fs.outputFile(dest, sass.css);
+ await fs.outputFile(`${dest}.map`, sass.map);
onProgress(f, dest, 'sass');
signatures[f] = newFileSignature;
count++;
diff --git a/scripts/watch.js b/scripts/watch.js
index 8f4657d5c3..b9e215c682 100644
--- a/scripts/watch.js
+++ b/scripts/watch.js
@@ -1,7 +1,7 @@
const path = require('path');
const chokidar = require('chokidar');
const multimatch = require('multimatch');
-const { dirs, jsFiles, ignoreMask, copyDirs, symlinkFiles } = require('./config');
+const { dirs, jsFiles, scssFiles, ignoreMask, copyDirs, symlinkFiles } = require('./config');
const { onSuccess, onError, getSignatures, writeSignatures, cleanUp, formatDirsForMatcher } = require('./utils');
const getJS = require('./js');
const getSass = require('./sass');
@@ -34,6 +34,7 @@ const source = [
const symlinks = symlinkFiles
.concat(dirs.map(d => `${d}/**`))
.concat([`!${formatDirsForMatcher(dirs)}/**/*.js`])
+ .concat([`!${formatDirsForMatcher(dirs)}/**/*.jsx`])
.concat([`!${formatDirsForMatcher(copyDirs)}/**`]);
var signatures;
@@ -49,8 +50,12 @@ function getWatch() {
try {
if (multimatch(path, jsFiles).length && !multimatch(path, ignoreMask).length) {
onSuccess(await getJS(path, { ignore: ignoreMask }, signatures));
- } else if (multimatch(path, 'scss/*.scss').length) {
- onSuccess(await getSass(path, {}, signatures));
+ } else if (multimatch(path, scssFiles).length) {
+ if (multimatch(path, '**/_*.scss').length) {
+ onSuccess(await getSass(scssFiles, { ignore: ignoreMask }));
+ } else {
+ onSuccess(await getSass(path, {}, signatures));
+ }
} else if (multimatch(path, copyDirs.map(d => `${d}/**`)).length) {
onSuccess(await getCopy(path, {}, signatures));
} else if (multimatch(path, symlinks).length) {
diff --git a/scss/_zotero-react-client.scss b/scss/_zotero-react-client.scss
new file mode 100644
index 0000000000..49a4c53d1a
--- /dev/null
+++ b/scss/_zotero-react-client.scss
@@ -0,0 +1,25 @@
+//
+// Zotero React Client
+// --------------------------------------------------
+
+
+// Abstracts
+// --------------------------------------------------
+
+@import "abstracts/variables";
+@import "abstracts/functions";
+@import "abstracts/mixins";
+@import "abstracts/placeholders";
+@import "abstracts/utilities";
+
+// Theme
+// --------------------------------------------------
+
+@import "themes/light";
+
+// Components
+// --------------------------------------------------
+
+@import "components/tag-selector";
+@import "components/button";
+@import "components/icons";
diff --git a/scss/abstracts/_functions.scss b/scss/abstracts/_functions.scss
new file mode 100644
index 0000000000..36f8ddba7c
--- /dev/null
+++ b/scss/abstracts/_functions.scss
@@ -0,0 +1,4 @@
+//
+// Functions
+// --------------------------------------------------
+
diff --git a/scss/abstracts/_mixins.scss b/scss/abstracts/_mixins.scss
new file mode 100644
index 0000000000..1727403388
--- /dev/null
+++ b/scss/abstracts/_mixins.scss
@@ -0,0 +1,4 @@
+//
+// Mixins
+// --------------------------------------------------
+
diff --git a/scss/abstracts/_placeholders.scss b/scss/abstracts/_placeholders.scss
new file mode 100644
index 0000000000..c9295e8679
--- /dev/null
+++ b/scss/abstracts/_placeholders.scss
@@ -0,0 +1,3 @@
+//
+// Placeholders
+// --------------------------------------------------
\ No newline at end of file
diff --git a/scss/abstracts/_utilities.scss b/scss/abstracts/_utilities.scss
new file mode 100644
index 0000000000..d8e3164bb6
--- /dev/null
+++ b/scss/abstracts/_utilities.scss
@@ -0,0 +1,4 @@
+//
+// Utilities
+// --------------------------------------------------
+
diff --git a/scss/abstracts/_variables.scss b/scss/abstracts/_variables.scss
new file mode 100644
index 0000000000..70571a7d29
--- /dev/null
+++ b/scss/abstracts/_variables.scss
@@ -0,0 +1,80 @@
+//
+// Variables
+// --------------------------------------------------
+
+
+// Dimensions
+// --------------------------------------------------
+
+$space-min: 4px;
+$space-xs: 8px;
+$space-sm: 12px;
+$space-md: 16px;
+$space-lg: 24px;
+$space-xl: 32px;
+$space-xxl: 48px;
+$space-max: 64px;
+
+$space-thumb: 42px;
+
+
+// Typography
+// --------------------------------------------------
+
+$font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
+$font-family-base: $font-family-sans-serif;
+
+$font-size-base: 13px;
+
+$font-size-h1: 20px;
+$font-size-h2: 16px;
+
+$line-height-base: 1.539;
+$line-height-computed: ceil($font-size-base * $line-height-base);
+
+// todo: line-height-small
+$line-height-large: 26px;
+$line-height-large-touch: $space-thumb;
+
+$link-hover-decoration: underline;
+
+$headings-font-family: inherit;
+$headings-font-weight: 400;
+$headings-line-height: 1.2;
+
+
+// Components
+// --------------------------------------------------
+
+$padding-base-vertical: 2px;
+
+$padding-base-horizontal: $space-xs;
+$padding-large-horizontal: $space-sm;
+
+$border-radius-small: 3px;
+$border-radius-base: 4px;
+$border-radius-large: 6px;
+
+$border-width: 1px;
+
+$separator-width: 1px;
+
+// Buttons
+// --------------------------------------------------
+
+$btn-disabled-opacity: 0.5;
+
+// Z-index master list
+// --------------------------------------------------
+
+$z-index-mobile-nav: 0;
+$z-index-navbar-bg: 10;
+$z-index-main: 10;
+$z-index-level: 10;
+$z-index-level-active: 20;
+$z-index-navbar: 20;
+$z-index-menu: 30;
+$z-index-modal: 40;
+$z-index-drag-layer: 50;
+$z-index-loading-cover: 60;
+
diff --git a/scss/components/_button.scss b/scss/components/_button.scss
new file mode 100644
index 0000000000..a18d4825f7
--- /dev/null
+++ b/scss/components/_button.scss
@@ -0,0 +1,40 @@
+//
+// Button
+// --------------------------------------------------
+
+.btn {
+ font: {
+ family: inherit;
+ size: inherit;
+ }
+ line-height: inherit;
+ color: inherit;
+ text-align: center;
+ -moz-appearance: toolbarbutton;
+
+ &[disabled],
+ &.disabled {
+ opacity: $btn-disabled-opacity;
+ }
+}
+
+.btn-icon {
+ .icon {
+ &:first-child {
+ margin-left: -5px;
+ }
+ &:last-child {
+ margin-right: -5px;
+ }
+ svg, img {
+ vertical-align: middle;
+ }
+ }
+ span.menu-marker {
+ -moz-appearance: toolbarbutton-dropdown;
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: -5px;
+ }
+}
+
diff --git a/scss/components/_icons.scss b/scss/components/_icons.scss
new file mode 100644
index 0000000000..355d214d1d
--- /dev/null
+++ b/scss/components/_icons.scss
@@ -0,0 +1,7 @@
+.icon > svg, .icon > img {
+ width: 16px;
+}
+
+.icon.icon-downchevron > img {
+ width: 7px;
+}
\ No newline at end of file
diff --git a/scss/components/_tag-selector.scss b/scss/components/_tag-selector.scss
new file mode 100644
index 0000000000..c56778ce0b
--- /dev/null
+++ b/scss/components/_tag-selector.scss
@@ -0,0 +1,92 @@
+//
+// Tag selector
+// --------------------------------------------------
+
+.tag-selector {
+ display: flex;
+ flex: 1 0;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.tag-selector-container {
+ flex: 1 1 auto;
+ justify-content: space-between;
+ overflow: auto;
+ height: 100px;
+ background-color: $tag-selector-bg;
+}
+
+.tag-selector-message {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+}
+
+.tag-selector-filter-container {
+ height: auto;
+ flex: 0 0 1em;
+ display: flex;
+ flex-direction: row;
+ padding: 0.125em 0 0.125em 0.5em;
+}
+
+.tag-selector-list {
+ list-style: none;
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+}
+
+.tag-selector-filter {
+ flex: 1 0;
+ min-width: 40px;
+}
+
+.tag-selector-actions {
+ flex: 0 1;
+ display: block;
+ white-space: nowrap;
+ background-color: inherit;
+}
+
+.tag-selector-item {
+ cursor: pointer;
+ display: inline-block;
+ margin: .15em .05em .15em .3em;
+ padding: 0 .25em 0 .25em;
+ max-width: 250px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &.colored {
+ font-weight: bold;
+ }
+
+ &.disabled {
+ opacity: .6;
+ cursor: default;
+ }
+
+ &.dragged-over {
+ color: $shade-0;
+ background: $shade-6;
+ }
+}
+
+#zotero-tag-selector-container {
+ display: flex;
+}
+
+#zotero-tag-selector {
+ min-height: 100px;
+ width: 100%;
+ display: flex;
+}
+
+#zotero-tag-selector[collapsed=true] {
+ visibility: collapse;
+}
+
diff --git a/scss/mac/_button.scss b/scss/mac/_button.scss
new file mode 100644
index 0000000000..de2c03cb48
--- /dev/null
+++ b/scss/mac/_button.scss
@@ -0,0 +1,11 @@
+//
+// Button
+// --------------------------------------------------
+
+.btn {
+ -moz-appearance: none;
+
+ span.menu-marker {
+ -moz-appearance: none;
+ }
+}
\ No newline at end of file
diff --git a/scss/mac/_tag-selector.scss b/scss/mac/_tag-selector.scss
new file mode 100644
index 0000000000..d8a33b0d96
--- /dev/null
+++ b/scss/mac/_tag-selector.scss
@@ -0,0 +1,20 @@
+//
+// Tag selector
+// --------------------------------------------------
+
+.tag-selector-filter-container {
+ padding: 0.25em 0 0.25em 0.5em;
+ border-top: 1px solid $shade-3;
+}
+
+.tag-selector-filter {
+ -moz-appearance: searchfield;
+ height: 24px;
+}
+
+.tag-selector-actions {
+ flex: none;
+ border: 0;
+ margin-right: 3px;
+ padding: 0 6px;
+}
diff --git a/scss/themes/_light.scss b/scss/themes/_light.scss
new file mode 100644
index 0000000000..e0658486ce
--- /dev/null
+++ b/scss/themes/_light.scss
@@ -0,0 +1,176 @@
+//
+// Light theme
+// --------------------------------------------------
+
+
+// Colors
+// --------------------------------------------------
+
+$red: #cc2936;
+$blue: rgb(89, 139, 236);
+$asphalt: #7a8799;
+$asphalt-light: #dadee3;
+
+$primary: $red;
+$secondary: $blue;
+
+$blue-dark: #333c66;
+$blue-darkest: #3d4466;
+
+$transparent: transparent;
+
+
+// Grays
+// --------------------------------------------------
+
+$shade-0: #fff;
+$shade-1: #f6f6f6;
+$shade-2: #e6e6e6;
+$shade-3: #ddd;
+$shade-4: #ccc;
+$shade-5: #bbb;
+$shade-6: #777;
+$shade-7: #555;
+$shade-8: #444;
+$shade-9: #222;
+$shade-10: #000;
+
+
+// Scaffolding
+// --------------------------------------------------
+
+$body-bg: $shade-0;
+$text-color: $shade-9;
+
+$headings-color: $shade-8;
+
+$link-color: $blue;
+$link-hover-color: $blue;
+
+$focus-color: $secondary;
+
+
+// Typography
+// --------------------------------------------------
+
+$text-selection-color: inherit;
+$text-selection-bg: rgba($secondary, 0.2);
+
+// Separator
+$separator-color: $shade-3;
+
+
+// Partials
+// --------------------------------------------------
+
+// Main
+$main-bg: $shade-0;
+
+// Sidebar
+$sidebar-bg: $shade-1;
+
+// Clicky
+$clicky-hover-bg-color: rgb(187, 206, 241);
+$clicky-active-bg-color: $secondary;
+$clicky-border-radius: 6px;
+$clicky-border-color: rgb(109, 149, 224);
+$clicky-active-color: $shade-0;
+
+
+// Components
+// --------------------------------------------------
+
+// Icon
+$icon-color: $secondary;
+$icon-active-color: $shade-9;
+
+// Collection tree
+$collection-tree-headings-color: $shade-6;
+$collection-tree-link-color: $shade-8;
+$collection-tree-active-link-color: $shade-9;
+$collection-tree-active-icon-color: $shade-7;
+$collection-tree-active-link-bg: $shade-2;
+$collection-tree-focus-color: $asphalt;
+$collection-tree-icon-color: $shade-6;
+$touch-collection-tree-active-link-color: $shade-0;
+$touch-collection-tree-active-link-bg: $blue;
+$touch-collection-tree-border: $shade-2;
+$touch-collection-tree-separator: $body-bg;
+$collection-tree-dnd-target-link-color: $shade-0;
+$collection-tree-dnd-target-bg-color: $blue;
+$collection-tree-dnd-target-icon-color: $shade-0;
+
+// Items
+$items-bg: $shade-0;
+$items-odd-bg: $shade-1;
+$item-list-head-color: $shade-8;
+$item-list-head-border: $shade-2;
+
+// Item list
+$item-odd-bg: $shade-1;
+$item-active-color: $shade-0;
+$item-active-bg: $secondary;
+
+// Item details
+$item-details-bg: $shade-1;
+$info-view-color: $shade-5;
+
+// Panel
+$panel-bg: $shade-1;
+$panel-header-color: $shade-8;
+$panel-header-shadow: rgba($shade-10, 0.08);
+
+// Tabs
+$tabs-border-color: $shade-3;
+$tab-active-color: $secondary;
+$tab-inactive-color: $headings-color;
+
+// Metadata list
+$key-color: $shade-6;
+$editable-hover-bg: rgba($shade-10, 0.04);
+$handle-color-touch: $shade-4;
+$handle-color-mouse: $shade-6;
+$metadata-heading-color: $shade-5;
+$metadata-separator-color: $shade-3;
+
+// Button
+$btn-primary-color: $shade-0;
+$btn-primary-bg: $asphalt;
+$btn-default-color: $text-color;
+$btn-border: $shade-3;
+$btn-default-bg: $shade-0;
+$btn-default-active-color: rgba($btn-default-color, 0.5);
+
+// Forms
+$input-color: $text-color;
+$input-bg: $body-bg;
+$input-border-color: $shade-3;
+$input-focus-color: $secondary;
+$placeholder-color: $shade-5;
+
+// Editable
+$editable-color: $text-color;
+$editable-bg: $shade-0;
+$editable-border-color: $secondary;
+
+// Menu (select, dropdown, …)
+$menu-bg: $body-bg;
+$menu-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08), 0 3px 8px rgba(0, 0, 0, 0.2);
+$menu-item-hover-bg: $shade-1;
+$menu-item-selected-bg: $shade-2;
+$menu-devider: $shade-2;
+$menu-no-results-color: $shade-5;
+
+// Drag layer
+$creator-drag-preview-shadow: rgba($shade-10, 0.2);
+
+// Modal
+$modal-backdrop-bg: rgba($shade-10, 0.4);
+$modal-content-bg: $shade-1;
+$modal-header-bg: $shade-0;
+$modal-footer-border-width: 1px;
+$modal-footer-border-color: $shade-2;
+$modal-icon-spin-color: $shade-0;
+
+// Tag selector
+$tag-selector-bg: $shade-0;
\ No newline at end of file
diff --git a/scss/zotero-react-client-mac.scss b/scss/zotero-react-client-mac.scss
new file mode 100644
index 0000000000..5273bbd65a
--- /dev/null
+++ b/scss/zotero-react-client-mac.scss
@@ -0,0 +1,8 @@
+@import "zotero-react-client";
+
+//
+// MacOS specific
+// --------------------------------------------------
+
+@import "mac/button";
+@import "mac/tag-selector";
diff --git a/scss/zotero-react-client-unix.scss b/scss/zotero-react-client-unix.scss
new file mode 100644
index 0000000000..ab6d0c925f
--- /dev/null
+++ b/scss/zotero-react-client-unix.scss
@@ -0,0 +1,6 @@
+@import "zotero-react-client";
+
+//
+// Linux specific
+// --------------------------------------------------
+
diff --git a/scss/zotero-react-client-win.scss b/scss/zotero-react-client-win.scss
new file mode 100644
index 0000000000..4ce4b848c7
--- /dev/null
+++ b/scss/zotero-react-client-win.scss
@@ -0,0 +1,6 @@
+@import "zotero-react-client";
+
+//
+// Windows specific
+// --------------------------------------------------
+
diff --git a/test/content/support.js b/test/content/support.js
index 1513ea4c73..a5702c72ad 100644
--- a/test/content/support.js
+++ b/test/content/support.js
@@ -213,12 +213,15 @@ var waitForTagSelector = function (win) {
var zp = win.ZoteroPane;
var deferred = Zotero.Promise.defer();
if (zp.tagSelectorShown()) {
- var tagSelector = win.document.getElementById('zotero-tag-selector');
- var onRefresh = () => {
- tagSelector.removeEventListener('refresh', onRefresh);
+ let tagSelector = zp.tagSelector;
+ let componentDidUpdate = tagSelector.componentDidUpdate;
+ tagSelector.componentDidUpdate = function() {
deferred.resolve();
- };
- tagSelector.addEventListener('refresh', onRefresh);
+ tagSelector.componentDidUpdate = componentDidUpdate;
+ if (typeof componentDidUpdate == 'function') {
+ componentDidUpdate.call(this, arguments);
+ }
+ }
}
else {
deferred.resolve();
diff --git a/test/tests/tagSelectorTest.js b/test/tests/tagSelectorTest.js
index 8ba26d239c..454b48ba31 100644
--- a/test/tests/tagSelectorTest.js
+++ b/test/tests/tagSelectorTest.js
@@ -1,7 +1,7 @@
"use strict";
describe("Tag Selector", function () {
- var win, doc, collectionsView, tagSelector;
+ var win, doc, collectionsView, tagSelectorElem, tagSelector;
var clearTagColors = Zotero.Promise.coroutine(function* (libraryID) {
var tagColors = Zotero.Tags.getColors(libraryID);
@@ -11,27 +11,13 @@ describe("Tag Selector", function () {
});
function getColoredTags() {
- 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;
+ var elems = Array.from(tagSelectorElem.querySelectorAll('.tag-selector-item.colored'));
+ return elems.map(elem => elem.textContent);
}
function getRegularTags() {
- 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;
+ var elems = Array.from(tagSelectorElem.querySelectorAll('.tag-selector-item:not(.colored)'));
+ return elems.map(elem => elem.textContent);
}
@@ -39,7 +25,8 @@ describe("Tag Selector", function () {
win = yield loadZoteroPane();
doc = win.document;
collectionsView = win.ZoteroPane.collectionsView;
- tagSelector = doc.getElementById('zotero-tag-selector');
+ tagSelectorElem = doc.getElementById('zotero-tag-selector');
+ tagSelector = win.ZoteroPane.tagSelector;
// Wait for things to settle
yield Zotero.Promise.delay(100);
@@ -49,16 +36,37 @@ describe("Tag Selector", function () {
var libraryID = Zotero.Libraries.userLibraryID;
yield clearTagColors(libraryID);
// Default "Display All Tags in This Library" off
- tagSelector.filterToScope = true;
- tagSelector.setSearch('');
- yield tagSelector.refresh(true);
- })
+ tagSelector.displayAllTags = false;
+ tagSelector.selectedTags = new Set();
+ tagSelector.handleSearch('');
+ tagSelector.onItemViewChanged({libraryID});
+ });
after(function () {
win.close();
});
- describe("#setSearch()", function () {
+ it('should not display duplicate tags when automatic and manual tag with same name exists', async function () {
+ var collection = await createDataObject('collection');
+ var item1 = createUnsavedDataObject('item', { collections: [collection.id] });
+ item1.setTags([{
+ tag: "A",
+ type: 1
+ }]);
+ var item2 = createUnsavedDataObject('item', { collections: [collection.id] });
+ item2.setTags(["A", "B"]);
+ var promise = waitForTagSelector(win);
+ await Zotero.DB.executeTransaction(async function () {
+ await item1.save();
+ await item2.save();
+ });
+ await promise;
+
+ var tags = getRegularTags();
+ assert.sameMembers(tags, ['A', 'B']);
+ });
+
+ describe("#handleSearch()", function () {
it("should filter to tags matching the search", function* () {
var collection = yield createDataObject('collection');
var item = createUnsavedDataObject('item', { collections: [collection.id] });
@@ -67,20 +75,23 @@ describe("Tag Selector", function () {
yield item.saveTx();
yield promise;
- var tagsSearch = doc.getElementById('tags-search');
- tagsSearch.value = 'a';
- tagsSearch.doCommand();
+ promise = waitForTagSelector(win);
+ tagSelector.handleSearch('a');
+ yield Zotero.Promise.delay(500);
+
+ yield promise;
var tags = getRegularTags();
assert.sameMembers(tags, ['a']);
+
+ tagSelector.handleSearch('');
+ yield Zotero.Promise.delay(500);
- tagsSearch.value = '';
- tagsSearch.doCommand();
yield item.eraseTx();
});
});
- describe("#refresh()", function () {
+ describe("#handleTagSelected()", function () {
it("should remove tags not on matching items on tag click", function* () {
var collection = yield createDataObject('collection');
var item1 = createUnsavedDataObject('item', { collections: [collection.id] });
@@ -112,13 +123,8 @@ describe("Tag Selector", function () {
});
yield promise;
- var buttons = tagSelector.id('tags-box').getElementsByTagName('button');
- var spy = sinon.spy(win.ZoteroPane, "updateTagFilter");
- buttons[0].click();
-
- yield spy.returnValues[0];
-
- spy.restore();
+ tagSelector.handleTagSelected('A');
+ yield waitForTagSelector(win);
var tags = getRegularTags();
assert.sameMembers(tags, ['A', 'B']);
@@ -126,9 +132,9 @@ describe("Tag Selector", function () {
});
- describe("#filterToScope", function () {
- it("should show all tags in library when false", function* () {
- tagSelector.filterToScope = false;
+ describe("#displayAllTags", function () {
+ it("should show all tags in library when true", function* () {
+ tagSelector.displayAllTags = true;
var collection = yield createDataObject('collection');
var item1 = createUnsavedDataObject('item');
@@ -165,7 +171,7 @@ describe("Tag Selector", function () {
describe("#notify()", function () {
it("should add a tag when added to an item in the library root", function* () {
- var promise, tagSelector;
+ var promise;
if (collectionsView.selection.currentIndex != 0) {
promise = waitForTagSelector(win);
@@ -256,11 +262,14 @@ describe("Tag Selector", function () {
it("should show a colored tag at the top of the list even when linked to no items", function* () {
var libraryID = Zotero.Libraries.userLibraryID;
- var tagElems = tagSelector.id('tags-box').getElementsByTagName('button');
+ var tagElems = tagSelectorElem.querySelectorAll('.tag-selector-item');
var count = tagElems.length;
-
+
+ var promise = waitForTagSelector(win);
yield Zotero.Tags.setColor(libraryID, "Top", '#AAAAAA');
-
+ yield promise;
+
+ tagElems = tagSelectorElem.querySelectorAll('.tag-selector-item');
assert.equal(tagElems.length, count + 1);
});
@@ -288,16 +297,16 @@ describe("Tag Selector", function () {
var promise = waitForTagSelector(win);
yield item.saveTx();
yield promise;
-
- var tagElems = tagSelector.id('tags-box').getElementsByTagName('button');
+
+ var tagElems = tagSelectorElem.querySelectorAll('.tag-selector-item');
// 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);
+ tags.set(tagElems[i].textContent, i);
}
- assert.isBelow(parseInt(tags.get("B")), 0);
- assert.isBelow(parseInt(tags.get("B")), parseInt(tags.get("A")));
+ assert.isAbove(tags.get("B"), 0);
+ assert.isAbove(tags.get("B"), tags.get("A"));
})
it("should remove a tag when an item is removed from a collection", function* () {
@@ -325,7 +334,7 @@ describe("Tag Selector", function () {
promise = waitForTagSelector(win);
yield item.saveTx();
yield promise;
-
+
// Tag selector shouldn't show the removed item's tag
assert.equal(getRegularTags().length, 0);
})
@@ -379,8 +388,9 @@ describe("Tag Selector", function () {
// Remove tag from library
promise = waitForTagSelector(win);
- var dialogPromise = waitForDialog();
- yield tagSelector.deleteTag("A");
+ waitForDialog();
+ tagSelector.contextTag = {name: "A"};
+ yield tagSelector.openDeletePrompt();
yield promise;
// Tag selector shouldn't show the deleted item's tag
@@ -388,7 +398,7 @@ describe("Tag Selector", function () {
})
})
- describe("#rename()", function () {
+ describe("#openRenamePrompt", function () {
it("should rename a tag and update the tag selector", function* () {
yield selectLibrary(win);
@@ -409,7 +419,8 @@ describe("Tag Selector", function () {
dialog.document.getElementById('loginTextbox').value = newTag;
dialog.document.documentElement.acceptDialog();
})
- yield tagSelector.rename(tag);
+ tagSelector.contextTag = {name: tag};
+ yield tagSelector.openRenamePrompt();
yield promise;
var tags = getRegularTags();
@@ -428,11 +439,12 @@ describe("Tag Selector", function () {
yield promise;
promise = waitForTagSelector(win);
- var promptPromise = waitForWindow("chrome://global/content/commonDialog.xul", function (dialog) {
+ waitForWindow("chrome://global/content/commonDialog.xul", function (dialog) {
dialog.document.getElementById('loginTextbox').value = newTag;
dialog.document.documentElement.acceptDialog();
- })
- yield tagSelector.rename(oldTag);
+ });
+ tagSelector.contextTag = {name: oldTag};
+ yield tagSelector.openRenamePrompt();
yield promise;
var tags = getColoredTags();
@@ -441,7 +453,7 @@ describe("Tag Selector", function () {
});
})
- describe("#_openColorPickerWindow()", function () {
+ describe("#openColorPickerWindow()", function () {
it("should assign a color to a tag", function* () {
yield selectLibrary(win);
var tag = "b " + Zotero.Utilities.randomString();
@@ -463,7 +475,8 @@ describe("Tag Selector", function () {
var dialogPromise = waitForDialog(false, undefined, 'chrome://zotero/content/tagColorChooser.xul');
var tagSelectorPromise = waitForTagSelector(win);
- yield tagSelector._openColorPickerWindow(tag);
+ tagSelector.contextTag = {name: tag};
+ yield tagSelector.openColorPickerWindow();
yield dialogPromise;
yield tagSelectorPromise;