Add tab context menu (#2162)
This commit is contained in:
parent
b47f5f51e7
commit
50732d1479
6 changed files with 185 additions and 27 deletions
|
@ -65,6 +65,15 @@ const TabBar = forwardRef(function (props, ref) {
|
|||
});
|
||||
|
||||
function handleTabMouseDown(event, id) {
|
||||
if (event.button === 2) {
|
||||
let { screenX, screenY } = event;
|
||||
// Popup gets immediately closed without this
|
||||
setTimeout(() => {
|
||||
props.onContextMenu(screenX, screenY, id);
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.closest('.tab-close')) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ var Zotero_Tabs = new function () {
|
|||
}];
|
||||
this._selectedID = 'zotero-pane';
|
||||
this._prevSelectedID = null;
|
||||
this._history = [];
|
||||
|
||||
this._getTab = function (id) {
|
||||
var tabIndex = this._tabs.findIndex(tab => tab.id == id);
|
||||
|
@ -78,6 +79,7 @@ var Zotero_Tabs = new function () {
|
|||
onTabSelect={this.select.bind(this)}
|
||||
onTabMove={this.move.bind(this)}
|
||||
onTabClose={this.close.bind(this)}
|
||||
onContextMenu={this._openMenu.bind(this)}
|
||||
/>,
|
||||
document.getElementById('tab-bar-container'),
|
||||
() => {
|
||||
|
@ -181,27 +183,41 @@ var Zotero_Tabs = new function () {
|
|||
};
|
||||
|
||||
/**
|
||||
* Close a tab
|
||||
* Close tabs
|
||||
*
|
||||
* @param {String} id
|
||||
* @param {String|Array<String>|undefined} ids One or more ids, or empty for the current tab
|
||||
*/
|
||||
this.close = function (id) {
|
||||
var { tab, tabIndex } = this._getTab(id || this._selectedID);
|
||||
if (tabIndex == 0) {
|
||||
this.close = function (ids) {
|
||||
if (!ids) {
|
||||
ids = [this._selectedID];
|
||||
}
|
||||
else if (!Array.isArray(ids)) {
|
||||
ids = [ids];
|
||||
}
|
||||
if (ids.includes('zotero-pane')) {
|
||||
throw new Error('Library tab cannot be closed');
|
||||
}
|
||||
if (!tab) {
|
||||
return;
|
||||
var historyEntry = [];
|
||||
var closedIDs = [];
|
||||
var tmpTabs = this._tabs.slice();
|
||||
for (var id of ids) {
|
||||
var { tab, tabIndex } = this._getTab(id);
|
||||
if (!tab) {
|
||||
continue;
|
||||
}
|
||||
if (tab.id == this._selectedID) {
|
||||
this.select(this._prevSelectedID || (this._tabs[tabIndex + 1] || this._tabs[tabIndex - 1]).id);
|
||||
}
|
||||
this._tabs.splice(tabIndex, 1);
|
||||
document.getElementById(tab.id).remove();
|
||||
if (tab.onClose) {
|
||||
tab.onClose();
|
||||
}
|
||||
historyEntry.push({ index: tmpTabs.indexOf(tab), data: tab.data });
|
||||
closedIDs.push(id);
|
||||
}
|
||||
if (tab.id == this._selectedID) {
|
||||
this.select(this._prevSelectedID || (this._tabs[tabIndex + 1] || this._tabs[tabIndex - 1]).id);
|
||||
}
|
||||
this._tabs.splice(tabIndex, 1);
|
||||
document.getElementById(tab.id).remove();
|
||||
if (tab.onClose) {
|
||||
tab.onClose();
|
||||
}
|
||||
Zotero.Notifier.trigger('close', 'tab', [tab.id], true);
|
||||
this._history.push(historyEntry);
|
||||
Zotero.Notifier.trigger('close', 'tab', [closedIDs]);
|
||||
this._update();
|
||||
};
|
||||
|
||||
|
@ -209,7 +225,27 @@ var Zotero_Tabs = new function () {
|
|||
* Close all tabs except the first one
|
||||
*/
|
||||
this.closeAll = function () {
|
||||
this._tabs.slice(1).map(tab => this.close(tab.id));
|
||||
this.close(this._tabs.slice(1).map(x => x.id));
|
||||
};
|
||||
|
||||
/**
|
||||
* Undo tabs closing
|
||||
*/
|
||||
this.undoClose = function () {
|
||||
var historyEntry = this._history.pop();
|
||||
if (historyEntry) {
|
||||
for (var tab of historyEntry) {
|
||||
if (Zotero.Items.exists(tab.data.itemID)) {
|
||||
Zotero.Reader.open(tab.data.itemID,
|
||||
null,
|
||||
{
|
||||
tabIndex: tab.index,
|
||||
openInBackground: true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -283,10 +319,99 @@ var Zotero_Tabs = new function () {
|
|||
*
|
||||
* @param {Integer} index
|
||||
*/
|
||||
this.jump = function(index) {
|
||||
this.jump = function (index) {
|
||||
this.select(this._tabs[Math.min(index, this._tabs.length - 1)].id);
|
||||
};
|
||||
|
||||
this._openMenu = function (x, y, id) {
|
||||
var { tabIndex } = this._getTab(id);
|
||||
window.Zotero_Tooltip.stop();
|
||||
let menuitem;
|
||||
let popup = document.createElement('menupopup');
|
||||
document.querySelector('popupset').appendChild(popup);
|
||||
popup.addEventListener('popuphidden', function (event) {
|
||||
if (event.target === popup) {
|
||||
popup.remove();
|
||||
}
|
||||
});
|
||||
if (id !== 'zotero-pane') {
|
||||
// Show in library
|
||||
menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('general.showInLibrary'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
var reader = Zotero.Reader.getByTabID(id);
|
||||
if (reader) {
|
||||
ZoteroPane_Local.selectItem(reader.itemID);
|
||||
this.select('zotero-pane');
|
||||
}
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Move tab
|
||||
let menu = document.createElement('menu');
|
||||
menu.setAttribute('label', Zotero.getString('tabs.move'));
|
||||
let menupopup = document.createElement('menupopup');
|
||||
menu.append(menupopup);
|
||||
popup.appendChild(menu);
|
||||
// Move to start
|
||||
menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('tabs.moveToStart'));
|
||||
menuitem.setAttribute('disabled', tabIndex == 1);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this.move(id, 1);
|
||||
});
|
||||
menupopup.appendChild(menuitem);
|
||||
// Move to end
|
||||
menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('tabs.moveToEnd'));
|
||||
menuitem.setAttribute('disabled', tabIndex == this._tabs.length - 1);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this.move(id, this._tabs.length);
|
||||
});
|
||||
menupopup.appendChild(menuitem);
|
||||
// Move to new window
|
||||
menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('tabs.moveToWindow'));
|
||||
menuitem.setAttribute('disabled', false);
|
||||
menuitem.addEventListener('command', () => {
|
||||
var reader = Zotero.Reader.getByTabID(id);
|
||||
if (reader) {
|
||||
this.close(id);
|
||||
Zotero.Reader.open(reader.itemID, null, { openInWindow: true });
|
||||
}
|
||||
});
|
||||
menupopup.appendChild(menuitem);
|
||||
// Separator
|
||||
popup.appendChild(document.createElement('menuseparator'));
|
||||
}
|
||||
// Undo close
|
||||
menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('tabs.undoClose'));
|
||||
menuitem.setAttribute('disabled', !this._history.length);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this.undoClose();
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
if (!(this._tabs.length == 2 && id != 'zotero-pane')) {
|
||||
// Close other tabs
|
||||
menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('tabs.closeOther'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
this.close(this._tabs.slice(1).filter(x => x.id != id).map(x => x.id));
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
}
|
||||
if (id != 'zotero-pane') {
|
||||
// Close
|
||||
menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('general.close'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
this.close(id);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
}
|
||||
popup.openPopupAtScreen(x, y, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update state of the tab bar.
|
||||
* Only used on Windows and Linux. On macOS, the tab bar is always shown.
|
||||
|
|
|
@ -664,7 +664,7 @@ class ReaderInstance {
|
|||
}
|
||||
|
||||
class ReaderTab extends ReaderInstance {
|
||||
constructor({ itemID, title, sidebarWidth, sidebarOpen, bottomPlaceholderHeight, background }) {
|
||||
constructor({ itemID, title, sidebarWidth, sidebarOpen, bottomPlaceholderHeight, index, background }) {
|
||||
super();
|
||||
this._itemID = itemID;
|
||||
this._sidebarWidth = sidebarWidth;
|
||||
|
@ -675,6 +675,7 @@ class ReaderTab extends ReaderInstance {
|
|||
let { id, container } = this._window.Zotero_Tabs.add({
|
||||
type: 'reader',
|
||||
title: title || '',
|
||||
index,
|
||||
data: {
|
||||
itemID
|
||||
},
|
||||
|
@ -906,12 +907,17 @@ class Reader {
|
|||
|
||||
notify(event, type, ids, extraData) {
|
||||
if (type === 'tab') {
|
||||
let reader = Zotero.Reader.getByTabID(ids[0]);
|
||||
if (reader) {
|
||||
if (event === 'close') {
|
||||
this._readers.splice(this._readers.indexOf(reader), 1);
|
||||
if (event === 'close') {
|
||||
for (let id of ids) {
|
||||
let reader = Zotero.Reader.getByTabID(id);
|
||||
if (reader) {
|
||||
this._readers.splice(this._readers.indexOf(reader), 1);
|
||||
}
|
||||
}
|
||||
else if (event === 'select') {
|
||||
}
|
||||
else if (event === 'select') {
|
||||
let reader = Zotero.Reader.getByTabID(ids[0]);
|
||||
if (reader) {
|
||||
this.triggerAnnotationsImportCheck(reader._itemID);
|
||||
}
|
||||
}
|
||||
|
@ -976,7 +982,7 @@ class Reader {
|
|||
await this.open(item.id, location, options);
|
||||
}
|
||||
|
||||
async open(itemID, location, { title, openInBackground, openInWindow } = {}) {
|
||||
async open(itemID, location, { title, tabIndex, openInBackground, openInWindow } = {}) {
|
||||
this._loadSidebarOpenState();
|
||||
this.triggerAnnotationsImportCheck(itemID);
|
||||
let reader;
|
||||
|
@ -1017,6 +1023,7 @@ class Reader {
|
|||
reader = new ReaderTab({
|
||||
itemID,
|
||||
title,
|
||||
index: tabIndex,
|
||||
background: openInBackground,
|
||||
sidebarWidth: this._sidebarWidth,
|
||||
sidebarOpen: this._sidebarOpen,
|
||||
|
|
|
@ -533,6 +533,15 @@ var ZoteroPane = new function()
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Undo closed tabs
|
||||
if ((Zotero.isMac && event.metaKey || !Zotero.isMac && event.ctrlKey)
|
||||
&& event.shiftKey && !event.altKey && event.key.toLowerCase() == 't') {
|
||||
Zotero_Tabs.undoClose();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Tab navigation: Ctrl-PageUp / PageDown
|
||||
// TODO: Select across tabs without selecting with Ctrl-Shift, as in Firefox?
|
||||
|
|
|
@ -50,6 +50,7 @@ general.export = Export
|
|||
general.moreInformation = More Information
|
||||
general.seeForMoreInformation = See %S for more information.
|
||||
general.open = Open %S
|
||||
general.close = Close
|
||||
general.enable = Enable
|
||||
general.disable = Disable
|
||||
general.remove = Remove
|
||||
|
@ -67,6 +68,7 @@ general.fix = Fix…
|
|||
general.tryAgain = Try Again
|
||||
general.tryLater = Try Later
|
||||
general.showDirectory = Show Directory
|
||||
general.showInLibrary = Show in Library
|
||||
general.continue = Continue
|
||||
general.copy = Copy
|
||||
general.copyToClipboard = Copy to Clipboard
|
||||
|
@ -1323,7 +1325,6 @@ noteEditor.set = Set
|
|||
noteEditor.edit = Edit
|
||||
noteEditor.addCitation = Add Citation
|
||||
noteEditor.findAndReplace = Find and Replace
|
||||
noteEditor.showInLibrary = Show in Library
|
||||
noteEditor.editInWindow = Edit in a Separate Window
|
||||
noteEditor.applyAnnotationColors = Apply Annotation Colors
|
||||
noteEditor.removeAnnotationColors = Remove Annotation Colors
|
||||
|
@ -1354,3 +1355,10 @@ spellCheck.addRemoveDictionaries = Add/Remove Dictionaries…
|
|||
spellCheck.dictionaryManager.title = Add/Remove Dictionaries
|
||||
spellCheck.dictionaryManager.updateAvailable = %S (update available)
|
||||
spellCheck.dictionaryManager.error.unableToInstall = Unable to install “%S”:
|
||||
|
||||
tabs.move = Move Tab
|
||||
tabs.moveToStart = Move to Start
|
||||
tabs.moveToEnd = Move to End
|
||||
tabs.moveToWindow = Move to New Window
|
||||
tabs.undoClose = Reopen Closed Tab
|
||||
tabs.closeOther = Close Other Tabs
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 9749a4b2be4baac5b16f28e70a7e7997b000ef9e
|
||||
Subproject commit 4ba41a41689448aecc21a72aec0559d39ca4509e
|
Loading…
Reference in a new issue