Restore tabs state (#2148)
This commit is contained in:
parent
f9035c8fda
commit
85b142ccb2
7 changed files with 243 additions and 68 deletions
|
@ -58,7 +58,7 @@ var ZoteroContextPane = new function () {
|
|||
this.update = _update;
|
||||
this.getActiveEditor = _getActiveEditor;
|
||||
|
||||
this.onLoad = function () {
|
||||
this.init = function () {
|
||||
if (!Zotero) {
|
||||
return;
|
||||
}
|
||||
|
@ -83,8 +83,33 @@ var ZoteroContextPane = new function () {
|
|||
else {
|
||||
_tabToolbar.style.right = 0;
|
||||
}
|
||||
|
||||
// vbox
|
||||
var vbox = document.createElement('vbox');
|
||||
vbox.setAttribute('flex', '1');
|
||||
|
||||
_init();
|
||||
_contextPaneInner.append(vbox);
|
||||
|
||||
// Toolbar extension
|
||||
var toolbarExtension = document.createElement('box');
|
||||
toolbarExtension.style.height = '32px';
|
||||
toolbarExtension.id = 'zotero-context-toolbar-extension';
|
||||
|
||||
_panesDeck = document.createElement('deck');
|
||||
_panesDeck.setAttribute('flex', 1);
|
||||
_panesDeck.setAttribute('selectedIndex', 0);
|
||||
|
||||
vbox.append(toolbarExtension, _panesDeck);
|
||||
|
||||
// Item pane deck
|
||||
_itemPaneDeck = document.createElement('deck');
|
||||
// Notes pane deck
|
||||
_notesPaneDeck = document.createElement('deck');
|
||||
_notesPaneDeck.style.backgroundColor = 'white';
|
||||
_notesPaneDeck.setAttribute('flex', 1);
|
||||
_notesPaneDeck.className = 'notes-pane-deck';
|
||||
|
||||
_panesDeck.append(_itemPaneDeck, _notesPaneDeck);
|
||||
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'tab'], 'contextPane');
|
||||
window.addEventListener('resize', _update);
|
||||
|
@ -94,7 +119,7 @@ var ZoteroContextPane = new function () {
|
|||
Zotero.Reader.onChangeSidebarOpen = _updatePaneWidth;
|
||||
};
|
||||
|
||||
this.onUnload = function () {
|
||||
this.destroy = function () {
|
||||
_itemToggle.removeEventListener('click', _toggleItemButton);
|
||||
_notesToggle.removeEventListener('click', _toggleNotesButton);
|
||||
window.removeEventListener('resize', _update);
|
||||
|
@ -106,7 +131,7 @@ var ZoteroContextPane = new function () {
|
|||
_notesContexts = [];
|
||||
};
|
||||
|
||||
this.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData) {
|
||||
this.notify = function (action, type, ids, extraData) {
|
||||
if (type == 'item') {
|
||||
// Update, remove or re-create item panes
|
||||
for (let context of _itemContexts.slice()) {
|
||||
|
@ -174,14 +199,14 @@ var ZoteroContextPane = new function () {
|
|||
|| !document.activeElement.closest('.context-node iframe[anonid="editor-view"]'))) {
|
||||
reader.focus();
|
||||
}
|
||||
|
||||
var attachment = await Zotero.Items.getAsync(reader.itemID);
|
||||
if (attachment) {
|
||||
_selectNotesContext(attachment.libraryID);
|
||||
var notesContext = _getNotesContext(attachment.libraryID);
|
||||
notesContext.updateFromCache();
|
||||
}
|
||||
})();
|
||||
|
||||
var attachment = Zotero.Items.get(reader.itemID);
|
||||
if (attachment) {
|
||||
_selectNotesContext(attachment.libraryID);
|
||||
var notesContext = _getNotesContext(attachment.libraryID);
|
||||
notesContext.updateFromCache();
|
||||
}
|
||||
}
|
||||
|
||||
_contextPaneSplitter.setAttribute('hidden', false);
|
||||
|
@ -193,7 +218,7 @@ var ZoteroContextPane = new function () {
|
|||
_update();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function _toggleItemButton() {
|
||||
_togglePane(0);
|
||||
|
@ -346,35 +371,6 @@ var ZoteroContextPane = new function () {
|
|||
splitter.setAttribute('state', hide ? 'collapsed' : 'open');
|
||||
_update();
|
||||
}
|
||||
|
||||
function _init() {
|
||||
// vbox
|
||||
var vbox = document.createElement('vbox');
|
||||
vbox.setAttribute('flex', '1');
|
||||
|
||||
_contextPaneInner.append(vbox);
|
||||
|
||||
// Toolbar extension
|
||||
var toolbarExtension = document.createElement('box');
|
||||
toolbarExtension.style.height = '32px';
|
||||
toolbarExtension.id = 'zotero-context-toolbar-extension';
|
||||
|
||||
_panesDeck = document.createElement('deck');
|
||||
_panesDeck.setAttribute('flex', 1);
|
||||
_panesDeck.setAttribute('selectedIndex', 0);
|
||||
|
||||
vbox.append(toolbarExtension, _panesDeck);
|
||||
|
||||
// Item pane deck
|
||||
_itemPaneDeck = document.createElement('deck');
|
||||
// Notes pane deck
|
||||
_notesPaneDeck = document.createElement('deck');
|
||||
_notesPaneDeck.style.backgroundColor = 'white';
|
||||
_notesPaneDeck.setAttribute('flex', 1);
|
||||
_notesPaneDeck.className = 'notes-pane-deck';
|
||||
|
||||
_panesDeck.append(_itemPaneDeck, _notesPaneDeck);
|
||||
}
|
||||
|
||||
function _getCurrentAttachment() {
|
||||
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
|
@ -737,7 +733,16 @@ var ZoteroContextPane = new function () {
|
|||
}
|
||||
}
|
||||
|
||||
function _addItemContext(tabID, itemID) {
|
||||
async function _addItemContext(tabID, itemID) {
|
||||
var container = document.createElement('vbox');
|
||||
container.id = tabID + '-context';
|
||||
container.className = 'zotero-item-pane-content';
|
||||
_itemPaneDeck.appendChild(container);
|
||||
|
||||
var { libraryID } = Zotero.Items.getLibraryAndKeyFromID(itemID);
|
||||
var library = Zotero.Libraries.get(libraryID);
|
||||
await library.waitForDataLoad('item');
|
||||
|
||||
var item = Zotero.Items.get(itemID);
|
||||
if (!item) {
|
||||
return;
|
||||
|
@ -745,11 +750,6 @@ var ZoteroContextPane = new function () {
|
|||
var libraryID = item.libraryID;
|
||||
var readOnly = _isLibraryReadOnly(libraryID);
|
||||
var parentID = item.parentID;
|
||||
|
||||
var container = document.createElement('vbox');
|
||||
container.id = tabID + '-context';
|
||||
container.className = 'zotero-item-pane-content';
|
||||
_itemPaneDeck.appendChild(container);
|
||||
|
||||
var context = {
|
||||
tabID,
|
||||
|
@ -786,6 +786,3 @@ var ZoteroContextPane = new function () {
|
|||
itemBox.item = parentItem;
|
||||
}
|
||||
};
|
||||
|
||||
addEventListener('load', function (e) { ZoteroContextPane.onLoad(e); }, false);
|
||||
addEventListener('unload', function (e) { ZoteroContextPane.onUnload(e); }, false);
|
||||
|
|
|
@ -85,17 +85,53 @@ var Zotero_Tabs = new function () {
|
|||
);
|
||||
};
|
||||
|
||||
this.getState = function () {
|
||||
return this._tabs.map((tab) => {
|
||||
var o = {
|
||||
type: tab.type,
|
||||
title: tab.title,
|
||||
};
|
||||
if (tab.data) {
|
||||
o.data = tab.data;
|
||||
}
|
||||
if (tab.id == this._selectedID) {
|
||||
o.selected = true;
|
||||
}
|
||||
return o;
|
||||
});
|
||||
};
|
||||
|
||||
this.restoreState = function(tabs) {
|
||||
for (let tab of tabs) {
|
||||
if (tab.type === 'library') {
|
||||
this.rename('zotero-pane', tab.title);
|
||||
}
|
||||
else if (tab.type === 'reader') {
|
||||
if (Zotero.Items.exists(tab.data.itemID)) {
|
||||
Zotero.Reader.open(tab.data.itemID,
|
||||
null,
|
||||
{
|
||||
title: tab.title,
|
||||
openInBackground: !tab.selected
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a new tab
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} title
|
||||
* @param {String} data - Extra data about the tab to pass to notifier and session
|
||||
* @param {Integer} index
|
||||
* @param {Boolean} select
|
||||
* @param {Function} onClose
|
||||
* @return {{ id: string, container: XULElement}} id - tab id, container - a new tab container created in the deck
|
||||
*/
|
||||
this.add = function ({ type, title, index, select, onClose, notifierData }) {
|
||||
this.add = function ({ type, data, title, index, select, onClose }) {
|
||||
if (typeof type != 'string') {
|
||||
throw new Error(`'type' should be a string (was ${typeof type})`);
|
||||
}
|
||||
|
@ -112,11 +148,11 @@ var Zotero_Tabs = new function () {
|
|||
var container = document.createElement('vbox');
|
||||
container.id = id;
|
||||
this.deck.appendChild(container);
|
||||
var tab = { id, type, title, onClose };
|
||||
var tab = { id, type, title, data, onClose };
|
||||
index = index || this._tabs.length;
|
||||
this._tabs.splice(index, 0, tab);
|
||||
this._update();
|
||||
Zotero.Notifier.trigger('add', 'tab', [id], { [id]: notifierData }, true);
|
||||
Zotero.Notifier.trigger('add', 'tab', [id], { [id]: data }, true);
|
||||
if (select) {
|
||||
this.select(id);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ class ReaderInstance {
|
|||
this._window = null;
|
||||
this._iframeWindow = null;
|
||||
this._itemID = null;
|
||||
this._title = '';
|
||||
this._isReaderInitialized = false;
|
||||
this._showItemPaneToggle = false;
|
||||
this._initPromise = new Promise((resolve, reject) => {
|
||||
|
@ -50,7 +51,11 @@ class ReaderInstance {
|
|||
}
|
||||
|
||||
async open({ itemID, state, location }) {
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
let { libraryID } = Zotero.Items.getLibraryAndKeyFromID(itemID);
|
||||
let library = Zotero.Libraries.get(libraryID);
|
||||
await library.waitForDataLoad('item');
|
||||
|
||||
let item = Zotero.Items.get(itemID);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
@ -101,6 +106,7 @@ class ReaderInstance {
|
|||
title = parentItem.getDisplayTitle();
|
||||
}
|
||||
}
|
||||
this._title = title;
|
||||
this._setTitleValue(title);
|
||||
}
|
||||
|
||||
|
@ -644,7 +650,7 @@ class ReaderInstance {
|
|||
}
|
||||
|
||||
class ReaderTab extends ReaderInstance {
|
||||
constructor({ itemID, sidebarWidth, sidebarOpen, bottomPlaceholderHeight }) {
|
||||
constructor({ itemID, title, sidebarWidth, sidebarOpen, bottomPlaceholderHeight, background }) {
|
||||
super();
|
||||
this._itemID = itemID;
|
||||
this._sidebarWidth = sidebarWidth;
|
||||
|
@ -654,11 +660,11 @@ class ReaderTab extends ReaderInstance {
|
|||
this._window = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
let { id, container } = this._window.Zotero_Tabs.add({
|
||||
type: 'reader',
|
||||
title: '',
|
||||
select: true,
|
||||
notifierData: {
|
||||
title: title || '',
|
||||
data: {
|
||||
itemID
|
||||
}
|
||||
},
|
||||
select: !background
|
||||
});
|
||||
this.tabID = id;
|
||||
this._tabContainer = container;
|
||||
|
@ -832,6 +838,13 @@ class Reader {
|
|||
return this._sidebarWidth;
|
||||
}
|
||||
|
||||
async init() {
|
||||
await Zotero.uiReadyPromise;
|
||||
Zotero.Session.state.windows
|
||||
.filter(x => x.type == 'reader' && Zotero.Items.exists(x.itemID))
|
||||
.forEach(x => this.open(x.itemID, null, { title: x.title, openInWindow: true }));
|
||||
}
|
||||
|
||||
_loadSidebarOpenState() {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
|
@ -888,6 +901,10 @@ class Reader {
|
|||
this.triggerAnnotationsImportCheck(reader._itemID);
|
||||
}
|
||||
}
|
||||
|
||||
if (event === 'add' || event === 'close') {
|
||||
Zotero.Session.debounceSave();
|
||||
}
|
||||
}
|
||||
// Listen for parent item, PDF attachment and its annotations updates
|
||||
else if (type === 'item') {
|
||||
|
@ -932,19 +949,25 @@ class Reader {
|
|||
getByTabID(tabID) {
|
||||
return this._readers.find(r => (r instanceof ReaderTab) && r.tabID === tabID);
|
||||
}
|
||||
|
||||
async openURI(itemURI, location, openWindow) {
|
||||
let item = await Zotero.URI.getURIItem(itemURI);
|
||||
if (!item) return;
|
||||
await this.open(item.id, location, openWindow);
|
||||
|
||||
getWindowStates() {
|
||||
return this._readers
|
||||
.filter(r => r instanceof ReaderWindow)
|
||||
.map(r => ({ type: 'reader', itemID: r._itemID, title: r._title }));
|
||||
}
|
||||
|
||||
async open(itemID, location, openWindow) {
|
||||
async openURI(itemURI, location, options) {
|
||||
let item = await Zotero.URI.getURIItem(itemURI);
|
||||
if (!item) return;
|
||||
await this.open(item.id, location, options);
|
||||
}
|
||||
|
||||
async open(itemID, location, { title, openInBackground, openInWindow } = {}) {
|
||||
this._loadSidebarOpenState();
|
||||
this.triggerAnnotationsImportCheck(itemID);
|
||||
let reader;
|
||||
|
||||
if (openWindow) {
|
||||
if (openInWindow) {
|
||||
reader = this._readers.find(r => r._itemID === itemID && (r instanceof ReaderWindow));
|
||||
}
|
||||
else {
|
||||
|
@ -960,7 +983,7 @@ class Reader {
|
|||
reader.navigate(location);
|
||||
}
|
||||
}
|
||||
else if (openWindow) {
|
||||
else if (openInWindow) {
|
||||
reader = new ReaderWindow({
|
||||
sidebarWidth: this._sidebarWidth,
|
||||
sidebarOpen: this._sidebarOpen,
|
||||
|
@ -970,13 +993,17 @@ class Reader {
|
|||
if (!(await reader.open({ itemID, location }))) {
|
||||
return;
|
||||
}
|
||||
Zotero.Session.debounceSave();
|
||||
reader._window.addEventListener('unload', () => {
|
||||
this._readers.splice(this._readers.indexOf(reader), 1);
|
||||
Zotero.Session.debounceSave();
|
||||
});
|
||||
}
|
||||
else {
|
||||
reader = new ReaderTab({
|
||||
itemID,
|
||||
title,
|
||||
background: openInBackground,
|
||||
sidebarWidth: this._sidebarWidth,
|
||||
sidebarOpen: this._sidebarOpen,
|
||||
bottomPlaceholderHeight: this._bottomPlaceholderHeight
|
||||
|
|
80
chrome/content/zotero/xpcom/session.js
Normal file
80
chrome/content/zotero/xpcom/session.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2021 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
http://digitalscholar.org/
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Session = new function () {
|
||||
const SESSION_FILE_NAME = 'session.json';
|
||||
const DEBOUNCED_SAVING_DELAY = 5 * 60 * 1000; // 5 min
|
||||
|
||||
let _state = {
|
||||
windows: []
|
||||
};
|
||||
|
||||
Zotero.defineProperty(this, 'state', {
|
||||
get: () => {
|
||||
return _state;
|
||||
}
|
||||
});
|
||||
|
||||
this.init = async function () {
|
||||
try {
|
||||
let sessionFile = OS.Path.join(Zotero.Profile.dir, SESSION_FILE_NAME);
|
||||
let state = await Zotero.File.getContentsAsync(sessionFile);
|
||||
_state = JSON.parse(state);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
};
|
||||
|
||||
this.setLastClosedZoteroPaneState = function (state) {
|
||||
_state.windows = [state];
|
||||
};
|
||||
|
||||
this.debounceSave = Zotero.Utilities.debounce(() => {
|
||||
this.save();
|
||||
}, DEBOUNCED_SAVING_DELAY);
|
||||
|
||||
this.save = async function () {
|
||||
try {
|
||||
// Saving is triggered in `zotero.js` when a quit event is received,
|
||||
// though if it was triggered by closing a window, ZoteroPane might
|
||||
// be already destroyed at the time
|
||||
let panes = Zotero.getZoteroPanes().map(x => x.getState());
|
||||
let readers = Zotero.Reader.getWindowStates();
|
||||
if (panes.length) {
|
||||
_state.windows = [...readers, ...panes];
|
||||
}
|
||||
else if (readers.length) {
|
||||
_state.windows = _state.windows.filter(x => x.type != 'reader');
|
||||
_state.windows = [..._state.windows, ...readers];
|
||||
}
|
||||
let sessionFile = OS.Path.join(Zotero.Profile.dir, SESSION_FILE_NAME);
|
||||
await Zotero.File.putContentsAsync(sessionFile, JSON.stringify(_state));
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
};
|
||||
};
|
|
@ -380,6 +380,12 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
// Make sure data directory isn't in Dropbox, etc.
|
||||
yield Zotero.DataDirectory.checkForUnsafeLocation(dataDir);
|
||||
|
||||
Services.obs.addObserver({
|
||||
observe: function () {
|
||||
Zotero.Session.save();
|
||||
}
|
||||
}, "quit-application-granted", false);
|
||||
|
||||
// Register shutdown handler to call Zotero.shutdown()
|
||||
var _shutdownObserver = {observe:function() { Zotero.shutdown().done() }};
|
||||
Services.obs.addObserver(_shutdownObserver, "quit-application", false);
|
||||
|
@ -690,6 +696,8 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
yield Zotero.CharacterSets.init();
|
||||
yield Zotero.RelationPredicates.init();
|
||||
|
||||
yield Zotero.Session.init();
|
||||
|
||||
Zotero.locked = false;
|
||||
|
||||
// Initialize various services
|
||||
|
@ -727,6 +735,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
yield Zotero.Retractions.init();
|
||||
yield Zotero.NoteBackups.init();
|
||||
yield Zotero.Dictionaries.init();
|
||||
Zotero.Reader.init();
|
||||
|
||||
// Migrate fields from Extra that can be moved to item fields after a schema update
|
||||
yield Zotero.Schema.migrateExtraFields();
|
||||
|
|
|
@ -153,6 +153,7 @@ var ZoteroPane = new function()
|
|||
Zotero.hiDPISuffix = Zotero.hiDPI ? "@2x" : "";
|
||||
|
||||
Zotero_Tabs.init();
|
||||
ZoteroContextPane.init();
|
||||
await ZoteroPane.initCollectionsTree();
|
||||
await ZoteroPane.initItemsTree();
|
||||
|
||||
|
@ -248,6 +249,17 @@ var ZoteroPane = new function()
|
|||
var importer = new Zotero_Import_Mendeley();
|
||||
importer.deleteNonPrimaryFiles();
|
||||
}, 10000)
|
||||
|
||||
// Restore pane state
|
||||
try {
|
||||
let state = Zotero.Session.state.windows.find(x => x.type == 'pane');
|
||||
if (state) {
|
||||
Zotero_Tabs.restoreState(state.tabs);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -355,6 +367,12 @@ var ZoteroPane = new function()
|
|||
|
||||
observerService.removeObserver(_reloadObserver, "zotero-reloaded");
|
||||
|
||||
ZoteroContextPane.destroy();
|
||||
|
||||
if (!Zotero.getZoteroPanes().length) {
|
||||
Zotero.Session.setLastClosedZoteroPaneState(this.getState());
|
||||
}
|
||||
|
||||
Zotero_Tabs.closeAll();
|
||||
}
|
||||
|
||||
|
@ -3801,7 +3819,7 @@ var ZoteroPane = new function()
|
|||
await Zotero.Reader.open(
|
||||
itemID,
|
||||
extraData && extraData.location,
|
||||
originalEvent && originalEvent.shiftKey
|
||||
{ openInWindow: originalEvent && originalEvent.shiftKey }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -4855,6 +4873,13 @@ var ZoteroPane = new function()
|
|||
}
|
||||
|
||||
|
||||
this.getState = function () {
|
||||
return {
|
||||
type: 'pane',
|
||||
tabs: Zotero_Tabs.getState()
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Unserializes zotero-persist elements from preferences
|
||||
*/
|
||||
|
|
|
@ -120,6 +120,7 @@ const xpcomFilesLocal = [
|
|||
'router',
|
||||
'schema',
|
||||
'server',
|
||||
'session',
|
||||
'streamer',
|
||||
'style',
|
||||
'sync',
|
||||
|
|
Loading…
Reference in a new issue