Add note as tab (#5528)
|
|
@ -274,8 +274,8 @@ const TabBar = forwardRef(function (props, ref) {
|
|||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
setDragging(false);
|
||||
props.refocusReader();
|
||||
}, [props.refocusReader]);
|
||||
props.onRefocus();
|
||||
}, [props.onRefocus]);
|
||||
|
||||
const handleTabBarDragOver = useCallback((event) => {
|
||||
event.preventDefault();
|
||||
|
|
@ -485,7 +485,7 @@ TabBar.propTypes = {
|
|||
onTabClose: PropTypes.func.isRequired,
|
||||
onLoad: PropTypes.func.isRequired,
|
||||
onTabMove: PropTypes.func.isRequired,
|
||||
refocusReader: PropTypes.func.isRequired,
|
||||
onRefocus: PropTypes.func.isRequired,
|
||||
onContextMenu: PropTypes.func.isRequired,
|
||||
tabs: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ var ZoteroContextPane = new function () {
|
|||
let _contextPaneSplitterStacked;
|
||||
let _librarySidenav;
|
||||
let _readerSidenav;
|
||||
let _sidePaneState;
|
||||
|
||||
Object.defineProperty(this, 'activeEditor', {
|
||||
get: () => _contextPaneInner.activeEditor
|
||||
|
|
@ -69,6 +70,42 @@ var ZoteroContextPane = new function () {
|
|||
_loadingMessageContainer.classList.toggle('hidden', !isShow);
|
||||
};
|
||||
|
||||
this.getSidePaneState = (tabType) => {
|
||||
if (!_sidePaneState) {
|
||||
_loadSidePaneState();
|
||||
}
|
||||
if (!_sidePaneState[tabType]) {
|
||||
_sidePaneState[tabType] = {
|
||||
width: 0,
|
||||
open: false,
|
||||
};
|
||||
}
|
||||
return _sidePaneState[tabType];
|
||||
};
|
||||
|
||||
this.updateSidePaneState = (tabType, state) => {
|
||||
if (!_sidePaneState) {
|
||||
_loadSidePaneState();
|
||||
}
|
||||
if (!_sidePaneState[tabType]) {
|
||||
_sidePaneState[tabType] = {};
|
||||
}
|
||||
state = state || {};
|
||||
let hasChanges = false;
|
||||
for (let key in state) {
|
||||
if (_sidePaneState[tabType][key] !== state[key]) {
|
||||
hasChanges = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasChanges) {
|
||||
return _sidePaneState[tabType];
|
||||
}
|
||||
Object.assign(_sidePaneState[tabType], state);
|
||||
_saveSidePaneState();
|
||||
return _sidePaneState[tabType];
|
||||
};
|
||||
|
||||
this.init = function () {
|
||||
if (!Zotero) {
|
||||
return;
|
||||
|
|
@ -83,6 +120,8 @@ var ZoteroContextPane = new function () {
|
|||
_librarySidenav = document.querySelector("#zotero-view-item-sidenav");
|
||||
_readerSidenav = document.getElementById('zotero-context-pane-sidenav');
|
||||
|
||||
_loadSidePaneState();
|
||||
|
||||
// Never use default status for the reader sidenav
|
||||
_readerSidenav.toggleDefaultStatus(false);
|
||||
|
||||
|
|
@ -91,14 +130,12 @@ var ZoteroContextPane = new function () {
|
|||
this.context = _contextPaneInner;
|
||||
|
||||
window.addEventListener('resize', this.update);
|
||||
Zotero.Reader.onChangeSidebarWidth = this._updatePaneWidth;
|
||||
Zotero.Reader.onToggleSidebar = this._updatePaneWidth;
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
window.removeEventListener('resize', this.update);
|
||||
Zotero.Reader.onChangeSidebarWidth = () => {};
|
||||
Zotero.Reader.onToggleSidebar = () => {};
|
||||
|
||||
_saveSidePaneState();
|
||||
};
|
||||
|
||||
this.updateAddToNote = () => {
|
||||
|
|
@ -111,28 +148,68 @@ var ZoteroContextPane = new function () {
|
|||
reader.enableAddToNote(!!editor && !libraryReadOnly && !noteReadOnly);
|
||||
}
|
||||
};
|
||||
|
||||
this._updatePaneWidth = () => {
|
||||
|
||||
/**
|
||||
* Update the layout of the context pane and side pane.
|
||||
* @param {Object} options - Options for updating the layout.
|
||||
* @param {number | boolean} [options.sidePaneWidth] - The width of the side pane in pixels.
|
||||
* If boolean, it indicates whether the side pane is open (true) or collapsed (false).
|
||||
* @param {number} [options.contextPaneWidth] - The width of the context pane in pixels.
|
||||
* @returns {Object} An object containing the updated layout state.
|
||||
*/
|
||||
this.updateLayout = ({ sidePaneWidth, contextPaneWidth } = {}) => {
|
||||
let stacked = _isStacked();
|
||||
let readerSidebarWidth = (Zotero.Reader.getSidebarOpen() ? Zotero.Reader.getSidebarWidth() : 0)
|
||||
+ 'px';
|
||||
let contextPaneWidth = _contextPane.getAttribute("width");
|
||||
let { tabContentType: tabType } = Zotero_Tabs.parseTabType();
|
||||
let sidePaneState;
|
||||
if (typeof sidePaneWidth === 'number') {
|
||||
// If sidePaneWidth is a number, update the width and open state
|
||||
sidePaneState = this.updateSidePaneState(tabType, { width: sidePaneWidth, open: sidePaneWidth > 0 });
|
||||
}
|
||||
else if (typeof sidePaneWidth === 'boolean') {
|
||||
// If sidePaneWidth is a boolean, update the open state only
|
||||
sidePaneState = this.updateSidePaneState(tabType, { open: sidePaneWidth });
|
||||
sidePaneWidth = sidePaneState.width || 0;
|
||||
}
|
||||
else {
|
||||
// If sidePaneWidth is not provided, use the saved state
|
||||
sidePaneState = this.getSidePaneState(tabType);
|
||||
sidePaneWidth = sidePaneState.width || 0;
|
||||
if (sidePaneState.open === false) {
|
||||
sidePaneWidth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof contextPaneWidth !== 'number') {
|
||||
contextPaneWidth = _contextPane.getAttribute("width");
|
||||
}
|
||||
|
||||
let sidebarWidth = `${sidePaneWidth}px`;
|
||||
if (contextPaneWidth && !_contextPane.style.width) {
|
||||
_contextPane.style.width = `${contextPaneWidth}px`;
|
||||
}
|
||||
if (Zotero.rtl) {
|
||||
_contextPane.style.left = 0;
|
||||
_contextPane.style.right = stacked ? readerSidebarWidth : 'unset';
|
||||
_contextPane.style.right = stacked ? sidebarWidth : 'unset';
|
||||
}
|
||||
else {
|
||||
_contextPane.style.left = stacked ? readerSidebarWidth : 'unset';
|
||||
_contextPane.style.left = stacked ? sidebarWidth : 'unset';
|
||||
_contextPane.style.right = 0;
|
||||
}
|
||||
|
||||
let placeholder = document.getElementById('zotero-reader-sidebar-pane');
|
||||
placeholder.setAttribute('collapsed', sidebarWidth ? 'false' : 'true');
|
||||
// Don't set width if 0 to prevent layout issues in older versions
|
||||
if (sidePaneWidth) {
|
||||
placeholder.setAttribute('width', sidebarWidth);
|
||||
}
|
||||
|
||||
return { sidePaneState };
|
||||
};
|
||||
|
||||
this.update = () => {
|
||||
let updatedState = {};
|
||||
if (Zotero_Tabs.selectedType === 'library') {
|
||||
return;
|
||||
return updatedState;
|
||||
}
|
||||
if (_isStacked()) {
|
||||
_contextPaneSplitterStacked.setAttribute('hidden', false);
|
||||
|
|
@ -171,8 +248,6 @@ var ZoteroContextPane = new function () {
|
|||
_contextPaneSplitterStacked.setAttribute('state', this.collapsed ? 'collapsed' : 'open');
|
||||
}
|
||||
|
||||
Zotero.Reader.setContextPaneOpen(!this.collapsed);
|
||||
|
||||
var height = null;
|
||||
if (_isStacked()) {
|
||||
height = 0;
|
||||
|
|
@ -180,18 +255,65 @@ var ZoteroContextPane = new function () {
|
|||
height = _contextPaneInner.getBoundingClientRect().height;
|
||||
}
|
||||
}
|
||||
Zotero.Reader.setBottomPlaceholderHeight(height);
|
||||
|
||||
this._updatePaneWidth();
|
||||
|
||||
_contextPaneInner.setAttribute('collapsed', this.collapsed ? 'true' : 'false');
|
||||
|
||||
let tabContent = _getTabContent();
|
||||
if (tabContent) {
|
||||
tabContent.setBottomPlaceholderHeight(height);
|
||||
tabContent.setContextPaneOpen(!this.collapsed);
|
||||
}
|
||||
|
||||
Object.assign(updatedState, this.updateLayout());
|
||||
this.updateAddToNote();
|
||||
|
||||
ZoteroPane.updateLayoutConstraints();
|
||||
return updatedState;
|
||||
};
|
||||
|
||||
this.togglePane = () => {
|
||||
this.collapsed = !this.collapsed;
|
||||
};
|
||||
|
||||
function _loadSidePaneState() {
|
||||
let sidePaneState = Zotero.Prefs.get('sidePaneState') || "{}";
|
||||
try {
|
||||
sidePaneState = JSON.parse(sidePaneState);
|
||||
}
|
||||
catch {
|
||||
sidePaneState = {};
|
||||
}
|
||||
_sidePaneState = sidePaneState;
|
||||
}
|
||||
|
||||
function _saveSidePaneState() {
|
||||
let sidePaneState;
|
||||
try {
|
||||
sidePaneState = JSON.stringify(_sidePaneState);
|
||||
}
|
||||
catch {
|
||||
// Default status if serialization fails
|
||||
sidePaneState = JSON.stringify({
|
||||
reader: {
|
||||
width: 0,
|
||||
open: false,
|
||||
},
|
||||
note: {
|
||||
width: 0,
|
||||
open: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
Zotero.Prefs.set('sidePaneState', sidePaneState);
|
||||
}
|
||||
|
||||
function _getTabContent(tabID) {
|
||||
if (!tabID) {
|
||||
tabID = Zotero_Tabs.selectedID;
|
||||
}
|
||||
return document.querySelector(`#${tabID}`);
|
||||
}
|
||||
|
||||
function _isStacked() {
|
||||
return Zotero.Prefs.get('layout') == 'stacked';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ Services.scriptloader.loadSubScript('chrome://zotero/content/elements/itemTreeMe
|
|||
['item-message-pane', 'chrome://zotero/content/elements/itemMessagePane.js'],
|
||||
['merge-group', 'chrome://zotero/content/elements/mergeGroup.js'],
|
||||
['menulist-item-types', 'chrome://zotero/content/elements/menulistItemTypes.js'],
|
||||
['note-box', 'chrome://zotero/content/elements/noteBox.js'],
|
||||
['note-editor', 'chrome://zotero/content/elements/noteEditor.js'],
|
||||
['notes-box', 'chrome://zotero/content/elements/notesBox.js'],
|
||||
['quick-search-textbox', 'chrome://zotero/content/elements/quickSearchTextbox.js'],
|
||||
|
|
@ -54,6 +55,7 @@ Services.scriptloader.loadSubScript('chrome://zotero/content/elements/itemTreeMe
|
|||
['shadow-autocomplete-input', 'chrome://zotero/content/elements/shadowAutocompleteInput.js'],
|
||||
['split-menu-button', 'chrome://zotero/content/elements/splitMenuButton.js'],
|
||||
['tabs-menu-panel', 'chrome://zotero/content/elements/tabsMenuPanel.js'],
|
||||
['tab-content', 'chrome://zotero/content/elements/tabContent.js'],
|
||||
['tags-box', 'chrome://zotero/content/elements/tagsBox.js'],
|
||||
['zotero-text-link', 'chrome://zotero/content/elements/textLink.js'],
|
||||
['zoterosearch', 'chrome://zotero/content/elements/zoteroSearch.js'],
|
||||
|
|
|
|||
|
|
@ -75,6 +75,26 @@
|
|||
setPaneCollapsed(this, val);
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['collapsed'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
switch (name) {
|
||||
case "collapsed": {
|
||||
this.handleCollapse(oldValue, newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleCollapse(prevState, newState) {
|
||||
if (prevState === "true" && (!newState || newState === "false")) {
|
||||
let itemContext = this._getItemContext(Zotero_Tabs.selectedID);
|
||||
itemContext?.render();
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this._panesDeck = this.querySelector('#zotero-context-pane-deck');
|
||||
// Item pane deck
|
||||
|
|
@ -118,11 +138,12 @@
|
|||
if (action === 'modify') {
|
||||
for (let itemDetails of Array.from(this._itemPaneDeck.children)) {
|
||||
let tabID = itemDetails.tabID;
|
||||
let item = Zotero.Items.get(Zotero_Tabs._getTab(tabID)?.tab.data.itemID);
|
||||
let tab = Zotero_Tabs._getTab(tabID).tab;
|
||||
let item = Zotero.Items.get(tab?.data.itemID);
|
||||
if ((item.parentID || itemDetails.parentID)
|
||||
&& item.parentID !== itemDetails.parentID) {
|
||||
this._removeItemContext(tabID);
|
||||
this._addItemContext(tabID, item.itemID);
|
||||
this._addItemContext(tabID, item.itemID, tab?.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -190,23 +211,28 @@
|
|||
ZoteroContextPane.showLoadingMessage(false);
|
||||
this._sidenav.hidden = true;
|
||||
}
|
||||
else if (tabType == 'reader'
|
||||
else if (Zotero_Tabs.hasContextPane(tabType)
|
||||
// The reader tab load event is triggered asynchronously.
|
||||
// If the tab is no longer selected by the time the event is triggered,
|
||||
// we don't need to update the context pane, since it must already be
|
||||
// updated by another select tab event.
|
||||
&& (action === 'select'
|
||||
|| (action === 'load' && Zotero_Tabs.selectedID == tabID))) {
|
||||
this._handleReaderReady(tabID);
|
||||
this._setupNotesContext(tabID);
|
||||
this._handleTabReady(tabID);
|
||||
if (Zotero_Tabs.hasNoteContext(tabType)) {
|
||||
this._setupNotesContext(tabID);
|
||||
}
|
||||
else {
|
||||
this._disableNotesContext();
|
||||
}
|
||||
_contextPaneSplitter.setAttribute('hidden', false);
|
||||
|
||||
_contextPane.setAttribute('collapsed', !(_contextPaneSplitter.getAttribute('state') != 'collapsed'));
|
||||
|
||||
this._sidenav.hidden = false;
|
||||
|
||||
let data = Zotero_Tabs._tabs.find(tab => tab.id === ids[0]).data;
|
||||
await this._addItemContext(ids[0], data.itemID, data.type);
|
||||
let tab = Zotero_Tabs._getTab(tabID).tab;
|
||||
await this._addItemContext(ids[0], tab.data.itemID, tab.type);
|
||||
|
||||
this._selectItemContext(tabID);
|
||||
}
|
||||
|
|
@ -226,15 +252,20 @@
|
|||
let currentNoteContext = this._getCurrentNotesContext();
|
||||
// Always switch to the current selected tab, since the selection might have changed
|
||||
currentNoteContext.switchToTab(Zotero_Tabs.selectedID);
|
||||
this.sidenav.contextNotesPaneEnabled = true;
|
||||
}
|
||||
|
||||
async _handleReaderReady(tabID) {
|
||||
let reader = Zotero.Reader.getByTabID(tabID);
|
||||
if (!reader) {
|
||||
_disableNotesContext() {
|
||||
this.sidenav.contextNotesPaneEnabled = false;
|
||||
}
|
||||
|
||||
async _handleTabReady(tabID) {
|
||||
let tabContent = Zotero_Tabs.getTabContent(tabID);
|
||||
if (!tabContent) {
|
||||
return;
|
||||
}
|
||||
// Focus reader pages view if context pane note editor is not selected
|
||||
if (Zotero_Tabs.selectedID == reader.tabID
|
||||
// Focus tab content (e.g. reader pages view) if context pane note editor is not selected
|
||||
if (Zotero_Tabs.selectedID == tabID
|
||||
&& !Zotero_Tabs.tabsMenuPanel.visible
|
||||
&& (!document.activeElement
|
||||
|| !document.activeElement.closest('.context-node iframe[id="editor-view"]'))) {
|
||||
|
|
@ -243,7 +274,7 @@
|
|||
setTimeout(() => {
|
||||
// Timeout to make sure focus does not stick to the tab
|
||||
// after click on windows
|
||||
reader.focus();
|
||||
tabContent.setFocus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -298,7 +329,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
async _addItemContext(tabID, itemID, _tabType = "") {
|
||||
async _addItemContext(tabID, itemID, tabType = "") {
|
||||
if (this._getItemContext(tabID)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -316,7 +347,13 @@
|
|||
|
||||
let previousPinnedPane = this._sidenav.container?.pinnedPane || "";
|
||||
|
||||
let targetItem = parentID ? Zotero.Items.get(parentID) : item;
|
||||
let targetItem;
|
||||
if (item.isNote()) {
|
||||
targetItem = item;
|
||||
}
|
||||
else {
|
||||
targetItem = parentID ? Zotero.Items.get(parentID) : item;
|
||||
}
|
||||
|
||||
let editable = Zotero.Libraries.get(libraryID).editable
|
||||
// If the parent item or the attachment itself is in trash, itemPane is not editable
|
||||
|
|
@ -330,7 +367,7 @@
|
|||
|
||||
itemDetails.editable = editable;
|
||||
itemDetails.tabID = tabID;
|
||||
itemDetails.tabType = "reader";
|
||||
itemDetails.tabType = Zotero_Tabs.parseTabType(tabType).tabContentType;
|
||||
itemDetails.item = targetItem;
|
||||
// Manually cache parentID
|
||||
itemDetails.parentID = parentID;
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@
|
|||
|
||||
<notes-box id="zotero-editpane-notes" class="zotero-editpane-notes" data-pane="notes"/>
|
||||
|
||||
<note-box id="zotero-note-box" data-pane="note-info" hidden="true"/>
|
||||
|
||||
<attachment-box id="zotero-attachment-box" data-pane="attachment-info" data-use-preview="true" hidden="true"/>
|
||||
|
||||
<attachment-annotations-box id="zotero-editpane-attachment-annotations" data-pane="attachment-annotations" hidden="true"/>
|
||||
|
|
@ -556,6 +558,11 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
// If the pane is already at the top, no need to scroll
|
||||
if (Math.abs(pane.getBoundingClientRect().top - this._paneParent.getBoundingClientRect().top) < 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Temporarily disable intersection observer to prevent unwanted rendering
|
||||
this._toggleIntersectionObserver(false);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
<annotation-items-pane id="zotero-annotations-pane" />
|
||||
</deck>
|
||||
<item-pane-sidenav id="zotero-view-item-sidenav" class="zotero-view-item-sidenav"/>
|
||||
<item-pane-sidenav id="zotero-view-item-sidenav" no-context-notes="true" class="zotero-view-item-sidenav"/>
|
||||
`);
|
||||
|
||||
init() {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@
|
|||
_item = null;
|
||||
|
||||
_titleFieldID = null;
|
||||
|
||||
_editable = true;
|
||||
|
||||
get item() {
|
||||
return this._item;
|
||||
|
|
@ -70,6 +72,18 @@
|
|||
set item(item) {
|
||||
this.blurOpenField();
|
||||
this._item = item;
|
||||
this.titleField.readOnly = !this.editable;
|
||||
}
|
||||
|
||||
get editable() {
|
||||
return this._editable && !this.item.isNote();
|
||||
}
|
||||
|
||||
set editable(editable) {
|
||||
this._editable = editable;
|
||||
if (this.titleField) {
|
||||
this.titleField.readOnly = !editable;
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
|
|
@ -243,9 +257,16 @@
|
|||
}
|
||||
|
||||
if (headerMode === 'title' || headerMode === 'titleCreatorYear') {
|
||||
this._titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this._item.itemTypeID, 'title');
|
||||
|
||||
let title = this.item.getField(this._titleFieldID);
|
||||
let title = "";
|
||||
if (!this._item.isNote()) {
|
||||
this._titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this._item.itemTypeID, 'title');
|
||||
title = this.item.getField(this._titleFieldID);
|
||||
}
|
||||
else {
|
||||
this._titleFieldID = "";
|
||||
title = this._item.getDisplayTitle() || Zotero.getString("item-title-empty-note");
|
||||
}
|
||||
|
||||
// If focused, update the value that will be restored on Escape;
|
||||
// otherwise, update the displayed value
|
||||
if (this.titleField.focused) {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
_container = null;
|
||||
|
||||
_contextNotesPane = null;
|
||||
|
||||
|
||||
_contextMenuTarget = null;
|
||||
|
||||
_draggedWrapper = null;
|
||||
|
|
@ -89,12 +89,20 @@
|
|||
|
||||
_prefObserverID = null;
|
||||
|
||||
get observedAttributes() {
|
||||
return ["no-context-notes"];
|
||||
}
|
||||
|
||||
attributeChangedCallback() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
get _defaultPanes() {
|
||||
return ["info", "abstract", "attachments", "notes", "libraries-collections", "tags", "related"];
|
||||
}
|
||||
|
||||
get _builtInPanes() {
|
||||
return ["info", "abstract", "attachments", "notes", "attachment-info", "attachment-annotations", "libraries-collections", "tags", "related"];
|
||||
return ["info", "abstract", "attachments", "notes", "note-info", "attachment-info", "attachment-annotations", "libraries-collections", "tags", "related"];
|
||||
}
|
||||
|
||||
get container() {
|
||||
|
|
@ -116,6 +124,20 @@
|
|||
this._contextNotesPane = val;
|
||||
this.render();
|
||||
}
|
||||
|
||||
get contextNotesPaneEnabled() {
|
||||
return !this.hasAttribute("no-context-notes");
|
||||
}
|
||||
|
||||
set contextNotesPaneEnabled(val) {
|
||||
if (this.contextNotesPaneEnabled === val) return;
|
||||
if (val) {
|
||||
this.removeAttribute("no-context-notes");
|
||||
}
|
||||
else {
|
||||
this.setAttribute("no-context-notes", "true");
|
||||
}
|
||||
}
|
||||
|
||||
get pinnedPane() {
|
||||
return this.container?.pinnedPane;
|
||||
|
|
@ -177,9 +199,9 @@
|
|||
}
|
||||
|
||||
isPaneOrderable(paneID) {
|
||||
let orderable =
|
||||
let orderable
|
||||
// Built-in or orderable custom sections
|
||||
this._builtInPanes.includes(paneID) || Zotero.ItemPaneManager.isSectionOrderable(paneID);
|
||||
= this._builtInPanes.includes(paneID) || Zotero.ItemPaneManager.isSectionOrderable(paneID);
|
||||
return orderable;
|
||||
}
|
||||
|
||||
|
|
@ -199,6 +221,7 @@
|
|||
else if (direction === 'down') {
|
||||
return isOrderable && isNextOrderable && !isLast;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isOrderChanged() {
|
||||
|
|
@ -235,7 +258,19 @@
|
|||
}
|
||||
event.preventDefault();
|
||||
let menu = button.querySelector('menupopup');
|
||||
await Zotero_LocateMenu.buildLocateMenu(menu);
|
||||
let locateMode;
|
||||
// If the container is not set, assume it's a standalone window
|
||||
if (!this.container?.tabType) {
|
||||
locateMode = "window";
|
||||
}
|
||||
// If it's library tab, we can open in either tab or window
|
||||
else if (this.container.tabType === "library") {
|
||||
locateMode = "library";
|
||||
}
|
||||
else {
|
||||
locateMode = "tab";
|
||||
}
|
||||
await Zotero_LocateMenu.buildLocateMenu(menu, { locateMode });
|
||||
|
||||
Zotero.MenuManager.updateMenuPopup(menu, "sidenav/locate", {
|
||||
event: undefined,
|
||||
|
|
@ -329,7 +364,7 @@
|
|||
}
|
||||
|
||||
if (pane == 'context-notes') {
|
||||
let hidden = !this._contextNotesPane;
|
||||
let hidden = !this.contextNotesPaneEnabled;
|
||||
let selected = contextNotesPaneVisible;
|
||||
|
||||
button.parentElement.hidden = hidden;
|
||||
|
|
@ -383,7 +418,7 @@
|
|||
currentOrder = [...currentOrder];
|
||||
// Restore the order from installed plugins but not registered in the current order
|
||||
let prevOrder = this.getPersistedOrder();
|
||||
let installedPluginIDs = undefined;
|
||||
let installedPluginIDs;
|
||||
for (let paneID of prevOrder) {
|
||||
if (currentOrder.includes(paneID)) {
|
||||
continue;
|
||||
|
|
@ -416,7 +451,7 @@
|
|||
try {
|
||||
return value.split(",");
|
||||
}
|
||||
catch(e) {
|
||||
catch {
|
||||
return this._builtInPanes;
|
||||
}
|
||||
}
|
||||
|
|
@ -450,7 +485,7 @@
|
|||
try {
|
||||
sidenavOptions = JSON.parse(pane.dataset.sidenavOptions);
|
||||
}
|
||||
catch (e) {}
|
||||
catch {}
|
||||
let { icon, darkIcon, l10nID, l10nArgs } = sidenavOptions;
|
||||
if (!darkIcon) darkIcon = icon;
|
||||
button.setAttribute("custom", "true");
|
||||
|
|
@ -823,7 +858,7 @@
|
|||
}
|
||||
// Insert at the index of the previous wrapper
|
||||
this.changePaneOrder(paneID, actualIndex);
|
||||
}
|
||||
};
|
||||
|
||||
handleButtonDragStart = (event) => {
|
||||
let wrapper = event.target.closest('.pin-wrapper');
|
||||
|
|
@ -922,7 +957,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
handleButtonDragLeave = (event) => {
|
||||
handleButtonDragLeave = (_event) => {
|
||||
if (this._dropIndicator) {
|
||||
this._dropIndicator.setAttribute("hidden", "true");
|
||||
}
|
||||
|
|
@ -950,7 +985,7 @@
|
|||
}
|
||||
this.container?.render();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
customElements.define("item-pane-sidenav", ItemPaneSidenav);
|
||||
}
|
||||
|
|
|
|||
210
chrome/content/zotero/elements/noteBox.js
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2022 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 *****
|
||||
*/
|
||||
|
||||
|
||||
{
|
||||
let { countWords } = ChromeUtils.importESModule("resource://zotero/allfaz.mjs").default;
|
||||
|
||||
class NoteBox extends ItemPaneSectionElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<collapsible-section data-l10n-id="section-note-info" data-pane="note-info">
|
||||
<html:div class="body">
|
||||
<html:div class="metadata-table">
|
||||
<html:div id="parentItemRow" class="meta-row">
|
||||
<html:div class="meta-label"><html:label id="parentItem-label" class="key" data-l10n-id="note-info-parent-item"/></html:div>
|
||||
<html:div class="meta-data clicky-item" tabindex="0"><html:span id="parentItem" class="clicky-text" data-l10n-id="note-info-parent-item-button" aria-labelledby="parentItem-label"></html:span></html:div>
|
||||
</html:div>
|
||||
<html:div id="wordCountRow" class="meta-row">
|
||||
<html:div class="meta-label"><html:label id="wordCount-label" class="key" data-l10n-id="note-info-word-count"/></html:div>
|
||||
<html:div class="meta-data"><editable-text id="wordCount" aria-labelledby="wordCount-label" nowrap="true" tight="true" readonly="true"/></html:div>
|
||||
</html:div>
|
||||
<html:div id="dateCreatedRow" class="meta-row">
|
||||
<html:div class="meta-label"><html:label id="dateCreated-label" class="key" data-l10n-id="note-info-date-created"/></html:div>
|
||||
<html:div class="meta-data"><editable-text id="dateCreated" aria-labelledby="dateCreated-label" nowrap="true" tight="true" readonly="true"/></html:div>
|
||||
</html:div>
|
||||
<html:div id="dateModifiedRow" class="meta-row">
|
||||
<html:div class="meta-label"><html:label id="dateModified-label" class="key" data-l10n-id="note-info-date-modified"/></html:div>
|
||||
<html:div class="meta-data"><editable-text id="dateModified" aria-labelledby="dateModified-label" nowrap="true" tight="true" readonly="true"/></html:div>
|
||||
</html:div>
|
||||
</html:div>
|
||||
</html:div>
|
||||
</collapsible-section>
|
||||
`);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._item = null;
|
||||
this._section = null;
|
||||
}
|
||||
|
||||
get item() {
|
||||
return this._item;
|
||||
}
|
||||
|
||||
set item(val) {
|
||||
if (!(val instanceof Zotero.Item)) {
|
||||
throw new Error("'item' must be a Zotero.Item");
|
||||
}
|
||||
|
||||
if (val.isNote()) {
|
||||
this._item = val;
|
||||
this.hidden = false;
|
||||
}
|
||||
else {
|
||||
this.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initCollapsibleSection();
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteBox');
|
||||
|
||||
for (let label of this.querySelectorAll(".meta-label")) {
|
||||
// Prevent default focus/blur behavior - we implement our own below
|
||||
label.addEventListener("mousedown", this._handleMetaLabelMousedown);
|
||||
label.addEventListener("click", this._handleMetaLabelClick);
|
||||
}
|
||||
|
||||
this._id("parentItem").addEventListener("click", this._handleViewParentItem);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
|
||||
for (let label of this.querySelectorAll(".meta-label")) {
|
||||
label.removeEventListener("mousedown", this._handleMetaLabelMousedown);
|
||||
label.removeEventListener("click", this._handleMetaLabelClick);
|
||||
}
|
||||
|
||||
this._id("parentItem")?.removeEventListener("click", this._handleViewParentItem);
|
||||
}
|
||||
|
||||
notify(event, _type, ids, _extraData) {
|
||||
if (event != 'modify' || !this.item?.id) return;
|
||||
|
||||
if (ids.includes(this.item.id)) {
|
||||
this._forceRenderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ids.includes(this.item.parentItemID)) {
|
||||
this._updateParentItemInfo();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.item) return;
|
||||
if (!this._section.open) return;
|
||||
if (this._isAlreadyRendered("sync")) return;
|
||||
|
||||
this.updateInfo();
|
||||
}
|
||||
|
||||
updateInfo() {
|
||||
if (!this._item || !this._item.isNote()) return;
|
||||
|
||||
let dateCreatedField = this._id('dateCreated');
|
||||
let dateModifiedField = this._id('dateModified');
|
||||
let wordCountField = this._id('wordCount');
|
||||
|
||||
this._updateParentItemInfo();
|
||||
|
||||
// Note word counts
|
||||
let noteContent = this._item.getNote();
|
||||
let wordCount = this._calculateWordCounts(noteContent);
|
||||
|
||||
wordCountField.value = wordCount.toLocaleString();
|
||||
|
||||
// Date created
|
||||
let dateAdded = this._item.getField('dateAdded');
|
||||
if (dateAdded) {
|
||||
let date = Zotero.Date.sqlToDate(dateAdded, true);
|
||||
dateCreatedField.value = date.toLocaleString();
|
||||
}
|
||||
|
||||
// Date modified
|
||||
let dateModified = this._item.getField('dateModified');
|
||||
if (dateModified) {
|
||||
let date = Zotero.Date.sqlToDate(dateModified, true);
|
||||
dateModifiedField.value = date.toLocaleString();
|
||||
}
|
||||
}
|
||||
|
||||
_updateParentItemInfo() {
|
||||
let parentItemButton = this._id("parentItem");
|
||||
let parentItemButtonL10nArgs;
|
||||
if (this._item.parentItemID) {
|
||||
parentItemButtonL10nArgs = `{"hasParentItem":true,"parentItemTitle":"${this._item.parentItem.getDisplayTitle()}"}`;
|
||||
}
|
||||
else {
|
||||
parentItemButtonL10nArgs = '{"hasParentItem":false}';
|
||||
}
|
||||
parentItemButton.setAttribute("data-l10n-args", parentItemButtonL10nArgs);
|
||||
}
|
||||
|
||||
_calculateWordCounts(noteContent) {
|
||||
if (!noteContent) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(noteContent, "text/html");
|
||||
const text = doc.body.textContent || "";
|
||||
return countWords(text);
|
||||
}
|
||||
|
||||
_handleMetaLabelClick = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
let labelWrapper = event.target.closest(".meta-label");
|
||||
if (labelWrapper.nextSibling.contains(document.activeElement)) {
|
||||
ZoteroPane.itemsView.focus();
|
||||
}
|
||||
else if (!labelWrapper.nextSibling.firstChild.readOnly) {
|
||||
labelWrapper.nextSibling.firstChild.focus();
|
||||
}
|
||||
};
|
||||
|
||||
_handleMetaLabelMousedown = (event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
_handleViewParentItem = (event) => {
|
||||
event.preventDefault();
|
||||
if (!this._item) return;
|
||||
if (!this._item.parentItemID) {
|
||||
ZoteroPane.selectItem(this._item.id);
|
||||
}
|
||||
ZoteroPane.selectItem(this._item.id);
|
||||
};
|
||||
|
||||
_id(id) {
|
||||
return this.querySelector(`#${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("note-box", NoteBox);
|
||||
}
|
||||
|
|
@ -38,6 +38,8 @@
|
|||
this._initialized = false;
|
||||
this._editorInstance = null;
|
||||
this._destroyed = false;
|
||||
this._bottomPlaceholder = null;
|
||||
this._contextPaneOpen = null;
|
||||
|
||||
this.content = MozXULElement.parseXULToFragment(`
|
||||
<html:div class="custom-head empty"></html:div>
|
||||
|
|
@ -140,6 +142,7 @@
|
|||
popup: this._id('editor-menu'),
|
||||
onNavigate: this._navigateHandler,
|
||||
viewMode: this.viewMode,
|
||||
tabID: this.tabID,
|
||||
readOnly: this._mode != 'edit',
|
||||
disableUI: this._mode == 'merge',
|
||||
onReturn: this._returnHandler,
|
||||
|
|
@ -148,6 +151,7 @@
|
|||
if (this._onInitCallback) {
|
||||
this._onInitCallback();
|
||||
}
|
||||
requestIdleCallback(() => this.setToggleContextPaneButtonMode());
|
||||
};
|
||||
|
||||
onInit = (callback) => {
|
||||
|
|
@ -301,6 +305,9 @@
|
|||
this._id('links-box').parentItem = val;
|
||||
}
|
||||
|
||||
// TODO: implement this
|
||||
async navigate(_location) {}
|
||||
|
||||
async focus() {
|
||||
let n = 0;
|
||||
while (!this._editorInstance && n++ < 100) {
|
||||
|
|
@ -321,10 +328,12 @@
|
|||
this._iframe.focus();
|
||||
this._editorInstance._iframeWindow.document.querySelector('.toolbar-button-return').focus();
|
||||
}
|
||||
catch (e) {
|
||||
catch {
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {}
|
||||
|
||||
renderCustomHead(callback) {
|
||||
let customHead = this.querySelector(".custom-head");
|
||||
customHead.replaceChildren();
|
||||
|
|
@ -335,6 +344,34 @@
|
|||
doc: document,
|
||||
append,
|
||||
});
|
||||
};
|
||||
|
||||
setBottomPlaceholderHeight(height) {
|
||||
// Store raw value as null/number for toggle contextPane button update
|
||||
this._bottomPlaceholder = height;
|
||||
if (typeof height !== 'number') {
|
||||
height = 0;
|
||||
}
|
||||
this.style.height = `calc(100% - ${height}px)`;
|
||||
this.setToggleContextPaneButtonMode();
|
||||
};
|
||||
|
||||
setContextPaneOpen(open) {
|
||||
this._contextPaneOpen = open;
|
||||
this.setToggleContextPaneButtonMode();
|
||||
}
|
||||
|
||||
setToggleContextPaneButtonMode() {
|
||||
if (!this._editorInstance) return;
|
||||
if (this._bottomPlaceholder === null && this._contextPaneOpen === null) return;
|
||||
let mode = null;
|
||||
if (this._bottomPlaceholder !== null) {
|
||||
mode = "stacked";
|
||||
}
|
||||
else if (!this._contextPaneOpen) {
|
||||
mode = "standard";
|
||||
}
|
||||
this._editorInstance.setToggleContextPaneButtonMode(mode);
|
||||
}
|
||||
|
||||
_id(id) {
|
||||
|
|
|
|||
|
|
@ -517,7 +517,7 @@
|
|||
break;
|
||||
|
||||
case 'context-pane-list-edit-in-window':
|
||||
ZoteroPane_Local.openNoteWindow(id);
|
||||
ZoteroPane.openNote(id, { openInWindow: true });
|
||||
break;
|
||||
|
||||
case 'context-pane-list-move-to-trash':
|
||||
|
|
|
|||
115
chrome/content/zotero/elements/tabContent.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2024 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 *****
|
||||
*/
|
||||
|
||||
{
|
||||
class TabContent extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment("");
|
||||
|
||||
get tabID() {
|
||||
return this.getAttribute("id");
|
||||
}
|
||||
|
||||
set tabID(id) {
|
||||
this.setAttribute("id", id);
|
||||
}
|
||||
|
||||
get tabData() {
|
||||
return Zotero_Tabs._getTab(this.tabID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number | null}
|
||||
* @description The width of the sidebar in pixels.
|
||||
*/
|
||||
get sidePaneWidth() {
|
||||
let state = ZoteroContextPane.getSidePaneState(this.tabData.type);
|
||||
if (state) {
|
||||
return state.width || 0;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
set sidePaneWidth(width) {
|
||||
ZoteroContextPane.updateLayout({ sidePaneWidth: width });
|
||||
}
|
||||
|
||||
async init() {
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the tab content that the tab has been selected or deselected.
|
||||
* Triggered by the Zotero_Tabs when a tab is selected or deselected.
|
||||
* @param {boolean} selected - Whether this tab is currently selected.
|
||||
*/
|
||||
onTabSelectionChanged(selected) {
|
||||
this.dispatchEvent(new CustomEvent("tab-selection-change", {
|
||||
detail: {
|
||||
selected
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the tab content that the bottom placeholder height has changed.
|
||||
* @param {number} height - The new height in pixels.
|
||||
*/
|
||||
setBottomPlaceholderHeight(height) {
|
||||
this.dispatchEvent(new CustomEvent("tab-bottom-placeholder-resize", {
|
||||
detail: {
|
||||
height,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the tab content that the context pane has been toggled.
|
||||
* @param {boolean} open - Whether the context pane is open or not.
|
||||
*/
|
||||
setContextPaneOpen(open) {
|
||||
this.dispatchEvent(new CustomEvent("tab-context-pane-toggle", {
|
||||
detail: {
|
||||
open,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the tab content that it has received focus.
|
||||
* Used by the context pane to move focus.
|
||||
*/
|
||||
setFocus() {
|
||||
this.dispatchEvent(new CustomEvent("tab-focus", {
|
||||
detail: {
|
||||
tabID: this.tabID
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("tab-content", TabContent);
|
||||
};
|
||||
|
|
@ -34,7 +34,7 @@ var Zotero_LocateMenu = new function () {
|
|||
/**
|
||||
* Clear and build the locate menu
|
||||
*/
|
||||
this.buildLocateMenu = async function (locateMenu) {
|
||||
this.buildLocateMenu = async function (locateMenu, { locateMode } = {}) {
|
||||
// clear menu
|
||||
while(locateMenu.childElementCount > 0) {
|
||||
locateMenu.removeChild(locateMenu.firstChild);
|
||||
|
|
@ -43,7 +43,9 @@ var Zotero_LocateMenu = new function () {
|
|||
var selectedItems = await _getSelectedItems();
|
||||
|
||||
if(selectedItems.length) {
|
||||
await _addViewOptions(locateMenu, selectedItems, true, true, true);
|
||||
await _addViewOptions(locateMenu, selectedItems, true, true, {
|
||||
locateMode, isToolbarMenu: true
|
||||
});
|
||||
|
||||
var availableEngines = _getAvailableLocateEngines(selectedItems);
|
||||
// add engines that are available for selected items
|
||||
|
|
@ -158,8 +160,10 @@ var Zotero_LocateMenu = new function () {
|
|||
* @param {Boolean} addExtraOptions Whether to add options that start with "_" below the separator
|
||||
* @param {Boolean} isToolbarMenu Whether the menu being populated is displayed in the toolbar
|
||||
* (and not the item tree context menu)
|
||||
* @param {"tab" | "window"} locateMode Whether the menu being populated is displayed in a tab or window
|
||||
*/
|
||||
var _addViewOptions = async function (locateMenu, selectedItems, showIcons, addExtraOptions, isToolbarMenu) {
|
||||
var _addViewOptions = async function (locateMenu, selectedItems, showIcons, addExtraOptions, options = {}) {
|
||||
let { isToolbarMenu, locateMode } = options;
|
||||
var optionsToShow = {};
|
||||
|
||||
// check which view options are available
|
||||
|
|
@ -167,7 +171,7 @@ var Zotero_LocateMenu = new function () {
|
|||
for(var viewOption in ViewOptions) {
|
||||
if (!optionsToShow[viewOption]
|
||||
&& (!isToolbarMenu || !ViewOptions[viewOption].hideInToolbar)) {
|
||||
optionsToShow[viewOption] = await ViewOptions[viewOption].canHandleItem(item);
|
||||
optionsToShow[viewOption] = await ViewOptions[viewOption].canHandleItem(item, { locateMode });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -358,9 +362,7 @@ var Zotero_LocateMenu = new function () {
|
|||
selectedItems.push(attachment);
|
||||
}
|
||||
}
|
||||
else if (!item.isNote()) {
|
||||
selectedItems.push(item);
|
||||
}
|
||||
selectedItems.push(item);
|
||||
}
|
||||
return selectedItems;
|
||||
}
|
||||
|
|
@ -368,23 +370,24 @@ var Zotero_LocateMenu = new function () {
|
|||
var ViewOptions = {};
|
||||
|
||||
/**
|
||||
* "Open PDF" option
|
||||
* "Open * in <tab/window>"
|
||||
*
|
||||
* Should appear only when the item is a PDF, or a linked or attached file or web attachment is
|
||||
* a PDF
|
||||
* Only for built-in tab item types: PDF, EPUB, Snapshot, Note
|
||||
*/
|
||||
function ViewAttachment(alternateWindowBehavior) {
|
||||
this._attachmentType = "mixed";
|
||||
this._numAttachments = 0;
|
||||
function ViewItem(alternateWindowBehavior) {
|
||||
this._viewItemType = "mixed";
|
||||
this._numItems = 0;
|
||||
Object.defineProperty(this, "className", {
|
||||
get() {
|
||||
switch (this._attachmentType) {
|
||||
switch (this._viewItemType) {
|
||||
case "pdf":
|
||||
return "zotero-menuitem-attachments-pdf";
|
||||
case "epub":
|
||||
return "zotero-menuitem-attachments-epub";
|
||||
case "snapshot":
|
||||
return "zotero-menuitem-attachments-snapshot";
|
||||
case "note":
|
||||
return "zotero-menuitem-attach-note";
|
||||
default: {
|
||||
let openInNewWindow = Zotero.Prefs.get("openReaderInNewWindow");
|
||||
if (alternateWindowBehavior) {
|
||||
|
|
@ -395,16 +398,12 @@ var Zotero_LocateMenu = new function () {
|
|||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Don't show alternate-behavior option ("in New Window" when openReaderInNewWindow is false,
|
||||
// "in New Tab" when it's true) in toolbar Locate menu
|
||||
this.hideInToolbar = alternateWindowBehavior;
|
||||
|
||||
this.l10nId = "item-menu-viewAttachment";
|
||||
Object.defineProperty(this, "l10nArgs", {
|
||||
get: () => {
|
||||
let openIn;
|
||||
if (this._attachmentType !== "mixed" && Zotero.Prefs.get(`fileHandler.${this._attachmentType}`)) {
|
||||
if (this._viewItemType !== "mixed" && Zotero.Prefs.get(`fileHandler.${this._viewItemType}`)) {
|
||||
openIn = "external";
|
||||
}
|
||||
else {
|
||||
|
|
@ -415,56 +414,77 @@ var Zotero_LocateMenu = new function () {
|
|||
openIn = openInNewWindow ? "window" : "tab";
|
||||
}
|
||||
return {
|
||||
attachmentType: this._attachmentType,
|
||||
numAttachments: this._numAttachments,
|
||||
attachmentType: this._viewItemType,
|
||||
numAttachments: this._numItems,
|
||||
openIn,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
this.canHandleItem = async function (item) {
|
||||
const attachment = await _getFirstUsableAttachment(item);
|
||||
this.canHandleItem = async function (item, { locateMode } = {}) {
|
||||
const usableItem = await _getFirstUsableItem(item);
|
||||
if (!usableItem) {
|
||||
return false;
|
||||
}
|
||||
// Don't show alternate-behavior option when using an external PDF viewer
|
||||
return attachment
|
||||
&& !(alternateWindowBehavior && Zotero.Prefs.get(`fileHandler.${attachment.attachmentReaderType}`));
|
||||
if (!item.isNote()
|
||||
&& Zotero.Prefs.get(`fileHandler.${usableItem.attachmentReaderType}`)
|
||||
&& alternateWindowBehavior) {
|
||||
return false;
|
||||
}
|
||||
if ((locateMode === "tab" && !alternateWindowBehavior)
|
||||
|| (locateMode === "window" && alternateWindowBehavior)) {
|
||||
// Don't show option if it would open in the same type of the current context
|
||||
return false;
|
||||
}
|
||||
return usableItem;
|
||||
};
|
||||
|
||||
this.updateMenuItem = async function (items) {
|
||||
let attachmentType = null;
|
||||
let numAttachments = 0;
|
||||
let viewItemType = null;
|
||||
let numItems = 0;
|
||||
for (let item of items) {
|
||||
let attachment = await _getFirstUsableAttachment(item);
|
||||
let thisAttachmentType = attachment?.attachmentReaderType;
|
||||
if (!thisAttachmentType) {
|
||||
let usableItem = await _getFirstUsableItem(item);
|
||||
if (!usableItem) {
|
||||
continue;
|
||||
}
|
||||
let thisViewItemType = usableItem.isNote() ? "note" : usableItem?.attachmentReaderType;
|
||||
if (!thisViewItemType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attachmentType === null) {
|
||||
attachmentType = thisAttachmentType;
|
||||
if (viewItemType === null) {
|
||||
viewItemType = thisViewItemType;
|
||||
}
|
||||
else if (attachmentType !== thisAttachmentType) {
|
||||
attachmentType = "mixed";
|
||||
else if (viewItemType !== thisViewItemType) {
|
||||
viewItemType = "mixed";
|
||||
}
|
||||
|
||||
numAttachments++;
|
||||
numItems++;
|
||||
}
|
||||
this._attachmentType = attachmentType;
|
||||
this._numAttachments = numAttachments;
|
||||
this._viewItemType = viewItemType;
|
||||
this._numItems = numItems;
|
||||
};
|
||||
|
||||
this.handleItems = async function (items, event) {
|
||||
var attachments = [];
|
||||
let usableItems = [];
|
||||
for (let item of items) {
|
||||
var attachment = await _getFirstUsableAttachment(item);
|
||||
if (attachment) attachments.push(attachment.id);
|
||||
let usableItem = await _getFirstUsableItem(item);
|
||||
if (usableItem) usableItems.push(usableItem);
|
||||
}
|
||||
|
||||
ZoteroPane_Local.viewAttachment(attachments, event, false,
|
||||
{ forceAlternateWindowBehavior: alternateWindowBehavior });
|
||||
ZoteroPane.viewItems(usableItems, event,
|
||||
{
|
||||
noLocateOnMissing: false,
|
||||
forceAlternateWindowBehavior: alternateWindowBehavior
|
||||
});
|
||||
};
|
||||
|
||||
var _getFirstUsableAttachment = async function (item) {
|
||||
var attachments = item.isAttachment() ? [item] : ((await item.getBestAttachments()));
|
||||
var _getFirstUsableItem = async function (item) {
|
||||
if (item.isNote()) {
|
||||
return item;
|
||||
}
|
||||
let attachments = item.isAttachment() ? [item] : ((await item.getBestAttachments()));
|
||||
for (let i = 0; i < attachments.length; i++) {
|
||||
let attachment = attachments[i];
|
||||
if (attachment.attachmentReaderType
|
||||
|
|
@ -476,8 +496,8 @@ var Zotero_LocateMenu = new function () {
|
|||
};
|
||||
}
|
||||
|
||||
ViewOptions.viewAttachmentInTab = new ViewAttachment(false);
|
||||
ViewOptions.viewAttachmentInWindow = new ViewAttachment(true);
|
||||
ViewOptions.viewItemInTab = new ViewItem(false);
|
||||
ViewOptions.viewItemInWindow = new ViewItem(true);
|
||||
|
||||
/**
|
||||
* "View Online" option
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ function showInLibrary() {
|
|||
async function onLoad() {
|
||||
if (window.arguments) {
|
||||
var io = window.arguments[0];
|
||||
if (io.wrappedJSObject) {
|
||||
io = io.wrappedJSObject;
|
||||
}
|
||||
}
|
||||
|
||||
let itemID = parseInt(io.itemID);
|
||||
|
|
@ -70,6 +73,9 @@ async function onLoad() {
|
|||
|
||||
noteEditor.focus();
|
||||
notifierUnregisterID = Zotero.Notifier.registerObserver(NotifyCallback, 'item', 'noteWindow');
|
||||
|
||||
io.noteEditor = noteEditor;
|
||||
io._initPromise?.resolve();
|
||||
}
|
||||
|
||||
// If there's an error saving a note, close the window and crash the app
|
||||
|
|
|
|||
|
|
@ -266,6 +266,15 @@
|
|||
native="true"
|
||||
/>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="zotero-prefpane-note-groupbox" aria-labelledby="preferences-note-title">
|
||||
<label><html:h2 id="preferences-note-title" data-l10n-id="preferences-note-title"/></label>
|
||||
<checkbox id="open-note-in-new-window"
|
||||
data-l10n-id="preferences-note-open-in-new-window"
|
||||
preference="extensions.zotero.openNoteInNewWindow"
|
||||
native="true"
|
||||
/>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="zotero-prefpane-locate-groupbox" aria-label="&zotero.preferences.prefpane.locate;" aria-describedby="preferences-locate-library-lookup-intro">
|
||||
<label><html:h2>&zotero.preferences.prefpane.locate;</html:h2></label>
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ const ZoteroStandalone = new function () {
|
|||
|
||||
//const NOTE_FONT_SIZES = ["11", "12", "13", "14", "18", "24", "36", "48", "64", "72", "96"];
|
||||
const NOTE_FONT_SIZE_DEFAULT = "14";
|
||||
const NOTE_TAB_FONT_SIZE_DEFAULT = "16";
|
||||
|
||||
Object.defineProperty(this, 'currentReader', {
|
||||
get: () => Zotero.Reader.getByTabID(Zotero_Tabs.selectedID)
|
||||
|
|
@ -495,7 +496,21 @@ const ZoteroStandalone = new function () {
|
|||
'view-menuitem-note-font-size-reset',
|
||||
noteFontSize != NOTE_FONT_SIZE_DEFAULT
|
||||
);
|
||||
|
||||
|
||||
let noteTabFontSize = Zotero.Prefs.get('note.tabFontSize');
|
||||
for (let menuitem of document.querySelectorAll(`#note-tab-font-size-menu menuitem`)) {
|
||||
if (parseInt(menuitem.getAttribute('label')) == noteTabFontSize) {
|
||||
menuitem.setAttribute('checked', true);
|
||||
}
|
||||
else {
|
||||
menuitem.removeAttribute('checked');
|
||||
}
|
||||
}
|
||||
this.updateMenuItemEnabled(
|
||||
'view-menuitem-note-tab-font-size-reset',
|
||||
noteTabFontSize != NOTE_TAB_FONT_SIZE_DEFAULT
|
||||
);
|
||||
|
||||
// Recursive collections
|
||||
this.updateMenuItemCheckmark(
|
||||
'view-menuitem-recursive-collections',
|
||||
|
|
@ -694,7 +709,11 @@ const ZoteroStandalone = new function () {
|
|||
var size = event.originalTarget.getAttribute('label');
|
||||
Zotero.Prefs.set('note.fontSize', size);
|
||||
};
|
||||
|
||||
|
||||
this.updateNoteTabFontSize = function (event) {
|
||||
var size = event.originalTarget.getAttribute('label');
|
||||
Zotero.Prefs.set('note.tabFontSize', size);
|
||||
};
|
||||
|
||||
this.promptForRestart = function () {
|
||||
// Prompt to restart
|
||||
|
|
|
|||
|
|
@ -76,6 +76,252 @@ var Zotero_Tabs = new function () {
|
|||
this._history = [];
|
||||
this._focusOptions = {};
|
||||
|
||||
this._loadableTypes = ['reader', 'note'];
|
||||
|
||||
this._hasContextPaneTypes = ['reader', 'note'];
|
||||
|
||||
this._hasNoteContextTypes = ['reader', 'note'];
|
||||
|
||||
this.hasContextPane = function (type) {
|
||||
return this._hasContextPaneTypes.includes(type);
|
||||
};
|
||||
|
||||
this.hasNoteContext = function (type) {
|
||||
return this._hasNoteContextTypes.includes(type);
|
||||
};
|
||||
|
||||
this.tabHooks = {
|
||||
load: {
|
||||
reader: async (tab, tabIndex, options) => {
|
||||
let reader = await Zotero.Reader.open(tab.data.itemID, options && options.location, {
|
||||
tabID: tab.id,
|
||||
title: tab.title,
|
||||
tabIndex,
|
||||
allowDuplicate: true,
|
||||
secondViewState: tab.data.secondViewState,
|
||||
preventJumpback: true
|
||||
});
|
||||
await reader._initPromise;
|
||||
},
|
||||
note: async (tab, tabIndex, options) => {
|
||||
let noteEditor = await Zotero.Notes.open(tab.data.itemID, options && options.location, {
|
||||
tabID: tab.id,
|
||||
title: tab.title,
|
||||
tabIndex,
|
||||
allowDuplicate: true,
|
||||
preventJumpback: true
|
||||
});
|
||||
await noteEditor;
|
||||
}
|
||||
},
|
||||
focus: {
|
||||
library: async () => {
|
||||
let collectionsPane = document.getElementById("zotero-collections-pane");
|
||||
if (collectionsPane.getAttribute("collapsed")) {
|
||||
document.getElementById('zotero-tb-add').focus();
|
||||
return;
|
||||
}
|
||||
document.getElementById('zotero-tb-collection-add').focus();
|
||||
},
|
||||
reader: async (tab) => {
|
||||
let reader = Zotero.Reader.getByTabID(tab.id);
|
||||
if (reader) {
|
||||
// Move focus to the reader and focus the toolbar
|
||||
reader.focusFirst();
|
||||
reader.focusToolbar();
|
||||
}
|
||||
},
|
||||
note: async (tab) => {
|
||||
let noteEditor = Zotero.Notes.getByTabID(tab.id);
|
||||
if (noteEditor) {
|
||||
// Move focus to the note editor and focus the toolbar
|
||||
noteEditor.focusToolbar();
|
||||
}
|
||||
},
|
||||
},
|
||||
refocus: {
|
||||
library: async (tab, tabIndex, options) => {
|
||||
// Move focus to the last focused element of zoteroPane if any or itemTree otherwise
|
||||
if (options.focusElementID) {
|
||||
tab.lastFocusedElement = document.getElementById(options.focusElementID);
|
||||
}
|
||||
// Small delay to make sure the focus does not remain on the actual
|
||||
// tab after mouse click
|
||||
setTimeout(() => {
|
||||
if (tab.lastFocusedElement) {
|
||||
tab.lastFocusedElement.focus();
|
||||
}
|
||||
if (document.activeElement !== tab.lastFocusedElement) {
|
||||
ZoteroPane.itemsView.focus();
|
||||
}
|
||||
tab.lastFocusedElement = null;
|
||||
});
|
||||
},
|
||||
reader: async (tab, _tabIndex, _options) => {
|
||||
let reader = Zotero.Reader.getByTabID(tab.id);
|
||||
if (reader) {
|
||||
reader.focus();
|
||||
}
|
||||
},
|
||||
note: async (tab, _tabIndex, _options) => {
|
||||
let noteEditor = Zotero.Notes.getByTabID(tab.id);
|
||||
if (noteEditor) {
|
||||
noteEditor.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
moveToNewWindow: {
|
||||
reader: async (tab, _tabIndex) => {
|
||||
Zotero_Tabs.close(tab.id);
|
||||
let { itemID, secondViewState } = tab.data;
|
||||
await Zotero.Reader.open(itemID, null, { openInWindow: true, secondViewState });
|
||||
},
|
||||
note: async (tab, _tabIndex) => {
|
||||
Zotero_Tabs.close(tab.id);
|
||||
let { itemID } = tab.data;
|
||||
await Zotero.Notes.open(itemID, null, { openInWindow: true });
|
||||
}
|
||||
},
|
||||
duplicate: {
|
||||
reader: async (tab, tabIndex) => {
|
||||
if (tab.data.itemID) {
|
||||
let { secondViewState } = tab.data;
|
||||
await Zotero.Reader.open(tab.data.itemID, null, { tabIndex: tabIndex + 1, allowDuplicate: true, secondViewState });
|
||||
}
|
||||
},
|
||||
note: async (tab, tabIndex) => {
|
||||
if (tab.data.itemID) {
|
||||
await Zotero.Notes.open(tab.data.itemID, null, { tabIndex: tabIndex + 1, allowDuplicate: true });
|
||||
}
|
||||
}
|
||||
},
|
||||
undoClose: {
|
||||
reader: async (tab, _tabIndex) => {
|
||||
if (Zotero.Items.exists(tab.data.itemID)) {
|
||||
await Zotero.Reader.open(tab.data.itemID,
|
||||
null,
|
||||
{
|
||||
tabIndex: tab.index,
|
||||
openInBackground: true,
|
||||
allowDuplicate: true
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
note: async (tab, _tabIndex) => {
|
||||
if (Zotero.Items.exists(tab.data.itemID)) {
|
||||
await Zotero.Notes.open(tab.data.itemID,
|
||||
null,
|
||||
{
|
||||
tabIndex: tab.index,
|
||||
openInBackground: true,
|
||||
allowDuplicate: true
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
restoreState: {
|
||||
library: async (tab, _tabIndex) => {
|
||||
this.rename('zotero-pane', tab.title);
|
||||
// At first, library tab is added without the icon data. We set it here once we know what it is
|
||||
let libraryTab = this._getTab('zotero-pane');
|
||||
libraryTab.tab.data = tab.data || {};
|
||||
return {
|
||||
itemID: null,
|
||||
};
|
||||
},
|
||||
reader: async (tab, tabIndex) => {
|
||||
if (Zotero.Items.exists(tab.data.itemID)) {
|
||||
// Strip non-printable characters, which can result in DOM syntax errors
|
||||
// ("An invalid or illegal string was specified") -- reproduced with "\u0001"
|
||||
// in a title in session.json
|
||||
let title = tab.title.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
|
||||
this.add({
|
||||
type: 'reader-unloaded',
|
||||
title,
|
||||
index: tabIndex,
|
||||
data: tab.data,
|
||||
select: tab.selected
|
||||
});
|
||||
return {
|
||||
itemID: tab.data.itemID,
|
||||
};
|
||||
}
|
||||
return {
|
||||
itemID: null,
|
||||
};
|
||||
},
|
||||
note: async (tab, tabIndex) => {
|
||||
if (Zotero.Items.exists(tab.data.itemID)) {
|
||||
let title = tab.title.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
|
||||
this.add({
|
||||
type: 'note-unloaded',
|
||||
title,
|
||||
index: tabIndex,
|
||||
data: tab.data,
|
||||
select: tab.selected
|
||||
});
|
||||
return {
|
||||
itemID: tab.data.itemID,
|
||||
};
|
||||
}
|
||||
return {
|
||||
itemID: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
getTitle: {
|
||||
reader: async (tab) => {
|
||||
let item = Zotero.Items.get(tab.data.itemID);
|
||||
return item ? item.getTabTitle() : "";
|
||||
},
|
||||
note: async (tab) => {
|
||||
let item = Zotero.Items.get(tab.data.itemID);
|
||||
if (!item) {
|
||||
return "";
|
||||
}
|
||||
let title = await item.getTabTitle();
|
||||
if (!title) {
|
||||
return Zotero.getString("item-title-empty-note");
|
||||
}
|
||||
return title;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this._getHook = function (type, action) {
|
||||
if (this.tabHooks[action] && this.tabHooks[action][type]) {
|
||||
return this.tabHooks[action][type];
|
||||
}
|
||||
return async () => {};
|
||||
};
|
||||
|
||||
this._hasHook = function (type, action) {
|
||||
return !!(this.tabHooks[action] && this.tabHooks[action][type]);
|
||||
};
|
||||
|
||||
this.parseTabType = function (type) {
|
||||
if (!type) {
|
||||
type = this.selectedType;
|
||||
}
|
||||
if (type === 'zotero-pane') {
|
||||
return {
|
||||
tabContentType: 'library',
|
||||
tabState: '',
|
||||
};
|
||||
}
|
||||
let [tabContentType, tabState] = type.split('-');
|
||||
return {
|
||||
tabContentType,
|
||||
tabState
|
||||
};
|
||||
};
|
||||
|
||||
// Keep track of item modifications to update the title
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'tabs');
|
||||
|
||||
|
|
@ -83,9 +329,7 @@ var Zotero_Tabs = new function () {
|
|||
this._prefsObserverID = Zotero.Prefs.registerObserver('tabs.title.reader', async () => {
|
||||
for (let tab of this._tabs) {
|
||||
if (!tab.data.itemID) continue;
|
||||
let item = Zotero.Items.get(tab.data.itemID);
|
||||
let title = await item.getTabTitle();
|
||||
this.rename(tab.id, title);
|
||||
this.rename(tab.id);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -103,6 +347,14 @@ var Zotero_Tabs = new function () {
|
|||
return { tab: this._tabs[tabIndex], tabIndex };
|
||||
};
|
||||
|
||||
this.getTabContent = function (id) {
|
||||
if (!id) {
|
||||
id = this._selectedID;
|
||||
}
|
||||
return document.getElementById(id);
|
||||
};
|
||||
|
||||
// TODO: update this
|
||||
this._update = function () {
|
||||
// Go through all tabs and try to save their icons to tab.data
|
||||
for (let tab of this._tabs) {
|
||||
|
|
@ -120,18 +372,19 @@ var Zotero_Tabs = new function () {
|
|||
let item = Zotero.Items.get(tab.data.itemID);
|
||||
tab.data.icon = item.getItemTypeIconName(true);
|
||||
}
|
||||
catch (e) {
|
||||
catch {
|
||||
// item might not yet be loaded, we will get the right icon on the next update
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._tabBarRef.current.setTabs(this._tabs.map((tab) => {
|
||||
let { tabContentType } = this.parseTabType(tab.type);
|
||||
return {
|
||||
id: tab.id,
|
||||
type: tab.type,
|
||||
title: tab.title,
|
||||
renderTitle: tab.type === 'reader' || tab.type === 'reader-unloaded',
|
||||
renderTitle: tabContentType === 'reader',
|
||||
selected: tab.id == this._selectedID,
|
||||
isItemType: tab.id !== 'zotero-pane',
|
||||
icon: tab.data?.icon || null
|
||||
|
|
@ -157,9 +410,9 @@ var Zotero_Tabs = new function () {
|
|||
return tab && tab.id;
|
||||
};
|
||||
|
||||
this.setSecondViewState = function (tabID, state) {
|
||||
this.setTabData = function (tabID, data) {
|
||||
let { tab } = this._getTab(tabID);
|
||||
tab.data.secondViewState = state;
|
||||
Object.assign(tab.data, data);
|
||||
Zotero.Session.debounceSave();
|
||||
};
|
||||
|
||||
|
|
@ -171,7 +424,7 @@ var Zotero_Tabs = new function () {
|
|||
onTabMove={this.move.bind(this)}
|
||||
onTabClose={this.close.bind(this)}
|
||||
onContextMenu={this._openMenu.bind(this)}
|
||||
refocusReader={this.refocusReader.bind(this)}
|
||||
onRefocus={this.refocus.bind(this)}
|
||||
onLoad={this._update.bind(this)}
|
||||
/>
|
||||
);
|
||||
|
|
@ -182,16 +435,23 @@ var Zotero_Tabs = new function () {
|
|||
if (event !== "modify") return;
|
||||
for (let id of ids) {
|
||||
let item = Zotero.Items.get(id);
|
||||
// If a top-level item is updated, update all tabs that have its attachments
|
||||
// If a top-level item is updated, update all tabs that have its attachments and notes
|
||||
// Otherwise, just update the tab with the updated attachment
|
||||
let attachmentIDs = item.isAttachment() ? [id] : item.getAttachments();
|
||||
for (let attachmentID of attachmentIDs) {
|
||||
let attachment = Zotero.Items.get(attachmentID);
|
||||
let relevantTabs = this._tabs.filter(tab => tab.data.itemID == attachmentID);
|
||||
let itemIDs = [];
|
||||
if (item.isAttachment() || item.isNote()) {
|
||||
itemIDs.push(id);
|
||||
}
|
||||
else if (item.isRegularItem()) {
|
||||
itemIDs.push(
|
||||
...item.getAttachments(),
|
||||
...item.getNotes()
|
||||
);
|
||||
}
|
||||
for (let itemID of itemIDs) {
|
||||
let relevantTabs = this._tabs.filter(tab => tab.data.itemID == itemID);
|
||||
if (!relevantTabs.length) continue;
|
||||
for (let tab of relevantTabs) {
|
||||
let title = await attachment.getTabTitle();
|
||||
this.rename(tab.id, title);
|
||||
this.rename(tab.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -200,8 +460,9 @@ var Zotero_Tabs = new function () {
|
|||
this.getState = function () {
|
||||
return this._tabs.map((tab) => {
|
||||
let type = tab.type;
|
||||
if (type === 'reader-unloaded') {
|
||||
type = 'reader';
|
||||
// If type matches *-unloaded, use the base type
|
||||
if (type.endsWith('-unloaded')) {
|
||||
type = type.replace(/-unloaded$/, '');
|
||||
}
|
||||
var o = {
|
||||
type,
|
||||
|
|
@ -222,27 +483,11 @@ var Zotero_Tabs = new function () {
|
|||
let itemIDs = [];
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
let tab = tabs[i];
|
||||
if (tab.type === 'library') {
|
||||
this.rename('zotero-pane', tab.title);
|
||||
// At first, library tab is added without the icon data. We set it here once we know what it is
|
||||
let libraryTab = this._getTab('zotero-pane');
|
||||
libraryTab.tab.data = tab.data || {};
|
||||
}
|
||||
else if (tab.type === 'reader') {
|
||||
if (Zotero.Items.exists(tab.data.itemID)) {
|
||||
// Strip non-printable characters, which can result in DOM syntax errors
|
||||
// ("An invalid or illegal string was specified") -- reproduced with "\u0001"
|
||||
// in a title in session.json
|
||||
let title = tab.title.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
|
||||
this.add({
|
||||
type: 'reader-unloaded',
|
||||
title,
|
||||
index: i,
|
||||
data: tab.data,
|
||||
select: tab.selected
|
||||
});
|
||||
itemIDs.push(tab.data.itemID);
|
||||
}
|
||||
let { tabContentType } = this.parseTabType(tab.type);
|
||||
let restoreStateHook = this._getHook(tabContentType, 'restoreState');
|
||||
let { itemID } = await restoreStateHook(tab, i);
|
||||
if (itemID) {
|
||||
itemIDs.push(itemID);
|
||||
}
|
||||
}
|
||||
// Unset the previously selected tab id, because it was set when restoring tabs
|
||||
|
|
@ -258,7 +503,7 @@ var Zotero_Tabs = new function () {
|
|||
* Add a new tab
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} title
|
||||
* @param {String} [title] - Tab title. If empty and data.itemID is set, the title will be fetched automatically
|
||||
* @param {String} data - Extra data about the tab to pass to notifier and session
|
||||
* @param {Integer} index
|
||||
* @param {Boolean} select
|
||||
|
|
@ -268,8 +513,11 @@ var Zotero_Tabs = new function () {
|
|||
this.add = function ({ id, type, data, title, index, select, onClose, preventJumpback }) {
|
||||
if (typeof type != 'string') {
|
||||
}
|
||||
if (typeof title != 'string') {
|
||||
throw new Error(`'title' should be a string (was ${typeof title})`);
|
||||
if (title && typeof title != 'string') {
|
||||
throw new Error(`'title' should be string or undefined (was ${typeof title})`);
|
||||
}
|
||||
if (!title) {
|
||||
title = "";
|
||||
}
|
||||
if (index !== undefined && (!Number.isInteger(index) || index < 1)) {
|
||||
throw new Error(`'index' should be an integer > 0 (was ${index} (${typeof index})`);
|
||||
|
|
@ -278,7 +526,7 @@ var Zotero_Tabs = new function () {
|
|||
throw new Error(`'onClose' should be a function (was ${typeof onClose})`);
|
||||
}
|
||||
id = id || 'tab-' + Zotero.Utilities.randomString();
|
||||
var container = document.createXULElement('vbox');
|
||||
var container = document.createXULElement('tab-content');
|
||||
container.id = id;
|
||||
this.deck.appendChild(container);
|
||||
var tab = { id, type, title, data, onClose };
|
||||
|
|
@ -293,15 +541,9 @@ var Zotero_Tabs = new function () {
|
|||
this._prevSelectedID = previousID;
|
||||
}
|
||||
}
|
||||
// When a new tab is opened synchronously by ReaderTab constructor, the title is empty.
|
||||
// However, { id, container } needs to return immediately, so do not wait for the new title
|
||||
// and construct it in async manner below.
|
||||
if (!title && data.itemID) {
|
||||
(async () => {
|
||||
let item = Zotero.Items.get(data.itemID);
|
||||
title = await item.getTabTitle();
|
||||
this.rename(tab.id, title);
|
||||
})();
|
||||
// Not awaited as the id and container should be returned synchronously
|
||||
this.rename(tab.id);
|
||||
}
|
||||
return { id, container };
|
||||
};
|
||||
|
|
@ -312,14 +554,21 @@ var Zotero_Tabs = new function () {
|
|||
* @param {String} id
|
||||
* @param {String} title
|
||||
*/
|
||||
this.rename = function (id, title) {
|
||||
if (typeof title != 'string') {
|
||||
throw new Error(`'title' should be a string (was ${typeof title})`);
|
||||
this.rename = async function (id, title) {
|
||||
if (title && typeof title != 'string') {
|
||||
throw new Error(`'title' should be string or undefined (was ${typeof title})`);
|
||||
}
|
||||
var { tab } = this._getTab(id);
|
||||
let { tab } = this._getTab(id);
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
if (!title) {
|
||||
let { tabContentType } = this.parseTabType(tab.type);
|
||||
if (this._hasHook(tabContentType, 'getTitle')) {
|
||||
title = await this._getHook(tabContentType, 'getTitle')(tab);
|
||||
}
|
||||
}
|
||||
|
||||
tab.title = title;
|
||||
this._update();
|
||||
};
|
||||
|
|
@ -366,7 +615,7 @@ var Zotero_Tabs = new function () {
|
|||
if (tab.onClose) {
|
||||
tab.onClose();
|
||||
}
|
||||
historyEntry.push({ index: tmpTabs.indexOf(tab), data: tab.data });
|
||||
historyEntry.push({ index: tmpTabs.indexOf(tab), data: tab.data, type: tab.type });
|
||||
closedIDs.push(id);
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
@ -399,19 +648,14 @@ var Zotero_Tabs = new function () {
|
|||
let maxIndex = -1;
|
||||
let openPromises = [];
|
||||
for (let tab of historyEntry) {
|
||||
if (Zotero.Items.exists(tab.data.itemID)) {
|
||||
openPromises.push(Zotero.Reader.open(tab.data.itemID,
|
||||
null,
|
||||
{
|
||||
tabIndex: tab.index,
|
||||
openInBackground: true,
|
||||
allowDuplicate: true
|
||||
let { tabContentType } = this.parseTabType(tab.type);
|
||||
let undoCloseHook = this._getHook(tabContentType, 'undoClose');
|
||||
openPromises.push(undoCloseHook({ data: tab.data }, tab.index)
|
||||
.then((opened) => {
|
||||
if (opened && tab.index > maxIndex) {
|
||||
maxIndex = tab.index;
|
||||
}
|
||||
));
|
||||
if (tab.index > maxIndex) {
|
||||
maxIndex = tab.index;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
await Promise.all(openPromises);
|
||||
// Select last reopened tab
|
||||
|
|
@ -453,39 +697,15 @@ var Zotero_Tabs = new function () {
|
|||
* @param {Boolean} reopening
|
||||
*/
|
||||
this.select = function (id, reopening, options = {}) {
|
||||
var { tab, tabIndex } = this._getTab(id);
|
||||
// Move focus to the last focused element of zoteroPane if any or itemTree otherwise
|
||||
let focusZoteroPane = () => {
|
||||
if (tab.id !== 'zotero-pane') return;
|
||||
if (options.focusElementID) {
|
||||
tab.lastFocusedElement = document.getElementById(options.focusElementID);
|
||||
}
|
||||
// Small delay to make sure the focus does not remain on the actual
|
||||
// tab after mouse click
|
||||
setTimeout(() => {
|
||||
if (this.tabsMenuPanel.visible) {
|
||||
this.tabsMenuPanel.resetFocus();
|
||||
}
|
||||
else if (tab.lastFocusedElement) {
|
||||
tab.lastFocusedElement.focus();
|
||||
}
|
||||
else if (document.activeElement !== tab.lastFocusedElement) {
|
||||
ZoteroPane_Local.itemsView.focus();
|
||||
}
|
||||
tab.lastFocusedElement = null;
|
||||
});
|
||||
};
|
||||
let { tab, tabIndex } = this._getTab(id);
|
||||
let { tabContentType, tabState } = this.parseTabType(tab.type);
|
||||
|
||||
if (!tab || tab.id === this._selectedID) {
|
||||
// Focus on reader or zotero pane when keepTabFocused is explicitly false
|
||||
// E.g. when a tab is selected via Space or Enter
|
||||
if (options.keepTabFocused === false && tab?.id === this._selectedID) {
|
||||
var reader = Zotero.Reader.getByTabID(this._selectedID);
|
||||
if (reader) {
|
||||
reader.focus();
|
||||
}
|
||||
if (tab.id == 'zotero-pane') {
|
||||
focusZoteroPane();
|
||||
}
|
||||
let focusHook = this._getHook(tabContentType, 'focus');
|
||||
focusHook(tab, tabIndex, options);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -525,32 +745,29 @@ var Zotero_Tabs = new function () {
|
|||
// never return focus to another tab or <window>
|
||||
selectedTab.lastFocusedElement = document.activeElement;
|
||||
}
|
||||
if (tab.type === 'reader-unloaded') {
|
||||
tab.type = "reader-loading";
|
||||
|
||||
if (tabState === 'unloaded') {
|
||||
tab.type = `${tabContentType}-loading`;
|
||||
// Make sure the loading message is displayed first.
|
||||
// Then, open reader and hide the loading message once it has loaded.
|
||||
ZoteroContextPane.showLoadingMessage(true);
|
||||
let hideMessageWhenReaderLoaded = async () => {
|
||||
let reader = await Zotero.Reader.open(tab.data.itemID, options && options.location, {
|
||||
tabID: tab.id,
|
||||
title: tab.title,
|
||||
tabIndex,
|
||||
allowDuplicate: true,
|
||||
secondViewState: tab.data.secondViewState,
|
||||
preventJumpback: true
|
||||
});
|
||||
await reader._initPromise;
|
||||
let loadHook = this._getHook(tabContentType, 'load');
|
||||
loadHook(tab, tabIndex, options).then(() => {
|
||||
ZoteroContextPane.showLoadingMessage(false);
|
||||
};
|
||||
hideMessageWhenReaderLoaded();
|
||||
this.markAsLoaded(tab.id);
|
||||
});
|
||||
}
|
||||
// Notify previously selected tab content about selection change
|
||||
this.getTabContent(this._selectedID)?.onTabSelectionChanged(false);
|
||||
|
||||
this._prevSelectedID = reopening ? this._selectedID : null;
|
||||
this._selectedID = id;
|
||||
this.deck.selectedIndex = Array.from(this.deck.children).findIndex(x => x.id == id);
|
||||
this._update();
|
||||
Zotero.Notifier.trigger('select', 'tab', [tab.id], { [tab.id]: { type: tab.type } }, true);
|
||||
if (tab.id === 'zotero-pane' && (options.keepTabFocused !== true)) {
|
||||
focusZoteroPane();
|
||||
if (options.keepTabFocused !== true) {
|
||||
let focusHook = this._getHook(tabContentType, 'focus');
|
||||
focusHook(tab, tabIndex, options);
|
||||
}
|
||||
let tabNode = document.querySelector(`#tab-bar-container .tab[data-id="${tab.id}"]`);
|
||||
if (this._focusOptions.keepTabFocused && document.activeElement.getAttribute('data-id') != tabNode.getAttribute('data-id')) {
|
||||
|
|
@ -569,17 +786,20 @@ var Zotero_Tabs = new function () {
|
|||
// tabs deck selection index bigger than the deck children count. It feels like something
|
||||
// isn't update synchronously
|
||||
setTimeout(() => this.unloadUnusedTabs());
|
||||
|
||||
// Notify tab content about selection change
|
||||
this.getTabContent(this._selectedID)?.onTabSelectionChanged(true);
|
||||
};
|
||||
|
||||
this.unload = function (id) {
|
||||
var { tab, tabIndex } = this._getTab(id);
|
||||
if (!tab || tab.id === this._selectedID || tab.type !== 'reader') {
|
||||
if (!tab || tab.id === this._selectedID || !this._loadableTypes.includes(tab.type)) {
|
||||
return;
|
||||
}
|
||||
this.close(tab.id);
|
||||
this.add({
|
||||
id: tab.id,
|
||||
type: 'reader-unloaded',
|
||||
type: `${tab.type}-unloaded`,
|
||||
title: tab.title,
|
||||
index: tabIndex,
|
||||
data: tab.data
|
||||
|
|
@ -589,9 +809,13 @@ var Zotero_Tabs = new function () {
|
|||
// Mark a tab as loaded
|
||||
this.markAsLoaded = function (id) {
|
||||
let { tab } = this._getTab(id);
|
||||
if (!tab || tab.type == "reader") return;
|
||||
if (!tab) return;
|
||||
let { tabContentType, tabState } = this.parseTabType(tab.type);
|
||||
if (tabState !== 'loading') {
|
||||
return;
|
||||
}
|
||||
let prevType = tab.type;
|
||||
tab.type = "reader";
|
||||
tab.type = tabContentType;
|
||||
Zotero.Notifier.trigger("load", "tab", [id], { [id]: Object.assign({}, tab, { prevType }) }, true);
|
||||
};
|
||||
|
||||
|
|
@ -601,6 +825,7 @@ var Zotero_Tabs = new function () {
|
|||
this.unload(tab.id);
|
||||
}
|
||||
}
|
||||
// TODO: also unload note tabs
|
||||
let tabs = this._tabs.slice().filter(x => x.type === 'reader');
|
||||
tabs.sort((a, b) => b.timeUnselected - a.timeUnselected);
|
||||
tabs = tabs.slice(MAX_LOADED_TABS);
|
||||
|
|
@ -633,15 +858,49 @@ var Zotero_Tabs = new function () {
|
|||
};
|
||||
|
||||
/**
|
||||
* Return focus into the reader of the selected tab.
|
||||
* Required to move focus from the tab into the reader after drag.
|
||||
* Return focus into the content of the selected tab.
|
||||
* Required to move focus from the tab into the content after drag.
|
||||
*/
|
||||
this.refocusReader = function () {
|
||||
var reader = Zotero.Reader.getByTabID(this._selectedID);
|
||||
if (!reader) return;
|
||||
setTimeout(() => {
|
||||
reader.focus();
|
||||
});
|
||||
this.refocus = function (id) {
|
||||
if (!id) id = this._selectedID;
|
||||
let { tab, tabIndex } = this._getTab(id);
|
||||
if (!tab) return;
|
||||
let { tabContentType } = this.parseTabType(tab.type);
|
||||
let refocusHook = this._getHook(tabContentType, 'refocus');
|
||||
refocusHook(tab, tabIndex, this._focusOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move focus into the first element in content of the selected tab.
|
||||
* Required to move focus from the outside into the tab content.
|
||||
*/
|
||||
this.focusContent = function (id) {
|
||||
if (!id) id = this._selectedID;
|
||||
let { tab, tabIndex } = this._getTab(id);
|
||||
if (!tab) return;
|
||||
let { tabContentType } = this.parseTabType(tab.type);
|
||||
let focusHook = this._getHook(tabContentType, 'focus');
|
||||
focusHook(tab, tabIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move focus back from the tab content,
|
||||
* e.g. shift-tab from the first focusable element.
|
||||
*/
|
||||
this.focusBack = function () {
|
||||
document.getElementById("zotero-tb-sync").focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Move focus to the next focusable element after the tab content,
|
||||
* e.g. tab from the last focusable element.
|
||||
*/
|
||||
this.focusForward = function () {
|
||||
let focused = ZoteroContextPane.focus();
|
||||
// If context pane wasn't focused (e.g. it's collapsed), focus the tab bar
|
||||
if (!focused) {
|
||||
this.moveFocus("current");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -697,7 +956,7 @@ var Zotero_Tabs = new function () {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Jump to the tab at a particular index. If the index points beyond the array, jump to the last
|
||||
* tab.
|
||||
|
|
@ -709,7 +968,8 @@ var Zotero_Tabs = new function () {
|
|||
};
|
||||
|
||||
this._openMenu = function (x, y, id) {
|
||||
var { tab, tabIndex } = this._getTab(id);
|
||||
let { tab, tabIndex } = this._getTab(id);
|
||||
let { tabContentType } = this.parseTabType(tab.type);
|
||||
let menuitem;
|
||||
let popup = document.createXULElement('menupopup');
|
||||
document.querySelector('popupset').appendChild(popup);
|
||||
|
|
@ -754,30 +1014,26 @@ var Zotero_Tabs = new function () {
|
|||
this.move(id, this._tabs.length);
|
||||
});
|
||||
menupopup.appendChild(menuitem);
|
||||
|
||||
if (tab.type === 'reader' || tab.type === 'reader-unloaded') {
|
||||
// Move to new window
|
||||
// Move to new window
|
||||
if (this._hasHook(tabContentType, 'moveToNewWindow')) {
|
||||
menuitem = document.createXULElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('tabs.moveToWindow'));
|
||||
menuitem.setAttribute('disabled', false);
|
||||
menuitem.addEventListener('command', () => {
|
||||
let { tab } = this._getTab(id);
|
||||
if (tab && (tab.type === 'reader' || tab.type === 'reader-unloaded')) {
|
||||
this.close(id);
|
||||
let { itemID, secondViewState } = tab.data;
|
||||
Zotero.Reader.open(itemID, null, { openInWindow: true, secondViewState });
|
||||
}
|
||||
let { tabContentType } = this.parseTabType(tab.type);
|
||||
let moveHook = this._getHook(tabContentType, 'moveToNewWindow');
|
||||
moveHook(tab, tabIndex);
|
||||
});
|
||||
menupopup.appendChild(menuitem);
|
||||
// Duplicate tab
|
||||
}
|
||||
// Duplicate tab
|
||||
if (this._hasHook(tabContentType, 'duplicate')) {
|
||||
menuitem = document.createXULElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('tabs.duplicate'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
if (tab.data.itemID) {
|
||||
tabIndex++;
|
||||
let { secondViewState } = tab.data;
|
||||
Zotero.Reader.open(tab.data.itemID, null, { tabIndex, allowDuplicate: true, secondViewState });
|
||||
}
|
||||
let { tabContentType } = this.parseTabType(tab.type);
|
||||
let duplicateHook = this._getHook(tabContentType, 'duplicate');
|
||||
duplicateHook(tab, tabIndex);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
}
|
||||
|
|
@ -803,7 +1059,7 @@ var Zotero_Tabs = new function () {
|
|||
popup.appendChild(menuitem);
|
||||
}
|
||||
// Undo close
|
||||
if (['reader', 'reader-unloaded'].includes(tab.type)) {
|
||||
if (this._hasHook(tabContentType, 'undoClose')) {
|
||||
menuitem = document.createXULElement('menuitem');
|
||||
menuitem.setAttribute(
|
||||
'label',
|
||||
|
|
|
|||
|
|
@ -1026,8 +1026,11 @@ Zotero.Item.prototype.updateDisplayTitle = function () {
|
|||
* @returns {String} title for the tab of this item
|
||||
*/
|
||||
Zotero.Item.prototype.getTabTitle = async function () {
|
||||
if (!this.isAttachment()) {
|
||||
throw new Error("Can only get tab title for attachments");
|
||||
if (!this.isAttachment() && !this.isNote()) {
|
||||
throw new Error("Can only get tab title for attachments and notes");
|
||||
}
|
||||
if (this.isNote()) {
|
||||
return this.getDisplayTitle();
|
||||
}
|
||||
let type = Zotero.Prefs.get('tabs.title.reader');
|
||||
let readerTitle = this.getDisplayTitle();
|
||||
|
|
|
|||
|
|
@ -34,6 +34,164 @@ Zotero.Notes = new function () {
|
|||
|
||||
this._editorInstances = [];
|
||||
this._downloadInProgressPromise = null;
|
||||
|
||||
this.open = async function (itemID, location, { title, tabIndex, tabID, openInBackground, openInWindow, allowDuplicate, preventJumpback, parentItemKey } = {}) {
|
||||
let { libraryID } = Zotero.Items.getLibraryAndKeyFromID(itemID);
|
||||
let library = Zotero.Libraries.get(libraryID);
|
||||
let win = Zotero.getMainWindow();
|
||||
|
||||
if (!win) {
|
||||
openInWindow = true;
|
||||
}
|
||||
|
||||
await library.waitForDataLoad('item');
|
||||
|
||||
let item = Zotero.Items.get(itemID);
|
||||
if (!item) {
|
||||
throw new Error('Item does not exist');
|
||||
}
|
||||
|
||||
let noteEditor;
|
||||
if (!openInWindow && !allowDuplicate && !this._editorInstances.find(r => r.itemID === itemID)) {
|
||||
if (win) {
|
||||
let existingTabID = win.Zotero_Tabs.getTabIDByItemID(itemID);
|
||||
if (existingTabID) {
|
||||
win.Zotero_Tabs.select(existingTabID, false, { location });
|
||||
return win.Zotero_Tabs.getTabContent(existingTabID).querySelector('note-editor.note-tab');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (openInWindow) {
|
||||
noteEditor = this._editorInstances.find(r => r.itemID === itemID && r.viewMode === 'window');
|
||||
}
|
||||
else if (!allowDuplicate) {
|
||||
noteEditor = this._editorInstances.find(r => r.itemID === itemID && r.viewMode === 'tab');
|
||||
}
|
||||
|
||||
if (noteEditor) {
|
||||
if (noteEditor.viewMode === 'tab') {
|
||||
win.Zotero_Tabs.select(noteEditor.tabID, true);
|
||||
}
|
||||
|
||||
if (location) {
|
||||
noteEditor.navigate(location);
|
||||
}
|
||||
}
|
||||
else if (openInWindow) {
|
||||
let name = null;
|
||||
|
||||
if (itemID) {
|
||||
// Create a name for this window so we can focus it later
|
||||
//
|
||||
// Collection is only used on new notes, so we don't need to
|
||||
// include it in the name
|
||||
name = 'zotero-note-' + itemID;
|
||||
}
|
||||
|
||||
let io = { itemID, parentItemKey, location, _initPromise: Zotero.Promise.defer() };
|
||||
Services.ww.openWindow(
|
||||
win,
|
||||
'chrome://zotero/content/note.xhtml',
|
||||
name,
|
||||
'chrome,resizable,centerscreen,dialog=no',
|
||||
io
|
||||
);
|
||||
await io._initPromise.promise;
|
||||
noteEditor = io.noteEditor;
|
||||
}
|
||||
else {
|
||||
let id;
|
||||
let container;
|
||||
let select = !openInBackground;
|
||||
if (tabID) {
|
||||
id = tabID;
|
||||
container = win.document.getElementById(tabID);
|
||||
noteEditor = container.querySelector('note-editor.note-tab');
|
||||
}
|
||||
else {
|
||||
({ id, container } = win.Zotero_Tabs.add({
|
||||
id: tabID,
|
||||
type: 'note-unloaded',
|
||||
title,
|
||||
index: tabIndex,
|
||||
data: {
|
||||
itemID,
|
||||
},
|
||||
select,
|
||||
preventJumpback,
|
||||
}));
|
||||
}
|
||||
|
||||
if (!noteEditor && !openInBackground) {
|
||||
noteEditor = win.document.createXULElement('note-editor');
|
||||
noteEditor.classList.add('note-tab');
|
||||
container.appendChild(noteEditor);
|
||||
|
||||
noteEditor.mode = item.isEditable() ? 'edit' : 'view';
|
||||
noteEditor.viewMode = 'tab';
|
||||
noteEditor.item = item;
|
||||
noteEditor.tabID = id;
|
||||
noteEditor._id('links-container').hidden = true;
|
||||
|
||||
container.addEventListener('tab-bottom-placeholder-resize', (event) => {
|
||||
this.setBottomPlaceholderHeight(noteEditor, event.detail.height);
|
||||
});
|
||||
|
||||
container.addEventListener('tab-context-pane-toggle', (event) => {
|
||||
this.setContextPaneOpen(noteEditor, event.detail.open);
|
||||
});
|
||||
|
||||
container.addEventListener('tab-focus', () => {
|
||||
noteEditor.focus();
|
||||
});
|
||||
|
||||
container.addEventListener('tab-selection-change', (event) => {
|
||||
if (event.detail.selected) {
|
||||
this._updateLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (select) {
|
||||
this._updateLayout();
|
||||
}
|
||||
}
|
||||
return noteEditor;
|
||||
};
|
||||
|
||||
this.setBottomPlaceholderHeight = function (noteEditor, height) {
|
||||
noteEditor.setBottomPlaceholderHeight(height);
|
||||
};
|
||||
|
||||
this.toggleSidePane = function (_open) {
|
||||
// TODO: Implement this once the note editor supports side pane
|
||||
};
|
||||
|
||||
this.setSidePaneWidth = function () {
|
||||
// TODO: Implement this once the note editor supports side pane
|
||||
};
|
||||
|
||||
this.setContextPaneOpen = function (noteEditor, open) {
|
||||
noteEditor.setContextPaneOpen(open);
|
||||
};
|
||||
|
||||
this._updateLayout = function () {
|
||||
let { sidePaneState } = Zotero.getMainWindow().ZoteroContextPane.update();
|
||||
this.toggleSidePane(sidePaneState.open);
|
||||
this.setSidePaneWidth(sidePaneState.width);
|
||||
};
|
||||
|
||||
this.getByTabID = function (tabID) {
|
||||
if (!tabID) {
|
||||
return null;
|
||||
}
|
||||
let noteEditor = this._editorInstances.find(x => x._tabID === tabID);
|
||||
if (noteEditor) {
|
||||
return noteEditor;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.noteToTitle = function (text) {
|
||||
Zotero.debug(`Zotero.Note.noteToTitle() is deprecated -- use Zotero.Utilities.Item.noteToTitle() instead`);
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class EditorInstance {
|
|||
this._item = options.item;
|
||||
this._reloaded = options.reloaded;
|
||||
this._viewMode = options.viewMode;
|
||||
this._tabID = options.tabID;
|
||||
this._readOnly = options.readOnly || this._isReadOnly();
|
||||
this._filesReadOnly = !Zotero.Libraries.get(this._item.libraryID).filesEditable;
|
||||
this._disableUI = options.disableUI;
|
||||
|
|
@ -73,6 +74,7 @@ class EditorInstance {
|
|||
});
|
||||
this._prefObserverIDs = [
|
||||
Zotero.Prefs.registerObserver('note.fontSize', this._handleFontChange),
|
||||
Zotero.Prefs.registerObserver('note.tabFontSize', this._handleFontChange),
|
||||
Zotero.Prefs.registerObserver('note.fontFamily', this._handleFontChange),
|
||||
Zotero.Prefs.registerObserver('note.css', this._handleStyleChange),
|
||||
Zotero.Prefs.registerObserver('layout.spellcheckDefault', this._handleSpellCheckChange, true)
|
||||
|
|
@ -244,7 +246,13 @@ class EditorInstance {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (type === 'item' && ['delete', 'trash'].includes(event) && this._tabID) {
|
||||
if (this._item && (ids.includes(this._item.id) || ids.includes(this._item.parentItemID))) {
|
||||
Zotero.getMainWindow().Zotero_Tabs.close(this._tabID);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._readOnly || !this._item) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -288,7 +296,14 @@ class EditorInstance {
|
|||
}
|
||||
|
||||
_getFont() {
|
||||
let fontSize = Zotero.Prefs.get('note.fontSize');
|
||||
let fontSizePrefKey;
|
||||
if (this._tabID) {
|
||||
fontSizePrefKey = `note.tabFontSize`;
|
||||
}
|
||||
else {
|
||||
fontSizePrefKey = `note.fontSize`;
|
||||
}
|
||||
let fontSize = Zotero.Prefs.get(fontSizePrefKey);
|
||||
// Fix empty old font prefs before a value was enforced
|
||||
if (fontSize < 6) {
|
||||
fontSize = 11;
|
||||
|
|
@ -317,7 +332,7 @@ class EditorInstance {
|
|||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_showInLibrary(ids) {
|
||||
if (!Array.isArray(ids)) {
|
||||
|
|
@ -330,6 +345,15 @@ class EditorInstance {
|
|||
}
|
||||
}
|
||||
|
||||
setToggleContextPaneButtonMode(mode) {
|
||||
this._postMessage({ action: 'setToggleContextPaneButtonMode', mode });
|
||||
}
|
||||
|
||||
focusToolbar() {
|
||||
this._iframeWindow.focus();
|
||||
this._postMessage({ action: 'focusToolbar' });
|
||||
}
|
||||
|
||||
async importImages(annotations) {
|
||||
for (let annotation of annotations) {
|
||||
if (annotation.image && !this._filesReadOnly) {
|
||||
|
|
@ -666,6 +690,21 @@ class EditorInstance {
|
|||
this._openPopup(x, y, pos, itemGroups);
|
||||
return;
|
||||
}
|
||||
case 'toggleContextPane': {
|
||||
let win = Zotero.getMainWindow();
|
||||
win.ZoteroContextPane.togglePane();
|
||||
return;
|
||||
}
|
||||
case 'focusBack': {
|
||||
let win = Zotero.getMainWindow();
|
||||
win.Zotero_Tabs.focusBack();
|
||||
return;
|
||||
}
|
||||
case 'focusForward': {
|
||||
let win = Zotero.getMainWindow();
|
||||
win.Zotero_Tabs.focusForward();
|
||||
return;
|
||||
}
|
||||
case 'return': {
|
||||
this._onReturn();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -358,6 +358,11 @@ Zotero.Prefs = new function () {
|
|||
Zotero.Prefs.set('note.fontSize', 11);
|
||||
}
|
||||
}],
|
||||
[ "note.tabFontSize", function (val) {
|
||||
if (val < 6) {
|
||||
Zotero.Prefs.set('note.tabFontSize', 11);
|
||||
}
|
||||
}],
|
||||
[ "sync.autoSync", function (val) {
|
||||
if (val) {
|
||||
Zotero.Sync.EventListeners.AutoSyncListener.register();
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ class ReaderInstance {
|
|||
else if (this.tabID) {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.Zotero_Tabs.setSecondViewState(this.tabID, state);
|
||||
win.Zotero_Tabs.setTabData(this.tabID, { secondViewState: state });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -536,17 +536,13 @@ class ReaderInstance {
|
|||
// Shift-tab from the toolbar focuses the sync button (if reader instance is opened in a tab)
|
||||
if (!this.tabID) return;
|
||||
let win = Zotero.getMainWindow();
|
||||
win.document.getElementById("zotero-tb-sync").focus();
|
||||
win.Zotero_Tabs.focusBack();
|
||||
},
|
||||
onIframeTab: () => {
|
||||
// Tab after the last tabstop will focus the contextPane (if reader instance is opened in a tab)
|
||||
if (!this.tabID) return;
|
||||
let win = Zotero.getMainWindow();
|
||||
let focused = win.ZoteroContextPane.focus();
|
||||
// If context pane wasn't focused (e.g. it's collapsed), focus the tab bar
|
||||
if (!focused) {
|
||||
win.Zotero_Tabs.moveFocus("current");
|
||||
}
|
||||
win.Zotero_Tabs.focusForward();
|
||||
},
|
||||
onSetZoom: (iframe, zoom) => {
|
||||
iframe.browsingContext.textZoom = 1;
|
||||
|
|
@ -1370,7 +1366,7 @@ class ReaderInstance {
|
|||
if (this.tabID) {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.Zotero_Tabs.setSecondViewState(this.tabID, this.getSecondViewState());
|
||||
win.Zotero_Tabs.setTabData(this.tabID, { secondViewState: this.getSecondViewState() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1425,10 +1421,9 @@ class ReaderTab extends ReaderInstance {
|
|||
this._contextPaneOpen = options.contextPaneOpen;
|
||||
this._bottomPlaceholderHeight = options.bottomPlaceholderHeight;
|
||||
this._showContextPaneToggle = true;
|
||||
this._onToggleSidebarCallback = options.onToggleSidebar;
|
||||
this._onChangeSidebarWidthCallback = options.onChangeSidebarWidth;
|
||||
this._window = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
let existingTabID = options.tabID;
|
||||
let select = !options.background;
|
||||
// If an unloaded tab for this item already exists, load the reader in it.
|
||||
// Otherwise, create a new tab
|
||||
if (existingTabID) {
|
||||
|
|
@ -1444,7 +1439,7 @@ class ReaderTab extends ReaderInstance {
|
|||
data: {
|
||||
itemID: this._item.id
|
||||
},
|
||||
select: !options.background,
|
||||
select,
|
||||
preventJumpback: options.preventJumpback
|
||||
});
|
||||
this.tabID = id;
|
||||
|
|
@ -1469,7 +1464,49 @@ class ReaderTab extends ReaderInstance {
|
|||
|
||||
this._iframe.setAttribute('tooltip', 'html-tooltip');
|
||||
|
||||
this._open({ location: options.location, secondViewState: options.secondViewState });
|
||||
this._onToggleSidebarCallback = (open) => {
|
||||
if (open) {
|
||||
this._window.ZoteroContextPane.updateLayout({ sidePaneWidth: true });
|
||||
}
|
||||
else {
|
||||
this._window.ZoteroContextPane.updateLayout({ sidePaneWidth: false });
|
||||
}
|
||||
|
||||
if (options.onToggleSidebar) {
|
||||
options.onToggleSidebar(open);
|
||||
}
|
||||
};
|
||||
|
||||
this._onChangeSidebarWidthCallback = (width) => {
|
||||
this._window.ZoteroContextPane.updateLayout({ sidePaneWidth: width });
|
||||
|
||||
if (options.onChangeSidebarWidth) {
|
||||
options.onChangeSidebarWidth(width);
|
||||
}
|
||||
};
|
||||
|
||||
this._open({ location: options.location, secondViewState: options.secondViewState }).then(() => {
|
||||
this._tabContainer.addEventListener('tab-bottom-placeholder-resize', (event) => {
|
||||
this.setBottomPlaceholderHeight(event.detail.height);
|
||||
});
|
||||
|
||||
this._tabContainer.addEventListener('tab-context-pane-toggle', (event) => {
|
||||
this.setContextPaneOpen(event.detail.open);
|
||||
});
|
||||
|
||||
this._tabContainer.addEventListener('tab-focus', () => {
|
||||
this.focus();
|
||||
});
|
||||
|
||||
this._tabContainer.addEventListener('tab-selection-change', (event) => {
|
||||
if (event.detail.selected) {
|
||||
this._updateLayout();
|
||||
}
|
||||
});
|
||||
if (select) {
|
||||
this._updateLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
|
|
@ -1552,6 +1589,12 @@ class ReaderTab extends ReaderInstance {
|
|||
editorInstance.insertAnnotations(annotations);
|
||||
}
|
||||
}
|
||||
|
||||
_updateLayout() {
|
||||
let { sidePaneState } = this._window.ZoteroContextPane.updateLayout();
|
||||
this.toggleSidebar(sidePaneState.open);
|
||||
this.setSidebarWidth(sidePaneState.width);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2099,62 +2142,14 @@ class Reader {
|
|||
_loadSidebarState() {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
let pane = win.document.getElementById('zotero-reader-sidebar-pane');
|
||||
this._sidebarOpen = pane.getAttribute('collapsed') == 'false';
|
||||
let width = pane.getAttribute('width');
|
||||
if (width) {
|
||||
this._sidebarWidth = parseInt(width);
|
||||
let state = win.ZoteroContextPane.getSidePaneState('reader');
|
||||
this._sidebarOpen = state.open;
|
||||
if (state.width) {
|
||||
this._sidebarWidth = parseInt(state.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setSidebarState() {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
let pane = win.document.getElementById('zotero-reader-sidebar-pane');
|
||||
pane.setAttribute('collapsed', this._sidebarOpen ? 'false' : 'true');
|
||||
pane.setAttribute('width', this._sidebarWidth);
|
||||
}
|
||||
}
|
||||
|
||||
getSidebarOpen() {
|
||||
return this._sidebarOpen;
|
||||
}
|
||||
|
||||
setSidebarWidth(width) {
|
||||
this._sidebarWidth = width;
|
||||
let readers = this._readers.filter(r => r instanceof ReaderTab);
|
||||
for (let reader of readers) {
|
||||
reader.setSidebarWidth(width);
|
||||
}
|
||||
this._setSidebarState();
|
||||
}
|
||||
|
||||
toggleSidebar(open) {
|
||||
this._sidebarOpen = open;
|
||||
let readers = this._readers.filter(r => r instanceof ReaderTab);
|
||||
for (let reader of readers) {
|
||||
reader.toggleSidebar(open);
|
||||
}
|
||||
this._setSidebarState();
|
||||
}
|
||||
|
||||
setContextPaneOpen(open) {
|
||||
this._contextPaneOpen = open;
|
||||
let readers = this._readers.filter(r => r instanceof ReaderTab);
|
||||
for (let reader of readers) {
|
||||
reader.setContextPaneOpen(open);
|
||||
}
|
||||
}
|
||||
|
||||
setBottomPlaceholderHeight(height) {
|
||||
this._bottomPlaceholderHeight = height;
|
||||
let readers = this._readers.filter(r => r instanceof ReaderTab);
|
||||
for (let reader of readers) {
|
||||
reader.setBottomPlaceholderHeight(height);
|
||||
}
|
||||
}
|
||||
|
||||
notify(event, type, ids, extraData) {
|
||||
if (type === 'tab') {
|
||||
if (event === 'close') {
|
||||
|
|
@ -2333,22 +2328,18 @@ class Reader {
|
|||
preventJumpback: preventJumpback,
|
||||
onToggleSidebar: (open) => {
|
||||
this._sidebarOpen = open;
|
||||
this.toggleSidebar(open);
|
||||
if (this.onToggleSidebar) {
|
||||
this.onToggleSidebar(open);
|
||||
}
|
||||
},
|
||||
onChangeSidebarWidth: (width) => {
|
||||
this._sidebarWidth = width;
|
||||
this._debounceSidebarWidthUpdate();
|
||||
if (this.onChangeSidebarWidth) {
|
||||
this.onChangeSidebarWidth(width);
|
||||
}
|
||||
}
|
||||
});
|
||||
this._readers.push(reader);
|
||||
// Change tab's type from "reader-unloaded" to "reader" after reader loaded
|
||||
win.Zotero_Tabs.markAsLoaded(tabID);
|
||||
}
|
||||
|
||||
if (!openInBackground
|
||||
|
|
|
|||
|
|
@ -262,19 +262,8 @@ var ZoteroPane = new function () {
|
|||
ArrowNext: () => null,
|
||||
ArrowPrevious: () => null,
|
||||
Tab: () => {
|
||||
if (Zotero_Tabs.selectedIndex > 0) {
|
||||
let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
// Move focus to the reader and focus the toolbar
|
||||
reader.focusFirst();
|
||||
reader.focusToolbar();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (collectionsPane.getAttribute("collapsed")) {
|
||||
return document.getElementById('zotero-tb-add');
|
||||
}
|
||||
return document.getElementById('zotero-tb-collection-add');
|
||||
Zotero_Tabs.focusContent();
|
||||
return null;
|
||||
},
|
||||
ShiftTab: () => document.getElementById('zotero-tb-sync-error')
|
||||
},
|
||||
|
|
@ -3183,6 +3172,14 @@ var ZoteroPane = new function () {
|
|||
}
|
||||
return [];
|
||||
}
|
||||
case 'note': {
|
||||
let tab = Zotero_Tabs.getTabInfo(Zotero_Tabs.selectedID);
|
||||
if (tab) {
|
||||
let item = Zotero.Items.get(tab.data.itemID);
|
||||
return asIDs ? [item.id] : [item];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
|
@ -4533,10 +4530,10 @@ var ZoteroPane = new function () {
|
|||
// TODO: _text_
|
||||
var c = this.getSelectedCollection();
|
||||
if (c) {
|
||||
this.openNoteWindow(null, c.id, parentKey);
|
||||
this.openNote(null, { col: c.id, parentKey });
|
||||
}
|
||||
else {
|
||||
this.openNoteWindow(null, null, parentKey);
|
||||
this.openNote(null, { parentKey });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -4628,34 +4625,32 @@ var ZoteroPane = new function () {
|
|||
};
|
||||
|
||||
|
||||
this.openNote = function (itemID, options = {
|
||||
col: undefined,
|
||||
parentKey: undefined,
|
||||
openInWindow: undefined
|
||||
}) {
|
||||
let {
|
||||
col,
|
||||
parentKey,
|
||||
openInWindow,
|
||||
} = options;
|
||||
if (openInWindow === undefined) {
|
||||
openInWindow = Zotero.Prefs.get('openNoteInNewWindow');
|
||||
}
|
||||
|
||||
return Zotero.Notes.open(itemID, {}, {
|
||||
openInWindow,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a note in a new window
|
||||
* @deprecated - use openNote() with openInWindow option
|
||||
*/
|
||||
this.openNoteWindow = function (itemID, col, parentKey) {
|
||||
var item = Zotero.Items.get(itemID);
|
||||
var type = Zotero.Libraries.get(item.libraryID).libraryType;
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
var name = null;
|
||||
|
||||
if (itemID) {
|
||||
let w = this.findNoteWindow(itemID);
|
||||
if (w) {
|
||||
w.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a name for this window so we can focus it later
|
||||
//
|
||||
// Collection is only used on new notes, so we don't need to
|
||||
// include it in the name
|
||||
name = 'zotero-note-' + itemID;
|
||||
}
|
||||
|
||||
var io = { itemID: itemID, collectionID: col, parentItemKey: parentKey };
|
||||
window.openDialog('chrome://zotero/content/note.xhtml', name, 'chrome,resizable,centerscreen,dialog=false', io);
|
||||
}
|
||||
|
||||
return this.openNote(itemID, { col, parentKey, openInWindow: true });
|
||||
};
|
||||
|
||||
this.findNoteWindow = function (itemID) {
|
||||
var name = 'zotero-note-' + itemID;
|
||||
|
|
@ -4976,14 +4971,15 @@ var ZoteroPane = new function () {
|
|||
};
|
||||
|
||||
|
||||
this.viewItems = async function (items, event) {
|
||||
this.viewItems = async function (items, event, options = {}) {
|
||||
let { noLocateOnMissing } = options;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i];
|
||||
if (item.isRegularItem()) {
|
||||
// Prefer local file attachments
|
||||
let attachment = await item.getBestAttachment();
|
||||
if (attachment) {
|
||||
await this.viewAttachment(attachment.id, event);
|
||||
await this.viewAttachment(attachment.id, event, noLocateOnMissing, options);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -5017,13 +5013,17 @@ var ZoteroPane = new function () {
|
|||
if (!this.collectionsView.editable) {
|
||||
continue;
|
||||
}
|
||||
ZoteroPane.openNoteWindow(item.id);
|
||||
let openInWindow = event?.shiftKey || options.forceAlternateWindowBehavior;
|
||||
ZoteroPane.openNote(item.id, { openInWindow });
|
||||
}
|
||||
else if (item.isAttachment()) {
|
||||
await this.viewAttachment(item.id, event);
|
||||
await this.viewAttachment(item.id, event, noLocateOnMissing, options);
|
||||
}
|
||||
else if (item.isAnnotation()) {
|
||||
this.viewAttachment(item.parentItemID, event, false, { location: { annotationID: item.key } });
|
||||
this.viewAttachment(item.parentItemID, event, false,
|
||||
Object.assign(
|
||||
{ location: { annotationID: item.key } }, options
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -5472,20 +5472,22 @@ var ZoteroPane = new function () {
|
|||
this.canEdit = function (row) {
|
||||
switch (Zotero_Tabs.selectedType) {
|
||||
case 'library':
|
||||
{
|
||||
// Currently selected row
|
||||
if (row === undefined) {
|
||||
row = this.collectionsView.selection.focused;
|
||||
}
|
||||
return this.collectionsView.getRow(row).editable;
|
||||
case 'reader': {
|
||||
let itemID = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID)?.itemID;
|
||||
if (!itemID) {
|
||||
throw new Error('Reader tab has no itemID');
|
||||
}
|
||||
return Zotero.Items.get(itemID).library.editable;
|
||||
}
|
||||
default:
|
||||
{
|
||||
let tabInfo = Zotero_Tabs.getTabInfo();
|
||||
let item = Zotero.Items.get(tabInfo.data?.itemID);
|
||||
if (item) {
|
||||
return item.isEditable();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -622,8 +622,7 @@
|
|||
<menuitem id="view-menuitem-font-size-reset" label="&zotero.general.reset;"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu id="note-font-size-menu"
|
||||
label="¬eFontSize.label;">
|
||||
<menu id="note-font-size-menu" data-l10n-id="menu-view-note-font-size">
|
||||
<!-- TODO: Maybe switch to Bigger/Smaller once we can update without restarting -->
|
||||
<!--<menupopup oncommand="ZoteroStandalone.onViewMenuItemClick(event)">
|
||||
<menuitem id="view-menuitem-note-font-size-bigger" label="&zotero.general.bigger;"/>
|
||||
|
|
@ -651,6 +650,28 @@
|
|||
oncommand="ZoteroStandalone.onViewMenuItemClick(event); event.stopPropagation();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu id="note-tab-font-size-menu" data-l10n-id="menu-view-note-tab-font-size">
|
||||
<menupopup oncommand="ZoteroStandalone.updateNoteTabFontSize(event)">
|
||||
<menuitem label="11" type="checkbox"/>
|
||||
<menuitem label="12" type="checkbox"/>
|
||||
<menuitem label="13" type="checkbox"/>
|
||||
<menuitem label="14" type="checkbox"/>
|
||||
<menuitem label="15" type="checkbox"/>
|
||||
<menuitem label="16" type="checkbox"/>
|
||||
<menuitem label="18" type="checkbox"/>
|
||||
<menuitem label="24" type="checkbox"/>
|
||||
<menuitem label="36" type="checkbox"/>
|
||||
<menuitem label="48" type="checkbox"/>
|
||||
<menuitem label="64" type="checkbox"/>
|
||||
<menuitem label="72" type="checkbox"/>
|
||||
<menuitem label="96" type="checkbox"/>
|
||||
<menuseparator/>
|
||||
<menuitem
|
||||
id="view-menuitem-note-tab-font-size-reset"
|
||||
label="&zotero.general.reset;"
|
||||
oncommand="ZoteroStandalone.onViewMenuItemClick(event); event.stopPropagation();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuseparator class="menu-type-library" />
|
||||
<menu id="column-picker-submenu"
|
||||
class="menu-type-library"
|
||||
|
|
@ -1070,7 +1091,7 @@
|
|||
<label>&zotero.general.loading;</label>
|
||||
</div>
|
||||
<deck id="tabs-deck">
|
||||
<vbox id="zotero-pane"
|
||||
<tab-content id="zotero-pane"
|
||||
onkeydown="ZoteroPane_Local.handleKeyDown(event, this.id)"
|
||||
onkeyup="ZoteroPane_Local.handleKeyUp(event, this.id)"
|
||||
onkeypress="ZoteroPane_Local.handleKeyPress(event)">
|
||||
|
|
@ -1346,7 +1367,7 @@
|
|||
<item-pane id="zotero-item-pane" zotero-persist="width height"/>
|
||||
</box>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</tab-content>
|
||||
</deck>
|
||||
|
||||
<splitter id="zotero-context-splitter"
|
||||
|
|
|
|||
|
|
@ -63,3 +63,4 @@ note-editor-delete-column = Delete Column
|
|||
note-editor-delete-table = Delete Table
|
||||
note-editor-link-popup-appeared = Link popup appeared. Use Shift-Tab to navigate it.
|
||||
note-editor-citation-popup-appeared = Citation popup appeared. Use Shift-Tab to navigate it.
|
||||
note-editor-toggle-context-pane = Toggle Context Pane
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ preferences-reader-ebook-font = Ebook font:
|
|||
preferences-reader-ebook-hyphenate =
|
||||
.label = Enable automatic hyphenation
|
||||
|
||||
preferences-note-title = Notes
|
||||
preferences-note-open-in-new-window =
|
||||
.label = Open notes in new windows instead of tabs
|
||||
|
||||
preferences-color-scheme = Color Scheme:
|
||||
preferences-color-scheme-auto =
|
||||
.label = Automatic
|
||||
|
|
|
|||
|
|
@ -134,6 +134,12 @@ menu-view-columns-move-left =
|
|||
menu-view-columns-move-right =
|
||||
.label = Move Column Right
|
||||
|
||||
menu-view-note-font-size =
|
||||
.label = Note Font Size
|
||||
|
||||
menu-view-note-tab-font-size =
|
||||
.label = Note Tab Font Size
|
||||
|
||||
menu-show-tabs-menu =
|
||||
.label = Show Tabs Menu
|
||||
|
||||
|
|
@ -189,12 +195,14 @@ item-menu-viewAttachment =
|
|||
[pdf] PDF
|
||||
[epub] EPUB
|
||||
[snapshot] Snapshot
|
||||
[note] Note
|
||||
*[other] Attachment
|
||||
}
|
||||
*[other] { $attachmentType ->
|
||||
[pdf] PDFs
|
||||
[epub] EPUBs
|
||||
[snapshot] Snapshots
|
||||
[note] Notes
|
||||
*[other] Attachments
|
||||
}
|
||||
} {
|
||||
|
|
@ -488,6 +496,7 @@ pane-info = Info
|
|||
pane-abstract = Abstract
|
||||
pane-attachments = Attachments
|
||||
pane-notes = Notes
|
||||
pane-note-info = Note Info
|
||||
pane-libraries-collections = Libraries and Collections
|
||||
pane-tags = Tags
|
||||
pane-related = Related
|
||||
|
|
@ -563,6 +572,8 @@ sidenav-attachments =
|
|||
.tooltiptext = { pane-attachments }
|
||||
sidenav-notes =
|
||||
.tooltiptext = { pane-notes }
|
||||
sidenav-note-info =
|
||||
.tooltiptext = { pane-note-info }
|
||||
sidenav-attachment-info =
|
||||
.tooltiptext = { pane-attachment-info }
|
||||
sidenav-attachment-preview =
|
||||
|
|
@ -643,6 +654,29 @@ attachment-info-convert-note =
|
|||
} Note
|
||||
.tooltiptext = Adding notes to attachments is no longer supported, but you can edit this note by migrating it to a separate note.
|
||||
|
||||
section-note-info =
|
||||
.label = { pane-note-info }
|
||||
|
||||
note-info-title = Title
|
||||
note-info-parent-item = Parent Item
|
||||
note-info-parent-item-button = {
|
||||
$hasParentItem ->
|
||||
[true] { $parentItemTitle }
|
||||
*[false] None
|
||||
}
|
||||
.title = {
|
||||
$hasParentItem ->
|
||||
[true] View parent item in library
|
||||
*[false] View note item in library
|
||||
}
|
||||
note-info-date-created = Created
|
||||
note-info-date-modified = Modified
|
||||
note-info-size = Size
|
||||
note-info-word-count = Word Count
|
||||
note-info-character-count = Character Count
|
||||
|
||||
item-title-empty-note = Untitled Note
|
||||
|
||||
attachment-preview-placeholder = No attachment to preview
|
||||
attachment-rename-from-parent =
|
||||
.tooltiptext = Rename File to Match Parent Item
|
||||
|
|
|
|||
3
chrome/skin/default/zotero/16/universal/note-info.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 7.25684C12.989 7.25296 12.9778 7.24989 12.9668 7.24609C14.7316 7.85452 16 9.52847 16 11.5C16 13.9853 13.9853 16 11.5 16C10.4288 16 9.44558 15.625 8.67285 15H5.29297L1 10.707V0H13V7.25684ZM11.5 8C9.567 8 8 9.567 8 11.5C8 13.433 9.567 15 11.5 15C13.433 15 15 13.433 15 11.5C15 9.567 13.433 8 11.5 8ZM2 10H6V14H7.75879C7.27996 13.2849 7 12.4253 7 11.5C7 9.01472 9.01472 7 11.5 7C11.669 7 11.8358 7.0092 12 7.02734V3H2V10ZM12 14H11V11H12V14ZM5 13.293V11H2.70703L5 13.293ZM11.5 9C11.8452 9 12.125 9.27982 12.125 9.625C12.125 9.97018 11.8452 10.25 11.5 10.25C11.1548 10.25 10.875 9.97018 10.875 9.625C10.875 9.27982 11.1548 9 11.5 9ZM2 2H12V1H2V2Z" fill="context-fill"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 781 B |
3
chrome/skin/default/zotero/itempane/16/note-info.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 7.25684C12.989 7.25296 12.9778 7.24989 12.9668 7.24609C14.7316 7.85452 16 9.52847 16 11.5C16 13.9853 13.9853 16 11.5 16C10.4288 16 9.44558 15.625 8.67285 15H5.29297L1 10.707V0H13V7.25684ZM11.5 8C9.567 8 8 9.567 8 11.5C8 13.433 9.567 15 11.5 15C13.433 15 15 13.433 15 11.5C15 9.567 13.433 8 11.5 8ZM2 10H6V14H7.75879C7.27996 13.2849 7 12.4253 7 11.5C7 9.01472 9.01472 7 11.5 7C11.669 7 11.8358 7.0092 12 7.02734V3H2V10ZM12 14H11V11H12V14ZM5 13.293V11H2.70703L5 13.293ZM11.5 9C11.8452 9 12.125 9.27982 12.125 9.625C12.125 9.97018 11.8452 10.25 11.5 10.25C11.1548 10.25 10.875 9.97018 10.875 9.625C10.875 9.27982 11.1548 9 11.5 9ZM2 2H12V1H2V2Z" fill="context-fill"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 781 B |
3
chrome/skin/default/zotero/itempane/20/note-info.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 d="M18 11.5674L18.1426 11.6689C19.2702 12.5124 20 13.8585 20 15.375C20 17.9293 17.9293 20 15.375 20C14.29 20 13.2928 19.6256 12.5039 19H8.36621L8.18262 18.8174L3.18262 13.8174L3 13.6338V1H18V11.5674ZM15.375 12C13.511 12 12 13.511 12 15.375C12 17.239 13.511 18.75 15.375 18.75C17.239 18.75 18.75 17.239 18.75 15.375C18.75 13.511 17.239 12 15.375 12ZM4.25 12.75H9.25V17.75H11.4062C10.9899 17.0557 10.75 16.2435 10.75 15.375C10.75 12.8207 12.8207 10.75 15.375 10.75C15.8538 10.75 16.3156 10.8229 16.75 10.958V5H4.25V12.75ZM16 17.75H14.75V15H16V17.75ZM8 16.8652V14H5.13477L8 16.8652ZM15.375 13C15.7892 13 16.125 13.3358 16.125 13.75C16.125 14.1642 15.7892 14.5 15.375 14.5C14.9608 14.5 14.625 14.1642 14.625 13.75C14.625 13.3358 14.9608 13 15.375 13ZM4.25 3.75H16.75V2.25H4.25V3.75Z" fill="context-fill"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 911 B |
3
chrome/skin/default/zotero/itempane/20/notes-1.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 d="M16 13.6338L15.8174 13.8174L10.8174 18.8174L10.6338 19H2V1H16V13.6338ZM18.25 14.6338L18.0674 14.8174L13.8848 19H12.1152L17 14.1152V2H18.25V14.6338ZM3.25 17.75H9.75V12.75H14.75V5H3.25V17.75ZM11 16.8652L13.8652 14H11V16.8652ZM3.25 3.75H14.75V2.25H3.25V3.75Z" fill="context-fill"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 391 B |
4
chrome/skin/default/zotero/itempane/20/notes-3.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 13.8838L17.8174 14.0674L13.0674 18.8174L12.8838 19H5V3.75H18V13.8838ZM6.25 7.25V17.75H12V13H16.75V7.25H6.25ZM13.25 16.8652L15.8652 14.25H13.25V16.8652ZM6.25 6H16.75V5H6.25V6Z" fill="context-fill"/>
|
||||
<path d="M14.4854 2.24512L2.78027 3.26953L4 17.2158L2.75488 17.3252L1.42578 2.13281L14.376 1L14.4854 2.24512Z" fill="context-fill"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 447 B |
|
|
@ -1,5 +1,4 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="note">
|
||||
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M3.625 1H3V1.625V3.75V5V18.375V19H3.625H12.375H12.6339L12.8169 18.8169L17.8169 13.8169L18 13.6339V13.375V1.625V1H17.375H3.625ZM4.25 5V17.75H11.75V13.375V12.75H12.375H16.75V5H4.25ZM16.75 3.75V2.25H4.25V3.75H16.75ZM13 14H15.8661L13 16.8661V14Z" fill="context-fill"/>
|
||||
</g>
|
||||
<path d="M16.0002 2.99988V19.2499H7.11639L6.9328 19.0673L1.9328 14.0673L1.75018 13.8837V2.99988H16.0002ZM3.00018 6.99988V12.9999H8.00018V17.9999H14.7502V6.99988H3.00018ZM3.88495 14.2499L6.75018 17.1151V14.2499H3.88495ZM3.00018 4.24988V5.74988H14.7502V4.24988H3.00018Z" fill="context-fill"/>
|
||||
<path d="M5 1.375H17.625V17" stroke="context-fill" stroke-width="1.25"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 448 B After Width: | Height: | Size: 467 B |
|
|
@ -111,6 +111,7 @@ pref("extensions.zotero.search.useLeftBound", true);
|
|||
// Notes
|
||||
pref("extensions.zotero.note.fontFamily", "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif");
|
||||
pref("extensions.zotero.note.fontSize", "14");
|
||||
pref("extensions.zotero.note.tabFontSize", "16");
|
||||
pref("extensions.zotero.note.css", "");
|
||||
pref("extensions.zotero.note.smartQuotes", true);
|
||||
|
||||
|
|
@ -202,6 +203,8 @@ pref("extensions.zotero.fileHandler.epub", "");
|
|||
pref("extensions.zotero.fileHandler.snapshot", "");
|
||||
pref("extensions.zotero.openReaderInNewWindow", false);
|
||||
|
||||
pref("extensions.zotero.openNoteInNewWindow", false);
|
||||
|
||||
// File/URL opening executable if launch() fails
|
||||
pref("extensions.zotero.fallbackLauncher.unix", "/usr/bin/xdg-open");
|
||||
pref("extensions.zotero.fallbackLauncher.windows", "");
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 1f1e9bfca8d7640f321046693616034d702c8151
|
||||
Subproject commit e3a088dfce14fa08c357bcd13c2220528d20779b
|
||||
206
resource/allfaz.mjs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
/*!
|
||||
Copyright (c) 2025 Abdullah Atta.
|
||||
Licensed under the MIT License (MIT), see
|
||||
https://github.com/thecodrr/alfaaz
|
||||
Build on commit a132fdb
|
||||
*/
|
||||
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __commonJS = (cb, mod) => function __require() {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
};
|
||||
|
||||
// dist/languages/burmese.js
|
||||
var require_burmese = __commonJS({
|
||||
"dist/languages/burmese.js"(exports) {
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BURMESE_UNICODE_RANGE = void 0;
|
||||
exports.BURMESE_UNICODE_RANGE = [[4096, 4255]];
|
||||
}
|
||||
});
|
||||
|
||||
// dist/languages/cjk.js
|
||||
var require_cjk = __commonJS({
|
||||
"dist/languages/cjk.js"(exports) {
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CJK_UNICODE_RANGES = void 0;
|
||||
exports.CJK_UNICODE_RANGES = [
|
||||
[19968, 40959],
|
||||
[13312, 19903],
|
||||
[131072, 173791],
|
||||
[173824, 177983],
|
||||
[177984, 178207],
|
||||
[178208, 183983],
|
||||
[183984, 191471],
|
||||
[196608, 201551],
|
||||
[201552, 205743],
|
||||
[63744, 64255],
|
||||
[194560, 195103],
|
||||
[12032, 12255],
|
||||
[11904, 12031],
|
||||
[12288, 12351],
|
||||
[13056, 13311],
|
||||
[65072, 65103]
|
||||
// CJK Compatibility Forms FE30-FE4F
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
// dist/languages/javanese.js
|
||||
var require_javanese = __commonJS({
|
||||
"dist/languages/javanese.js"(exports) {
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.JAVANESE_UNICODE_RANGE = void 0;
|
||||
exports.JAVANESE_UNICODE_RANGE = [[43392, 43487]];
|
||||
}
|
||||
});
|
||||
|
||||
// dist/languages/khmer.js
|
||||
var require_khmer = __commonJS({
|
||||
"dist/languages/khmer.js"(exports) {
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.KHMER_UNICODE_RANGE = void 0;
|
||||
exports.KHMER_UNICODE_RANGE = [[6016, 6143]];
|
||||
}
|
||||
});
|
||||
|
||||
// dist/languages/lao.js
|
||||
var require_lao = __commonJS({
|
||||
"dist/languages/lao.js"(exports) {
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.LAO_UNICODE_RANGE = void 0;
|
||||
exports.LAO_UNICODE_RANGE = [[3712, 3839]];
|
||||
}
|
||||
});
|
||||
|
||||
// dist/languages/thai.js
|
||||
var require_thai = __commonJS({
|
||||
"dist/languages/thai.js"(exports) {
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.THAI_UNICODE_RANGE = void 0;
|
||||
exports.THAI_UNICODE_RANGE = [[3584, 3711]];
|
||||
}
|
||||
});
|
||||
|
||||
// dist/languages/vai.js
|
||||
var require_vai = __commonJS({
|
||||
"dist/languages/vai.js"(exports) {
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.VAI_UNICODE_RANGE = void 0;
|
||||
exports.VAI_UNICODE_RANGE = [[42240, 42559]];
|
||||
}
|
||||
});
|
||||
|
||||
// dist/languages/index.js
|
||||
var require_languages = __commonJS({
|
||||
"dist/languages/index.js"(exports) {
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UNICODE_RANGES = void 0;
|
||||
var burmese_1 = require_burmese();
|
||||
var cjk_1 = require_cjk();
|
||||
var javanese_1 = require_javanese();
|
||||
var khmer_1 = require_khmer();
|
||||
var lao_1 = require_lao();
|
||||
var thai_1 = require_thai();
|
||||
var vai_1 = require_vai();
|
||||
exports.UNICODE_RANGES = [
|
||||
...thai_1.THAI_UNICODE_RANGE,
|
||||
...lao_1.LAO_UNICODE_RANGE,
|
||||
...burmese_1.BURMESE_UNICODE_RANGE,
|
||||
...khmer_1.KHMER_UNICODE_RANGE,
|
||||
...javanese_1.JAVANESE_UNICODE_RANGE,
|
||||
...vai_1.VAI_UNICODE_RANGE,
|
||||
...cjk_1.CJK_UNICODE_RANGES
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
// dist/index.js
|
||||
var require_index = __commonJS({
|
||||
"dist/index.js"(exports) {
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.countLines = exports.countWords = void 0;
|
||||
var languages_1 = require_languages();
|
||||
var CHINESE_MAX_CODE_POINT = 205743;
|
||||
var BYTE_SIZE = 8;
|
||||
var BITMAP = new Uint8Array(CHINESE_MAX_CODE_POINT / BYTE_SIZE + 1);
|
||||
function insertCharsIntoMap(...chars) {
|
||||
for (const char of chars) {
|
||||
const charCode = char.charCodeAt(0);
|
||||
const byteIndex = Math.floor(charCode / BYTE_SIZE);
|
||||
const bitIndex = charCode % BYTE_SIZE;
|
||||
BITMAP[byteIndex] = BITMAP[byteIndex] ^ 1 << bitIndex;
|
||||
}
|
||||
}
|
||||
function insertRangeIntoMap(from, to) {
|
||||
for (let i = from / BYTE_SIZE; i < Math.ceil(to / BYTE_SIZE); i++) {
|
||||
BITMAP[i] = 255;
|
||||
}
|
||||
}
|
||||
var NEWLINE = "\n";
|
||||
insertCharsIntoMap(
|
||||
" ",
|
||||
"\n",
|
||||
" ",
|
||||
"\v",
|
||||
"*",
|
||||
"/",
|
||||
"&",
|
||||
":",
|
||||
";",
|
||||
".",
|
||||
",",
|
||||
"?",
|
||||
"=",
|
||||
"\u0F0B",
|
||||
// Tibetan uses [U+0F0B TIBETAN MARK INTERSYLLABIC TSHEG] (pronounced tsek) to signal the end of a syllable.
|
||||
"\u1361",
|
||||
// Ethiopic text uses the traditional wordspace character [U+1361 ETHIOPIC WORDSPACE] to indicate word boundaries
|
||||
"\u200B"
|
||||
// ZERO-WIDTH-SPACE can also be considered a word boundary
|
||||
);
|
||||
for (const range of languages_1.UNICODE_RANGES) {
|
||||
insertRangeIntoMap(range[0], range[1]);
|
||||
}
|
||||
function countWords(str) {
|
||||
let count = 0;
|
||||
let shouldCount = false;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const charCode = str.charCodeAt(i);
|
||||
const byteIndex = charCode / BYTE_SIZE | 0;
|
||||
const bitIndex = charCode % BYTE_SIZE;
|
||||
const byteAtIndex = BITMAP[byteIndex];
|
||||
const isMatch = (byteAtIndex >> bitIndex & 1) === 1;
|
||||
if (isMatch && (shouldCount || byteAtIndex === 255)) count++;
|
||||
shouldCount = !isMatch;
|
||||
}
|
||||
if (shouldCount) count++;
|
||||
return count;
|
||||
}
|
||||
exports.countWords = countWords;
|
||||
function countLines(str) {
|
||||
let count = 0;
|
||||
for (let i = -1; (i = str.indexOf(NEWLINE, ++i)) !== -1 && i < str.length; count++) ;
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
exports.countLines = countLines;
|
||||
}
|
||||
});
|
||||
export default require_index();
|
||||
|
|
@ -83,6 +83,7 @@
|
|||
@import "elements/colorPicker";
|
||||
@import "elements/guidancePanel";
|
||||
@import "elements/infoBox";
|
||||
@import "elements/noteBox";
|
||||
@import "elements/noteEditor";
|
||||
@import "elements/notesBox";
|
||||
@import "elements/publicationsLicenseInfo";
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ $item-pane-sections: (
|
|||
"abstract": var(--accent-azure),
|
||||
"attachments": var(--accent-green),
|
||||
"notes": var(--accent-yellow),
|
||||
"note-info": var(--accent-yellow),
|
||||
"attachment-info": var(--accent-green),
|
||||
"attachment-preview": #926d70,
|
||||
"attachment-annotations": var(--tag-purple),
|
||||
|
|
@ -119,3 +120,4 @@ $width-sidenav: 37px;
|
|||
|
||||
$height-toolbar: 41px;
|
||||
$min-height-items-pane: 150px;
|
||||
$min-height-tab-context: 300px;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
}
|
||||
|
||||
.stacked-context-placeholder {
|
||||
min-height: 300px;
|
||||
min-height: $min-height-tab-context - $width-sidenav;
|
||||
}
|
||||
|
||||
&.standard .stacked-context-placeholder {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#zotero-pane {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#zotero-pane-stack > hbox {
|
||||
|
|
|
|||
41
scss/elements/_noteBox.scss
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
note-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.metadata-table {
|
||||
@include meta-table;
|
||||
|
||||
.clicky-item {
|
||||
@include clicky-item;
|
||||
@include focus-ring;
|
||||
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
min-height: 20px;
|
||||
@include comfortable {
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
.clicky-text {
|
||||
width: 100%;
|
||||
--editable-text-padding-inline: var(--editable-text-tight-padding-inline);
|
||||
--editable-text-padding-block: var(--editable-text-tight-padding-block);
|
||||
padding-inline: var(--editable-text-padding-inline);
|
||||
padding-block: var(--editable-text-padding-block);
|
||||
border: 1px solid rgba(0,0,0,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,12 @@ note-editor {
|
|||
@include elements-custom-head;
|
||||
}
|
||||
|
||||
tab-content {
|
||||
note-editor.note-tab {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
links-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
55
test/tests/noteTabTest.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
describe("Note Tab", function () {
|
||||
var win, doc, ZoteroPane, Zotero_Tabs, ZoteroContextPane;
|
||||
|
||||
before(async function () {
|
||||
win = await loadZoteroPane();
|
||||
doc = win.document;
|
||||
ZoteroPane = win.ZoteroPane;
|
||||
Zotero_Tabs = win.Zotero_Tabs;
|
||||
ZoteroContextPane = win.ZoteroContextPane;
|
||||
});
|
||||
|
||||
after(function () {
|
||||
Zotero_Tabs.closeAll();
|
||||
win.close();
|
||||
});
|
||||
|
||||
describe("Note Tab Operations", function () {
|
||||
beforeEach(function () {
|
||||
// Reset the state before each test
|
||||
Zotero_Tabs.closeAll();
|
||||
});
|
||||
|
||||
it("should open note in tab", async function () {
|
||||
let item = new Zotero.Item('note');
|
||||
item.setNote('This is a test note.');
|
||||
await item.saveTx();
|
||||
|
||||
let noteEditor = await Zotero.Notes.open(item.id);
|
||||
|
||||
assert.isNotNull(noteEditor, "Note editor should be opened");
|
||||
assert.equal(noteEditor.item.id, item.id, "Note editor should be associated with the correct item");
|
||||
|
||||
let sameNoteEditor = await Zotero.Notes.open(item.id, undefined, {
|
||||
tabID: Zotero_Tabs.selectedID,
|
||||
});
|
||||
assert.equal(noteEditor, sameNoteEditor, "Opening the same note should return the existing editor");
|
||||
|
||||
let duplicateNoteEditor = await Zotero.Notes.open(item.id, undefined, {
|
||||
allowDuplicate: true,
|
||||
});
|
||||
|
||||
assert.isNotNull(duplicateNoteEditor, "Duplicate note editor should be opened");
|
||||
assert.notEqual(noteEditor, duplicateNoteEditor, "Duplicate note editor should be a new instance");
|
||||
assert.equal(duplicateNoteEditor.item.id, item.id, "Duplicate note editor should be associated with the correct item");
|
||||
|
||||
Zotero_Tabs.closeAll();
|
||||
|
||||
await Zotero.Notes.open(item.id, undefined, {
|
||||
openInBackground: true,
|
||||
});
|
||||
|
||||
assert.equal(Zotero_Tabs.selectedType, 'library', "Tab should be opened in background");
|
||||
});
|
||||
});
|
||||
});
|
||||