diff --git a/chrome/content/zotero-platform/mac/overlay.css b/chrome/content/zotero-platform/mac/overlay.css
index 578332313f..ddc6a6c350 100644
--- a/chrome/content/zotero-platform/mac/overlay.css
+++ b/chrome/content/zotero-platform/mac/overlay.css
@@ -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;
diff --git a/chrome/content/zotero/components/itemPane/notesList.jsx b/chrome/content/zotero/components/itemPane/notesList.jsx
deleted file mode 100644
index ba6db84770..0000000000
--- a/chrome/content/zotero/components/itemPane/notesList.jsx
+++ /dev/null
@@ -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 .
-
- ***** 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 (
-
onClick(id)}
- onContextMenu={(event) => onContextMenu(id, event)}
- onKeyDown={onKeyDown}
- >
-
- { parentItemType
- ?
-
- {parentTitle}
-
- : null
- }
-
-
-
-
- );
-});
-
-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 (
-
- {hasParent &&
-
-
{Zotero.getString('pane.context.itemNotes')}
-
-
- {!childNotes.length && {Zotero.getString('pane.context.noNotes')}
}
- {childNotes.map(note => )}
- }
-
-
-
{Zotero.getString('pane.context.allNotes')}
-
-
- {!allNotes.length && {Zotero.getString('pane.context.noNotes')}
}
- {visibleNotes.map(note => )}
- {allNotes.length > visibleNotes.length
- && {
- Zotero.getString('general.numMore', Zotero.Utilities.numberFormat(
- [allNotes.length - visibleNotes.length], 0))
- }
- }
-
-
- );
-});
-
-export default NotesList;
diff --git a/chrome/content/zotero/components/itemPane/tagsBox.jsx b/chrome/content/zotero/components/itemPane/tagsBox.jsx
deleted file mode 100644
index 8d5f0db0c5..0000000000
--- a/chrome/content/zotero/components/itemPane/tagsBox.jsx
+++ /dev/null
@@ -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 .
-
- ***** 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 (
-
-
window.Zotero_Tooltip.start(title)}
- onMouseOut={() => window.Zotero_Tooltip.stop()}
- style={{ width: "16px", height: "16px" }}
- onClick={props.editable ? (() => setSelectedTag(tag.tag)) : undefined}
- />
-
-
-
- {props.editable
- && ()}
-
- );
- }
-
- // 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 (
-
-
-
{renderCount()}
- { props.editable &&
}
-
-
-
- {displayTags.map(tag => renderTagRow(tag))}
-
- { props.editable &&
}
-
-
- );
-});
-
-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;
\ No newline at end of file
diff --git a/chrome/content/zotero/containers/tagsBoxContainer.jsx b/chrome/content/zotero/containers/tagsBoxContainer.jsx
deleted file mode 100644
index 72fc46af87..0000000000
--- a/chrome/content/zotero/containers/tagsBoxContainer.jsx
+++ /dev/null
@@ -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 .
-
- ***** 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 ;
-}
-
-export default React.forwardRef(TagsBoxContainer);
diff --git a/chrome/content/zotero/contextPane.js b/chrome/content/zotero/contextPane.js
index 2d2602c8f1..a7f1035fce 100644
--- a/chrome/content/zotero/contextPane.js
+++ b/chrome/content/zotero/contextPane.js
@@ -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(
- {
- 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;
+ }
}
};
diff --git a/chrome/content/zotero/customElements.js b/chrome/content/zotero/customElements.js
index 794ad5d6ba..350d7c022d 100644
--- a/chrome/content/zotero/customElements.js
+++ b/chrome/content/zotero/customElements.js
@@ -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 s
diff --git a/chrome/content/zotero/elements/collapsibleSection.js b/chrome/content/zotero/elements/collapsibleSection.js
index c96955c4e7..ce9977720c 100644
--- a/chrome/content/zotero/elements/collapsibleSection.js
+++ b/chrome/content/zotero/elements/collapsibleSection.js
@@ -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() {
diff --git a/chrome/content/zotero/elements/contextNotesList.js b/chrome/content/zotero/elements/contextNotesList.js
new file mode 100644
index 0000000000..f1639bab09
--- /dev/null
+++ b/chrome/content/zotero/elements/contextNotesList.js
@@ -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 .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+"use strict";
+
+{
+ const MAX_UNEXPANDED_ALL_NOTES = 7;
+
+ class ContextNotesList extends XULElementBase {
+ content = MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+
+
+
+ `, ['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);
+}
diff --git a/chrome/content/zotero/elements/itemPaneSidenav.js b/chrome/content/zotero/elements/itemPaneSidenav.js
index 06708aed7a..2bf8d62245 100644
--- a/chrome/content/zotero/elements/itemPaneSidenav.js
+++ b/chrome/content/zotero/elements/itemPaneSidenav.js
@@ -28,6 +28,11 @@
{
class ItemPaneSidenav extends XULElementBase {
content = MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+