Restyle Tag Selector
* Render a colored dot for colored tags * Improve spacing and positioning, especially on 2x screens * Add scss map for tag colors in light/dark scheme * Add support for compact/comfortable in tag selector * Restyle filter box, add a placeholder and a new icon
This commit is contained in:
parent
ff115b0873
commit
9ff76d2dd9
21 changed files with 334 additions and 131 deletions
|
@ -27,7 +27,7 @@
|
|||
|
||||
const React = require('react')
|
||||
const { PureComponent, createElement: create } = React
|
||||
const { IconDownChevron } = require('./icons')
|
||||
const { CSSIcon } = require('./icons')
|
||||
const cx = require('classnames')
|
||||
const {
|
||||
bool, element, func, node, number, oneOf, string
|
||||
|
@ -77,7 +77,7 @@ class Button extends PureComponent {
|
|||
if (!Zotero.isNode && Zotero.isLinux) {
|
||||
return this.props.isMenu && <span className="menu-marker"/>
|
||||
}
|
||||
return this.props.isMenu && <IconDownChevron className="menu-marker"/>
|
||||
return this.props.isMenu && <CSSIcon name="chevron-6" className="menu-marker icon-8"/>
|
||||
}
|
||||
|
||||
get attributes() {
|
||||
|
|
|
@ -29,7 +29,7 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const TagList = require('./tagSelector/tagSelectorList');
|
||||
const { Button } = require('./button');
|
||||
const { IconTagSelectorMenu } = require('./icons');
|
||||
const { CSSIcon } = require('./icons');
|
||||
const Search = require('./search');
|
||||
|
||||
class TagSelector extends React.PureComponent {
|
||||
|
@ -46,21 +46,26 @@ class TagSelector extends React.PureComponent {
|
|||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
fontSize={this.props.fontSize}
|
||||
lineHeight={this.props.lineHeight}
|
||||
uiDensity={this.props.uiDensity}
|
||||
/>
|
||||
<div className="tag-selector-filter-container">
|
||||
<Search
|
||||
ref={this.props.searchBoxRef}
|
||||
value={this.props.searchString}
|
||||
onSearch={this.props.onSearch}
|
||||
className="tag-selector-filter"
|
||||
/>
|
||||
<Button
|
||||
icon={<IconTagSelectorMenu />}
|
||||
title="zotero.toolbar.actions.label"
|
||||
className="tag-selector-actions"
|
||||
isMenu
|
||||
onMouseDown={ev => this.props.onSettings(ev)}
|
||||
/>
|
||||
<div className="tag-selector-filter-pane">
|
||||
<div className="tag-selector-filter-container">
|
||||
<Search
|
||||
ref={this.props.searchBoxRef}
|
||||
value={this.props.searchString}
|
||||
onSearch={this.props.onSearch}
|
||||
className="tag-selector-filter"
|
||||
data-l10n-id="tagselector-search"
|
||||
/>
|
||||
<Button
|
||||
icon={<CSSIcon name="filter" className="icon-16" />}
|
||||
title="zotero.toolbar.actions.label"
|
||||
className="tag-selector-actions"
|
||||
isMenu
|
||||
onMouseDown={ev => this.props.onSettings(ev)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -88,6 +93,8 @@ TagSelector.propTypes = {
|
|||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
lineHeight: PropTypes.number.isRequired,
|
||||
uiDensity: PropTypes.string.isRequired,
|
||||
|
||||
// Search
|
||||
searchBoxRef: PropTypes.object,
|
||||
|
|
|
@ -26,21 +26,21 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
var { Collection } = require('react-virtualized');
|
||||
const { props } = require("bluebird");
|
||||
|
||||
// See also .tag-selector-item in _tag-selector.scss
|
||||
var filterBarHeight = 32;
|
||||
var tagPaddingTop = 4;
|
||||
var tagPaddingLeft = 2;
|
||||
var tagPaddingRight = 2;
|
||||
var tagPaddingBottom = 4;
|
||||
var tagSpaceBetweenX = 7;
|
||||
var filterBarHeight = 36;
|
||||
var tagPaddingLeft = 4;
|
||||
var tagPaddingRight = 4;
|
||||
var tagSpaceBetweenX = 2;
|
||||
var tagSpaceBetweenY = 4;
|
||||
var panePaddingTop = 2;
|
||||
var panePaddingLeft = 2;
|
||||
var panePaddingRight = 25;
|
||||
var panePaddingTop = 8;
|
||||
var panePaddingLeft = 8;
|
||||
var panePaddingRight = 2; // + scrollbar width
|
||||
//var panePaddingBottom = 2;
|
||||
var minHorizontalPadding = panePaddingLeft + tagPaddingLeft + tagPaddingRight + panePaddingRight;
|
||||
|
||||
|
||||
class TagList extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -53,11 +53,13 @@ class TagList extends React.PureComponent {
|
|||
// Redraw all tags on every refresh
|
||||
if (this.collectionRef && this.collectionRef.current) {
|
||||
// If width or height changed, recompute positions. It seems like this should happen
|
||||
// automatically, but it doesn't as of 9.21.0.
|
||||
// automatically, but it doesn't as of 9.21.0. Also check for density change.
|
||||
|
||||
if (prevProps.height != this.props.height
|
||||
|| prevProps.width != this.props.width
|
||||
|| prevProps.fontSize != this.props.fontSize
|
||||
|| prevProps.tags != this.props.tags) {
|
||||
|| prevProps.lineHeight != this.props.lineHeight
|
||||
|| prevProps.tags != this.props.tags
|
||||
|| prevProps.uiDensity !== this.props.uiDensity) {
|
||||
this.collectionRef.current.recomputeCellSizesAndPositions();
|
||||
}
|
||||
// If dimensions didn't change, just redraw at current positions. Without this, clicking
|
||||
|
@ -94,8 +96,12 @@ class TagList extends React.PureComponent {
|
|||
* Calculate the x,y coordinates of all tags
|
||||
*/
|
||||
updatePositions() {
|
||||
var tagMaxWidth = this.props.width - minHorizontalPadding;
|
||||
var rowHeight = tagPaddingTop + this.props.fontSize + tagPaddingBottom + tagSpaceBetweenY;
|
||||
const tagPaddingTop = this.props.uiDensity === 'comfortable' ? 2 : 1;
|
||||
const tagPaddingBottom = tagPaddingTop;
|
||||
this.scrollbarWidth = Zotero.Utilities.Internal.getScrollbarWidth();
|
||||
|
||||
var tagMaxWidth = this.props.width - minHorizontalPadding - this.scrollbarWidth;
|
||||
var rowHeight = tagPaddingTop + this.props.lineHeight + tagPaddingBottom + tagSpaceBetweenY;
|
||||
var positions = [];
|
||||
var row = 0;
|
||||
let rowX = panePaddingLeft;
|
||||
|
@ -113,9 +119,11 @@ class TagList extends React.PureComponent {
|
|||
shouldAddSeparator = true;
|
||||
forceNewLine = true;
|
||||
}
|
||||
let tagWidth = tagPaddingLeft + Math.min(tag.width, tagMaxWidth) + tagPaddingRight;
|
||||
// size of the colored dot + space between the dot and the tag name always sums up to fontSize (e.g., 8px + 3px at 11px fontSize)
|
||||
const tagColorWidth = (tag.color && !Zotero.Utilities.Internal.isOnlyEmoji(tag.name)) ? this.props.fontSize : 0;
|
||||
let tagWidth = tagPaddingLeft + Math.min(tag.width, tagMaxWidth) + tagPaddingRight + tagColorWidth;
|
||||
// If first row or cell fits, add to current row
|
||||
if (!forceNewLine && (i == 0 || ((rowX + tagWidth) < (this.props.width - panePaddingLeft - panePaddingRight)))) {
|
||||
if (!forceNewLine && (i == 0 || ((rowX + tagWidth) < (this.props.width - panePaddingRight - this.scrollbarWidth)))) {
|
||||
positions[i] = [rowX, panePaddingTop + (row * rowHeight)];
|
||||
}
|
||||
// Otherwise, start new row
|
||||
|
@ -135,10 +143,10 @@ class TagList extends React.PureComponent {
|
|||
}
|
||||
|
||||
cellSizeAndPositionGetter = ({ index }) => {
|
||||
var tagMaxWidth = this.props.width - minHorizontalPadding;
|
||||
var tagMaxWidth = this.props.width - minHorizontalPadding - this.scrollbarWidth;
|
||||
return {
|
||||
width: Math.min(this.props.tags[index].width, tagMaxWidth),
|
||||
height: this.props.fontSize,
|
||||
height: this.props.lineHeight,
|
||||
x: this.positions[index][0],
|
||||
y: this.positions[index][1]
|
||||
};
|
||||
|
@ -149,7 +157,7 @@ class TagList extends React.PureComponent {
|
|||
|
||||
const { onDragOver, onDragExit, onDrop } = this.props.dragObserver;
|
||||
|
||||
var className = 'tag-selector-item zotero-clicky';
|
||||
var className = 'tag-selector-item';
|
||||
if (tag.selected) {
|
||||
className += ' selected';
|
||||
}
|
||||
|
@ -159,6 +167,9 @@ class TagList extends React.PureComponent {
|
|||
if (tag.disabled) {
|
||||
className += ' disabled';
|
||||
}
|
||||
if (Zotero.Utilities.Internal.isOnlyEmoji(tag.name)) {
|
||||
className += ' emoji';
|
||||
}
|
||||
|
||||
let props = {
|
||||
className,
|
||||
|
@ -176,7 +187,7 @@ class TagList extends React.PureComponent {
|
|||
// Don't specify explicit width unless we're truncating, because for some reason the width
|
||||
// from canvas can sometimes be slightly smaller than the actual width, resulting in an
|
||||
// unnecessary ellipsis.
|
||||
var tagMaxWidth = this.props.width - minHorizontalPadding;
|
||||
var tagMaxWidth = this.props.width - minHorizontalPadding - this.scrollbarWidth;
|
||||
if (props.style.width < tagMaxWidth) {
|
||||
delete props.style.width;
|
||||
}
|
||||
|
@ -190,11 +201,12 @@ class TagList extends React.PureComponent {
|
|||
|
||||
if (tag.color) {
|
||||
props.style.color = tag.color;
|
||||
props['data-color'] = tag.color.toLowerCase();
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={tag.name} {...props}>
|
||||
{tag.name}
|
||||
<span>{tag.name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -265,6 +277,8 @@ class TagList extends React.PureComponent {
|
|||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
lineHeight: PropTypes.number.isRequired,
|
||||
uiDensity: PropTypes.string.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ const defaults = {
|
|||
};
|
||||
const { Cc, Ci } = require('chrome');
|
||||
|
||||
// first n tags will be measured using DOM method for more accurate measurment (at the cost of performance)
|
||||
const FORCE_DOM_TAGS_FOR_COUNT = 200;
|
||||
|
||||
Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -50,7 +53,10 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
|||
['collection-item', 'item', 'item-tag', 'tag', 'setting'],
|
||||
'tagSelector'
|
||||
);
|
||||
this._prefObserverID = Zotero.Prefs.registerObserver('fontSize', this.handleFontChange.bind(this));
|
||||
this._prefObserverID = Zotero.Prefs.registerObserver('fontSize', this.handleUIPropertiesChange.bind(this));
|
||||
this._prefObserverID = Zotero.Prefs.registerObserver('uiDensity', this.handleUIPropertiesChange.bind(this));
|
||||
this._mediaQueryList = window.matchMedia("(min-resolution: 1.5dppx)");
|
||||
this._mediaQueryList.addEventListener("change", this.handleUIPropertiesChange.bind(this));
|
||||
|
||||
this.tagListRef = React.createRef();
|
||||
this.searchBoxRef = React.createRef();
|
||||
|
@ -66,7 +72,8 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
|||
this.state = {
|
||||
...defaults,
|
||||
...this.getContainerDimensions(),
|
||||
...this.getFontInfo()
|
||||
...this.getFontInfo(),
|
||||
isHighDensity: this._mediaQueryList.matches
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -357,6 +364,7 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
|||
container.appendChild(elem);
|
||||
var style = window.getComputedStyle(elem);
|
||||
var props = {
|
||||
lineHeight: style.getPropertyValue('line-height'),
|
||||
fontSize: style.getPropertyValue('font-size'),
|
||||
fontFamily: style.getPropertyValue('font-family')
|
||||
};
|
||||
|
@ -365,36 +373,61 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
|||
}
|
||||
|
||||
/**
|
||||
* Recompute tag widths based on the current font settings
|
||||
* Recompute tag widths when either font, UI density or pixel density changes
|
||||
*/
|
||||
handleFontChange() {
|
||||
handleUIPropertiesChange(ev) {
|
||||
this.widths.clear();
|
||||
this.widthsBold.clear();
|
||||
const isHighDensity = ev.target instanceof MediaQueryList ? ev.matches : this.state.isHighDensity;
|
||||
this.setState({
|
||||
...this.getFontInfo()
|
||||
...this.getFontInfo(),
|
||||
uiDensity: Zotero.Prefs.get('uiDensity'),
|
||||
isHighDensity
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
|
||||
* Except for emoji tags, where, on high-density screens, we use actual DOM element for more accurate
|
||||
* measurement (which is 4-5x slower) because canvas method can be off by enough to cause visible artifacts.
|
||||
* It's possible to force use of DOM method for other tags using forceUseDOM parameter.
|
||||
*
|
||||
* @param {String} text The text to be rendered.
|
||||
* @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
|
||||
* @param {String} forceUseDOM Force use of DOM method for measuring text width
|
||||
*
|
||||
* @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
|
||||
*/
|
||||
getTextWidth(text, font) {
|
||||
// re-use canvas object for better performance
|
||||
var canvas = this.canvas || (this.canvas = document.createElement("canvas"));
|
||||
var context = canvas.getContext("2d");
|
||||
context.font = font;
|
||||
// Add a little more to make sure we don't crop
|
||||
var metrics = context.measureText(text);
|
||||
return Math.ceil(metrics.width);
|
||||
getTextWidth(text, font, forceUseDOM = false) {
|
||||
let width;
|
||||
const useDOM = forceUseDOM || (this.state.isHighDensity && Zotero.Utilities.Internal.includesEmoji(text));
|
||||
if (useDOM) {
|
||||
if (!this.divMeasure) {
|
||||
this.divMeasure = document.createElement('div');
|
||||
this.divMeasure.style.position = 'absolute';
|
||||
this.divMeasure.style.top = '-9999px';
|
||||
this.divMeasure.whiteSpace = 'nowrap';
|
||||
document.querySelector('#zotero-tag-selector').appendChild(this.divMeasure);
|
||||
}
|
||||
|
||||
this.divMeasure.style.font = font;
|
||||
this.divMeasure.textContent = text;
|
||||
width = this.divMeasure.clientWidth;
|
||||
this.divMeasure.textContent = '';
|
||||
}
|
||||
else {
|
||||
// re-use canvas object for better performance
|
||||
var canvas = this.canvas || (this.canvas = document.createElement("canvas"));
|
||||
var context = canvas.getContext("2d");
|
||||
context.font = font;
|
||||
var metrics = context.measureText(text);
|
||||
width = metrics.width;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
getWidth(name) {
|
||||
var num = 0;
|
||||
getWidth(name, forceUseDOM = false) {
|
||||
var font = this.state.fontSize + ' ' + this.state.fontFamily;
|
||||
// Colored tags are shown in bold, which results in a different width
|
||||
var fontBold = 'bold ' + font;
|
||||
|
@ -402,8 +435,8 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
|||
let widths = hasColor ? this.widthsBold : this.widths;
|
||||
let width = widths.get(name);
|
||||
if (width === undefined) {
|
||||
width = this.getTextWidth(name, hasColor ? fontBold : font);
|
||||
//Zotero.debug(`Calculated ${hasColor ? 'bold ' : ''}width of ${width} for tag '${name}'`);
|
||||
width = this.getTextWidth(name, hasColor ? fontBold : font, forceUseDOM);
|
||||
// Zotero.debug(`Calculated ${hasColor ? 'bold ' : ''}width of ${width} for tag '${name}' using ${forceUseDOM ? 'DOM' : 'hybrid'} method`);
|
||||
widths.set(name, width);
|
||||
}
|
||||
return width;
|
||||
|
@ -460,7 +493,7 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
|||
// Prepare tag objects for list component
|
||||
//var d = new Date();
|
||||
var inTagColors = true;
|
||||
tags = tags.map((tag) => {
|
||||
tags = tags.map((tag, i) => {
|
||||
let name = tag.tag;
|
||||
tag = {
|
||||
name,
|
||||
|
@ -480,10 +513,14 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
|||
if ((this.displayAllTags || inTagColors) && !this.state.scope.has(name)) {
|
||||
tag.disabled = true;
|
||||
}
|
||||
tag.width = this.getWidth(name);
|
||||
const forceUseDOM = this.state.isHighDensity && i < FORCE_DOM_TAGS_FOR_COUNT;
|
||||
tag.width = this.getWidth(name, forceUseDOM);
|
||||
return tag;
|
||||
});
|
||||
//Zotero.debug(`Prepared tags in ${new Date() - d} ms`);
|
||||
// clean up divMeasure, which might have been used for measuring emoji tags
|
||||
this.divMeasure?.parentNode?.removeChild?.(this.divMeasure);
|
||||
this.divMeasure = null;
|
||||
// Zotero.debug(`Prepared ${tags.length} tags in ${new Date() - d} ms`);
|
||||
return <TagSelector
|
||||
tags={tags}
|
||||
searchBoxRef={this.searchBoxRef}
|
||||
|
@ -498,6 +535,8 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
|||
width={this.state.width}
|
||||
height={this.state.height}
|
||||
fontSize={parseInt(this.state.fontSize.replace('px', ''))}
|
||||
lineHeight={parseInt(this.state.lineHeight.replace('px', ''))}
|
||||
uiDensity={Zotero.Prefs.get('uiDensity')}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
@ -416,6 +416,12 @@ Zotero.Utilities.Internal = {
|
|||
const re = /\p{Extended_Pictographic}|\u200D|\uFE0F/gu;
|
||||
return !str.replace(re, '');
|
||||
},
|
||||
|
||||
includesEmoji: function (str) {
|
||||
// Remove emoji, Zero Width Joiner, and Variation Selector-16 and compare lengths
|
||||
const re = /\p{Extended_Pictographic}|\u200D|\uFE0F/gu;
|
||||
return str.replace(re, '').length !== str.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Display a prompt from an error with custom buttons and a callback
|
||||
|
@ -2304,6 +2310,21 @@ Zotero.Utilities.Internal = {
|
|||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getScrollbarWidth() {
|
||||
let document = Zotero.getMainWindow().document;
|
||||
let scrollDiv = document.createElement('div');
|
||||
scrollDiv.style.position = 'absolute';
|
||||
scrollDiv.style.top = '-9999px';
|
||||
scrollDiv.style.width = '50px';
|
||||
scrollDiv.style.height = '50px';
|
||||
scrollDiv.style.overflow = 'scroll';
|
||||
document.documentElement.appendChild(scrollDiv);
|
||||
const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth;
|
||||
document.documentElement.removeChild(scrollDiv);
|
||||
|
||||
return scrollbarWidth;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -240,3 +240,6 @@ sidenav-related =
|
|||
|
||||
abstract-field =
|
||||
.label = Add abstract…
|
||||
|
||||
tagselector-search =
|
||||
.placeholder = Filter Tags
|
4
chrome/skin/default/zotero/16/dark/filter.svg
Normal file
4
chrome/skin/default/zotero/16/dark/filter.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.99998 1.70711C1.37001 1.07714 1.81618 0 2.70708 0H14.2929C15.1838 0 15.6299 1.07714 15 1.70711L9.99998 6.70711V12.7071L6.99998 15.7071V6.70711L1.99998 1.70711ZM14.2929 1L2.70708 1L7.99998 6.29289V13.2929L8.99998 12.2929V6.29289L14.2929 1Z" fill="#FFFFFF8C" />
|
||||
</svg>
|
After Width: | Height: | Size: 423 B |
4
chrome/skin/default/zotero/16/light/filter.svg
Normal file
4
chrome/skin/default/zotero/16/light/filter.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.99998 1.70711C1.37001 1.07714 1.81618 0 2.70708 0H14.2929C15.1838 0 15.6299 1.07714 15 1.70711L9.99998 6.70711V12.7071L6.99998 15.7071V6.70711L1.99998 1.70711ZM14.2929 1L2.70708 1L7.99998 6.29289V13.2929L8.99998 12.2929V6.29289L14.2929 1Z" fill="#00000080" />
|
||||
</svg>
|
After Width: | Height: | Size: 423 B |
3
chrome/skin/default/zotero/8/dark/chevron-6.svg
Normal file
3
chrome/skin/default/zotero/8/dark/chevron-6.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.70711 2L1 2.70711L4 5.70711L7 2.70711L6.29289 2L4 4.29289L1.70711 2Z" fill="#FFFFFF8C"/>
|
||||
</svg>
|
After Width: | Height: | Size: 200 B |
3
chrome/skin/default/zotero/8/light/chevron-6.svg
Normal file
3
chrome/skin/default/zotero/8/light/chevron-6.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.70711 2L1 2.70711L4 5.70711L7 2.70711L6.29289 2L4 4.29289L1.70711 2Z" fill="#00000080"/>
|
||||
</svg>
|
After Width: | Height: | Size: 200 B |
|
@ -88,3 +88,15 @@ $item-pane-sections: (
|
|||
"tags": var(--accent-orange),
|
||||
"related": var(--accent-wood),
|
||||
);
|
||||
|
||||
$tagColorsLookup: (
|
||||
'#ff6666': --tag-red,
|
||||
'#ff8c19': --tag-orange,
|
||||
'#999999': --tag-gray,
|
||||
'#5fb236': --tag-green,
|
||||
'#009980': --tag-teal,
|
||||
'#2ea8e5': --tag-blue,
|
||||
'#576dd9': --tag-indigo,
|
||||
'#a28ae5': --tag-purple,
|
||||
'#a6507b': --tag-plum,
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
#zotero-collections-pane {
|
||||
#zotero-collections-pane, #zotero-item-pane {
|
||||
background: var(--material-sidepane);
|
||||
}
|
||||
|
||||
|
@ -18,4 +18,4 @@
|
|||
height: 150px;
|
||||
width: 290px;
|
||||
background: var(--material-background);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,37 +3,31 @@
|
|||
// --------------------------------------------------
|
||||
|
||||
.btn {
|
||||
font: {
|
||||
family: inherit;
|
||||
size: inherit;
|
||||
}
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
text-align: center;
|
||||
-moz-appearance: toolbarbutton;
|
||||
font: {
|
||||
family: inherit;
|
||||
size: inherit;
|
||||
}
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
text-align: center;
|
||||
-moz-appearance: toolbarbutton;
|
||||
|
||||
&[disabled],
|
||||
&.disabled {
|
||||
opacity: $btn-disabled-opacity;
|
||||
}
|
||||
&[disabled],
|
||||
&.disabled {
|
||||
opacity: $btn-disabled-opacity;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
.icon {
|
||||
&:first-child {
|
||||
margin-left: -5px;
|
||||
}
|
||||
&:last-child {
|
||||
margin-right: -5px;
|
||||
}
|
||||
svg, img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
span.menu-marker {
|
||||
-moz-appearance: toolbarbutton-dropdown;
|
||||
display: inline-block;
|
||||
margin-right: -5px;
|
||||
}
|
||||
.icon {
|
||||
svg, img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
span.menu-marker {
|
||||
-moz-appearance: toolbarbutton-dropdown;
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,49 @@
|
|||
.icon > svg, .icon > img {
|
||||
width: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.icon-bg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
vertical-align: middle;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon-css {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon.icon-downchevron {
|
||||
width: 7px !important;
|
||||
width: 7px !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
-moz-appearance: none !important;
|
||||
-moz-appearance: none !important;
|
||||
}
|
||||
|
||||
.icon-16 {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.icon-8 {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
$-icons: (
|
||||
filter: 16,
|
||||
chevron-6: 8
|
||||
);
|
||||
|
||||
@each $icon, $size in $-icons {
|
||||
.icon-#{$icon} {
|
||||
@include color-scheme using($color) {
|
||||
@include svgicon($icon, $color, $size);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -133,14 +133,13 @@
|
|||
#tab-bar-container .tab.selected {
|
||||
background: var(--material-tabbar)
|
||||
}
|
||||
|
||||
|
||||
.zotero-toolbar {
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
#zotero-layout-switcher .zotero-toolbar {
|
||||
background: var(--material-tabbar);
|
||||
border-bottom: var(--material-panedivider);
|
||||
}
|
||||
|
||||
#zotero-collections-tree > div, #zotero-item-pane {
|
||||
background: var(--material-sidepane);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,25 @@
|
|||
}
|
||||
|
||||
.search input {
|
||||
background: var(--material-background);
|
||||
border-radius: 5px;
|
||||
border: var(--material-border-quinary);
|
||||
color: var(--fill-primary);
|
||||
flex: 1 0;
|
||||
font-size: 1em;
|
||||
margin: 6px 4px;
|
||||
min-width: 40px;
|
||||
padding-left: 4px;
|
||||
padding: 3px 7px;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--fill-tertiary);
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: SelectedItem;
|
||||
box-shadow: 0 0 0 2px SelectedItem;
|
||||
}
|
||||
}
|
||||
|
||||
.search .search-cancel-button {
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
display: flex;
|
||||
overflow: hidden;
|
||||
height: 100px;
|
||||
background: var(--material-sidepane);
|
||||
}
|
||||
|
||||
.tag-selector-list-container > div {
|
||||
|
@ -43,18 +42,22 @@
|
|||
}
|
||||
|
||||
.tag-selector-list {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
scrollbar-color: var(--color-scrollbar) var(--color-scrollbar-background);
|
||||
}
|
||||
|
||||
.tag-selector-filter-pane {
|
||||
padding: 0 8px 0;
|
||||
}
|
||||
|
||||
.tag-selector-filter-container {
|
||||
height: 30px;
|
||||
flex: 0 0 1em;
|
||||
border-top: var(--material-panedivider);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0.125em 0 0.125em 0.5em;
|
||||
flex: 0 0 1em;
|
||||
}
|
||||
|
||||
.tag-selector-filter-container .search {
|
||||
|
@ -63,21 +66,79 @@
|
|||
}
|
||||
|
||||
.tag-selector-actions {
|
||||
flex: 0 1;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
align-self: center;
|
||||
background-color: inherit;
|
||||
border: 0;
|
||||
display: block;
|
||||
flex: 0 1;
|
||||
padding: 3px 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tag-selector-item {
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.916666667em;
|
||||
line-height: 1.272727273;
|
||||
overflow: hidden;
|
||||
padding: 1px 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: pre;
|
||||
padding: 1px 4px 3px; // See also TagSelectorList.jsx
|
||||
|
||||
@include comfortable {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--fill-quinary);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--fill-secondary);
|
||||
|
||||
&:hover {
|
||||
opacity: .75;
|
||||
}
|
||||
}
|
||||
|
||||
&.colored {
|
||||
font-weight: bold;
|
||||
|
||||
&.selected {
|
||||
background-color: currentcolor;
|
||||
|
||||
@each $colorHex, $colorVar in $tagColorsLookup {
|
||||
@include state('.tag-selector-item[data-color="#{$colorHex}"]') {
|
||||
background-color: var($colorVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.emoji) {
|
||||
&::before {
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
width: 0.636363636em; // 7px (+ 1px border = 8px)
|
||||
height: 0.636363636em;
|
||||
margin-right: .272727273em;
|
||||
border-radius: 50%;
|
||||
background-color: currentcolor; // fallback for non-standard colors
|
||||
vertical-align: -0.363636364em; // -4px
|
||||
border: var(--material-border-transparent);
|
||||
|
||||
@include state('.tag-selector-item.selected') {
|
||||
border-color: var(--color-background);
|
||||
}
|
||||
}
|
||||
|
||||
@each $colorHex, $colorVar in $tagColorsLookup {
|
||||
@include state('.tag-selector-item[data-color="#{$colorHex}"]') {
|
||||
&::before {
|
||||
background-color: var($colorVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
@ -89,4 +150,12 @@
|
|||
color: var(--color-background);
|
||||
background-color: var(--fill-secondary);
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--fill-primary);
|
||||
|
||||
@include state('.tag-selector-item.selected') {
|
||||
color: var(--color-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
.search input {
|
||||
-moz-appearance: searchfield;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.search .search-cancel-button {
|
||||
|
|
|
@ -2,18 +2,4 @@
|
|||
// Tag selector
|
||||
// --------------------------------------------------
|
||||
|
||||
.tag-selector-filter-container {
|
||||
padding: 0.25em 0 0.25em 0.5em;
|
||||
border-top: var(--material-panedivider);
|
||||
}
|
||||
|
||||
.tag-selector-item {
|
||||
padding-bottom: .3em;
|
||||
}
|
||||
|
||||
.tag-selector-actions {
|
||||
flex: none;
|
||||
border: 0;
|
||||
margin-right: 3px;
|
||||
padding: 1px 6px 0;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
--color-sidepane: #303030;
|
||||
--color-tabbar: #1e1e1e;
|
||||
--color-toolbar: #272727;
|
||||
--color-scrollbar: rgb(117, 117, 117);
|
||||
--color-scrollbar-hover: rgb(158, 158, 158);
|
||||
--color-scrollbar-background: transparent;
|
||||
--tag-blue: #55a6dfd9;
|
||||
--tag-gray: #aaac;
|
||||
--tag-green: #74b04ad9;
|
||||
|
@ -59,5 +62,6 @@
|
|||
--material-border: 1px solid var(--color-border);
|
||||
--material-border50: 1px solid var(--color-border50);
|
||||
--material-panedivider: 1px solid var(--color-panedivider);
|
||||
--material-border-quinary: 1px solid var(--fill-quinary);
|
||||
}
|
||||
}
|
|
@ -31,6 +31,9 @@
|
|||
--color-sidepane: #f2f2f2;
|
||||
--color-tabbar: #f2f2f2;
|
||||
--color-toolbar: #f9f9f9;
|
||||
--color-scrollbar: rgb(194, 194, 194);
|
||||
--color-scrollbar-hover: rgb(125, 125, 125);
|
||||
--color-scrollbar-background: transparent;
|
||||
--tag-blue: #55a6df;
|
||||
--tag-gray: #aaa;
|
||||
--tag-green: #74b04a;
|
||||
|
@ -59,5 +62,6 @@
|
|||
--material-border: 1px solid var(--color-border);
|
||||
--material-border50: 1px solid var(--color-border50);
|
||||
--material-panedivider: 1px solid var(--color-panedivider);
|
||||
--material-border-quinary: 1px solid var(--fill-quinary);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue