Reactify item tags box
Improvements: - Fixes autocomplete text remaining in field after selection in Fx60 - No more text or icon shifting on select (tested on macOS) Changes: - Tags are now selected on mousedown with no active state, as in web library Regressions: - Tooltip with tag type doesn't appear when hovering over icon - Pressing Tab after modifying a tag loses focus - Right-click in textbox shows custom menu instead of default text editing context menu (Cut/Copy/Paste) To-do: - Switch to this version for note tags box - Style colored tags in autocomplete drop-down? Sort to top? - Only show delete button on row hover, as in web library?
This commit is contained in:
parent
963329df28
commit
5791ffeb16
26 changed files with 2179 additions and 145 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
/* Force use of Lucida for HTML input elements (e.g., Editable) */
|
||||||
|
input {
|
||||||
|
font-family: Lucida Grande, Lucida Sans Unicode, Lucida Sans, Geneva, -apple-system, sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
#zotero-items-toolbar[state=collapsed]
|
#zotero-items-toolbar[state=collapsed]
|
||||||
{
|
{
|
||||||
margin-left: -8px !important;
|
margin-left: -8px !important;
|
||||||
|
|
117
chrome/content/zotero/components/editable.jsx
Normal file
117
chrome/content/zotero/components/editable.jsx
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2019 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://digitalscholar.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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import EditableContent from './editable/content';
|
||||||
|
import Input from './form/input';
|
||||||
|
import TextAreaInput from './form/textArea';
|
||||||
|
import SelectInput from './form/select';
|
||||||
|
import { noop } from './utils';
|
||||||
|
|
||||||
|
class Editable extends React.PureComponent {
|
||||||
|
get isActive() {
|
||||||
|
return (this.props.isActive || this.props.isBusy) && !this.props.isDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isReadOnly() {
|
||||||
|
return this.props.isReadOnly || this.props.isBusy;
|
||||||
|
}
|
||||||
|
|
||||||
|
get className() {
|
||||||
|
const { input, inputComponent } = this.props;
|
||||||
|
return {
|
||||||
|
'editable': true,
|
||||||
|
'editing': this.isActive,
|
||||||
|
'textarea': inputComponent === TextAreaInput || input && input.type === TextAreaInput,
|
||||||
|
'select': inputComponent === SelectInput || input && input.type === SelectInput,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
|
const hasChildren = typeof this.props.children !== 'undefined';
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{
|
||||||
|
hasChildren ?
|
||||||
|
this.props.children :
|
||||||
|
<EditableContent { ...this.props } />
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderControls() {
|
||||||
|
const { input: InputElement, inputComponent: InputComponent } = this.props;
|
||||||
|
if(InputElement) {
|
||||||
|
return InputElement;
|
||||||
|
} else {
|
||||||
|
const { className, innerRef, ...props } = this.props;
|
||||||
|
props.ref = innerRef;
|
||||||
|
|
||||||
|
return <InputComponent
|
||||||
|
className={ cx(className, "editable-control") }
|
||||||
|
{ ...props }
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isDisabled } = this.props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
tabIndex={ isDisabled ? null : this.isActive ? null : 0 }
|
||||||
|
onClick={ event => this.props.onClick(event) }
|
||||||
|
onFocus={ event => this.props.onFocus(event) }
|
||||||
|
onMouseDown={ event => this.props.onMouseDown(event) }
|
||||||
|
className={ cx(this.className) }
|
||||||
|
>
|
||||||
|
{ this.isActive ? this.renderControls() : this.renderContent() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
static defaultProps = {
|
||||||
|
inputComponent: Input,
|
||||||
|
onClick: noop,
|
||||||
|
onFocus: noop,
|
||||||
|
onMouseDown: noop,
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]),
|
||||||
|
input: PropTypes.element,
|
||||||
|
inputComponent: PropTypes.elementType,
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
isBusy: PropTypes.bool,
|
||||||
|
isDisabled: PropTypes.bool,
|
||||||
|
isReadOnly: PropTypes.bool,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default React.forwardRef((props, ref) => <Editable
|
||||||
|
innerRef={ref} {...props}
|
||||||
|
/>);
|
91
chrome/content/zotero/components/editable/content.jsx
Normal file
91
chrome/content/zotero/components/editable/content.jsx
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2019 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://digitalscholar.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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import TextAreaInput from '../form/textArea';
|
||||||
|
import Select from '../form/select';
|
||||||
|
|
||||||
|
class EditableContent extends React.PureComponent {
|
||||||
|
get hasValue() {
|
||||||
|
const { input, value } = this.props;
|
||||||
|
return !!(value || input && input.props.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSelect() {
|
||||||
|
const { input, inputComponent } = this.props;
|
||||||
|
return inputComponent === Select || input && input.type == Select;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTextarea() {
|
||||||
|
const { input, inputComponent } = this.props;
|
||||||
|
return inputComponent === TextAreaInput || input && input.type === TextAreaInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayValue() {
|
||||||
|
const { options, display, input } = this.props;
|
||||||
|
const value = this.props.value || input && input.props.value;
|
||||||
|
const placeholder = this.props.placeholder || input && input.props.placeholder;
|
||||||
|
|
||||||
|
if(!this.hasValue) { return placeholder; }
|
||||||
|
if(display) { return display; }
|
||||||
|
|
||||||
|
if(this.isSelect && options) {
|
||||||
|
const displayValue = options.find(e => e.value == value);
|
||||||
|
return displayValue ? displayValue.label : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const className = {
|
||||||
|
'editable-content': true,
|
||||||
|
'placeholder': !this.hasValue
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div className={ cx(this.props.className, className) }>{ this.displayValue }</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
value: '',
|
||||||
|
placeholder: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
display: PropTypes.string,
|
||||||
|
input: PropTypes.element,
|
||||||
|
inputComponent: PropTypes.elementType,
|
||||||
|
options: PropTypes.array,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
value: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number
|
||||||
|
])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditableContent;
|
|
@ -1,116 +1,209 @@
|
||||||
/* eslint-disable react/no-deprecated */
|
import React from 'react';
|
||||||
'use strict';
|
import PropTypes from 'prop-types';
|
||||||
|
import cx from 'classnames';
|
||||||
const React = require('react');
|
import { noop } from '../utils';
|
||||||
const PropTypes = require('prop-types');
|
import { pickKeys } from '@zotero/immutable';
|
||||||
const cx = require('classnames');
|
//import AutoResizer from './auto-resizer';
|
||||||
const { noop } = () => {};
|
import Autosuggest from 'react-autosuggest';
|
||||||
|
|
||||||
class Input extends React.PureComponent {
|
class Input extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
suggestions: [],
|
||||||
value: props.value
|
value: props.value
|
||||||
};
|
};
|
||||||
|
this.suggestions = React.createRef();
|
||||||
|
this.showSuggestions = React.createRef(false);
|
||||||
|
this.preSuggestionValue = React.createRef();
|
||||||
|
this.selectedSuggestion = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(event = null) {
|
cancel(event = null) {
|
||||||
this.props.onCancel && this.props.onCancel(this.hasChanged, event);
|
this.props.onCancel(this.hasChanged, event);
|
||||||
this.hasBeenCancelled = true;
|
this.hasBeenCancelled = true;
|
||||||
this.input.blur();
|
this.props.innerRef.current && this.props.innerRef.current.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
commit(event = null) {
|
commit(event = null) {
|
||||||
this.props.onCommit && this.props.onCommit(this.state.value, this.hasChanged, event);
|
this.props.onCommit(this.state.value, this.hasChanged, event);
|
||||||
this.hasBeenCommitted = true;
|
this.hasBeenCommitted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
if(this.input != null) {
|
if (this.props.innerRef.current != null) {
|
||||||
this.input.focus();
|
this.props.innerRef.current.focus();
|
||||||
this.props.selectOnFocus && this.input.select();
|
this.props.selectOnFocus && this.props.innerRef.current.select();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps({ value }) {
|
UNSAFE_componentWillReceiveProps({ value }) {
|
||||||
if (value !== this.props.value) {
|
if (value !== this.props.value) {
|
||||||
this.setState({ value });
|
this.setState({ value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange({ target }) {
|
handleChange({ target }, options) {
|
||||||
this.setState({ value: target.value });
|
var newValue = options.newValue || target.value;
|
||||||
this.props.onChange && this.props.onChange(target.value);
|
this.setState({
|
||||||
|
value: newValue,
|
||||||
|
});
|
||||||
|
this.props.onChange(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlur(event) {
|
handleBlur(event) {
|
||||||
const shouldCancel = this.props.onBlur && this.props.onBlur(event);
|
if (this.selectedSuggestion.current) {
|
||||||
|
this.selectedSuggestion.current = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.hasBeenCancelled || this.hasBeenCommitted) { return; }
|
if (this.hasBeenCancelled || this.hasBeenCommitted) { return; }
|
||||||
|
const shouldCancel = this.props.onBlur(event);
|
||||||
shouldCancel ? this.cancel(event) : this.commit(event);
|
shouldCancel ? this.cancel(event) : this.commit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFocus(event) {
|
handleFocus(event) {
|
||||||
this.props.selectOnFocus && event.target.select();
|
!this.focused && this.props.selectOnFocus && event.target.select();
|
||||||
this.props.onFocus && this.props.onFocus(event);
|
// Only focus the input once so that the entered text doesn't get selected when it matches
|
||||||
|
// a suggestion and the input gets rerendered with the suggestions drop-down
|
||||||
|
this.focused = true;
|
||||||
|
this.showSuggestions.current = false;
|
||||||
|
this.props.onFocus(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown(event) {
|
handleKeyDown(event) {
|
||||||
|
this.showSuggestions.current = true;
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
this.cancel(event);
|
this.cancel(event);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
this.commit(event);
|
if (this.selectedSuggestion.current) {
|
||||||
|
let value = this.selectedSuggestion.current;
|
||||||
|
this.selectedSuggestion.current = null;
|
||||||
|
this.setState({ value });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.commit(event);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
this.props.onKeyDown(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePaste(event) {
|
||||||
|
this.props.onPaste && this.props.onPaste(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autosuggest will call this function every time you need to update suggestions.
|
||||||
|
// You already implemented this logic above, so just use it.
|
||||||
|
async handleSuggestionsFetchRequested({ value }) {
|
||||||
|
this.setState({
|
||||||
|
suggestions: await this.props.getSuggestions(value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autosuggest will call this function every time you need to clear suggestions.
|
||||||
|
handleSuggestionsClearRequested() {
|
||||||
|
this.setState({
|
||||||
|
suggestions: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSuggestionValue(suggestion) {
|
||||||
|
return suggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldRenderSuggestions(value) {
|
||||||
|
return value.length && this.showSuggestions.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSuggestion(suggestion) {
|
||||||
|
return <span>
|
||||||
|
{suggestion}
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSuggestionSelected = (event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) => {
|
||||||
|
this.selectedSuggestion.current = suggestionValue;
|
||||||
|
// focusInputOnSuggestionClick in Autosuggest doesn't work with a custom renderInputComponent,
|
||||||
|
// so refocus the textbox manually
|
||||||
|
setTimeout(() => this.props.innerRef.current.focus());
|
||||||
|
}
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return this.state.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasChanged() {
|
get hasChanged() {
|
||||||
return this.state.value !== this.props.value;
|
return this.state.value !== this.props.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderInput() {
|
||||||
this.hasBeenCancelled = false;
|
this.hasBeenCancelled = false;
|
||||||
this.hasBeenCommitted = false;
|
this.hasBeenCommitted = false;
|
||||||
const extraProps = Object.keys(this.props).reduce((aggr, key) => {
|
|
||||||
if(key.match(/^(aria-|data-).*/)) {
|
const inputProps = {
|
||||||
aggr[key] = this.props[key];
|
disabled: this.props.isDisabled,
|
||||||
}
|
onBlur: this.handleBlur.bind(this),
|
||||||
return aggr;
|
onChange: this.handleChange.bind(this),
|
||||||
}, {});
|
onFocus: this.handleFocus.bind(this),
|
||||||
const input = <input
|
onKeyDown: this.handleKeyDown.bind(this),
|
||||||
autoFocus={ this.props.autoFocus }
|
onPaste: this.handlePaste.bind(this),
|
||||||
className={ this.props.className }
|
readOnly: this.props.isReadOnly,
|
||||||
disabled={ this.props.isDisabled }
|
required: this.props.isRequired,
|
||||||
form={ this.props.form }
|
value: this.state.value,
|
||||||
id={ this.props.id }
|
...pickKeys(this.props, ['autoFocus', 'className', 'form', 'id', 'inputMode', 'max',
|
||||||
inputMode={ this.props.inputMode }
|
'maxLength', 'min', 'minLength', 'name', 'placeholder', 'type', 'spellCheck',
|
||||||
max={ this.props.max }
|
'step', 'tabIndex']),
|
||||||
maxLength={ this.props.maxLength }
|
...pickKeys(this.props, key => key.match(/^(aria-|data-).*/))
|
||||||
min={ this.props.min }
|
};
|
||||||
minLength={ this.props.minLength }
|
|
||||||
name={ this.props.name }
|
var input = this.props.autoComplete ? (
|
||||||
onBlur={ this.handleBlur.bind(this) }
|
<Autosuggest
|
||||||
onChange={ this.handleChange.bind(this) }
|
suggestions={this.state.suggestions}
|
||||||
onFocus={ this.handleFocus.bind(this) }
|
onSuggestionsFetchRequested={this.handleSuggestionsFetchRequested.bind(this)}
|
||||||
onKeyDown={ this.handleKeyDown.bind(this) }
|
onSuggestionsClearRequested={this.handleSuggestionsClearRequested.bind(this)}
|
||||||
placeholder={ this.props.placeholder }
|
onSuggestionSelected={this.handleSuggestionSelected}
|
||||||
readOnly={ this.props.isReadOnly }
|
getSuggestionValue={this.getSuggestionValue.bind(this)}
|
||||||
ref={ input => this.input = input }
|
renderSuggestion={this.renderSuggestion.bind(this)}
|
||||||
required={ this.props.isRequired }
|
// https://github.com/moroshko/react-autosuggest/issues/474
|
||||||
size={ this.props.size }
|
renderInputComponent={(inputProps) => <input {...inputProps} ref={this.props.innerRef} />}
|
||||||
spellCheck={ this.props.spellCheck }
|
focusInputOnSuggestionClick={false}
|
||||||
step={ this.props.step }
|
shouldRenderSuggestions={this.shouldRenderSuggestions.bind(this)}
|
||||||
tabIndex={ this.props.tabIndex }
|
inputProps={inputProps}
|
||||||
type={ this.props.type }
|
/>
|
||||||
value={ this.state.value }
|
) : (
|
||||||
{ ...extraProps }
|
<input { ...inputProps } />
|
||||||
/>;
|
);
|
||||||
|
|
||||||
|
if(this.props.resize) {
|
||||||
|
/*input = (
|
||||||
|
<AutoResizer
|
||||||
|
content={ this.state.value }
|
||||||
|
vertical={ this.props.resize === 'vertical' }
|
||||||
|
>
|
||||||
|
{ input }
|
||||||
|
</AutoResizer>
|
||||||
|
);*/
|
||||||
|
}
|
||||||
|
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const className = cx({
|
||||||
|
'input-group': true,
|
||||||
|
'input': true,
|
||||||
|
'busy': this.props.isBusy
|
||||||
|
}, this.props.inputGroupClassName);
|
||||||
|
return (
|
||||||
|
<div className={ className }>
|
||||||
|
{ this.renderInput() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
className: 'form-control',
|
className: 'form-control',
|
||||||
onBlur: noop,
|
onBlur: noop,
|
||||||
|
@ -118,17 +211,23 @@ class Input extends React.PureComponent {
|
||||||
onChange: noop,
|
onChange: noop,
|
||||||
onCommit: noop,
|
onCommit: noop,
|
||||||
onFocus: noop,
|
onFocus: noop,
|
||||||
|
onKeyDown: noop,
|
||||||
|
onPaste: noop,
|
||||||
tabIndex: -1,
|
tabIndex: -1,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: '',
|
value: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
autoComplete: PropTypes.bool,
|
||||||
autoFocus: PropTypes.bool,
|
autoFocus: PropTypes.bool,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
form: PropTypes.string,
|
form: PropTypes.string,
|
||||||
|
getSuggestions: PropTypes.func,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
|
inputGroupClassName: PropTypes.string,
|
||||||
inputMode: PropTypes.string,
|
inputMode: PropTypes.string,
|
||||||
|
isBusy: PropTypes.bool,
|
||||||
isDisabled: PropTypes.bool,
|
isDisabled: PropTypes.bool,
|
||||||
isReadOnly: PropTypes.bool,
|
isReadOnly: PropTypes.bool,
|
||||||
isRequired: PropTypes.bool,
|
isRequired: PropTypes.bool,
|
||||||
|
@ -137,12 +236,15 @@ class Input extends React.PureComponent {
|
||||||
min: PropTypes.number,
|
min: PropTypes.number,
|
||||||
minLength: PropTypes.number,
|
minLength: PropTypes.number,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func.isRequired,
|
||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func.isRequired,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func.isRequired,
|
||||||
onCommit: PropTypes.func,
|
onCommit: PropTypes.func.isRequired,
|
||||||
onFocus: PropTypes.func,
|
onFocus: PropTypes.func.isRequired,
|
||||||
|
onKeyDown: PropTypes.func,
|
||||||
|
onPaste: PropTypes.func,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
|
resize: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
|
||||||
selectOnFocus: PropTypes.bool,
|
selectOnFocus: PropTypes.bool,
|
||||||
spellCheck: PropTypes.bool,
|
spellCheck: PropTypes.bool,
|
||||||
step: PropTypes.number,
|
step: PropTypes.number,
|
||||||
|
@ -152,4 +254,6 @@ class Input extends React.PureComponent {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Input;
|
export default React.forwardRef((props, ref) => <Input
|
||||||
|
innerRef={ref} {...props}
|
||||||
|
/>);
|
221
chrome/content/zotero/components/form/select.jsx
Normal file
221
chrome/content/zotero/components/form/select.jsx
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2019 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://digitalscholar.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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { noop } from '../utils';
|
||||||
|
//import Spinner from '../ui/spinner';
|
||||||
|
import Select from 'react-select';
|
||||||
|
|
||||||
|
class SelectInput extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
value: props.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(event = null) {
|
||||||
|
this.props.onCancel(this.hasChanged, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
commit(event = null, value = null, force = false) {
|
||||||
|
this.props.onCommit(value || this.state.value, force ? true : this.hasChanged, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
if(this.input != null) {
|
||||||
|
this.input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillReceiveProps({ value }) {
|
||||||
|
if (value !== this.props.value) {
|
||||||
|
this.setState({ value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(value, ev) {
|
||||||
|
value = value !== null || (value === null && this.props.clearable) ?
|
||||||
|
value : this.props.value;
|
||||||
|
this.setState({ value });
|
||||||
|
|
||||||
|
if(this.props.onChange(value) || this.forceCommitOnNextChange) {
|
||||||
|
if(!ev) {
|
||||||
|
//@NOTE: this is using undocumeneted feature of react-selct v1, but see #131
|
||||||
|
const source = typeof this.input.input.getInput === 'function' ?
|
||||||
|
this.input.input.getInput() : this.input.input;
|
||||||
|
ev = {
|
||||||
|
type: 'change',
|
||||||
|
currentTarget: source,
|
||||||
|
target: source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.commit(ev, value, value !== this.props.value);
|
||||||
|
}
|
||||||
|
this.forceCommitOnNextChange = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlur(event) {
|
||||||
|
this.props.onBlur(event);
|
||||||
|
this.cancel(event);
|
||||||
|
if(this.props.autoBlur) {
|
||||||
|
this.forceCommitOnNextChange = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFocus(event) {
|
||||||
|
this.props.onFocus(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(event) {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
this.cancel(event);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasChanged() {
|
||||||
|
return this.state.value !== this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultSelectProps() {
|
||||||
|
return {
|
||||||
|
simpleValue: true,
|
||||||
|
clearable: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInput(userType, viewport) {
|
||||||
|
const {
|
||||||
|
options,
|
||||||
|
autoFocus,
|
||||||
|
className,
|
||||||
|
id,
|
||||||
|
placeholder,
|
||||||
|
tabIndex,
|
||||||
|
value,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const commonProps = {
|
||||||
|
disabled: this.props.isDisabled,
|
||||||
|
onBlur: this.handleBlur.bind(this),
|
||||||
|
onFocus: this.handleFocus.bind(this),
|
||||||
|
readOnly: this.props.isReadOnly,
|
||||||
|
ref: input => this.input = input,
|
||||||
|
required: this.props.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(userType === 'touch' || viewport.xxs || viewport.xs || viewport.sm) {
|
||||||
|
const props = {
|
||||||
|
...commonProps,
|
||||||
|
onKeyDown: this.handleKeyDown.bind(this),
|
||||||
|
onChange: ev => this.handleChange(ev.target.value, ev),
|
||||||
|
autoFocus, id, placeholder, tabIndex, value
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="native-select-wrap" >
|
||||||
|
<select { ...props }>
|
||||||
|
{ options.map(({ value, label }) => (
|
||||||
|
<option key={ value } value={ value }>{ label }</option>)
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
<div className={ className }>
|
||||||
|
{ (options.find(o => o.value === value) || options[0] || {}).label }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const props = {
|
||||||
|
...this.defaultSelectProps,
|
||||||
|
...this.props,
|
||||||
|
...commonProps,
|
||||||
|
onInputKeyDown: this.handleKeyDown.bind(this),
|
||||||
|
onChange: this.handleChange.bind(this),
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Select { ...props } />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSpinner() {
|
||||||
|
return null;
|
||||||
|
//return this.props.isBusy ? <Spinner className="small" /> : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { userType, viewport } = this.props.device;
|
||||||
|
const className = cx({
|
||||||
|
'input-group': true,
|
||||||
|
'select': true,
|
||||||
|
'busy': this.props.isBusy
|
||||||
|
}, this.props.inputGroupClassName);
|
||||||
|
return (
|
||||||
|
<div className={ className }>
|
||||||
|
{ this.renderInput(userType, viewport) }
|
||||||
|
{ this.renderSpinner() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
className: 'form-control',
|
||||||
|
onBlur: noop,
|
||||||
|
onCancel: noop,
|
||||||
|
onChange: noop,
|
||||||
|
onCommit: noop,
|
||||||
|
onFocus: noop,
|
||||||
|
options: [],
|
||||||
|
tabIndex: -1,
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
|
className: PropTypes.string,
|
||||||
|
id: PropTypes.string,
|
||||||
|
inputGroupClassName: PropTypes.string,
|
||||||
|
isBusy: PropTypes.bool,
|
||||||
|
isDisabled: PropTypes.bool,
|
||||||
|
isReadOnly: PropTypes.bool,
|
||||||
|
isRequired: PropTypes.bool,
|
||||||
|
onBlur: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
onCommit: PropTypes.func.isRequired,
|
||||||
|
onFocus: PropTypes.func.isRequired,
|
||||||
|
options: PropTypes.array.isRequired,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
tabIndex: PropTypes.number,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectInput;
|
208
chrome/content/zotero/components/form/textArea.jsx
Normal file
208
chrome/content/zotero/components/form/textArea.jsx
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2019 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://digitalscholar.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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { noop } from '../utils';
|
||||||
|
//import AutoResizer from './auto-resizer';
|
||||||
|
//import Spinner from '../ui/spinner';
|
||||||
|
|
||||||
|
class TextAreaInput extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
value: props.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(event = null) {
|
||||||
|
this.props.onCancel(this.hasChanged, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
commit(event = null) {
|
||||||
|
this.props.onCommit(this.state.value, this.hasChanged, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
if (this.props.innerRef != null) {
|
||||||
|
this.props.innerRef.focus();
|
||||||
|
this.props.selectOnFocus && this.props.innerRef.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillReceiveProps({ value }) {
|
||||||
|
if (value !== this.props.value) {
|
||||||
|
this.setState({ value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange({ target }) {
|
||||||
|
this.setState({ value: target.value });
|
||||||
|
this.props.onChange(target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlur(event) {
|
||||||
|
const shouldCancel = this.props.onBlur(event);
|
||||||
|
shouldCancel ? this.cancel(event) : this.commit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFocus(event) {
|
||||||
|
this.props.selectOnFocus && event.target.select();
|
||||||
|
this.props.onFocus(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(event) {
|
||||||
|
const { isSingleLine } = this.props;
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
this.cancel(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
if(event.shiftKey || isSingleLine) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.commit(event);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasChanged() {
|
||||||
|
return this.state.value !== this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInput() {
|
||||||
|
const extraProps = Object.keys(this.props).reduce((aggr, key) => {
|
||||||
|
if(key.match(/^(aria-|data-).*/)) {
|
||||||
|
aggr[key] = this.props[key];
|
||||||
|
}
|
||||||
|
return aggr;
|
||||||
|
}, {});
|
||||||
|
const input = <textarea
|
||||||
|
//autoComplete={ this.props.autoComplete }
|
||||||
|
autoFocus={ this.props.autoFocus }
|
||||||
|
className={ this.props.className }
|
||||||
|
cols={ this.props.cols }
|
||||||
|
disabled={ this.props.isDisabled }
|
||||||
|
form={ this.props.form }
|
||||||
|
id={ this.props.id }
|
||||||
|
maxLength={ this.props.maxLength }
|
||||||
|
minLength={ this.props.minLength }
|
||||||
|
name={ this.props.name }
|
||||||
|
onBlur={ this.handleBlur.bind(this) }
|
||||||
|
onChange={ this.handleChange.bind(this) }
|
||||||
|
onFocus={ this.handleFocus.bind(this) }
|
||||||
|
onKeyDown={ this.handleKeyDown.bind(this) }
|
||||||
|
placeholder={ this.props.placeholder }
|
||||||
|
readOnly={ this.props.isReadOnly }
|
||||||
|
ref={ this.props.innerRef }
|
||||||
|
required={ this.props.isRequired }
|
||||||
|
rows={ this.props.rows }
|
||||||
|
spellCheck={ this.props.spellCheck }
|
||||||
|
tabIndex={ this.props.tabIndex }
|
||||||
|
value={ this.state.value }
|
||||||
|
wrap={ this.props.wrap }
|
||||||
|
{ ...extraProps }
|
||||||
|
/>;
|
||||||
|
|
||||||
|
return this.props.resize ?
|
||||||
|
//<AutoResizer
|
||||||
|
// content={ this.state.value }
|
||||||
|
// vertical={ this.props.resize === 'vertical' }
|
||||||
|
//>
|
||||||
|
{ input }
|
||||||
|
/*</AutoResizer> */:
|
||||||
|
input;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSpinner() {
|
||||||
|
return null;
|
||||||
|
//return this.props.isBusy ? <Spinner /> : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const className = cx({
|
||||||
|
'input-group': true,
|
||||||
|
'textarea': true,
|
||||||
|
'busy': this.props.isBusy
|
||||||
|
}, this.props.inputGroupClassName);
|
||||||
|
return (
|
||||||
|
<div className={ cx(className) }>
|
||||||
|
{ this.renderInput() }
|
||||||
|
{ this.renderSpinner() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
className: 'form-control',
|
||||||
|
onBlur: noop,
|
||||||
|
onCancel: noop,
|
||||||
|
onChange: noop,
|
||||||
|
onCommit: noop,
|
||||||
|
onFocus: noop,
|
||||||
|
tabIndex: -1,
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
//autoComplete: PropTypes.bool,
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
|
className: PropTypes.string,
|
||||||
|
cols: PropTypes.number,
|
||||||
|
form: PropTypes.string,
|
||||||
|
id: PropTypes.string,
|
||||||
|
inputGroupClassName: PropTypes.string,
|
||||||
|
isBusy: PropTypes.bool,
|
||||||
|
isDisabled: PropTypes.bool,
|
||||||
|
isReadOnly: PropTypes.bool,
|
||||||
|
isRequired: PropTypes.bool,
|
||||||
|
isSingleLine: PropTypes.bool,
|
||||||
|
maxLength: PropTypes.number,
|
||||||
|
minLength: PropTypes.number,
|
||||||
|
name: PropTypes.string,
|
||||||
|
onBlur: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
onCommit: PropTypes.func.isRequired,
|
||||||
|
onFocus: PropTypes.func.isRequired,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
resize: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
|
||||||
|
rows: PropTypes.number,
|
||||||
|
selectOnFocus: PropTypes.bool,
|
||||||
|
spellCheck: PropTypes.bool,
|
||||||
|
tabIndex: PropTypes.number,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
wrap: PropTypes.bool,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.forwardRef((props, ref) => <TextAreaInput
|
||||||
|
innerRef={ref} {...props}
|
||||||
|
/>);
|
433
chrome/content/zotero/components/itemPane/tagsBox.jsx
Normal file
433
chrome/content/zotero/components/itemPane/tagsBox.jsx
Normal file
|
@ -0,0 +1,433 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2019 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://digitalscholar.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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState, useEffect, useRef, useMemo, useImperativeHandle } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import Editable from '../editable';
|
||||||
|
import Input from '../form/input';
|
||||||
|
import TextAreaInput from '../form/textArea';
|
||||||
|
//import Button from '../form/button';
|
||||||
|
|
||||||
|
const TagsBox = React.forwardRef((props, ref) => {
|
||||||
|
const [prevInitialTags, setPrevInitialTags] = useState([]);
|
||||||
|
const [tags, setTags] = useState([]);
|
||||||
|
const tagNames = useMemo(() => new Set(tags.map(t => t.tag)), [tags]);
|
||||||
|
const [selectedTag, setSelectedTag] = useState('');
|
||||||
|
const [newRow, setNewRow] = useState(false);
|
||||||
|
const [currentValue, setCurrentValue] = useState('');
|
||||||
|
const [isMultiline, setIsMultiline] = useState(false);
|
||||||
|
const [rows, setRows] = useState(1);
|
||||||
|
const rootRef = useRef(null);
|
||||||
|
const textboxRef = useRef(null);
|
||||||
|
const requestID = useRef(1);
|
||||||
|
const newRowID = useRef(1);
|
||||||
|
const resetSelectionOnRender = useRef(false);
|
||||||
|
const skipNextEdit = useRef(false);
|
||||||
|
|
||||||
|
const removeStr = Zotero.getString('general.remove');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Move cursor to end of textarea after paste
|
||||||
|
if (isMultiline) {
|
||||||
|
//let textarea = window.getSelection().anchorNode.querySelector('textarea');
|
||||||
|
let textarea = rootRef.current && rootRef.current.querySelector('textarea');
|
||||||
|
if (textarea) {
|
||||||
|
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resetSelectionOnRender.current) {
|
||||||
|
resetSelectionOnRender.current = false;
|
||||||
|
if (props.onResetSelection) {
|
||||||
|
props.onResetSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
blurOpenField
|
||||||
|
}));
|
||||||
|
|
||||||
|
function handleAddTag() {
|
||||||
|
setSelectedTag('');
|
||||||
|
showNewRow(true);
|
||||||
|
requestID.current++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNewRow(show) {
|
||||||
|
setNewRow(show);
|
||||||
|
if (show) {
|
||||||
|
setSelectedTag('');
|
||||||
|
newRowID.current++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(event) {
|
||||||
|
if (skipNextEdit.current) {
|
||||||
|
skipNextEdit.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tag = event.currentTarget.closest('[data-tag]').dataset.tag;
|
||||||
|
if (tag === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If switching from input to textarea, don't change anything
|
||||||
|
if (isMultiline) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentValue(tag);
|
||||||
|
setSelectedTag(tag);
|
||||||
|
showNewRow(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(event) {
|
||||||
|
// With the delete button set to tabindex=-1, Tab doesn't work in the last tag for some
|
||||||
|
// reason, so blur it manually
|
||||||
|
if (!isMultiline && event.key == 'Tab' && !event.shiftKey) {
|
||||||
|
let target = event.currentTarget || event.target;
|
||||||
|
let oldTag = target.closest('[data-tag]').dataset.tag;
|
||||||
|
if (oldTag === '' && target.value === '') {
|
||||||
|
textboxRef.current.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseDown(event) {
|
||||||
|
// Prevent right-click on a tag from switching to edit mode
|
||||||
|
if (event.button != 0) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
// The above works on its own, but setting the XUL context popup allows the event to go
|
||||||
|
// through if the confirmation prompt for "Remove All Tags" is cancelled, so we need
|
||||||
|
// to skip the next edit event as well
|
||||||
|
skipNextEdit.current = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCommit(newTag, hasChanged, event) {
|
||||||
|
var oldTag = (event.currentTarget || event.target).closest('[data-tag]').dataset.tag;
|
||||||
|
|
||||||
|
var oldTags = tags;
|
||||||
|
var sortedTags = getSortedTags(oldTags);
|
||||||
|
var lastTag = sortedTags.length ? sortedTags[oldTags.length - 1] : null;
|
||||||
|
|
||||||
|
if (!isMultiline
|
||||||
|
&& event.key == 'Enter'
|
||||||
|
&& event.shiftKey) {
|
||||||
|
let trimmed = newTag.trim();
|
||||||
|
if (trimmed !== '') {
|
||||||
|
trimmed += "\n";
|
||||||
|
}
|
||||||
|
setCurrentValue(trimmed);
|
||||||
|
setIsMultiline(true);
|
||||||
|
setRows(6);
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentValue('');
|
||||||
|
setSelectedTag('');
|
||||||
|
setIsMultiline(false);
|
||||||
|
|
||||||
|
// Tag hasn't changed
|
||||||
|
if (oldTag === newTag) {
|
||||||
|
// If Enter was pressed in an empty text box, hide it
|
||||||
|
if (newTag === '') {
|
||||||
|
showNewRow(false);
|
||||||
|
}
|
||||||
|
/*else if (oldTag == lastTag.tag) {
|
||||||
|
showNewRow(true);
|
||||||
|
}*/
|
||||||
|
resetSelectionOnRender.current = event.key == 'Enter';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newTags = [];
|
||||||
|
|
||||||
|
if (newTag !== '') {
|
||||||
|
// Split by newlines
|
||||||
|
let splitTags = newTag.split(/\r\n?|\n/)
|
||||||
|
.map(val => val.trim())
|
||||||
|
.filter(x => x);
|
||||||
|
let newTagsMap = new Map();
|
||||||
|
|
||||||
|
// Get all tags
|
||||||
|
for (let i = 0; i < oldTags.length; i++) {
|
||||||
|
let tag = oldTags[i];
|
||||||
|
|
||||||
|
// If this was the tag being edited, add the new value(s)
|
||||||
|
if (tag.tag == oldTag) {
|
||||||
|
for (let t of splitTags) {
|
||||||
|
newTagsMap.set(t, { tag: t });
|
||||||
|
}
|
||||||
|
if (oldTag == lastTag) {
|
||||||
|
showNewRow(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise add the old one
|
||||||
|
else {
|
||||||
|
newTagsMap.set(tag.tag, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New tag at end
|
||||||
|
if (oldTag === '') {
|
||||||
|
for (let t of splitTags) {
|
||||||
|
newTagsMap.set(t, { tag: t });
|
||||||
|
}
|
||||||
|
// Call this again to increment the ref and avoid reusing the entered value in the
|
||||||
|
// next new row
|
||||||
|
showNewRow(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resetSelectionOnRender.current = event.key == 'Enter';
|
||||||
|
}
|
||||||
|
|
||||||
|
newTags = [...newTagsMap.values()];
|
||||||
|
}
|
||||||
|
// Tag cleared
|
||||||
|
else {
|
||||||
|
newTags = oldTags.filter(tag => tag.tag != oldTag);
|
||||||
|
showNewRow(false);
|
||||||
|
resetSelectionOnRender.current = event.key == 'Enter';
|
||||||
|
}
|
||||||
|
|
||||||
|
setTags(getSortedTags(newTags));
|
||||||
|
props.onTagsUpdate(newTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
setCurrentValue('');
|
||||||
|
setSelectedTag('');
|
||||||
|
setIsMultiline(false);
|
||||||
|
showNewRow(false);
|
||||||
|
//setSuggestions([]);
|
||||||
|
resetSelectionOnRender.current = true;
|
||||||
|
requestID.current++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(event) {
|
||||||
|
var tag = event.currentTarget.closest('[data-tag]').dataset.tag;
|
||||||
|
var oldTags = tags;
|
||||||
|
|
||||||
|
setSelectedTag('');
|
||||||
|
|
||||||
|
var newTags = oldTags.filter(t => t.tag !== tag);
|
||||||
|
setTags(newTags);
|
||||||
|
props.onTagsUpdate(newTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePaste(event) {
|
||||||
|
var text = event.clipboardData.getData('text');
|
||||||
|
//paste = paste.toUpperCase();
|
||||||
|
|
||||||
|
var multiline = !!text.trim().match(/\n/);
|
||||||
|
if (multiline) {
|
||||||
|
//setCurrentValue(str.trim());
|
||||||
|
|
||||||
|
let field = event.target;
|
||||||
|
let newValue;
|
||||||
|
// TODO: Add newlines before and after if necessary
|
||||||
|
if (field.selectionStart || field.selectionStart == '0') {
|
||||||
|
let startPos = field.selectionStart;
|
||||||
|
let endPos = field.selectionEnd;
|
||||||
|
newValue = field.value.substring(0, startPos)
|
||||||
|
+ text
|
||||||
|
+ field.value.substring(endPos, field.value.length);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newValue = field.value + text;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentValue(newValue);
|
||||||
|
setIsMultiline(true);
|
||||||
|
setRows(newValue.split(/\n/).length);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function blurOpenField(event) {
|
||||||
|
if (textboxRef.current && event.target != textboxRef.current) {
|
||||||
|
textboxRef.current.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSortedTags(tags) {
|
||||||
|
var sortedTags = [...tags];
|
||||||
|
sortedTags.sort((a, b) => a.tag.localeCompare(b.tag));
|
||||||
|
return sortedTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFilteredSuggestions(value) {
|
||||||
|
var suggestions = await props.getSuggestions(value);
|
||||||
|
return suggestions.filter(s => !tagNames.has(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
function tagsEqual(a, b) {
|
||||||
|
if (a.length != b.length) return false;
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (a[i].tag !== b[i].tag || a[i].type !== b[i].type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCount() {
|
||||||
|
var count = tags.length;
|
||||||
|
var str = 'pane.item.tags.count.';
|
||||||
|
// TODO: Switch to plural rules
|
||||||
|
switch (count) {
|
||||||
|
case 0:
|
||||||
|
str += 'zero';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
str += 'singular';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
str += 'plural';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Zotero.getString(str, [count]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTagRow(tag) {
|
||||||
|
// Icon
|
||||||
|
var iconFile = 'tag';
|
||||||
|
var title = '';
|
||||||
|
if (!tag.type || tag.newRow) {
|
||||||
|
title = Zotero.getString('pane.item.tags.icon.user');
|
||||||
|
}
|
||||||
|
else if (tag.type == 1) {
|
||||||
|
title = Zotero.getString('pane.item.tags.icon.automatic');
|
||||||
|
iconFile += '-automatic';
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected = tag.tag === selectedTag;
|
||||||
|
|
||||||
|
// Style colored tags
|
||||||
|
var style = {};
|
||||||
|
if (!selected) {
|
||||||
|
let colorData = props.colors.get(tag.tag);
|
||||||
|
if (colorData) {
|
||||||
|
style.fontWeight = 'bold';
|
||||||
|
style.color = colorData.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={cx({ tag: true, multiline: selected && isMultiline })}
|
||||||
|
key={tag.newRow ? newRowID.current + '' : tag.tag}
|
||||||
|
data-tag={tag.tag}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`chrome://zotero/skin/${iconFile}${Zotero.hiDPISuffix}.png`}
|
||||||
|
alt={title}
|
||||||
|
title={title}
|
||||||
|
tooltiptext={title}
|
||||||
|
style={{ width: "16px", height: "16px" }}
|
||||||
|
onClick={() => setSelectedTag(tag.tag)}
|
||||||
|
/>
|
||||||
|
<div className="editable-container" style={style}>
|
||||||
|
<Editable
|
||||||
|
autoComplete={!isMultiline}
|
||||||
|
autoFocus
|
||||||
|
className={cx({ 'zotero-clicky': !selected })}
|
||||||
|
getSuggestions={getFilteredSuggestions}
|
||||||
|
inputComponent={isMultiline ? TextAreaInput : Input}
|
||||||
|
isActive={selected}
|
||||||
|
isReadOnly={!props.editable}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onClick={handleEdit}
|
||||||
|
onCommit={handleCommit}
|
||||||
|
onFocus={handleEdit}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onPaste={handlePaste}
|
||||||
|
ref={textboxRef}
|
||||||
|
selectOnFocus={!isMultiline}
|
||||||
|
value={(selected && isMultiline) ? currentValue : tag.tag}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{props.editable
|
||||||
|
&& (<button
|
||||||
|
onClick={handleDelete}
|
||||||
|
tabIndex="-1"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt={removeStr}
|
||||||
|
height="18"
|
||||||
|
width="18"
|
||||||
|
title={removeStr}
|
||||||
|
tooltiptext={removeStr}
|
||||||
|
src={`chrome://zotero/skin/minus${Zotero.hiDPISuffix}.png`}/>
|
||||||
|
</button>)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the initial tags change (because the item was updated), update state with those
|
||||||
|
var initialTags = getSortedTags(props.initialTags);
|
||||||
|
if (!tagsEqual(initialTags, prevInitialTags)) {
|
||||||
|
setTags(initialTags);
|
||||||
|
setPrevInitialTags(initialTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayTags = [...tags];
|
||||||
|
if (newRow) {
|
||||||
|
displayTags.push({
|
||||||
|
tag: '',
|
||||||
|
newRow: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tags-box" ref={rootRef} onClick={blurOpenField}>
|
||||||
|
<div className="tags-box-header">
|
||||||
|
<div className="tags-box-count">{renderCount()}</div>
|
||||||
|
<div><button onClick={handleAddTag}>Add</button></div>
|
||||||
|
</div>
|
||||||
|
<ul className="tags-box-list">
|
||||||
|
{displayTags.map(tag => renderTagRow(tag))}
|
||||||
|
</ul>
|
||||||
|
<span
|
||||||
|
tabIndex="0"
|
||||||
|
onFocus={handleAddTag}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TagsBox.propTypes = {
|
||||||
|
colors: PropTypes.instanceOf(Map),
|
||||||
|
editable: PropTypes.bool,
|
||||||
|
getSuggestions: PropTypes.func,
|
||||||
|
initialTags: PropTypes.array.isRequired,
|
||||||
|
onResetSelection: PropTypes.func,
|
||||||
|
onTagsUpdate: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TagsBox;
|
5
chrome/content/zotero/components/utils.js
Normal file
5
chrome/content/zotero/components/utils.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
const noop = () => {};
|
||||||
|
|
||||||
|
export {
|
||||||
|
noop
|
||||||
|
};
|
|
@ -25,6 +25,7 @@
|
||||||
-->
|
-->
|
||||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
|
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
|
||||||
<?xul-overlay href="chrome://zotero/content/containers/tagSelector.xul"?>
|
<?xul-overlay href="chrome://zotero/content/containers/tagSelector.xul"?>
|
||||||
|
<?xul-overlay href="chrome://zotero/content/containers/tagsBox.xul"?>
|
||||||
|
|
||||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
<script src="chrome://zotero/content/include.js"></script>
|
<script src="chrome://zotero/content/include.js"></script>
|
||||||
|
|
38
chrome/content/zotero/containers/tagsBox.xul
Normal file
38
chrome/content/zotero/containers/tagsBox.xul
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2019 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://digitalscholar.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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
-->
|
||||||
|
<!DOCTYPE overlay [
|
||||||
|
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
|
||||||
|
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd"> %zoteroDTD;
|
||||||
|
]>
|
||||||
|
|
||||||
|
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
<popupset>
|
||||||
|
<menupopup id="tags-context-menu">
|
||||||
|
<menuitem id="remove-all-item-tags" label="&zotero.item.tags.removeAll;"
|
||||||
|
oncommand="ZoteroItemPane.removeAllTags()"/>
|
||||||
|
</menupopup>
|
||||||
|
</popupset>
|
||||||
|
</overlay>
|
123
chrome/content/zotero/containers/tagsBoxContainer.jsx
Normal file
123
chrome/content/zotero/containers/tagsBoxContainer.jsx
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2019 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://digitalscholar.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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
//import PropTypes from 'prop-types';
|
||||||
|
import { Cc, Ci } from 'chrome';
|
||||||
|
import TagsBox from 'components/itemPane/tagsBox.js';
|
||||||
|
|
||||||
|
var search = Cc["@mozilla.org/autocomplete/search;1?name=zotero"]
|
||||||
|
.createInstance(Ci.nsIAutoCompleteSearch);
|
||||||
|
|
||||||
|
function TagsBoxContainer(props, ref) {
|
||||||
|
var map = Zotero.Tags.getColors(props.item.libraryID);
|
||||||
|
const [tags, setTags] = useState(props.item.getTags());
|
||||||
|
const [colors, setColors] = useState(Zotero.Tags.getColors(props.item.libraryID));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
var observer = {
|
||||||
|
notify: async function (action, type, ids, extraData) {
|
||||||
|
if (type == 'setting') {
|
||||||
|
if (ids.some(val => val.split("/")[1] == 'tagColors')) {
|
||||||
|
setColors(Zotero.Tags.getColors(props.item.libraryID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == 'item-tag') {
|
||||||
|
for (let i = 0; i < ids.length; i++) {
|
||||||
|
let [itemID, _tagID] = ids[i].split('-').map(x => parseInt(x));
|
||||||
|
if (itemID == props.item.id) {
|
||||||
|
setTags(props.item.getTags());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var id = Zotero.Notifier.registerObserver(observer, ['item-tag', 'setting'], 'tagsBox');
|
||||||
|
|
||||||
|
return function cleanup() {
|
||||||
|
Zotero.Notifier.unregisterObserver(id);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getSuggestions(value) {
|
||||||
|
return new Zotero.Promise(function (resolve, reject) {
|
||||||
|
var results = [];
|
||||||
|
search.startSearch(
|
||||||
|
value,
|
||||||
|
JSON.stringify({
|
||||||
|
libraryID: props.item.libraryID,
|
||||||
|
fieldName: 'tag',
|
||||||
|
itemID: props.item.id
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
onSearchResult: function (search, result) {
|
||||||
|
if (result.searchResult == result.RESULT_IGNORED
|
||||||
|
|| result.searchResult == result.RESULT_FAILURE) {
|
||||||
|
reject(result.errorDescription);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.searchResult == result.RESULT_SUCCESS
|
||||||
|
|| result.searchResult == result.RESULT_SUCCESS_ONGOING) {
|
||||||
|
for (let i = 0; i < result.matchCount; i++) {
|
||||||
|
results.push(result.getValueAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.searchResult != result.RESULT_SUCCESS_ONGOING &&
|
||||||
|
result.searchResult != result.RESULT_NOMATCH_ONGOING) {
|
||||||
|
resolve(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResetSelection() {
|
||||||
|
if (props.onResetSelection) {
|
||||||
|
props.onResetSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTagsUpdate(newTags) {
|
||||||
|
var item = props.item;
|
||||||
|
item.setTags(newTags);
|
||||||
|
await item.saveTx();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <TagsBox
|
||||||
|
colors={colors}
|
||||||
|
editable={props.editable}
|
||||||
|
getSuggestions={getSuggestions}
|
||||||
|
initialTags={tags}
|
||||||
|
onResetSelection={handleResetSelection}
|
||||||
|
onTagsUpdate={handleTagsUpdate}
|
||||||
|
ref={ref}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.forwardRef(TagsBoxContainer);
|
|
@ -23,6 +23,10 @@
|
||||||
***** END LICENSE BLOCK *****
|
***** END LICENSE BLOCK *****
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import TagsBoxContainer from 'containers/tagsBoxContainer';
|
||||||
|
|
||||||
var ZoteroItemPane = new function() {
|
var ZoteroItemPane = new function() {
|
||||||
var _lastItem, _itemBox, _notesLabel, _notesButton, _notesList, _tagsBox, _relatedBox;
|
var _lastItem, _itemBox, _notesLabel, _notesButton, _notesList, _tagsBox, _relatedBox;
|
||||||
var _selectedNoteID;
|
var _selectedNoteID;
|
||||||
|
@ -45,7 +49,10 @@ var ZoteroItemPane = new function() {
|
||||||
_notesLabel = document.getElementById('zotero-editpane-notes-label');
|
_notesLabel = document.getElementById('zotero-editpane-notes-label');
|
||||||
_notesButton = document.getElementById('zotero-editpane-notes-add');
|
_notesButton = document.getElementById('zotero-editpane-notes-add');
|
||||||
_notesList = document.getElementById('zotero-editpane-dynamic-notes');
|
_notesList = document.getElementById('zotero-editpane-dynamic-notes');
|
||||||
_tagsBox = document.getElementById('zotero-editpane-tags');
|
// Fake a ref
|
||||||
|
_tagsBox = {
|
||||||
|
current: null
|
||||||
|
};
|
||||||
_relatedBox = document.getElementById('zotero-editpane-related');
|
_relatedBox = document.getElementById('zotero-editpane-related');
|
||||||
|
|
||||||
this._unregisterID = Zotero.Notifier.registerObserver(this, ['item'], 'itemPane');
|
this._unregisterID = Zotero.Notifier.registerObserver(this, ['item'], 'itemPane');
|
||||||
|
@ -72,10 +79,6 @@ var ZoteroItemPane = new function() {
|
||||||
var box = _itemBox;
|
var box = _itemBox;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
|
||||||
var box = _tagsBox;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
var box = _relatedBox;
|
var box = _relatedBox;
|
||||||
break;
|
break;
|
||||||
|
@ -86,11 +89,14 @@ var ZoteroItemPane = new function() {
|
||||||
if (_lastItem && _lastItem != item) {
|
if (_lastItem && _lastItem != item) {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
case 2:
|
|
||||||
yield box.blurOpenField();
|
yield box.blurOpenField();
|
||||||
// DEBUG: Currently broken
|
// DEBUG: Currently broken
|
||||||
//box.scrollToTop();
|
//box.scrollToTop();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
_tagsBox.current.blurOpenField();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,19 +181,33 @@ var ZoteroItemPane = new function() {
|
||||||
_updateNoteCount();
|
_updateNoteCount();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (index == 2) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<TagsBoxContainer
|
||||||
|
key={"tagsBox-" + item.id}
|
||||||
|
item={item}
|
||||||
|
editable={mode != 'view'}
|
||||||
|
ref={_tagsBox}
|
||||||
|
onResetSelection={focusItemsList}
|
||||||
|
/>,
|
||||||
|
document.getElementById('tags-box-container')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (mode) {
|
if (box) {
|
||||||
box.mode = mode;
|
if (mode) {
|
||||||
|
box.mode = mode;
|
||||||
if (box.mode == 'view') {
|
|
||||||
box.hideEmptyFields = true;
|
if (box.mode == 'view') {
|
||||||
|
box.hideEmptyFields = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
box.mode = 'edit';
|
||||||
|
}
|
||||||
|
|
||||||
|
box.item = item;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
box.mode = 'edit';
|
|
||||||
}
|
|
||||||
|
|
||||||
box.item = item;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -214,7 +234,7 @@ var ZoteroItemPane = new function() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
var box = _tagsBox;
|
var box = _tagsBox.current;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (box) {
|
if (box) {
|
||||||
|
@ -223,6 +243,14 @@ var ZoteroItemPane = new function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function focusItemsList() {
|
||||||
|
var tree = document.getElementById('zotero-items-tree');
|
||||||
|
if (tree) {
|
||||||
|
tree.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.onNoteSelected = function (item, editable) {
|
this.onNoteSelected = function (item, editable) {
|
||||||
_selectedNoteID = item.id;
|
_selectedNoteID = item.id;
|
||||||
|
|
||||||
|
@ -292,6 +320,14 @@ var ZoteroItemPane = new function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.removeAllTags = async function () {
|
||||||
|
if (Services.prompt.confirm(null, "", Zotero.getString('pane.item.tags.removeAll'))) {
|
||||||
|
_lastItem.setTags([]);
|
||||||
|
await _lastItem.saveTx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.translateSelectedItems = Zotero.Promise.coroutine(function* () {
|
this.translateSelectedItems = Zotero.Promise.coroutine(function* () {
|
||||||
var collectionID = _translationTarget.objectType == 'collection' ? _translationTarget.id : undefined;
|
var collectionID = _translationTarget.objectType == 'collection' ? _translationTarget.id : undefined;
|
||||||
var items = ZoteroPane_Local.itemsView.getSelectedItems();
|
var items = ZoteroPane_Local.itemsView.getSelectedItems();
|
||||||
|
|
|
@ -28,9 +28,11 @@
|
||||||
|
|
||||||
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||||
|
|
||||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
<overlay
|
||||||
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||||
|
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||||
<script src="include.js"/>
|
<script src="include.js"/>
|
||||||
<script src="itemPane.js" type="application/javascript"/>
|
<script src="itemPane.js"></script>
|
||||||
|
|
||||||
<vbox id="zotero-item-pane" zotero-persist="width height">
|
<vbox id="zotero-item-pane" zotero-persist="width height">
|
||||||
<!-- My Publications -->
|
<!-- My Publications -->
|
||||||
|
@ -95,8 +97,8 @@
|
||||||
</vbox>
|
</vbox>
|
||||||
</tabpanel>
|
</tabpanel>
|
||||||
|
|
||||||
<tabpanel>
|
<tabpanel id="tags-pane" orient="vertical" context="tags-context-menu">
|
||||||
<tagsbox id="zotero-editpane-tags" flex="1"/>
|
<html:div id="tags-box-container"></html:div>
|
||||||
</tabpanel>
|
</tabpanel>
|
||||||
|
|
||||||
<tabpanel>
|
<tabpanel>
|
||||||
|
|
57
chrome/content/zotero/modules/immutable.js
Normal file
57
chrome/content/zotero/modules/immutable.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2019 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://digitalscholar.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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
const removeKeys = (object, deleteKeys) => {
|
||||||
|
if(!Array.isArray(deleteKeys)) {
|
||||||
|
deleteKeys = [deleteKeys];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(object)
|
||||||
|
.reduce((aggr, [key, value]) => {
|
||||||
|
if(!deleteKeys.includes(key)) { aggr[key] = value; }
|
||||||
|
return aggr;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickKeys = (object, pickKeys) => {
|
||||||
|
if(typeof(pickKeys) === 'function') {
|
||||||
|
return Object.entries(object)
|
||||||
|
.reduce((aggr, [key, value]) => {
|
||||||
|
if(pickKeys(key)) { aggr[key] = value; }
|
||||||
|
return aggr;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
if(!Array.isArray(pickKeys)) {
|
||||||
|
pickKeys = [pickKeys];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(object)
|
||||||
|
.reduce((aggr, [key, value]) => {
|
||||||
|
if(pickKeys.includes(key)) { aggr[key] = value; }
|
||||||
|
return aggr;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { removeKeys, pickKeys };
|
421
package-lock.json
generated
421
package-lock.json
generated
|
@ -274,6 +274,14 @@
|
||||||
"@babel/types": "^7.0.0"
|
"@babel/types": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/helper-module-imports": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-optimise-call-expression": {
|
"@babel/helper-optimise-call-expression": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz",
|
||||||
|
@ -651,7 +659,6 @@
|
||||||
"version": "7.4.2",
|
"version": "7.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.2.tgz",
|
||||||
"integrity": "sha512-7Bl2rALb7HpvXFL7TETNzKSAeBVCPHELzc0C//9FCxN8nsiueWSJBqaF+2oIJScyILStASR/Cx5WMkXGYTiJFA==",
|
"integrity": "sha512-7Bl2rALb7HpvXFL7TETNzKSAeBVCPHELzc0C//9FCxN8nsiueWSJBqaF+2oIJScyILStASR/Cx5WMkXGYTiJFA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.2"
|
"regenerator-runtime": "^0.13.2"
|
||||||
},
|
},
|
||||||
|
@ -659,8 +666,7 @@
|
||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.13.2",
|
"version": "0.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
|
||||||
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==",
|
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -719,7 +725,6 @@
|
||||||
"version": "7.3.4",
|
"version": "7.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz",
|
||||||
"integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==",
|
"integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"esutils": "^2.0.2",
|
"esutils": "^2.0.2",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
|
@ -729,11 +734,106 @@
|
||||||
"to-fast-properties": {
|
"to-fast-properties": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
|
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@emotion/cache": {
|
||||||
|
"version": "10.0.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.19.tgz",
|
||||||
|
"integrity": "sha512-BoiLlk4vEsGBg2dAqGSJu0vJl/PgVtCYLBFJaEO8RmQzPugXewQCXZJNXTDFaRlfCs0W+quesayav4fvaif5WQ==",
|
||||||
|
"requires": {
|
||||||
|
"@emotion/sheet": "0.9.3",
|
||||||
|
"@emotion/stylis": "0.8.4",
|
||||||
|
"@emotion/utils": "0.11.2",
|
||||||
|
"@emotion/weak-memoize": "0.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@emotion/core": {
|
||||||
|
"version": "10.0.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.22.tgz",
|
||||||
|
"integrity": "sha512-7eoP6KQVUyOjAkE6y4fdlxbZRA4ILs7dqkkm6oZUJmihtHv0UBq98VgPirq9T8F9K2gKu0J/au/TpKryKMinaA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"@emotion/cache": "^10.0.17",
|
||||||
|
"@emotion/css": "^10.0.22",
|
||||||
|
"@emotion/serialize": "^0.11.12",
|
||||||
|
"@emotion/sheet": "0.9.3",
|
||||||
|
"@emotion/utils": "0.11.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
|
||||||
|
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.13.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
|
||||||
|
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@emotion/css": {
|
||||||
|
"version": "10.0.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.22.tgz",
|
||||||
|
"integrity": "sha512-8phfa5mC/OadBTmGpMpwykIVH0gFCbUoO684LUkyixPq4F1Wwri7fK5Xlm8lURNBrd2TuvTbPUGxFsGxF9UacA==",
|
||||||
|
"requires": {
|
||||||
|
"@emotion/serialize": "^0.11.12",
|
||||||
|
"@emotion/utils": "0.11.2",
|
||||||
|
"babel-plugin-emotion": "^10.0.22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@emotion/hash": {
|
||||||
|
"version": "0.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz",
|
||||||
|
"integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw=="
|
||||||
|
},
|
||||||
|
"@emotion/memoize": {
|
||||||
|
"version": "0.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.3.tgz",
|
||||||
|
"integrity": "sha512-2Md9mH6mvo+ygq1trTeVp2uzAKwE2P7In0cRpD/M9Q70aH8L+rxMLbb3JCN2JoSWsV2O+DdFjfbbXoMoLBczow=="
|
||||||
|
},
|
||||||
|
"@emotion/serialize": {
|
||||||
|
"version": "0.11.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.14.tgz",
|
||||||
|
"integrity": "sha512-6hTsySIuQTbDbv00AnUO6O6Xafdwo5GswRlMZ5hHqiFx+4pZ7uGWXUQFW46Kc2taGhP89uXMXn/lWQkdyTosPA==",
|
||||||
|
"requires": {
|
||||||
|
"@emotion/hash": "0.7.3",
|
||||||
|
"@emotion/memoize": "0.7.3",
|
||||||
|
"@emotion/unitless": "0.7.4",
|
||||||
|
"@emotion/utils": "0.11.2",
|
||||||
|
"csstype": "^2.5.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@emotion/sheet": {
|
||||||
|
"version": "0.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.3.tgz",
|
||||||
|
"integrity": "sha512-c3Q6V7Df7jfwSq5AzQWbXHa5soeE4F5cbqi40xn0CzXxWW9/6Mxq48WJEtqfWzbZtW9odZdnRAkwCQwN12ob4A=="
|
||||||
|
},
|
||||||
|
"@emotion/stylis": {
|
||||||
|
"version": "0.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.4.tgz",
|
||||||
|
"integrity": "sha512-TLmkCVm8f8gH0oLv+HWKiu7e8xmBIaokhxcEKPh1m8pXiV/akCiq50FvYgOwY42rjejck8nsdQxZlXZ7pmyBUQ=="
|
||||||
|
},
|
||||||
|
"@emotion/unitless": {
|
||||||
|
"version": "0.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.4.tgz",
|
||||||
|
"integrity": "sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ=="
|
||||||
|
},
|
||||||
|
"@emotion/utils": {
|
||||||
|
"version": "0.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.2.tgz",
|
||||||
|
"integrity": "sha512-UHX2XklLl3sIaP6oiMmlVzT0J+2ATTVpf0dHQVyPJHTkOITvXfaSqnRk6mdDhV9pR8T/tHc3cex78IKXssmzrA=="
|
||||||
|
},
|
||||||
|
"@emotion/weak-memoize": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-6PYY5DVdAY1ifaQW6XYTnOMihmBVT27elqSjEoodchsGjzYlEsTQMcEhSud99kVawatyTZRTiVkJ/c6lwbQ7nA=="
|
||||||
|
},
|
||||||
"@formatjs/intl-relativetimeformat": {
|
"@formatjs/intl-relativetimeformat": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-4.2.1.tgz",
|
||||||
|
@ -938,7 +1038,6 @@
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"sprintf-js": "~1.0.2"
|
"sprintf-js": "~1.0.2"
|
||||||
}
|
}
|
||||||
|
@ -1186,6 +1285,53 @@
|
||||||
"babel-runtime": "^6.22.0"
|
"babel-runtime": "^6.22.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"babel-plugin-emotion": {
|
||||||
|
"version": "10.0.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.23.tgz",
|
||||||
|
"integrity": "sha512-1JiCyXU0t5S2xCbItejCduLGGcKmF3POT0Ujbexog2MI4IlRcIn/kWjkYwCUZlxpON0O5FC635yPl/3slr7cKQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-module-imports": "^7.0.0",
|
||||||
|
"@emotion/hash": "0.7.3",
|
||||||
|
"@emotion/memoize": "0.7.3",
|
||||||
|
"@emotion/serialize": "^0.11.14",
|
||||||
|
"babel-plugin-macros": "^2.0.0",
|
||||||
|
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||||
|
"convert-source-map": "^1.5.0",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"find-root": "^1.1.0",
|
||||||
|
"source-map": "^0.5.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"babel-plugin-macros": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-6W2nwiXme6j1n2erPOnmRiWfObUhWH7Qw1LMi9XZy8cj+KtESu3T6asZvtk5bMQQjX8te35o7CFueiSdL/2NmQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.2",
|
||||||
|
"cosmiconfig": "^5.2.0",
|
||||||
|
"resolve": "^1.10.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"path-parse": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"version": "1.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
|
||||||
|
"integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
|
||||||
|
"requires": {
|
||||||
|
"path-parse": "^1.0.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"babel-plugin-syntax-jsx": {
|
||||||
|
"version": "6.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
|
||||||
|
"integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
|
||||||
|
},
|
||||||
"babel-plugin-transform-es2015-modules-commonjs": {
|
"babel-plugin-transform-es2015-modules-commonjs": {
|
||||||
"version": "6.26.2",
|
"version": "6.26.2",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz",
|
||||||
|
@ -1635,6 +1781,29 @@
|
||||||
"integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=",
|
"integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"caller-callsite": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
|
||||||
|
"requires": {
|
||||||
|
"callsites": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"callsites": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"caller-path": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
|
||||||
|
"requires": {
|
||||||
|
"caller-callsite": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"callsites": {
|
"callsites": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz",
|
||||||
|
@ -1956,7 +2125,6 @@
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
|
||||||
"integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
|
"integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "~5.1.1"
|
"safe-buffer": "~5.1.1"
|
||||||
},
|
},
|
||||||
|
@ -1964,8 +2132,7 @@
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1987,6 +2154,51 @@
|
||||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"cosmiconfig": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
|
||||||
|
"requires": {
|
||||||
|
"import-fresh": "^2.0.0",
|
||||||
|
"is-directory": "^0.3.1",
|
||||||
|
"js-yaml": "^3.13.1",
|
||||||
|
"parse-json": "^4.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"import-fresh": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
|
||||||
|
"requires": {
|
||||||
|
"caller-path": "^2.0.0",
|
||||||
|
"resolve-from": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"js-yaml": {
|
||||||
|
"version": "3.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||||
|
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||||
|
"requires": {
|
||||||
|
"argparse": "^1.0.7",
|
||||||
|
"esprima": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parse-json": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
|
||||||
|
"requires": {
|
||||||
|
"error-ex": "^1.3.1",
|
||||||
|
"json-parse-better-errors": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resolve-from": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"create-ecdh": {
|
"create-ecdh": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz",
|
||||||
|
@ -2255,7 +2467,6 @@
|
||||||
"version": "3.4.0",
|
"version": "3.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
|
||||||
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
|
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.1.2"
|
"@babel/runtime": "^7.1.2"
|
||||||
}
|
}
|
||||||
|
@ -2310,7 +2521,6 @@
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-arrayish": "^0.2.1"
|
"is-arrayish": "^0.2.1"
|
||||||
}
|
}
|
||||||
|
@ -2354,8 +2564,7 @@
|
||||||
"escape-string-regexp": {
|
"escape-string-regexp": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "5.14.1",
|
"version": "5.14.1",
|
||||||
|
@ -2593,8 +2802,7 @@
|
||||||
"esprima": {
|
"esprima": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"esquery": {
|
"esquery": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -2623,8 +2831,7 @@
|
||||||
"esutils": {
|
"esutils": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
|
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
|
@ -2864,6 +3071,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"find-root": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
|
||||||
|
},
|
||||||
"find-up": {
|
"find-up": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
|
||||||
|
@ -4100,8 +4312,7 @@
|
||||||
"is-arrayish": {
|
"is-arrayish": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
|
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-binary-path": {
|
"is-binary-path": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -4169,6 +4380,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"is-directory": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
|
||||||
|
"integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE="
|
||||||
|
},
|
||||||
"is-extendable": {
|
"is-extendable": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||||
|
@ -4348,6 +4564,11 @@
|
||||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"json-parse-better-errors": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
|
||||||
|
},
|
||||||
"json-schema": {
|
"json-schema": {
|
||||||
"version": "0.2.3",
|
"version": "0.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||||
|
@ -4510,8 +4731,7 @@
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.11",
|
"version": "4.17.11",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"lodash._baseassign": {
|
"lodash._baseassign": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
|
@ -4664,6 +4884,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"memoize-one": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA=="
|
||||||
|
},
|
||||||
"meow": {
|
"meow": {
|
||||||
"version": "3.7.0",
|
"version": "3.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
||||||
|
@ -5448,12 +5673,23 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.6.2",
|
"version": "15.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
"integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
|
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.3.1",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.8.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"requires": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pseudomap": {
|
"pseudomap": {
|
||||||
|
@ -5524,25 +5760,52 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react": {
|
"react": {
|
||||||
"version": "16.8.6",
|
"version": "16.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-16.11.0.tgz",
|
||||||
"integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==",
|
"integrity": "sha512-M5Y8yITaLmU0ynd0r1Yvfq98Rmll6q8AxaEe88c8e7LxO8fZ2cNgmFt0aGAS9wzf1Ao32NKXtCl+/tVVtkxq6g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2"
|
||||||
"scheduler": "^0.13.6"
|
}
|
||||||
|
},
|
||||||
|
"react-autosuggest": {
|
||||||
|
"version": "9.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-9.4.3.tgz",
|
||||||
|
"integrity": "sha512-wFbp5QpgFQRfw9cwKvcgLR8theikOUkv8PFsuLYqI2PUgVlx186Cz8MYt5bLxculi+jxGGUUVt+h0esaBZZouw==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.5.10",
|
||||||
|
"react-autowhatever": "^10.1.2",
|
||||||
|
"shallow-equal": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-autowhatever": {
|
||||||
|
"version": "10.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-autowhatever/-/react-autowhatever-10.2.0.tgz",
|
||||||
|
"integrity": "sha512-dqHH4uqiJldPMbL8hl/i2HV4E8FMTDEdVlOIbRqYnJi0kTpWseF9fJslk/KS9pGDnm80JkYzVI+nzFjnOG/u+g==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.5.8",
|
||||||
|
"react-themeable": "^1.1.0",
|
||||||
|
"section-iterator": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-dom": {
|
"react-dom": {
|
||||||
"version": "16.8.6",
|
"version": "16.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz",
|
||||||
"integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==",
|
"integrity": "sha512-nrRyIUE1e7j8PaXSPtyRKtz+2y9ubW/ghNgqKFHHAHaeP0fpF5uXR+sq8IMRHC+ZUxw7W9NyCDTBtwWxvkb0iA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"scheduler": "^0.13.6"
|
"scheduler": "^0.17.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-input-autosize": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.5.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-intl": {
|
"react-intl": {
|
||||||
|
@ -5571,8 +5834,73 @@
|
||||||
"react-lifecycles-compat": {
|
"react-lifecycles-compat": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
|
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||||
"dev": true
|
},
|
||||||
|
"react-select": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-select/-/react-select-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-v9LpOhckLlRmXN5A6/mGGEft4FMrfaBFTGAnuPHcUgVId7Je42kTq9y0Z+Ye5z8/j0XDT3zUqza8gaRaI1PZIg==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4",
|
||||||
|
"@emotion/cache": "^10.0.9",
|
||||||
|
"@emotion/core": "^10.0.9",
|
||||||
|
"@emotion/css": "^10.0.9",
|
||||||
|
"memoize-one": "^5.0.0",
|
||||||
|
"prop-types": "^15.6.0",
|
||||||
|
"react-input-autosize": "^2.2.2",
|
||||||
|
"react-transition-group": "^2.2.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
|
||||||
|
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.13.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
|
||||||
|
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-themeable": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz",
|
||||||
|
"integrity": "sha1-fURm3ZsrX6dQWHJ4JenxUro3mg4=",
|
||||||
|
"requires": {
|
||||||
|
"object-assign": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"object-assign": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-transition-group": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
|
||||||
|
"requires": {
|
||||||
|
"dom-helpers": "^3.4.0",
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-lifecycles-compat": "^3.0.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"requires": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"react-virtualized": {
|
"react-virtualized": {
|
||||||
"version": "9.21.0",
|
"version": "9.21.0",
|
||||||
|
@ -5875,9 +6203,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scheduler": {
|
"scheduler": {
|
||||||
"version": "0.13.6",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.17.0.tgz",
|
||||||
"integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
|
"integrity": "sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
|
@ -5904,6 +6232,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"section-iterator": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio="
|
||||||
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
|
||||||
|
@ -6173,8 +6506,7 @@
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"source-map-resolve": {
|
"source-map-resolve": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
|
@ -6239,8 +6571,7 @@
|
||||||
"sprintf-js": {
|
"sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"sshpk": {
|
"sshpk": {
|
||||||
"version": "1.16.1",
|
"version": "1.16.1",
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": "^3.5.1",
|
"bluebird": "^3.5.1",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.11.0",
|
||||||
"react-dom": "^16.8.6",
|
"react-autosuggest": "^9.4.3",
|
||||||
|
"react-dom": "^16.11.0",
|
||||||
"react-intl": "^3.4.0",
|
"react-intl": "^3.4.0",
|
||||||
|
"react-select": "^3.0.8",
|
||||||
"url": "^0.11.0"
|
"url": "^0.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -758,6 +758,9 @@ const Require = iced(function Require(loader, requirer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _require(id) {
|
function _require(id) {
|
||||||
|
// Fix require() from react-autosuggest
|
||||||
|
if (id == 'React') id = 'react';
|
||||||
|
|
||||||
let { uri, requirement } = getRequirements(id);
|
let { uri, requirement } = getRequirements(id);
|
||||||
let module = null;
|
let module = null;
|
||||||
// If module is already cached by loader then just use it.
|
// If module is already cached by loader then just use it.
|
||||||
|
|
1
resource/react-autosuggest.js
vendored
Symbolic link
1
resource/react-autosuggest.js
vendored
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../node_modules/react-autosuggest/dist/standalone/autosuggest.min.js
|
|
@ -93,8 +93,10 @@ var require = (function() {
|
||||||
id: 'zotero/require',
|
id: 'zotero/require',
|
||||||
paths: {
|
paths: {
|
||||||
'': 'resource://zotero/',
|
'': 'resource://zotero/',
|
||||||
|
'containers/': 'chrome://zotero/content/containers/',
|
||||||
'components/': 'chrome://zotero/content/components/',
|
'components/': 'chrome://zotero/content/components/',
|
||||||
'zotero/': 'chrome://zotero/content/modules/'
|
'zotero/': 'chrome://zotero/content/modules/',
|
||||||
|
'@zotero/': 'chrome://zotero/content/modules/'
|
||||||
},
|
},
|
||||||
globals
|
globals
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,6 +38,13 @@ const symlinkFiles = [
|
||||||
|
|
||||||
// these files will be browserified during the build
|
// these files will be browserified during the build
|
||||||
const browserifyConfigs = [
|
const browserifyConfigs = [
|
||||||
|
{
|
||||||
|
src: 'node_modules/react-select/dist/react-select.cjs.prod.js',
|
||||||
|
dest: 'resource/react-select.js',
|
||||||
|
config: {
|
||||||
|
standalone: 'react-select'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
src: 'node_modules/url/url.js',
|
src: 'node_modules/url/url.js',
|
||||||
dest: 'resource/url.js',
|
dest: 'resource/url.js',
|
||||||
|
|
|
@ -20,7 +20,10 @@
|
||||||
// Components
|
// Components
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
@import "components/autosuggest";
|
||||||
@import "components/button";
|
@import "components/button";
|
||||||
|
@import "components/editable";
|
||||||
@import "components/icons";
|
@import "components/icons";
|
||||||
@import "components/search";
|
@import "components/search";
|
||||||
|
@import "components/tagsBox";
|
||||||
@import "components/tagSelector";
|
@import "components/tagSelector";
|
||||||
|
|
59
scss/components/_autosuggest.scss
Normal file
59
scss/components/_autosuggest.scss
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
.react-autosuggest__container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.react-autosuggest__input {
|
||||||
|
width: 240px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-family: Helvetica, sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-autosuggest__input--focused {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-autosuggest__input--open {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.react-autosuggest__suggestions-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-autosuggest__suggestions-container--open {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 1.75em;
|
||||||
|
width: calc(100% + 1px);
|
||||||
|
max-height: 10em;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
background-color: #fff;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
margin-left: -3px;
|
||||||
|
box-shadow: -1px 10px 17px -7px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-autosuggest__suggestions-list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-autosuggest__suggestion {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-autosuggest__suggestion--highlighted {
|
||||||
|
background-color: Highlight;
|
||||||
|
color: HighlightText;
|
||||||
|
}
|
11
scss/components/_editable.scss
Normal file
11
scss/components/_editable.scss
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
input.editable-control {
|
||||||
|
flex: 1 1 auto; // Stretch within react-autosuggest__container
|
||||||
|
margin-left: -3px;
|
||||||
|
padding: 2px 1px;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.editable-control {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
83
scss/components/_tag-manager.scss
Normal file
83
scss/components/_tag-manager.scss
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
.tag-manager {
|
||||||
|
margin-top: .5em;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: $font-family-base;
|
||||||
|
|
||||||
|
.library-menu {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
select {
|
||||||
|
flex: 0 0 15em;
|
||||||
|
margin-left: .45em;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
border-left: 1px solid rgb(218, 218, 218);
|
||||||
|
border-bottom: 1px solid rgb(218, 218, 218);
|
||||||
|
margin: 1em 0 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs li {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs li a {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: .7em .5em;
|
||||||
|
min-width: 6em;
|
||||||
|
color: #444;
|
||||||
|
height: unset;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs li a:hover {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs li.active {
|
||||||
|
background: #fff;
|
||||||
|
z-index: 1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-list {
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
border: 1px rgb(218, 218, 218) solid;
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-list > option {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: .1em .4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-bar {
|
||||||
|
margin-top: .7em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: .1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
-moz-appearance: button;
|
||||||
|
font-family: $font-family-base;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
91
scss/components/_tagsBox.scss
Normal file
91
scss/components/_tagsBox.scss
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
#tags-pane {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tags-box-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-box {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.tags-box-header {
|
||||||
|
display: flex;
|
||||||
|
padding-left: 10px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
min-width: 79px;
|
||||||
|
margin: 5px 6px 3px;
|
||||||
|
padding-top: 1px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
color: ButtonText;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-box-count {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tags-box-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tags-box-list > li {
|
||||||
|
display: flex;
|
||||||
|
margin: 3px 6px 3px 6px;
|
||||||
|
align-items: center;
|
||||||
|
height: 1.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tags-box-list > li:not(.multiline) .editable-container {
|
||||||
|
padding: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift-Enter
|
||||||
|
ul.tags-box-list > li.multiline {
|
||||||
|
align-items: start;
|
||||||
|
height: 9em;
|
||||||
|
|
||||||
|
.editable-container {
|
||||||
|
align-self: stretch;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable, .input-group {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.editable-control {
|
||||||
|
flex: 1;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.editable-control {
|
||||||
|
width: 100px; // Dummy value that somehow prevents field from going off screen at large font size
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.editable-control {
|
||||||
|
width: 100%; // DEBUG: This still runs off the screen at large font size, though it keeps the delete button visible
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,14 +32,14 @@ describe("Item Tags Box", function () {
|
||||||
|
|
||||||
var tabbox = doc.getElementById('zotero-view-tabbox');
|
var tabbox = doc.getElementById('zotero-view-tabbox');
|
||||||
tabbox.selectedIndex = 2;
|
tabbox.selectedIndex = 2;
|
||||||
var tagsbox = doc.getElementById('zotero-editpane-tags');
|
var tagsbox = doc.querySelector('.tags-box');
|
||||||
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
var rows = tagsbox.getElementsByTagName('li');
|
||||||
assert.equal(rows.length, 1);
|
assert.equal(rows.length, 1);
|
||||||
assert.equal(rows[0].textContent, tag);
|
assert.equal(rows[0].textContent, tag);
|
||||||
|
|
||||||
yield Zotero.Tags.rename(Zotero.Libraries.userLibraryID, tag, newTag);
|
yield Zotero.Tags.rename(Zotero.Libraries.userLibraryID, tag, newTag);
|
||||||
|
|
||||||
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
var rows = tagsbox.getElementsByTagName('li');
|
||||||
assert.equal(rows.length, 1);
|
assert.equal(rows.length, 1);
|
||||||
assert.equal(rows[0].textContent, newTag);
|
assert.equal(rows[0].textContent, newTag);
|
||||||
})
|
})
|
||||||
|
@ -65,18 +65,18 @@ describe("Item Tags Box", function () {
|
||||||
|
|
||||||
var tabbox = doc.getElementById('zotero-view-tabbox');
|
var tabbox = doc.getElementById('zotero-view-tabbox');
|
||||||
tabbox.selectedIndex = 2;
|
tabbox.selectedIndex = 2;
|
||||||
var tagsbox = doc.getElementById('zotero-editpane-tags');
|
var tagsbox = doc.querySelector('.tags-box');
|
||||||
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
var rows = tagsbox.getElementsByTagName('li');
|
||||||
|
|
||||||
// Colored tags aren't sorted first, for now
|
// Colored tags aren't sorted first, for now
|
||||||
assert.notOk(rows[0].getElementsByTagName('label')[0].style.color);
|
assert.notOk(rows[0].querySelector('.editable-container').style.color);
|
||||||
assert.ok(rows[1].getElementsByTagName('label')[0].style.color);
|
assert.ok(rows[1].querySelector('.editable-container').style.color);
|
||||||
assert.equal(rows[0].textContent, "_A");
|
assert.equal(rows[0].textContent, "_A");
|
||||||
assert.equal(rows[1].textContent, tag);
|
assert.equal(rows[1].textContent, tag);
|
||||||
|
|
||||||
yield Zotero.Tags.setColor(libraryID, tag, false);
|
yield Zotero.Tags.setColor(libraryID, tag, false);
|
||||||
|
|
||||||
assert.notOk(rows[1].getElementsByTagName('label')[0].style.color);
|
assert.notOk(rows[1].querySelector('.editable-container').style.color);
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should update when a tag is removed from the library", function* () {
|
it("should update when a tag is removed from the library", function* () {
|
||||||
|
@ -95,14 +95,14 @@ describe("Item Tags Box", function () {
|
||||||
|
|
||||||
var tabbox = doc.getElementById('zotero-view-tabbox');
|
var tabbox = doc.getElementById('zotero-view-tabbox');
|
||||||
tabbox.selectedIndex = 2;
|
tabbox.selectedIndex = 2;
|
||||||
var tagsbox = doc.getElementById('zotero-editpane-tags');
|
var tagsbox = doc.querySelector('.tags-box');
|
||||||
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
var rows = tagsbox.getElementsByTagName('li');
|
||||||
assert.equal(rows.length, 1);
|
assert.equal(rows.length, 1);
|
||||||
assert.equal(rows[0].textContent, tag);
|
assert.equal(rows[0].textContent, tag);
|
||||||
|
|
||||||
yield Zotero.Tags.removeFromLibrary(Zotero.Libraries.userLibraryID, Zotero.Tags.getID(tag));
|
yield Zotero.Tags.removeFromLibrary(Zotero.Libraries.userLibraryID, Zotero.Tags.getID(tag));
|
||||||
|
|
||||||
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
|
var rows = tagsbox.getElementsByTagName('li');
|
||||||
assert.equal(rows.length, 0);
|
assert.equal(rows.length, 0);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue