Unify context pane into new item pane/sidenav design
This commit is contained in:
parent
beaf03197e
commit
583c6328a0
27 changed files with 722 additions and 1345 deletions
|
@ -37,12 +37,12 @@
|
|||
#zotero-items-splitter[orient=vertical],
|
||||
#zotero-context-splitter-stacked
|
||||
{
|
||||
-moz-border-start: none !important;
|
||||
-moz-border-end: none !important;
|
||||
background-color: #bdbdbd !important;
|
||||
max-height: 1px !important;
|
||||
min-height: 1px !important;
|
||||
height: 1px !important;
|
||||
-moz-border-start: none;
|
||||
-moz-border-end: none;
|
||||
background-color: #bdbdbd;
|
||||
max-height: 1px;
|
||||
min-height: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter:not([state=collapsed]) > grippy,
|
||||
|
@ -53,18 +53,12 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter[state=collapsed],
|
||||
#zotero-items-splitter[state=collapsed],
|
||||
#zotero-context-splitter[state=collapsed],
|
||||
#zotero-context-splitter-stacked[state=collapsed]
|
||||
{
|
||||
#zotero-collections-splitter[state=collapsed] {
|
||||
border: 0 solid #d6d6d6 !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter[state=collapsed],
|
||||
#zotero-items-splitter[state=collapsed][orient=horizontal],
|
||||
#zotero-context-splitter[state=collapsed][orient=horizontal]
|
||||
#zotero-collections-splitter[state=collapsed]
|
||||
{
|
||||
background-image: url("chrome://zotero/skin/mac/vsplitter.png");
|
||||
background-repeat: repeat-y;
|
||||
|
@ -73,14 +67,24 @@
|
|||
width: 8px !important;
|
||||
}
|
||||
|
||||
#zotero-items-splitter[state=collapsed][orient=horizontal],
|
||||
#zotero-context-splitter[state=collapsed][orient=horizontal] {
|
||||
max-width: 1px;
|
||||
min-width: 1px;
|
||||
width: 1px;
|
||||
background: transparent;
|
||||
margin-inline-start: -1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#zotero-items-splitter[state=collapsed][orient=vertical],
|
||||
#zotero-context-splitter-stacked[state=collapsed][orient=vertical]
|
||||
{
|
||||
background-image: url("chrome://zotero/skin/mac/hsplitter.png");
|
||||
background-repeat: repeat-x;
|
||||
max-height: 8px !important;
|
||||
min-height: 8px !important;
|
||||
height: 8px !important;
|
||||
#zotero-context-splitter-stacked[state=collapsed][orient=vertical] {
|
||||
max-height: 1px;
|
||||
min-height: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
margin-top: -1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter[state=collapsed] {
|
||||
|
@ -104,11 +108,6 @@
|
|||
width: 8px;
|
||||
}
|
||||
|
||||
#zotero-context-toolbar-extension {
|
||||
/* To cover #zotero-context-splitter 1px border */
|
||||
margin-inline-start: -1px;
|
||||
}
|
||||
|
||||
/* How to get active twisty?
|
||||
treechildren::-moz-tree-twisty(active) {
|
||||
-moz-appearance: none;
|
||||
|
|
|
@ -1,168 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2020 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
import React, { forwardRef, useImperativeHandle, useState, memo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { CSSItemTypeIcon } from 'components/icons';
|
||||
|
||||
const MAX_UNEXPANDED_ALL_NOTES = 7;
|
||||
|
||||
const NoteRow = memo(({ id, title, body, date, onClick, onKeyDown, onContextMenu, parentItemType, parentTitle }) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={-1}
|
||||
className={cx('note-row', { 'standalone-note-row': !parentItemType })}
|
||||
onClick={() => onClick(id)}
|
||||
onContextMenu={(event) => onContextMenu(id, event)}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<div className="inner">
|
||||
{ parentItemType
|
||||
? <div className="parent-line">
|
||||
<CSSItemTypeIcon className="parent-item-type" itemType={parentItemType} />
|
||||
<span className="parent-title">{parentTitle}</span>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<div className="title-line">
|
||||
<div className="title">{title}</div>
|
||||
</div>
|
||||
<div className="body-line">
|
||||
<div className="date">{date}</div>
|
||||
<div className="body">{body}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const NotesList = forwardRef(({ onClick, onContextMenu, onAddChildButtonDown, onAddStandaloneButtonDown }, ref) => {
|
||||
const [notes, setNotes] = useState([]);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [numVisible, setNumVisible] = useState(0);
|
||||
const [hasParent, setHasParent] = useState(true);
|
||||
|
||||
const _setExpanded = (value) => {
|
||||
setExpanded(value);
|
||||
if (value) {
|
||||
setNumVisible(numVisible + 1000);
|
||||
}
|
||||
else {
|
||||
setNumVisible(0);
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setNotes,
|
||||
setHasParent,
|
||||
setExpanded: _setExpanded
|
||||
}));
|
||||
|
||||
function handleClickMore() {
|
||||
_setExpanded(true);
|
||||
}
|
||||
|
||||
function handleButtonKeydown(event) {
|
||||
if (event.key === 'Tab' && !event.shiftKey) {
|
||||
let node = event.target.parentElement.parentElement.querySelector('[tabindex="-1"]');
|
||||
if (node) {
|
||||
node.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
else if (event.key === 'Tab' && event.shiftKey) {
|
||||
let prevSection = event.target.parentElement.parentElement.previousElementSibling;
|
||||
if (prevSection) {
|
||||
let node = prevSection.querySelector('[tabindex="-1"]:last-child');
|
||||
if (node) {
|
||||
node.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleRowKeyDown(event) {
|
||||
if (['Enter', 'Space'].includes(event.key)) {
|
||||
// Focus the previous row, because "more-row" will disappear
|
||||
if (event.target.classList.contains('more-row')) {
|
||||
let node = event.target.previousElementSibling;
|
||||
if (node) {
|
||||
node.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
event.target.click();
|
||||
}
|
||||
else if (event.key === 'ArrowUp') {
|
||||
let node = event.target.previousElementSibling;
|
||||
if (node) {
|
||||
node.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
else if (event.key === 'ArrowDown') {
|
||||
let node = event.target.nextElementSibling;
|
||||
if (node) {
|
||||
node.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let childNotes = notes.filter(x => x.isCurrentChild);
|
||||
let allNotes = notes.filter(x => !x.isCurrentChild);
|
||||
let visibleNotes = allNotes.slice(0, expanded ? numVisible : MAX_UNEXPANDED_ALL_NOTES);
|
||||
return (
|
||||
<div className="notes-list">
|
||||
{hasParent && <section>
|
||||
<div className="header-row">
|
||||
<h2>{Zotero.getString('pane.context.itemNotes')}</h2>
|
||||
<button onMouseDown={onAddChildButtonDown} onClick={onAddChildButtonDown} onKeyDown={handleButtonKeydown}>+</button>
|
||||
</div>
|
||||
{!childNotes.length && <div className="empty-row">{Zotero.getString('pane.context.noNotes')}</div>}
|
||||
{childNotes.map(note => <NoteRow key={note.id} {...note}
|
||||
onClick={onClick} onKeyDown={handleRowKeyDown} onContextMenu={onContextMenu}/>)}
|
||||
</section>}
|
||||
<section>
|
||||
<div className="header-row">
|
||||
<h2>{Zotero.getString('pane.context.allNotes')}</h2>
|
||||
<button onMouseDown={onAddStandaloneButtonDown} onClick={onAddStandaloneButtonDown} onKeyDown={handleButtonKeydown}>+</button>
|
||||
</div>
|
||||
{!allNotes.length && <div className="empty-row">{Zotero.getString('pane.context.noNotes')}</div>}
|
||||
{visibleNotes.map(note => <NoteRow key={note.id} {...note}
|
||||
onClick={onClick} onKeyDown={handleRowKeyDown} onContextMenu={onContextMenu}/>)}
|
||||
{allNotes.length > visibleNotes.length
|
||||
&& <div className="more-row" tabIndex={-1} onClick={handleClickMore} onKeyDown={handleRowKeyDown}>{
|
||||
Zotero.getString('general.numMore', Zotero.Utilities.numberFormat(
|
||||
[allNotes.length - visibleNotes.length], 0))
|
||||
}</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default NotesList;
|
|
@ -1,442 +0,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 *****
|
||||
*/
|
||||
|
||||
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 (!props.editable) {
|
||||
return;
|
||||
}
|
||||
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 || 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}
|
||||
/* Fix 'title' not working for HTML-in-XUL */
|
||||
onMouseOver={() => window.Zotero_Tooltip.start(title)}
|
||||
onMouseOut={() => window.Zotero_Tooltip.stop()}
|
||||
style={{ width: "16px", height: "16px" }}
|
||||
onClick={props.editable ? (() => setSelectedTag(tag.tag)) : undefined}
|
||||
/>
|
||||
<div className="editable-container" style={style}>
|
||||
<Editable
|
||||
autoComplete={!isMultiline}
|
||||
autoFocus
|
||||
className={cx({ 'zotero-clicky': props.editable && !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}
|
||||
/* Fix 'title' not working for HTML-in-XUL */
|
||||
onMouseOver={() => window.Zotero_Tooltip.start(removeStr)}
|
||||
onMouseOut={() => window.Zotero_Tooltip.stop()}
|
||||
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>
|
||||
{ props.editable && <div><button onClick={handleAddTag}>Add</button></div> }
|
||||
</div>
|
||||
<div className="tags-box-list-container">
|
||||
<ul className="tags-box-list">
|
||||
{displayTags.map(tag => renderTagRow(tag))}
|
||||
</ul>
|
||||
{ props.editable && <span
|
||||
tabIndex="0"
|
||||
onFocus={handleAddTag}
|
||||
/> }
|
||||
</div>
|
||||
</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;
|
|
@ -1,126 +0,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 *****
|
||||
*/
|
||||
|
||||
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) {
|
||||
var i = 0;
|
||||
|
||||
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) {
|
||||
// Pick up where we left off
|
||||
for (; 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,11 +23,6 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
// TODO: Fix import/require related issues that might be
|
||||
// related with `require` not reusing the context
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
var NotesList = require('components/itemPane/notesList').default;
|
||||
var { getCSSItemTypeIcon } = require('components/icons');
|
||||
|
||||
var ZoteroContextPane = new function () {
|
||||
|
@ -36,17 +31,11 @@ var ZoteroContextPane = new function () {
|
|||
var _contextPaneInner;
|
||||
var _contextPaneSplitter;
|
||||
var _contextPaneSplitterStacked;
|
||||
var _itemToggle;
|
||||
var _notesToggle;
|
||||
var _sidenav;
|
||||
var _panesDeck;
|
||||
var _itemPaneDeck;
|
||||
var _notesPaneDeck;
|
||||
|
||||
var _splitButton;
|
||||
var _itemPaneToggle;
|
||||
var _notesPaneToggle;
|
||||
var _tabToolbar;
|
||||
|
||||
var _itemContexts = [];
|
||||
var _notesContexts = [];
|
||||
|
||||
|
@ -66,42 +55,17 @@ var ZoteroContextPane = new function () {
|
|||
}
|
||||
|
||||
_tabCover = document.getElementById('zotero-tab-cover');
|
||||
_itemToggle = document.getElementById('zotero-tb-toggle-item-pane');
|
||||
_notesToggle = document.getElementById('zotero-tb-toggle-notes-pane');
|
||||
_contextPane = document.getElementById('zotero-context-pane');
|
||||
_contextPaneInner = document.getElementById('zotero-context-pane-inner');
|
||||
_contextPaneSplitter = document.getElementById('zotero-context-splitter');
|
||||
_contextPaneSplitterStacked = document.getElementById('zotero-context-splitter-stacked');
|
||||
|
||||
_splitButton = document.getElementById('zotero-tb-split');
|
||||
_itemPaneToggle = document.getElementById('zotero-tb-toggle-item-pane');
|
||||
_notesPaneToggle = document.getElementById('zotero-tb-toggle-notes-pane');
|
||||
_tabToolbar = document.getElementById('zotero-tab-toolbar');
|
||||
|
||||
if (Zotero.rtl) {
|
||||
_tabToolbar.style.left = 0;
|
||||
_splitButton.style.transform = 'scaleX(-1)';
|
||||
}
|
||||
else {
|
||||
_tabToolbar.style.right = 0;
|
||||
}
|
||||
|
||||
// vbox
|
||||
var vbox = document.createXULElement('vbox');
|
||||
vbox.setAttribute('flex', '1');
|
||||
|
||||
_contextPaneInner.append(vbox);
|
||||
|
||||
// Toolbar extension
|
||||
var toolbarExtension = document.createXULElement('box');
|
||||
toolbarExtension.style.height = '32px';
|
||||
toolbarExtension.id = 'zotero-context-toolbar-extension';
|
||||
_sidenav = document.getElementById('zotero-context-pane-sidenav');
|
||||
|
||||
_panesDeck = document.createXULElement('deck');
|
||||
_panesDeck.setAttribute('flex', 1);
|
||||
_panesDeck.setAttribute('selectedIndex', 0);
|
||||
|
||||
vbox.append(toolbarExtension, _panesDeck);
|
||||
_contextPaneInner.append(_panesDeck);
|
||||
|
||||
// Item pane deck
|
||||
_itemPaneDeck = document.createXULElement('deck');
|
||||
|
@ -113,17 +77,15 @@ var ZoteroContextPane = new function () {
|
|||
|
||||
_panesDeck.append(_itemPaneDeck, _notesPaneDeck);
|
||||
|
||||
_sidenav.contextNotesPane = _notesPaneDeck;
|
||||
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'tab'], 'contextPane');
|
||||
window.addEventListener('resize', _update);
|
||||
_itemToggle.addEventListener('click', _toggleItemButton);
|
||||
_notesToggle.addEventListener('click', _toggleNotesButton);
|
||||
Zotero.Reader.onChangeSidebarWidth = _updatePaneWidth;
|
||||
Zotero.Reader.onToggleSidebar = _updatePaneWidth;
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
_itemToggle.removeEventListener('click', _toggleItemButton);
|
||||
_notesToggle.removeEventListener('click', _toggleNotesButton);
|
||||
window.removeEventListener('resize', _update);
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
Zotero.Reader.onChangeSidebarWidth = () => {};
|
||||
|
@ -175,7 +137,7 @@ var ZoteroContextPane = new function () {
|
|||
else if (action == 'close') {
|
||||
_removeItemContext(ids[0]);
|
||||
if (Zotero_Tabs.deck.children.length == 1) {
|
||||
_notesContexts.forEach(x => x.notesListRef.current.setExpanded(false));
|
||||
_notesContexts.forEach(x => x.notesList.expanded = false);
|
||||
}
|
||||
// Close tab specific notes if tab id no longer exists, but
|
||||
// do that only when unloaded tab is reloaded
|
||||
|
@ -201,8 +163,8 @@ var ZoteroContextPane = new function () {
|
|||
if (Zotero_Tabs.selectedType == 'library') {
|
||||
_contextPaneSplitter.setAttribute('hidden', true);
|
||||
_contextPane.setAttribute('collapsed', true);
|
||||
_tabToolbar.hidden = true;
|
||||
_tabCover.classList.add('hidden');
|
||||
_sidenav.hidden = true;
|
||||
}
|
||||
else if (Zotero_Tabs.selectedType == 'reader') {
|
||||
if (_panesDeck.selectedIndex == 1
|
||||
|
@ -258,7 +220,8 @@ var ZoteroContextPane = new function () {
|
|||
setTimeout(() => {
|
||||
_contextPane.setAttribute('collapsed', !(_contextPaneSplitter.getAttribute('state') != 'collapsed'));
|
||||
});
|
||||
_tabToolbar.hidden = false;
|
||||
|
||||
_sidenav.hidden = false;
|
||||
}
|
||||
|
||||
_selectItemContext(ids[0]);
|
||||
|
@ -356,25 +319,11 @@ var ZoteroContextPane = new function () {
|
|||
}
|
||||
}
|
||||
|
||||
function _updateToolbarWidth() {
|
||||
var stacked = Zotero.Prefs.get('layout') == 'stacked';
|
||||
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
if ((stacked || _contextPaneSplitter.getAttribute('state') == 'collapsed')) {
|
||||
reader.setToolbarPlaceholderWidth(_tabToolbar.offsetWidth);
|
||||
}
|
||||
else {
|
||||
reader.setToolbarPlaceholderWidth(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _update() {
|
||||
if (Zotero_Tabs.selectedIndex == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var splitter;
|
||||
var stacked = Zotero.Prefs.get('layout') == 'stacked';
|
||||
if (stacked) {
|
||||
_contextPaneSplitterStacked.setAttribute('hidden', false);
|
||||
|
@ -382,7 +331,8 @@ var ZoteroContextPane = new function () {
|
|||
_contextPaneSplitter.setAttribute('hidden', true);
|
||||
_contextPane.classList.add('stacked');
|
||||
_contextPane.classList.remove('standard');
|
||||
splitter = _contextPaneSplitterStacked;
|
||||
_sidenav.classList.add('stacked');
|
||||
_contextPaneInner.after(_sidenav);
|
||||
}
|
||||
else {
|
||||
_contextPaneSplitter.setAttribute('hidden', false);
|
||||
|
@ -390,24 +340,8 @@ var ZoteroContextPane = new function () {
|
|||
_contextPaneSplitterStacked.setAttribute('state', 'open');
|
||||
_contextPane.classList.add('standard');
|
||||
_contextPane.classList.remove('stacked');
|
||||
splitter = _contextPaneSplitter;
|
||||
}
|
||||
|
||||
var collapsed = splitter.getAttribute('state') == 'collapsed';
|
||||
|
||||
var selectedIndex = _panesDeck.selectedIndex;
|
||||
if (!collapsed && selectedIndex == 0) {
|
||||
_itemPaneToggle.classList.add('toggled');
|
||||
}
|
||||
else {
|
||||
_itemPaneToggle.classList.remove('toggled');
|
||||
}
|
||||
|
||||
if (!collapsed && selectedIndex == 1) {
|
||||
_notesPaneToggle.classList.add('toggled');
|
||||
}
|
||||
else {
|
||||
_notesPaneToggle.classList.remove('toggled');
|
||||
_sidenav.classList.remove('stacked');
|
||||
_contextPane.after(_sidenav);
|
||||
}
|
||||
|
||||
if (Zotero_Tabs.selectedIndex > 0) {
|
||||
|
@ -420,7 +354,6 @@ var ZoteroContextPane = new function () {
|
|||
}
|
||||
|
||||
_updatePaneWidth();
|
||||
_updateToolbarWidth();
|
||||
_updateAddToNote();
|
||||
}
|
||||
|
||||
|
@ -537,11 +470,13 @@ var ZoteroContextPane = new function () {
|
|||
var vbox = document.createXULElement('vbox');
|
||||
vbox.style.flex = '1';
|
||||
var input = document.createXULElement('search-textbox');
|
||||
input.style.margin = '4px 7px';
|
||||
input.setAttribute('data-l10n-id', 'context-notes-search');
|
||||
input.setAttribute('data-l10n-attrs', 'placeholder');
|
||||
input.style.margin = '6px 8px 7px 8px';
|
||||
input.setAttribute('type', 'search');
|
||||
input.setAttribute('timeout', '250');
|
||||
input.addEventListener('command', () => {
|
||||
notesListRef.current.setExpanded(false);
|
||||
notesList.expanded = false;
|
||||
_updateNotesList();
|
||||
});
|
||||
vbox.append(input);
|
||||
|
@ -550,6 +485,7 @@ var ZoteroContextPane = new function () {
|
|||
|
||||
var listBox = document.createXULElement('vbox');
|
||||
listBox.style.display = 'flex';
|
||||
listBox.style.minWidth = '0';
|
||||
listBox.setAttribute('flex', '1');
|
||||
var listInner = document.createElement('div');
|
||||
listInner.className = 'notes-list-container';
|
||||
|
@ -559,7 +495,44 @@ var ZoteroContextPane = new function () {
|
|||
|
||||
list.append(head, listBox);
|
||||
|
||||
var notesListRef = React.createRef();
|
||||
var notesList = document.createXULElement('context-notes-list');
|
||||
notesList.addEventListener('note-click', (event) => {
|
||||
let { id } = event.detail;
|
||||
let item = Zotero.Items.get(id);
|
||||
if (item) {
|
||||
_setPinnedNote(item);
|
||||
}
|
||||
});
|
||||
notesList.addEventListener('note-contextmenu', (event) => {
|
||||
let { id, screenX, screenY } = event.detail;
|
||||
let item = Zotero.Items.get(id);
|
||||
if (item) {
|
||||
document.getElementById('context-pane-list-move-to-trash').setAttribute('disabled', readOnly);
|
||||
var popup = document.getElementById('context-pane-list-popup');
|
||||
let handleCommand = (event) => _handleListPopupClick(id, event);
|
||||
popup.addEventListener('popupshowing', () => {
|
||||
popup.addEventListener('command', handleCommand, { once: true });
|
||||
popup.addEventListener('popuphiding', () => {
|
||||
popup.removeEventListener('command', handleCommand);
|
||||
}, { once: true });
|
||||
}, { once: true });
|
||||
popup.openPopupAtScreen(screenX, screenY, true);
|
||||
}
|
||||
});
|
||||
notesList.addEventListener('add-child', (event) => {
|
||||
document.getElementById('context-pane-add-child-note').setAttribute('disabled', readOnly);
|
||||
document.getElementById('context-pane-add-child-note-from-annotations').setAttribute('disabled', readOnly);
|
||||
var popup = document.getElementById('context-pane-add-child-note-button-popup');
|
||||
popup.onclick = _handleAddChildNotePopupClick;
|
||||
popup.openPopup(event.detail.button, 'after_end');
|
||||
});
|
||||
notesList.addEventListener('add-standalone', (event) => {
|
||||
document.getElementById('context-pane-add-standalone-note').setAttribute('disabled', readOnly);
|
||||
document.getElementById('context-pane-add-standalone-note-from-annotations').setAttribute('disabled', readOnly);
|
||||
var popup = document.getElementById('context-pane-add-standalone-note-button-popup');
|
||||
popup.onclick = _handleAddStandaloneNotePopupClick;
|
||||
popup.openPopup(event.detail.button, 'after_end');
|
||||
});
|
||||
|
||||
function _isVisible() {
|
||||
let splitter = Zotero.Prefs.get('layout') == 'stacked'
|
||||
|
@ -653,18 +626,18 @@ var ZoteroContextPane = new function () {
|
|||
|
||||
var attachment = _getCurrentAttachment();
|
||||
var parentID = attachment && attachment.parentID;
|
||||
notesListRef.current.setHasParent(!!parentID);
|
||||
notesListRef.current.setNotes(notes.map(note => ({
|
||||
notesList.hasParent = !!parentID;
|
||||
notesList.notes = notes.map(note => ({
|
||||
...note,
|
||||
isCurrentChild: parentID && note.parentID == parentID
|
||||
})));
|
||||
}));
|
||||
}
|
||||
|
||||
var context = {
|
||||
libraryID,
|
||||
node: contextNode,
|
||||
editor,
|
||||
notesListRef,
|
||||
notesList,
|
||||
cachedNotes: [],
|
||||
affectedIDs: new Set(),
|
||||
update: Zotero.Utilities.throttle(_updateNotesList, 1000, { leading: false }),
|
||||
|
@ -728,41 +701,8 @@ var ZoteroContextPane = new function () {
|
|||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<NotesList
|
||||
ref={notesListRef}
|
||||
onClick={(id) => {
|
||||
let item = Zotero.Items.get(id);
|
||||
if (item) {
|
||||
_setPinnedNote(item);
|
||||
}
|
||||
}}
|
||||
onContextMenu={(id, event) => {
|
||||
document.getElementById('context-pane-list-move-to-trash').setAttribute('disabled', readOnly);
|
||||
var popup = document.getElementById('context-pane-list-popup');
|
||||
popup.onclick = (event) => _handleListPopupClick(id, event);
|
||||
popup.openPopupAtScreen(event.screenX, event.screenY);
|
||||
}}
|
||||
onAddChildButtonDown={(event) => {
|
||||
document.getElementById('context-pane-add-child-note').setAttribute('disabled', readOnly);
|
||||
document.getElementById('context-pane-add-child-note-from-annotations').setAttribute('disabled', readOnly);
|
||||
var popup = document.getElementById('context-pane-add-child-note-button-popup');
|
||||
popup.onclick = _handleAddChildNotePopupClick;
|
||||
popup.openPopup(event.target, 'after_end');
|
||||
}}
|
||||
onAddStandaloneButtonDown={(event) => {
|
||||
document.getElementById('context-pane-add-standalone-note').setAttribute('disabled', readOnly);
|
||||
document.getElementById('context-pane-add-standalone-note-from-annotations').setAttribute('disabled', readOnly);
|
||||
var popup = document.getElementById('context-pane-add-standalone-note-button-popup');
|
||||
popup.onclick = _handleAddStandaloneNotePopupClick;
|
||||
popup.openPopup(event.target, 'after_end');
|
||||
}}
|
||||
/>,
|
||||
listInner,
|
||||
() => {
|
||||
_updateNotesList();
|
||||
}
|
||||
);
|
||||
listInner.append(notesList);
|
||||
_updateNotesList();
|
||||
_notesContexts.push(context);
|
||||
return context;
|
||||
}
|
||||
|
@ -870,9 +810,15 @@ var ZoteroContextPane = new function () {
|
|||
}
|
||||
|
||||
function _selectItemContext(tabID) {
|
||||
let selectedIndex = Array.from(_itemPaneDeck.children).findIndex(x => x.id == tabID + '-context');
|
||||
if (selectedIndex != -1) {
|
||||
_itemPaneDeck.setAttribute('selectedIndex', selectedIndex);
|
||||
let selectedPanel = Array.from(_itemPaneDeck.children).find(x => x.id == tabID + '-context');
|
||||
if (selectedPanel) {
|
||||
_itemPaneDeck.selectedPanel = selectedPanel;
|
||||
let div = selectedPanel.querySelector('.zotero-view-item');
|
||||
// _addItemContext() awaits, so the div may not have been created yet. We'll set _sidenav.container
|
||||
// below even if we don't set it here.
|
||||
if (div) {
|
||||
_sidenav.container = div;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -963,12 +909,6 @@ var ZoteroContextPane = new function () {
|
|||
relatedBox.className = 'zotero-editpane-related';
|
||||
relatedBox.setAttribute('data-pane', 'related');
|
||||
div.append(relatedBox);
|
||||
|
||||
// item-pane-sidenav
|
||||
var sidenav = document.createXULElement('item-pane-sidenav');
|
||||
sidenav.className = 'zotero-view-item-sidenav';
|
||||
hbox.append(sidenav);
|
||||
sidenav.container = div;
|
||||
|
||||
paneHeader.mode = readOnly ? 'view' : 'edit';
|
||||
paneHeader.item = parentItem;
|
||||
|
@ -984,5 +924,9 @@ var ZoteroContextPane = new function () {
|
|||
|
||||
relatedBox.mode = readOnly ? 'view' : 'edit';
|
||||
relatedBox.item = parentItem;
|
||||
|
||||
if (_itemPaneDeck.selectedPanel === container) {
|
||||
_sidenav.container = div;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -53,6 +53,8 @@ Services.scriptloader.loadSubScript('chrome://zotero/content/elements/collapsibl
|
|||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/attachmentsBox.js', this);
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/attachmentRow.js', this);
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/annotationRow.js', this);
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/contextNotesList.js', this);
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/noteRow.js', this);
|
||||
|
||||
{
|
||||
// Fix missing property bug that breaks arrow key navigation between <tab>s
|
||||
|
|
|
@ -74,14 +74,14 @@
|
|||
}
|
||||
|
||||
set empty(val) {
|
||||
this.toggleAttribute('empty', !!val);
|
||||
this._runWithTransitionsDisabled(() => {
|
||||
this.toggleAttribute('empty', !!val);
|
||||
});
|
||||
}
|
||||
|
||||
setCount(count) {
|
||||
this.setAttribute('data-l10n-args', JSON.stringify({ count }));
|
||||
this._runWithTransitionsDisabled(() => {
|
||||
this.empty = !count;
|
||||
});
|
||||
this.empty = !count;
|
||||
}
|
||||
|
||||
get label() {
|
||||
|
@ -129,14 +129,20 @@
|
|||
this._addButton = document.createXULElement('toolbarbutton');
|
||||
this._addButton.className = 'add';
|
||||
this._addButton.addEventListener('command', (event) => {
|
||||
this.dispatchEvent(new CustomEvent('add', { ...event, bubbles: false }));
|
||||
this.dispatchEvent(new CustomEvent('add', {
|
||||
...event,
|
||||
detail: { button: this._addButton },
|
||||
bubbles: false
|
||||
}));
|
||||
});
|
||||
this._head.append(this._addButton);
|
||||
|
||||
this._contextMenu = this._buildContextMenu();
|
||||
let popupset = document.createXULElement('popupset');
|
||||
popupset.append(this._contextMenu);
|
||||
this._head.append(popupset);
|
||||
if (this._contextMenu) {
|
||||
let popupset = document.createXULElement('popupset');
|
||||
popupset.append(this._contextMenu);
|
||||
this._head.append(popupset);
|
||||
}
|
||||
|
||||
let twisty = document.createXULElement('toolbarbutton');
|
||||
twisty.className = 'twisty';
|
||||
|
@ -157,13 +163,15 @@
|
|||
}
|
||||
|
||||
_buildContextMenu() {
|
||||
let containerRoot = this.closest('.zotero-view-item-container');
|
||||
let containerRoot = this.closest('.zotero-view-item-container, context-notes-list');
|
||||
|
||||
let contextMenu = document.createXULElement('menupopup');
|
||||
let collapseOtherSections = document.createXULElement('menuitem');
|
||||
collapseOtherSections.classList.add('menuitem-iconic', 'zotero-menuitem-collapse-others');
|
||||
collapseOtherSections.setAttribute('data-l10n-id', 'collapse-other-sections');
|
||||
collapseOtherSections.addEventListener('command', () => {
|
||||
// Scroll to the top (first section), so we don't end up scrolled past the end
|
||||
containerRoot.querySelector('collapsible-section').scrollIntoView({ block: 'start' });
|
||||
for (let section of containerRoot.querySelectorAll('collapsible-section')) {
|
||||
if (section !== this) {
|
||||
section.open = false;
|
||||
|
@ -207,7 +215,7 @@
|
|||
contextMenu.addEventListener('popupshowing', () => {
|
||||
let sections = Array.from(containerRoot.querySelectorAll('collapsible-section'));
|
||||
collapseOtherSections.disabled = sections.every(section => section === this || !section.open);
|
||||
expandAllSections.disabled = sections.every(section => section.open);
|
||||
expandAllSections.disabled = sections.every(section => section.open || section.empty);
|
||||
|
||||
let sidenav = this._getSidenav();
|
||||
if (sidenav?.isPanePinnable(this.dataset.pane)) {
|
||||
|
@ -268,7 +276,7 @@
|
|||
_handleContextMenu = (event) => {
|
||||
if (event.target.closest('.add')) return;
|
||||
event.preventDefault();
|
||||
this._contextMenu.openPopupAtScreen(event.screenX, event.screenY, true);
|
||||
this._contextMenu?.openPopupAtScreen(event.screenX, event.screenY, true);
|
||||
};
|
||||
|
||||
_getSidenav() {
|
||||
|
|
194
chrome/content/zotero/elements/contextNotesList.js
Normal file
194
chrome/content/zotero/elements/contextNotesList.js
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2023 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
{
|
||||
const MAX_UNEXPANDED_ALL_NOTES = 7;
|
||||
|
||||
class ContextNotesList extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<html:div>
|
||||
<collapsible-section data-pane="context-item-notes" show-add="true" class="item-notes">
|
||||
<html:div class="body"/>
|
||||
</collapsible-section>
|
||||
</html:div>
|
||||
<html:div>
|
||||
<collapsible-section data-pane="context-all-notes" show-add="true" class="all-notes">
|
||||
<html:div class="body"/>
|
||||
</collapsible-section>
|
||||
</html:div>
|
||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||
|
||||
_itemNotes = [];
|
||||
|
||||
_allNotes = [];
|
||||
|
||||
_expanded = false;
|
||||
|
||||
_numVisible = 0;
|
||||
|
||||
_hasParent = false;
|
||||
|
||||
get notes() {
|
||||
return [...this._itemNotes, ...this._allNotes];
|
||||
}
|
||||
|
||||
set notes(val) {
|
||||
let itemNotes = [];
|
||||
let allNotes = [];
|
||||
for (let note of val) {
|
||||
if (note.isCurrentChild) {
|
||||
itemNotes.push(note);
|
||||
}
|
||||
else {
|
||||
allNotes.push(note);
|
||||
}
|
||||
}
|
||||
this._itemNotes = itemNotes;
|
||||
this._allNotes = allNotes;
|
||||
this.render();
|
||||
}
|
||||
|
||||
get expanded() {
|
||||
return this._expanded;
|
||||
}
|
||||
|
||||
set expanded(val) {
|
||||
this._expanded = val;
|
||||
if (val) {
|
||||
this.numVisible += 1000;
|
||||
}
|
||||
else {
|
||||
this.numVisible = 0;
|
||||
}
|
||||
}
|
||||
|
||||
get numVisible() {
|
||||
return this._numVisible;
|
||||
}
|
||||
|
||||
set numVisible(val) {
|
||||
this._numVisible = val;
|
||||
this.render();
|
||||
}
|
||||
|
||||
get hasParent() {
|
||||
return this._hasParent;
|
||||
}
|
||||
|
||||
set hasParent(val) {
|
||||
this._hasParent = val;
|
||||
this.render();
|
||||
}
|
||||
|
||||
init() {
|
||||
this._itemNotesSection = this.querySelector('.item-notes');
|
||||
this._allNotesSection = this.querySelector('.all-notes');
|
||||
|
||||
this._itemNotesSection.label = Zotero.getString('pane.context.itemNotes');
|
||||
this._allNotesSection.label = Zotero.getString('pane.context.allNotes');
|
||||
|
||||
this._itemNotesSection.addEventListener('add', this._handleAddNote);
|
||||
this._allNotesSection.addEventListener('add', this._handleAddNote);
|
||||
|
||||
this.addEventListener('click', this._handleClick);
|
||||
this.addEventListener('contextmenu', this._handleContextMenu);
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this._itemNotesSection.empty = !this._itemNotes.length;
|
||||
this._allNotesSection.empty = !this._allNotes.length;
|
||||
|
||||
let itemNotesBody = this._itemNotesSection.querySelector('.body');
|
||||
let allNotesBody = this._allNotesSection.querySelector('.body');
|
||||
|
||||
itemNotesBody.replaceChildren();
|
||||
for (let note of this._itemNotes) {
|
||||
itemNotesBody.append(this._makeRow(note));
|
||||
}
|
||||
|
||||
allNotesBody.replaceChildren();
|
||||
let visibleNotes = this._allNotes.slice(0, this._expanded ? this._numVisible : MAX_UNEXPANDED_ALL_NOTES);
|
||||
for (let note of visibleNotes) {
|
||||
allNotesBody.append(this._makeRow(note));
|
||||
}
|
||||
if (visibleNotes.length < this._allNotes.length) {
|
||||
let moreButton = document.createElement('button');
|
||||
moreButton.className = 'more';
|
||||
moreButton.textContent = Zotero.getString('general.numMore', Zotero.Utilities.numberFormat(
|
||||
[this._allNotes.length - visibleNotes.length], 0));
|
||||
moreButton.addEventListener('click', () => {
|
||||
this.expanded = true;
|
||||
});
|
||||
allNotesBody.append(moreButton);
|
||||
}
|
||||
}
|
||||
|
||||
_makeRow(note) {
|
||||
let row = document.createXULElement('note-row');
|
||||
row.note = note;
|
||||
return row;
|
||||
}
|
||||
|
||||
_handleClick = (event) => {
|
||||
if (event.button !== 0) return;
|
||||
let note = event.target.closest('note-row')?.note;
|
||||
if (note) {
|
||||
this.dispatchEvent(new CustomEvent('note-click', {
|
||||
...event,
|
||||
bubbles: true,
|
||||
detail: { id: note.id }
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
_handleContextMenu = (event) => {
|
||||
let note = event.target.closest('note-row')?.note;
|
||||
if (note) {
|
||||
this.dispatchEvent(new CustomEvent('note-contextmenu', {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
screenX: event.screenX,
|
||||
screenY: event.screenY,
|
||||
id: note.id
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
_handleAddNote = (event) => {
|
||||
let eventName = event.target.closest('collapsible-section') == this._itemNotesSection
|
||||
? 'add-child'
|
||||
: 'add-standalone';
|
||||
this.dispatchEvent(new CustomEvent(eventName, {
|
||||
bubbles: true,
|
||||
detail: { button: event.detail.button }
|
||||
}));
|
||||
};
|
||||
}
|
||||
customElements.define("context-notes-list", ContextNotesList);
|
||||
}
|
|
@ -28,6 +28,11 @@
|
|||
{
|
||||
class ItemPaneSidenav extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<html:div class="toolbarbutton-container">
|
||||
<toolbarbutton
|
||||
data-pane="toggle-collapse"/>
|
||||
</html:div>
|
||||
<html:div class="divider"/>
|
||||
<html:div class="toolbarbutton-container">
|
||||
<toolbarbutton
|
||||
data-l10n-id="sidenav-info"
|
||||
|
@ -58,6 +63,12 @@
|
|||
data-l10n-id="sidenav-related"
|
||||
data-pane="related"/>
|
||||
</html:div>
|
||||
<html:div class="divider"/>
|
||||
<html:div class="toolbarbutton-container">
|
||||
<toolbarbutton
|
||||
data-l10n-id="sidenav-notes"
|
||||
data-pane="context-notes"/>
|
||||
</html:div>
|
||||
|
||||
<popupset>
|
||||
<menupopup class="context-menu">
|
||||
|
@ -69,6 +80,8 @@
|
|||
|
||||
_container = null;
|
||||
|
||||
_contextNotesPane = null;
|
||||
|
||||
_contextMenuTarget = null;
|
||||
|
||||
_preserveMinScrollHeightTimeout = null;
|
||||
|
@ -85,6 +98,16 @@
|
|||
this.render();
|
||||
}
|
||||
|
||||
get contextNotesPane() {
|
||||
return this._contextNotesPane;
|
||||
}
|
||||
|
||||
set contextNotesPane(val) {
|
||||
if (this._contextNotesPane == val) return;
|
||||
this._contextNotesPane = val;
|
||||
this.render();
|
||||
}
|
||||
|
||||
get pinnedPane() {
|
||||
return this.getAttribute('pinnedPane');
|
||||
}
|
||||
|
@ -103,6 +126,49 @@
|
|||
set _minScrollHeight(val) {
|
||||
this._container.style.setProperty('--min-scroll-height', val + 'px');
|
||||
}
|
||||
|
||||
get _contextNotesPaneVisible() {
|
||||
return this._contextNotesPane
|
||||
&& this._contextNotesPane.parentElement.selectedPanel == this._contextNotesPane;
|
||||
}
|
||||
|
||||
set _contextNotesPaneVisible(val) {
|
||||
if (!this._contextNotesPane) return;
|
||||
// The context notes pane will always be a direct child of the deck we need to update
|
||||
let deck = this._contextNotesPane.parentElement;
|
||||
if (val) {
|
||||
deck.selectedPanel = this._contextNotesPane;
|
||||
this._collapsed = false;
|
||||
}
|
||||
else {
|
||||
// But our _container is not a direct child of the deck,
|
||||
// so find the child that contains it
|
||||
deck.selectedPanel = Array.from(deck.children).find(child => child.contains(this._container));
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
get _collapsed() {
|
||||
let collapsible = this.container.closest('splitter:not([hidden="true"]) + *');
|
||||
return collapsible.getAttribute('collapsed') === 'true';
|
||||
}
|
||||
|
||||
set _collapsed(val) {
|
||||
let collapsible = this.container.closest('splitter:not([hidden="true"]) + *');
|
||||
let splitter = collapsible.previousElementSibling;
|
||||
if (val) {
|
||||
collapsible.setAttribute('collapsed', 'true');
|
||||
splitter.setAttribute('state', 'collapsed');
|
||||
splitter.setAttribute('substate', 'after');
|
||||
}
|
||||
else {
|
||||
collapsible.removeAttribute('collapsed');
|
||||
splitter.setAttribute('state', '');
|
||||
splitter.setAttribute('substate', 'after');
|
||||
}
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
this.render();
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['pinnedPane'];
|
||||
|
@ -113,6 +179,15 @@
|
|||
}
|
||||
|
||||
scrollToPane(id, behavior = 'smooth') {
|
||||
if (this._collapsed) {
|
||||
this._collapsed = false;
|
||||
behavior = 'instant';
|
||||
}
|
||||
if (this._contextNotesPane && this._contextNotesPaneVisible) {
|
||||
this._contextNotesPaneVisible = false;
|
||||
behavior = 'instant';
|
||||
}
|
||||
|
||||
let pane = this.getPane(id);
|
||||
if (!pane) return;
|
||||
|
||||
|
@ -175,7 +250,7 @@
|
|||
isPanePinnable(id) {
|
||||
return id !== 'info';
|
||||
}
|
||||
|
||||
|
||||
init() {
|
||||
if (!this.container) {
|
||||
this.container = document.getElementById('zotero-view-item');
|
||||
|
@ -183,6 +258,29 @@
|
|||
|
||||
for (let toolbarbutton of this.querySelectorAll('toolbarbutton')) {
|
||||
let pane = toolbarbutton.dataset.pane;
|
||||
|
||||
if (pane === 'context-notes') {
|
||||
toolbarbutton.addEventListener('click', (event) => {
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
if (event.detail == 2) {
|
||||
this.pinnedPane = null;
|
||||
}
|
||||
this._contextNotesPaneVisible = true;
|
||||
});
|
||||
continue;
|
||||
}
|
||||
else if (pane === 'toggle-collapse') {
|
||||
toolbarbutton.addEventListener('click', (event) => {
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
this._collapsed = !this._collapsed;
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let pinnable = this.isPanePinnable(pane);
|
||||
toolbarbutton.parentElement.classList.toggle('pinnable', pinnable);
|
||||
|
||||
|
@ -232,7 +330,27 @@
|
|||
let pinnedPane = this.pinnedPane;
|
||||
for (let toolbarbutton of this.querySelectorAll('toolbarbutton')) {
|
||||
let pane = toolbarbutton.dataset.pane;
|
||||
toolbarbutton.setAttribute('aria-selected', pane == pinnedPane);
|
||||
|
||||
if (pane == 'context-notes') {
|
||||
let hidden = !this._contextNotesPane;
|
||||
let selected = this._contextNotesPaneVisible;
|
||||
|
||||
toolbarbutton.parentElement.hidden = hidden;
|
||||
toolbarbutton.parentElement.previousElementSibling.hidden = hidden; // Divider
|
||||
|
||||
toolbarbutton.setAttribute('aria-selected', selected);
|
||||
toolbarbutton.classList.toggle('active', selected);
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (pane == 'toggle-collapse') {
|
||||
toolbarbutton.setAttribute('data-l10n-id', 'sidenav-' + (this._collapsed ? 'expand' : 'collapse'));
|
||||
toolbarbutton.classList.toggle('collapsed', this._collapsed);
|
||||
continue;
|
||||
}
|
||||
|
||||
toolbarbutton.setAttribute('aria-selected',
|
||||
!this._contextNotesPaneVisible && pane == pinnedPane);
|
||||
toolbarbutton.parentElement.hidden = !this.getPane(pane);
|
||||
|
||||
// Set .pinned on the container, for pin styling
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<div id="note-editor" style="display: flex;flex-direction: column;flex-grow: 1;" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<iframe id="editor-view" style="border: 0;width: 100%;flex-grow: 1;" src="resource://zotero/note-editor/editor.html" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" type="content"/>
|
||||
<div id="links-container">
|
||||
<links-box id="links-box" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
|
||||
<links-box id="links-box" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
|
||||
</div>
|
||||
</div>
|
||||
</box>
|
||||
|
@ -338,26 +338,12 @@
|
|||
this._destroyed = false;
|
||||
|
||||
this.content = MozXULElement.parseXULToFragment(`
|
||||
<div id="links-box" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<div class="grid">
|
||||
<div id="parent-label" class="label" hidden="true"/>
|
||||
<div id="parent-value" class="value zotero-clicky" hidden="true"/>
|
||||
|
||||
<div id="related-label" class="label"/>
|
||||
<div id="related-value" class="value zotero-clicky"/>
|
||||
|
||||
<div id="tags-label" class="label"/>
|
||||
<div id="tags-value" class="value zotero-clicky"/>
|
||||
</div>
|
||||
</div>
|
||||
<popupset xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<menupopup id="related-popup" width="300">
|
||||
<related-box id="related"/>
|
||||
</menupopup>
|
||||
<menupopup id="tags-popup" width="300" ignorekeys="true">
|
||||
<tags-box id="tags"/>
|
||||
</menupopup>
|
||||
</popupset>
|
||||
<!--
|
||||
<html:div id="parent-label" class="label" hidden="true"/>
|
||||
<html:div id="parent-value" class="value zotero-clicky" hidden="true"/>
|
||||
-->
|
||||
<tags-box id="tags"/>
|
||||
<related-box id="related"/>
|
||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||
}
|
||||
|
||||
|
@ -366,10 +352,6 @@
|
|||
window.addEventListener("unload", () => this.destroy(), { once: true });
|
||||
|
||||
this.append(document.importNode(this.content, true));
|
||||
|
||||
this._id('parent-value').addEventListener('click', this._parentClickHandler);
|
||||
this._id('related-value').addEventListener('click', this._relatedClickHandler);
|
||||
this._id('tags-value').addEventListener('click', this._tagsClickHandler);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -390,15 +372,6 @@
|
|||
this._id('tags').item = this._item;
|
||||
|
||||
this.refresh();
|
||||
|
||||
// Hide popup to prevent it being visible out of the context or
|
||||
// in some cases even invisible but still blocking the next click
|
||||
this._id('related-popup').addEventListener('click', (event) => {
|
||||
let target = event.originalTarget;
|
||||
if (target.classList.contains('zotero-box-label')) {
|
||||
this._id('related-popup').hidePopup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
set mode(val) {
|
||||
|
@ -419,110 +392,8 @@
|
|||
}
|
||||
|
||||
refresh() {
|
||||
this._updateParentRow();
|
||||
this._updateTagsSummary();
|
||||
this._updateRelatedSummary();
|
||||
}
|
||||
|
||||
_updateParentRow() {
|
||||
let hidden = !this._parentItem || !!this._notitle;
|
||||
this._id('parent-value').hidden = hidden;
|
||||
this._id('parent-label').hidden = hidden;
|
||||
if (!hidden) {
|
||||
this._id('parent-label').replaceChildren(Zotero.getString('pane.item.parentItem'));
|
||||
this._id('parent-value').replaceChildren(document.createTextNode(this._parentItem.getDisplayTitle(true)));
|
||||
}
|
||||
}
|
||||
|
||||
_updateRelatedSummary() {
|
||||
var r = '';
|
||||
if (this._item) {
|
||||
var keys = this._item.relatedItems;
|
||||
if (keys.length) {
|
||||
for (let key of keys) {
|
||||
let item = Zotero.Items.getByLibraryAndKey(this._item.libraryID, key);
|
||||
if (!item) {
|
||||
Zotero.debug(`Related item ${this._item.libraryID}/${key} not found `
|
||||
+ `for item ${this._item.libraryKey}`, 2);
|
||||
continue;
|
||||
}
|
||||
r = r + item.getDisplayTitle() + ", ";
|
||||
}
|
||||
r = r.slice(0, -2);
|
||||
}
|
||||
}
|
||||
|
||||
let v = r;
|
||||
|
||||
if (!v || v == '') {
|
||||
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||
}
|
||||
|
||||
this._id('related-label').innerText = Zotero.getString('itemFields.related')
|
||||
+ Zotero.getString('punctuation.colon');
|
||||
this._id('related-value').innerText = v;
|
||||
}
|
||||
|
||||
_updateTagsSummary() {
|
||||
var r = '';
|
||||
|
||||
if (this._item) {
|
||||
var tags = this._item.getTags();
|
||||
|
||||
// Sort tags alphabetically
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
tags.sort((a, b) => collation.compareString(1, a.tag, b.tag));
|
||||
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
r = r + tags[i].tag + ", ";
|
||||
}
|
||||
r = r.slice(0, -2);
|
||||
}
|
||||
|
||||
let v = r;
|
||||
|
||||
if (!v || v == '') {
|
||||
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||
}
|
||||
|
||||
this._id('tags-label').innerText = Zotero.getString('itemFields.tags')
|
||||
+ Zotero.getString('punctuation.colon');
|
||||
this._id('tags-value').innerText = v;
|
||||
}
|
||||
|
||||
_parentClickHandler = () => {
|
||||
if (!this._item || !this._item.id) {
|
||||
return;
|
||||
}
|
||||
var parentID = this._item.parentID;
|
||||
var win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.ZoteroPane.selectItem(parentID);
|
||||
win.Zotero_Tabs.select('zotero-pane');
|
||||
win.focus();
|
||||
}
|
||||
};
|
||||
|
||||
_relatedClickHandler = (event) => {
|
||||
var relatedList = this._item.relatedItems;
|
||||
if (relatedList.length > 0) {
|
||||
this._id('related-popup').openPopup(this, 'topleft topleft', 0, 0, false);
|
||||
}
|
||||
else if (this._mode == 'edit') {
|
||||
this._id('related').add();
|
||||
}
|
||||
};
|
||||
|
||||
_tagsClickHandler = (event) => {
|
||||
this._id('tags-popup').openPopup(this, 'topleft topleft', 0, 0, true);
|
||||
// If editable and no existing tags, open new empty row
|
||||
if (this._mode == 'edit' && !this._item.getTags().length) {
|
||||
setTimeout(() => {
|
||||
this._id('tags').addNew();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_id(id) {
|
||||
return this.querySelector(`#${id}`);
|
||||
}
|
||||
|
|
91
chrome/content/zotero/elements/noteRow.js
Normal file
91
chrome/content/zotero/elements/noteRow.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2023 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
import { getCSSItemTypeIcon } from 'components/icons';
|
||||
|
||||
{
|
||||
class NoteRow extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<html:div class="head">
|
||||
<html:span class="icon"/>
|
||||
<html:div class="parent-title"/>
|
||||
</html:div>
|
||||
<html:div class="body">
|
||||
<html:div class="note-title"/>
|
||||
<html:div class="note-content-container">
|
||||
<html:div class="note-content"/>
|
||||
<html:span class="note-date"/>
|
||||
</html:div>
|
||||
</html:div>
|
||||
`);
|
||||
|
||||
_note = null;
|
||||
|
||||
get note() {
|
||||
return this._note;
|
||||
}
|
||||
|
||||
set note(val) {
|
||||
this._note = val;
|
||||
this.render();
|
||||
}
|
||||
|
||||
init() {
|
||||
this._parentTitle = this.querySelector('.parent-title');
|
||||
this._noteTitle = this.querySelector('.note-title');
|
||||
this._noteContent = this.querySelector('.note-content');
|
||||
this._noteDate = this.querySelector('.note-date');
|
||||
this.tabIndex = 0;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.initialized) return;
|
||||
|
||||
let note = this._note;
|
||||
if (!note) return;
|
||||
|
||||
if (note.parentItemType) {
|
||||
this.querySelector('.icon').replaceWith(getCSSItemTypeIcon(note.parentItemType));
|
||||
this._parentTitle.textContent = note.parentTitle;
|
||||
this._noteTitle.hidden = false;
|
||||
this._noteTitle.textContent = note.title;
|
||||
}
|
||||
else {
|
||||
this.querySelector('.icon').replaceWith(getCSSItemTypeIcon('note'));
|
||||
this._parentTitle.textContent = note.title;
|
||||
this._noteTitle.hidden = true;
|
||||
this._noteTitle.textContent = '';
|
||||
}
|
||||
this._noteContent.textContent = note.body;
|
||||
this._noteContent.hidden = !note.body;
|
||||
this._noteDate.textContent = note.date;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('note-row', NoteRow);
|
||||
}
|
|
@ -338,14 +338,6 @@ class ReaderInstance {
|
|||
this._onChangeSidebarWidthCallback(width);
|
||||
}
|
||||
},
|
||||
onFocusSplitButton: () => {
|
||||
if (this instanceof ReaderTab) {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.document.getElementById('zotero-tb-toggle-item-pane').focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
onFocusContextPane: () => {
|
||||
if (this instanceof ReaderWindow || !this._window.ZoteroContextPane.focus()) {
|
||||
this.focusFirst();
|
||||
|
|
|
@ -790,44 +790,7 @@ var ZoteroPane = new function()
|
|||
*/
|
||||
function handleKeyDown(event, from) {
|
||||
if (Zotero_Tabs.selectedIndex > 0) {
|
||||
let itemPaneToggle = document.getElementById('zotero-tb-toggle-item-pane');
|
||||
let notesPaneToggle = document.getElementById('zotero-tb-toggle-notes-pane');
|
||||
// Using ArrowDown and ArrowUp to be consistent with pdf-reader
|
||||
if (!Zotero.rtl && event.key === 'ArrowRight'
|
||||
|| Zotero.rtl && event.key === 'ArrowLeft'
|
||||
|| event.key === 'ArrowDown') {
|
||||
if (event.target === itemPaneToggle) {
|
||||
notesPaneToggle.focus();
|
||||
}
|
||||
}
|
||||
else if (!Zotero.rtl && event.key === 'ArrowLeft'
|
||||
|| Zotero.rtl && event.key === 'ArrowRight'
|
||||
|| event.key === 'ArrowUp') {
|
||||
if (event.target === notesPaneToggle) {
|
||||
itemPaneToggle.focus();
|
||||
}
|
||||
else if (event.target === itemPaneToggle) {
|
||||
let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
reader.focusLastToolbarButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event.key === 'Tab'
|
||||
&& [itemPaneToggle, notesPaneToggle].includes(event.target)) {
|
||||
if (event.shiftKey) {
|
||||
ZoteroContextPane.focus();
|
||||
}
|
||||
else {
|
||||
let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
reader.tabToolbar();
|
||||
}
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
else if (event.key === 'Escape') {
|
||||
if (event.key === 'Escape') {
|
||||
if (!document.activeElement.classList.contains('reader')) {
|
||||
let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
|
@ -6209,13 +6172,16 @@ var ZoteroPane = new function()
|
|||
this.updateLayout = function() {
|
||||
var layoutSwitcher = document.getElementById("zotero-layout-switcher");
|
||||
var itemsSplitter = document.getElementById("zotero-items-splitter");
|
||||
var sidenav = document.getElementById("zotero-view-item-sidenav");
|
||||
|
||||
if(Zotero.Prefs.get("layout") === "stacked") { // itemsPane above itemPane
|
||||
layoutSwitcher.setAttribute("orient", "vertical");
|
||||
itemsSplitter.setAttribute("orient", "vertical");
|
||||
sidenav.classList.add("stacked");
|
||||
} else { // three-vertical-pane
|
||||
layoutSwitcher.setAttribute("orient", "horizontal");
|
||||
itemsSplitter.setAttribute("orient", "horizontal");
|
||||
sidenav.classList.remove("stacked");
|
||||
}
|
||||
|
||||
this.updateToolbarPosition();
|
||||
|
@ -6323,6 +6289,7 @@ var ZoteroPane = new function()
|
|||
|
||||
var collectionsPane = document.getElementById("zotero-collections-pane");
|
||||
var tagSelector = document.getElementById("zotero-tag-selector");
|
||||
var sidenav = document.getElementById("zotero-view-item-sidenav");
|
||||
|
||||
var collectionsPaneWidth = collectionsPane.getBoundingClientRect().width;
|
||||
tagSelector.style.maxWidth = collectionsPaneWidth + 'px';
|
||||
|
@ -6331,6 +6298,8 @@ var ZoteroPane = new function()
|
|||
}
|
||||
|
||||
this.handleTagSelectorResize();
|
||||
|
||||
sidenav.render();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -839,12 +839,6 @@
|
|||
<div id="zotero-tab-cover" class="hidden" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<label>&zotero.general.loading;</label>
|
||||
</div>
|
||||
<div id="zotero-tab-toolbar" class="toolbar" hidden="true" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<div id="zotero-tb-split" class="split-button">
|
||||
<button id="zotero-tb-toggle-item-pane" class="toolbarButton item" title="&zotero.toolbar.context.item;" tabindex="-1"><span/></button>
|
||||
<button id="zotero-tb-toggle-notes-pane" class="toolbarButton notes" title="&zotero.toolbar.context.notes;" tabindex="-1"><span/></button>
|
||||
</div>
|
||||
</div>
|
||||
<deck id="tabs-deck" flex="1">
|
||||
<vbox id="zotero-pane"
|
||||
onkeydown="ZoteroPane_Local.handleKeyDown(event, this.id)"
|
||||
|
@ -1141,8 +1135,6 @@
|
|||
<related-box id="zotero-editpane-related" class="zotero-editpane-related" data-pane="related"/>
|
||||
</html:div>
|
||||
</html:div>
|
||||
|
||||
<item-pane-sidenav id="zotero-view-item-sidenav" class="zotero-view-item-sidenav"/>
|
||||
</hbox>
|
||||
|
||||
<!-- Note item -->
|
||||
|
@ -1182,6 +1174,8 @@
|
|||
</vbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
|
||||
<item-pane-sidenav id="zotero-view-item-sidenav" class="zotero-view-item-sidenav"/>
|
||||
</box>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
@ -1222,6 +1216,8 @@
|
|||
</vbox>
|
||||
</box>
|
||||
|
||||
<item-pane-sidenav id="zotero-context-pane-sidenav" class="zotero-view-item-sidenav" hidden="true"/>
|
||||
|
||||
<popupset>
|
||||
<menupopup id="context-pane-add-child-note-button-popup">
|
||||
<menuitem id="context-pane-add-child-note" label="&zotero.context.addChildNote;"/>
|
||||
|
|
|
@ -272,6 +272,10 @@ sidenav-tags =
|
|||
.tooltiptext = { pane-tags }
|
||||
sidenav-related =
|
||||
.tooltiptext = { pane-related }
|
||||
sidenav-collapse =
|
||||
.tooltiptext = Collapse Sidebar
|
||||
sidenav-expand =
|
||||
.tooltiptext = Expand Sidebar
|
||||
|
||||
pin-section =
|
||||
.label = Pin Section
|
||||
|
@ -288,3 +292,6 @@ abstract-field =
|
|||
|
||||
tagselector-search =
|
||||
.placeholder = Filter Tags
|
||||
|
||||
context-notes-search =
|
||||
.placeholder = Search Notes
|
||||
|
|
|
@ -458,7 +458,6 @@ pane.item.viewOnline.tooltip = Go to this item online
|
|||
pane.context.noParent = No parent item
|
||||
pane.context.itemNotes = Item Notes
|
||||
pane.context.allNotes = All Notes
|
||||
pane.context.noNotes = No notes
|
||||
|
||||
itemTypes.note = Note
|
||||
itemTypes.annotation = Annotation
|
||||
|
|
|
@ -16,10 +16,6 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
#zotero-context-pane.stacked #zotero-context-toolbar-extension {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#zotero-context-pane .stacked-context-placeholder {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
@ -34,6 +30,7 @@
|
|||
|
||||
#zotero-context-pane-inner {
|
||||
width: 0;
|
||||
background: var(--material-sidepane);
|
||||
font: message-box;
|
||||
font-size: inherit;
|
||||
min-height: 100px;
|
||||
|
@ -41,38 +38,12 @@
|
|||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/*#zotero-context-pane.standard .stacked-splitter {*/
|
||||
/* display: none;*/
|
||||
/*}*/
|
||||
|
||||
#zotero-tab-toolbar {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
-moz-appearance: none;
|
||||
background: linear-gradient(to top, #a9a9a9 0, #a9a9a9 1px, #f6f6f6 1px, #f6f6f6 100%);
|
||||
}
|
||||
|
||||
#zotero-tab-toolbar .toolbarButton {
|
||||
-moz-window-dragging: no-drag;
|
||||
}
|
||||
|
||||
#zotero-item-toolbar {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
#zotero-context-toolbar-extension {
|
||||
height: 32px;
|
||||
/* To cover the splitter that has a higher stacking order than our parent */
|
||||
opacity: 0.99;
|
||||
width: 100%;
|
||||
margin-inline-start: -5px;
|
||||
background: linear-gradient(to top, #a9a9a9 0, #a9a9a9 1px, #f6f6f6 1px, #f6f6f6 100%);
|
||||
-moz-window-dragging: drag;
|
||||
}
|
||||
|
||||
.zotero-context-notes-list {
|
||||
padding-top: 5px;
|
||||
background-color: #d2d8e2;
|
||||
background-color: var(--material-sidepane);
|
||||
}
|
||||
|
||||
.zotero-context-pane-editor-parent-line {
|
||||
|
|
3
chrome/skin/default/zotero/itempane/20/collapse.svg
Normal file
3
chrome/skin/default/zotero/itempane/20/collapse.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 18H16.75L16.75 2H18V18ZM12.4911 10.625L2 10.625V9.375L12.4911 9.375L9 5.88388L9.88388 5L14.8839 10L9.88388 15L9 14.1161L12.4911 10.625Z" fill="context-fill"/>
|
||||
</svg>
|
After Width: | Height: | Size: 315 B |
3
chrome/skin/default/zotero/itempane/20/expand.svg
Normal file
3
chrome/skin/default/zotero/itempane/20/expand.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 18H16.75L16.75 2H18L18 18ZM4.50898 9.375L8.00009 5.88388L7.11621 5L2.11621 10L7.11621 15L8.00009 14.1161L4.50898 10.625L15 10.625V9.375L4.50898 9.375Z" fill="context-fill"/>
|
||||
</svg>
|
After Width: | Height: | Size: 330 B |
|
@ -74,3 +74,4 @@
|
|||
@import "elements/attachmentsBox";
|
||||
@import "elements/attachmentRow";
|
||||
@import "elements/annotationRow";
|
||||
@import "elements/noteRow";
|
||||
|
|
|
@ -454,44 +454,3 @@ $toolbar-btn-icon-active-offset: 0;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Move this out from mixins
|
||||
#zotero-tb-split {
|
||||
direction: ltr;
|
||||
$item-tool-icon: "mac/item";
|
||||
$item-tool-icon-active: "mac/item-white";
|
||||
$notes-tool-icon: "mac/notes";
|
||||
$notes-tool-icon-active: "mac/notes-white";
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
@include icon($item-tool-icon);
|
||||
|
||||
&.toggled {
|
||||
@include icon($item-tool-icon-active);
|
||||
}
|
||||
}
|
||||
|
||||
.notes {
|
||||
@include icon($notes-tool-icon);
|
||||
|
||||
&.toggled {
|
||||
@include icon($notes-tool-icon-active);
|
||||
}
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
box-sizing: border-box;
|
||||
|
||||
@include split-button(
|
||||
$height: 24px,
|
||||
$button: "menubutton",
|
||||
$padding-x: 11px,
|
||||
$padding-y: 4px
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,12 +81,12 @@ $z-index-loading-cover: 60;
|
|||
// Item pane
|
||||
// --------------------------------------------------
|
||||
$item-pane-sections: (
|
||||
"info": var(--accent-blue),
|
||||
"abstract": var(--accent-teal),
|
||||
"attachments": var(--accent-green),
|
||||
"notes": var(--accent-yellow),
|
||||
"tags": var(--accent-orange),
|
||||
"related": var(--accent-wood),
|
||||
"info": var(--accent-blue),
|
||||
"abstract": var(--accent-teal),
|
||||
"attachments": var(--accent-green),
|
||||
"notes": var(--accent-yellow),
|
||||
"tags": var(--accent-orange),
|
||||
"related": var(--accent-wood),
|
||||
);
|
||||
|
||||
$tagColorsLookup: (
|
||||
|
|
|
@ -4,150 +4,49 @@
|
|||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
background: var(--material-sidepane);
|
||||
border-top: var(--material-border);
|
||||
border-top: var(--material-border-quinary);
|
||||
padding-inline: 8px;
|
||||
scrollbar-color: var(--color-scrollbar) var(--color-scrollbar-background);
|
||||
}
|
||||
|
||||
.notes-list {
|
||||
context-notes-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 0;
|
||||
flex-grow: 1;
|
||||
padding-top: 2px;
|
||||
|
||||
& > section {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.header-row {
|
||||
margin: 0 7px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 24px;
|
||||
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 24px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
// Necessary on linux to horizontaly center text
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-row {
|
||||
margin: 4px 7px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.note-row {
|
||||
border: 1px solid var(--fill-quinary);
|
||||
border-radius: 5px;
|
||||
margin: 4px 7px;
|
||||
background: var(--material-background);
|
||||
|
||||
&:active {
|
||||
background: var(--accent-blue50);
|
||||
}
|
||||
|
||||
.inner {
|
||||
> *:not(:first-child) {
|
||||
margin-top: 3px;
|
||||
.item-notes > .body, .all-notes > .body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
gap: 6px;
|
||||
|
||||
@include comfortable {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.parent-line {
|
||||
display: flex;
|
||||
width: calc(100% - 16px);
|
||||
color: var(--fill-secondary);
|
||||
border-bottom: 1px solid var(--fill-quinary);
|
||||
align-items: center;
|
||||
padding: 5px 8px 4px;
|
||||
margin-bottom: 5px;
|
||||
&::before, &::after {
|
||||
// Apply gap to start and end
|
||||
content: '';
|
||||
}
|
||||
|
||||
.parent-item-type {
|
||||
margin-right: 3px;
|
||||
width: 16px; // Don't show HiDPI icons at 2x size
|
||||
}
|
||||
|
||||
.parent-title {
|
||||
flex-grow: 1;
|
||||
width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.title-line {
|
||||
display: flex;
|
||||
padding: 0 8px 0;
|
||||
|
||||
.title {
|
||||
flex-grow: 1;
|
||||
width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.body-line {
|
||||
display: flex;
|
||||
padding: 0 8px 6px;
|
||||
|
||||
.date {
|
||||
color: var(--fill-primary);
|
||||
> .more {
|
||||
appearance: none;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
outline: 1px solid var(--fill-quinary);
|
||||
background: var(--material-background);
|
||||
|
||||
padding: 2px 8px;
|
||||
|
||||
@include comfortable {
|
||||
padding-block: 4px;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex-grow: 1;
|
||||
width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: 10px;
|
||||
color: var(--fill-primary);
|
||||
&:active {
|
||||
background-color: var(--accent-blue10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.more-row {
|
||||
color: var(--fill-primary);
|
||||
border: 1px solid var(--fill-quinary);
|
||||
border-radius: 5px;
|
||||
margin: 4px 7px;
|
||||
background: var(--material-background);
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
|
||||
&:active {
|
||||
background-color: var(--accent-blue50);
|
||||
}
|
||||
}
|
||||
|
||||
.standalone-note-row {
|
||||
.title-line {
|
||||
padding-top: 6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.note-row, .more-row {
|
||||
outline: 0;
|
||||
|
||||
&:-moz-focusring {
|
||||
box-shadow: $toolbar-btn-focus-box-shadow;
|
||||
z-index: 1;
|
||||
|
||||
@include retina {
|
||||
box-shadow: $toolbar-btn-focus-box-shadow-2x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,6 @@ collapsible-section {
|
|||
gap: 4px;
|
||||
color: var(--fill-secondary);
|
||||
font-weight: 590;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
toolbarbutton {
|
||||
|
@ -78,6 +72,9 @@ collapsible-section {
|
|||
@each $pane, $color in $item-pane-sections {
|
||||
&[data-pane="#{$pane}"] {
|
||||
& > .head > .title::before {
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: icon-url("itempane/16/#{$pane}.svg") no-repeat center;
|
||||
-moz-context-properties: fill, fill-opacity, stroke, stroke-opacity;
|
||||
fill: $color;
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
@use "sass:map";
|
||||
|
||||
item-pane-sidenav {
|
||||
display: flex;
|
||||
&:not([hidden]) {
|
||||
display: flex;
|
||||
}
|
||||
flex-direction: column;
|
||||
padding: 6px 4px;
|
||||
align-items: flex-start;
|
||||
border-inline-start: var(--material-panedivider);
|
||||
padding: 6px 4px 0;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border-left: var(--material-panedivider);
|
||||
background: var(--material-sidepane);
|
||||
position: relative; // Stop item pane content from overlapping
|
||||
|
||||
&.stacked {
|
||||
flex-direction: row;
|
||||
border-inline-start: none;
|
||||
border-top: var(--material-panedivider);
|
||||
padding-inline-start: 6px;
|
||||
padding-inline-end: 0;
|
||||
padding-block: 4px;
|
||||
}
|
||||
|
||||
.toolbarbutton-container {
|
||||
position: relative;
|
||||
|
@ -49,7 +64,7 @@ item-pane-sidenav {
|
|||
background-color: var(--fill-quinary);
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:active, &.active {
|
||||
background-color: var(--fill-quarternary);
|
||||
}
|
||||
|
||||
|
@ -65,6 +80,45 @@ item-pane-sidenav {
|
|||
stroke: $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for Notes context pane button:
|
||||
&[data-pane="context-notes"] {
|
||||
list-style-image: url("chrome://zotero/skin/itempane/20/notes.svg");
|
||||
fill: map.get($item-pane-sections, "notes");
|
||||
stroke: map.get($item-pane-sections, "notes");
|
||||
}
|
||||
|
||||
// Special case for Expand/Collapse button:
|
||||
&[data-pane="toggle-collapse"] {
|
||||
list-style-image: url("chrome://zotero/skin/itempane/20/collapse.svg");
|
||||
|
||||
&.collapsed {
|
||||
list-style-image: url("chrome://zotero/skin/itempane/20/expand.svg");
|
||||
}
|
||||
|
||||
:dir(rtl) {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
fill: var(--fill-secondary);
|
||||
stroke: var(--fill-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
&.stacked toolbarbutton[data-pane="toggle-collapse"] {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
&:not(.stacked) > .divider {
|
||||
width: 20px;
|
||||
height: 0;
|
||||
border-bottom: 1px solid var(--fill-quinary);
|
||||
}
|
||||
|
||||
&.stacked > .divider {
|
||||
width: 0;
|
||||
height: 20px;
|
||||
border-inline-end: 1px solid var(--fill-quinary);
|
||||
}
|
||||
|
||||
menupopup {
|
||||
|
|
|
@ -1,30 +1,7 @@
|
|||
#links-box {
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
links-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-inline: 8px;
|
||||
|
||||
& > * {
|
||||
margin-top: 1px !important;
|
||||
margin-bottom: 1px !important;
|
||||
padding: 0 2px 0 2px !important;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #7f7f7f;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
min-width: 62px;
|
||||
border: var(--material-border-transparent);
|
||||
}
|
||||
|
||||
.value {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#related-popup, #tags-popup {
|
||||
font: inherit;
|
||||
border-top: 1px solid var(--fill-quinary);
|
||||
}
|
||||
|
|
59
scss/elements/_noteRow.scss
Normal file
59
scss/elements/_noteRow.scss
Normal file
|
@ -0,0 +1,59 @@
|
|||
note-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
border-radius: 5px;
|
||||
outline: 1px solid var(--fill-quinary);
|
||||
background: var(--material-background);
|
||||
|
||||
&:active {
|
||||
background-color: var(--accent-blue10);
|
||||
}
|
||||
|
||||
.head, .body {
|
||||
padding: 2px 8px;
|
||||
|
||||
@include comfortable {
|
||||
padding-block: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
border-bottom: 1px solid var(--fill-quinary);
|
||||
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.parent-title {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
color: var(--fill-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@include comfortable {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.note-title {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-weight: 590;
|
||||
}
|
||||
|
||||
.note-date {
|
||||
float: right;
|
||||
color: var(--fill-secondary);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue