XUL -> JS tree megacommit

- Just a single huge commit. This has been developed over too long a
time, required many tiny changes across too many files and has seen too
many iterations to be separated into separate commits.
The original branch with all the messy commits will be kept around for
posterity
bb220ad0f2...adomasven:feature/react-item-tree
- Replaces XUL <tree> element across the whole zotero client codebase
with a custom supermegafast virtualized-table inspired by
react-virtualized yet mimicking old XUL treeview API. The
virtualized-table sits on top on a raw-to-the-metal,
interpreted-at-runtime JS based windowing solution inspired by
react-window. React-based solutions could not be used because they were
slow and Zotero UI needs to be responsive and be able to
display thousands of rows in a treeview without any slowdowns.
- Attempts were made at making this screen-reader friendly, but yet to
be tested with something like JAWS
- RTL-friendly
- Styling and behaviour across all platforms was copied as closely as
possible to the original XUL tree
- Instead of row-based scroll snapping this has smooth-scrolling. If
you're using arrow keys to browse through the tree then it effectively
snap-scrolls. Current CSS snap scroll attributes do not seem to work in
the way we would require even on up-to-date browsers, yet alone the ESR
version of FX that Zotero is on. JS solutions are either terrible for
performance or produce inexcusable jitter.
- When dragging-and-dropping items the initial drag freezes the UI for
a fairly jarring amount of time. Does not seem to be fixable due to
the synchronous code that needs to be run in the dragstart handler.
Used to be possible to run that code async with the XUL tree.
- Item tree column picker no longer has a dedicated button. Just
right-click the columns. The column preferences (width, order, etc) are
no longer handled by XUL, which required a custom serialization and
storage solution that throws warnings in the developer console due to
the amount of data being stored. Might cause temporary freezing on HDDs
upon column resize/reorder/visibility toggling.
- Context menu handling code basically unchanged, but any UI changes
that plugins may have wanted to do (including adding new columns) will
have to be redone by them. No serious thought has gone into how plugin
developers would achieve that yet.
- Opens up the possibility for awesome alternative ways to render the
tree items, including things like multiple-row view for the item tree,
which has been requested for a long while especially by users switching
from other referencing software
This commit is contained in:
Adomas Venčkauskas 2020-06-03 10:29:46 +03:00 committed by Dan Stillman
parent f274323478
commit cbbff600a6
102 changed files with 11008 additions and 9886 deletions

View file

@ -333,37 +333,6 @@ input {
margin-inline-start: -1px;
}
#zotero-items-tree
{
-moz-appearance: none;
border: none;
margin: 0;
padding: 0;
}
#zotero-items-tree treechildren::-moz-tree-cell,
#zotero-items-tree treechildren::-moz-tree-column {
border-right: 1px solid #d7dad7;
}
treechildren::-moz-tree-twisty {
-moz-appearance: none;
width: 16px;
height: 16px;
list-style-image: url("chrome://zotero/skin/mac/twisty.svg");
-moz-padding-start: 5px;
-moz-padding-end: 6px;
}
treechildren::-moz-tree-twisty(open) {
-moz-appearance: none;
width: 16px;
height: 16px;
list-style-image: url("chrome://zotero/skin/mac/twisty-open.svg");
-moz-padding-start: 4px;
-moz-padding-end: 7px;
}
/* How to get active twisty?
treechildren::-moz-tree-twisty(active) {
-moz-appearance: none;

View file

@ -1,22 +1,3 @@
/*
Override selected, unfocused tree row highlight color, which is too similar to the alternating
row color by default
*/
#zotero-collections-tree treechildren::-moz-tree-row(selected),
#zotero-items-tree treechildren::-moz-tree-row(selected) {
background-color: #D4D4D4;
}
#zotero-collections-tree treechildren::-moz-tree-row(selected, focus),
#zotero-items-tree treechildren::-moz-tree-row(selected, focus) {
background-color: Highlight;
}
#zotero-collections-tree treechildren::-moz-tree-row {
height: 1.3em;
}
@media (min-resolution: 1.25dppx) {
#zotero-pane-stack .toolbarbutton-icon {
width: 16px;

View file

@ -111,34 +111,10 @@
display: none;
}
#zotero-collections-tree, #zotero-items-tree, .zotero-view-item {
-moz-appearance: none;
border-style: solid;
border-color: #818790;
margin: 0;
padding: 0;
}
treechildren::-moz-tree-twisty {
padding: 0 4px;
}
/* Undo tree row spacing change in Fx25 on Windows */
#zotero-collections-tree treechildren::-moz-tree-row,
#zotero-items-tree treechildren::-moz-tree-row,
#zotero-prefs treechildren::-moz-tree-row {
height: 1.6em;
}
tree {
border-width: 0;
}
/* Restore row highlighting on drag over, though I'm not sure how we're losing it to begin with. */
#zotero-collections-tree treechildren::-moz-tree-row(dropOn) {
background-color: Highlight;
}
#zotero-tag-selector groupbox {
-moz-appearance: none;
padding: 0;

View file

@ -25,16 +25,18 @@
Components.utils.import("resource://gre/modules/Services.jsm");
import ItemTree from 'containers/itemTree';
import { getDefaultColumnsByDataKeys } from 'containers/itemTreeColumns';
var ZoteroAdvancedSearch = new function() {
this.onLoad = onLoad;
this.search = search;
this.clear = clear;
this.onDblClick = onDblClick;
this.onUnload = onUnload;
this.onItemActivate = onItemActivate;
this.itemsView = false;
var _searchBox;
var _libraryID;
@ -52,8 +54,21 @@ var ZoteroAdvancedSearch = new function() {
.then(function () {
_searchBox.search = io.dataIn.search;
});
var elem = document.getElementById('zotero-items-tree');
ItemTree.init(elem, {
id: "advanced-search",
dragAndDrop: true,
onActivate: this.onItemActivate.bind(this),
columns: getDefaultColumnsByDataKeys(['title', 'firstCreator']),
}).then((itemsView) => {
this.itemsView = itemsView;
});
}
this.onUnload = function () {
this.itemsView.unregister();
}
function search() {
_searchBox.updateSearch();
@ -74,30 +89,23 @@ var ZoteroAdvancedSearch = new function() {
isCollection: function () { return false; },
isSearch: function () { return true; },
isPublications: () => false,
isDuplicates: () => false,
isFeed: () => false,
isShare: function () { return false; },
isTrash: function () { return false; }
}
};
if (this.itemsView) {
this.itemsView.unregister();
}
this.itemsView = new Zotero.ItemTreeView(collectionTreeRow, false);
document.getElementById('zotero-items-tree').view = this.itemsView;
this.itemsView.changeCollectionTreeRow(collectionTreeRow);
}
function clear() {
if (this.itemsView) {
this.itemsView.unregister();
}
document.getElementById('zotero-items-tree').view = null;
this.itemsView.changeCollectionTreeRow(null);
var s = new Zotero.Search();
// Don't clear the selected library
s.libraryID = _searchBox.search.libraryID;
s.addCondition('title', 'contains', '')
s.addCondition('title', 'contains', '');
_searchBox.search = s;
_searchBox.active = false;
}
@ -149,42 +157,18 @@ var ZoteroAdvancedSearch = new function() {
}
// Adapted from: http://www.xulplanet.com/references/elemref/ref_tree.html#cmnote-9
function onDblClick(event, tree)
function onItemActivate(event, items)
{
if (event && tree && event.type == "dblclick")
{
var row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
// obj.value == cell/text/image
// TODO: handle collection double-click
if (obj.value && this.itemsView && this.itemsView.selection.currentIndex > -1)
{
var item = this.itemsView.getSelectedItems()[0];
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var lastWin = wm.getMostRecentWindow("navigator:browser");
if (!lastWin) {
window.open();
var newWindow = wm.getMostRecentWindow("navigator:browser");
var b = newWindow.getBrowser();
return;
}
lastWin.ZoteroPane.selectItem(item.getID(), false, true);
lastWin.focus();
}
}
}
function onUnload() {
// Unregister search from Notifier
if (this.itemsView) {
this.itemsView.unregister();
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var lastWin = wm.getMostRecentWindow("navigator:browser");
if (!lastWin) {
return;
}
lastWin.ZoteroPane.selectItems(items.map(item => item.id), false);
lastWin.focus();
}
}

View file

@ -3,6 +3,7 @@
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!DOCTYPE window [
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
@ -17,8 +18,9 @@
orient="vertical"
persist="screenX screenY width height"
onload="ZoteroAdvancedSearch.onLoad()"
onunload="ZoteroAdvancedSearch.onUnload()"
onunload="ZoteroAdvancedSearch.onUnload();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
windowtype="zotero:search">
<script src="include.js"/>
@ -34,24 +36,9 @@
<button id="zotero-search-save" label="&zotero.search.saveSearch;" oncommand="ZoteroAdvancedSearch.save()"/>
</hbox>
</vbox>
<tree id="zotero-items-tree" flex="1" hidecolumnpicker="true" seltype="multiple"
ondblclick="ZoteroAdvancedSearch.onDblClick(event, this)"
ondragstart="if (event.target.localName == 'treechildren') { ZoteroAdvancedSearch.itemsView.onDragStart(event); }">
<treecols>
<treecol
id="zotero-items-column-title" primary="true"
label="&zotero.items.title_column;"
flex="4" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-firstCreator"
label="&zotero.items.creator_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
</treecols>
<treechildren alternatingbackground="true"/>
</tree>
<hbox class="virtualized-table-container" flex="1">
<html:div id="zotero-items-tree"/>
</hbox>
</vbox>
<keyset>

View file

@ -190,11 +190,13 @@
<method name="add">
<body><![CDATA[
return Zotero.spawn(function* () {
var io = {dataIn: null, dataOut: null};
var io = {dataIn: null, dataOut: null, deferred: Zotero.Promise.defer()};
window.openDialog('chrome://zotero/content/selectItemsDialog.xul', '',
'chrome,dialog=no,modal,centerscreen,resizable=yes', io);
'chrome,dialog=no,centerscreen,resizable=yes', io);
yield io.deferred.promise;
if (!io.dataOut || !io.dataOut.length) {
return;
}

View file

@ -0,0 +1,121 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2019 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
'use strict';
const React = require('react');
const { PureComponent } = React;
const { createDragHandler } = require('./utils');
const { func, string, number, node } = require('prop-types');
const cx = require('classnames');
const DRAG = { START: 1, ACTIVE: 2, NONE: 3 };
/**
* Creates a synthetic draggable element which does not use the standard
* dragstart, dragover, dragend events. Useful for custom interactions
* like element resize or column dragging
*/
class Draggable extends PureComponent {
componentWillUnmount() {
this.drag.stop();
}
handleMouseDown = (event) => {
if (this.dragstate > DRAG.NONE) this.drag.stop();
if (event.button !== 0) return;
if (this.props.onDragStart) {
if (this.props.onDragStart(event) === false) return;
}
this.dragstart = Date.now();
this.dragstate = DRAG.START;
this.drag.start();
const { pageX, pageY, clientX } = event;
if (this.props.delay > 0) {
this.delay = setTimeout(() => this.handleDrag({ pageX, pageY, clientX }),
this.props.delay);
}
}
handleDrag = (event) => {
if (this.props.delay && (Date.now() - this.dragstart) <= this.props.delay) return;
this.dragstate = DRAG.ACTIVE;
this.clear();
this.props.onDrag(event, this.dragstate);
}
handleDragStop = (event, hasBeenCancelled) => {
try {
switch (this.dragstate) {
case DRAG.START:
this.props.onDragStop(event, true);
break;
case DRAG.ACTIVE:
this.props.onDragStop(event, hasBeenCancelled);
break;
}
}
finally {
this.clear();
this.dragstate = DRAG.NONE;
}
}
drag = createDragHandler({
handleDrag: this.handleDrag,
handleDragStop: this.handleDragStop
})
clear() {
if (this.delay) clearTimeout(this.delay);
this.delay = null;
}
render() {
return React.cloneElement(React.Children.only(this.props.children), {
className: cx('draggable', this.props.className),
onMouseDown: this.handleMouseDown,
});
}
static propTypes = {
children: node,
className: string,
delay: number,
onDrag: func.isRequired,
onDragStart: func.isRequired,
onDragStop: func.isRequired
}
static defaultProps = {
delay: 0,
}
}
module.exports = Draggable;

View file

@ -1,42 +1,60 @@
'use strict';
const React = require('react')
const { PureComponent } = React
const { element, string } = require('prop-types')
const cx = require('classnames')
const React = require('react');
const { renderToStaticMarkup } = require('react-dom-server');
const { PureComponent } = React;
const { element, string, object } = require('prop-types');
const Icon = ({ children, className, name }) => (
<span className={cx('icon', `icon-${name}`, className)}>
{children}
</span>
)
const Icon = (props) => {
props = Object.assign({}, props);
props.className = `icon icon-${props.name} ${props.className || ""}`;
delete props.name;
// Pass the props forward
return <span {...props}></span>;
};
Icon.propTypes = {
children: element.isRequired,
children: element,
className: string,
name: string.isRequired
name: string.isRequired,
style: object
}
module.exports = { Icon }
function i(name, svgOrSrc, hasDPI=true) {
function i(name, svgOrSrc, hasHiDPI=true) {
const icon = class extends PureComponent {
render() {
const { className } = this.props
let props = Object.assign({}, this.props);
props.name = name.toLowerCase();
if (typeof svgOrSrc == 'string') {
let finalSrc = svgOrSrc;
if (hasDPI && window.devicePixelRatio >= 1.25) {
// N.B. In Electron we can use css-image-set
// Also N.B. this modifies svgOrSrc and hasHiDPI for all future invocations
// of this function
if (hasHiDPI && window.devicePixelRatio >= 1.25) {
let parts = svgOrSrc.split('.');
parts[parts.length-2] = parts[parts.length-2] + '@2x';
finalSrc = parts.join('.')
parts[parts.length - 2] = parts[parts.length - 2] + '@2x';
finalSrc = parts.join('.');
hasHiDPI = false;
}
return <Icon className={className} name={name.toLowerCase()}><img src={finalSrc}/></Icon>
if (!("style" in props)) props.style = {};
props.style.backgroundImage = `url(${finalSrc})`;
props.className = props.className || "";
props.className += " icon-bg";
// We use css background-image.
// This is a performance optimization for fast-scrolling trees.
// If we use img elements they are slow to render
// and produce pop-in when fast-scrolling.
return (
<Icon {...props} />
);
}
return (
<Icon className={className} name={name.toLowerCase()}>{svgOrImg}</Icon>
<Icon {...props}>{svgOrSrc}</Icon>
)
}
}
@ -53,7 +71,133 @@ function i(name, svgOrSrc, hasDPI=true) {
/* eslint-disable max-len */
i('TagSelectorMenu', "chrome://zotero/skin/tag-selector-menu.png")
i('DownChevron', "chrome://zotero/skin/searchbar-dropmarker.png")
i('TagSelectorMenu', "chrome://zotero/skin/tag-selector-menu.png");
i('SortMarker', "chrome://zotero/skin/tag-selector-menu.png");
i('DownChevron', "chrome://zotero/skin/searchbar-dropmarker.png");
i('Xmark', "chrome://zotero/skin/xmark.png")
i('Twisty', (
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13.4c-.5 0-.9-.2-1.2-.6L.4 5.2C0 4.7-.1 4.3.2 3.7S1 3 1.6 3h12.8c.6 0 1.2.1 1.4.7.3.6.2 1.1-.2 1.6l-6.4 7.6c-.3.4-.7.5-1.2.5z"/>
</svg>
));
i('Cross', "chrome://zotero/skin/cross.png");
i('Tick', "chrome://zotero/skin/tick.png");
i('ArrowRefresh', "chrome://zotero/skin/arrow_refresh.png");
i('RTFScanAccept', "chrome://zotero/skin/rtfscan-accept.png");
i('RTFScanLink', "chrome://zotero/skin/rtfscan-link.png");
i('Attach', "chrome://zotero/skin/attach.png");
i('AttachSmall', "chrome://zotero/skin/attach-small.png");
i('BulletBlue', "chrome://zotero/skin/bullet_blue.png");
i('BulletBlueEmpty', "chrome://zotero/skin/bullet_blue_empty.png");
// TreeItems
i('TreeitemArtwork', 'chrome://zotero/skin/treeitem-artwork.png');
i('TreeitemAttachmentLink', 'chrome://zotero/skin/treeitem-attachment-link.png');
i('TreeitemAttachmentPdf', 'chrome://zotero/skin/treeitem-attachment-pdf.png');
i('TreeitemAttachmentPdfLink', 'chrome://zotero/skin/treeitem-attachment-pdf-link.png');
i('TreeitemAttachmentSnapshot', 'chrome://zotero/skin/treeitem-attachment-snapshot.png');
i('TreeitemAttachmentWebLink', 'chrome://zotero/skin/treeitem-attachment-web-link.png');
i('TreeitemAudioRecording', 'chrome://zotero/skin/treeitem-audioRecording.png');
i('TreeitemBill', 'chrome://zotero/skin/treeitem-bill.png');
i('TreeitemBlogPost', 'chrome://zotero/skin/treeitem-blogPost.png');
i('TreeitemBook', 'chrome://zotero/skin/treeitem-book.png');
i('TreeitemBookSection', 'chrome://zotero/skin/treeitem-bookSection.png');
i('TreeitemCase', 'chrome://zotero/skin/treeitem-case.png');
i('TreeitemComputerProgram', 'chrome://zotero/skin/treeitem-computerProgram.png');
i('TreeitemConferencePaper', 'chrome://zotero/skin/treeitem-conferencePaper.png');
i('TreeitemDictionaryEntry', 'chrome://zotero/skin/treeitem-dictionaryEntry.png');
i('TreeitemEmail', 'chrome://zotero/skin/treeitem-email.png');
i('TreeitemEncyclopediaArticle', 'chrome://zotero/skin/treeitem-encyclopediaArticle.png');
i('TreeitemFilm', 'chrome://zotero/skin/treeitem-film.png');
i('TreeitemForumPost', 'chrome://zotero/skin/treeitem-forumPost.png');
i('TreeitemHearing', 'chrome://zotero/skin/treeitem-hearing.png');
i('TreeitemInstantMessage', 'chrome://zotero/skin/treeitem-instantMessage.png');
i('TreeitemInterview', 'chrome://zotero/skin/treeitem-interview.png');
i('TreeitemJournalArticle', 'chrome://zotero/skin/treeitem-journalArticle.png');
i('TreeitemLetter', 'chrome://zotero/skin/treeitem-letter.png');
i('TreeitemMagazineArticle', 'chrome://zotero/skin/treeitem-magazineArticle.png');
i('TreeitemManuscript', 'chrome://zotero/skin/treeitem-manuscript.png');
i('TreeitemMap', 'chrome://zotero/skin/treeitem-map.png', false);
i('TreeitemNewspaperArticle', 'chrome://zotero/skin/treeitem-newspaperArticle.png');
i('TreeitemNote', 'chrome://zotero/skin/treeitem-note.png');
i('TreeitemNoteSmall', 'chrome://zotero/skin/treeitem-note-small.png');
i('TreeitemPatent', 'chrome://zotero/skin/treeitem-patent.png');
i('Treeitem', 'chrome://zotero/skin/treeitem.png');
i('TreeitemPodcast', 'chrome://zotero/skin/treeitem-podcast.png', false);
i('TreeitemPresentation', 'chrome://zotero/skin/treeitem-presentation.png');
i('TreeitemRadioBroadcast', 'chrome://zotero/skin/treeitem-radioBroadcast.png', false);
i('TreeitemReport', 'chrome://zotero/skin/treeitem-report.png');
i('TreeitemStatute', 'chrome://zotero/skin/treeitem-statute.png');
i('TreeitemThesis', 'chrome://zotero/skin/treeitem-thesis.png');
i('TreeitemTvBroadcast', 'chrome://zotero/skin/treeitem-tvBroadcast.png', false);
i('TreeitemVideoRecording', 'chrome://zotero/skin/treeitem-videoRecording.png', false);
i('TreeitemWebpageGray', 'chrome://zotero/skin/treeitem-webpage-gray.png');
i('TreeitemWebpage', 'chrome://zotero/skin/treeitem-webpage.png', false);
// Treesource
i('TreesourceBucket', 'chrome://zotero/skin/treesource-bucket.png', false);
i('TreesourceCollection', 'chrome://zotero/skin/treesource-collection.png');
i('TreesourceCommons', 'chrome://zotero/skin/treesource-commons.png', false);
i('TreesourceDuplicates', 'chrome://zotero/skin/treesource-duplicates.png');
i('TreesourceFeedError', 'chrome://zotero/skin/treesource-feed-error.png');
i('TreesourceFeedLibrary', 'chrome://zotero/skin/treesource-feedLibrary.png');
i('TreesourceFeed', 'chrome://zotero/skin/treesource-feed.png');
i('TreesourceFeedUpdating', 'chrome://zotero/skin/treesource-feed-updating.png', false);
i('TreesourceGroups', 'chrome://zotero/skin/treesource-groups.png');
i('TreesourceLibrary', 'chrome://zotero/skin/treesource-library.png');
i('TreesourceSearch', 'chrome://zotero/skin/treesource-search.png');
i('TreesourceShare', 'chrome://zotero/skin/treesource-share.png', false);
i('TreesourceTrashFull', 'chrome://zotero/skin/treesource-trash-full.png');
i('TreesourceTrash', 'chrome://zotero/skin/treesource-trash.png');
i('TreesourceUnfiled', 'chrome://zotero/skin/treesource-unfiled.png');
if (Zotero.isMac) {
i('TreesourceCollection', 'chrome://zotero-platform/content/treesource-collection.png', true);
i('TreesourceSearch', 'chrome://zotero-platform/content/treesource-search.png', true);
i('Twisty', (
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<polyline points="3 4 12 4 7.5 12"/>
</svg>
));
}
if (Zotero.isWin) {
i('Twisty', (
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1792 1792">
<path d="M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z"/>
</svg>
));
}
let domElementCache = {};
/**
* Returns a DOM element for the icon class
*
* To be used in itemTree where rendering is done without react
* for performance reasons
* @param {String} icon
* @returns {Element}
*/
module.exports.getDomElement = function (icon) {
if (domElementCache[icon]) return domElementCache[icon].cloneNode(true);
if (!module.exports[icon]) {
Zotero.debug(`Attempting to get non-existant icon ${icon}`);
return "";
}
let div = document.createElementNS("http://www.w3.org/1999/xhtml", 'div');
div.innerHTML = renderToStaticMarkup(React.createElement(module.exports[icon]));
domElementCache[icon] = div.firstChild;
return domElementCache[icon].cloneNode(true);
}

View file

@ -0,0 +1,288 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2019 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
*/
const requiredOptions = ['getItemCount', 'itemHeight', 'renderItem', 'targetElement'];
/**
* A windowed list for performant display of an essentially infinite number of items
* Inspired by https://github.com/bvaughn/react-window
*
* The main principle here is to display a div with a height set to itemHeight * getItemCount()
* and only render rows visible in the scrollbox area, unloading them and rendering new ones
* as needed.
*
* This was created after the measured performance of react-window was not satisfactory
* for a 100% fluid experience, especially once rows with multiple cells that needed
* responsive resizing were introduced
*
* The class requires careful handholding to achieve good performance. Read method documentation!
*/
module.exports = class {
/**
* @param options (required):
* - getItemCount {Function} a function that returns the number of items currently on display
* - renderItem {Function} a function that returns a DOM element for an individual row to display
* - itemHeight {Integer}
* - targetElement {DOMElement} a container DOM element for the js-window
*/
constructor(options) {
for (let option of requiredOptions) {
if (!options.hasOwnProperty(option)) {
throw new Error('Attempted to initialize js-window without a required option: ' + option);
}
}
this.scrollDirection = 0;
this.scrollOffset = 0;
this.overscanCount = 6;
this._lastItemCount = null;
Object.assign(this, options);
this._renderedRows = new Map();
}
/**
* Call once to add the js-window DOM element to the container
*/
initialize() {
const { targetElement } = this;
this.innerElem = document.createElementNS("http://www.w3.org/1999/xhtml", 'div');
this.innerElem.className = "js-window";
targetElement.appendChild(this.innerElem);
targetElement.addEventListener('scroll', this._handleScroll);
this.update();
}
/**
* Call to remove the js-window from the container
*/
destroy() {
if (this.innerElem) {
this.targetElement.removeEventListener('scroll', this._handleScroll);
this.targetElement.removeChild(this.innerElem);
}
}
/**
* Rerender an individual item. A no-op if the item is not in view
* @param index {Integer}
*/
rerenderItem(index) {
if (!this._renderedRows.has(index)) return;
let oldElem = this._renderedRows.get(index);
let elem = this.renderItem(index, oldElem);
elem.style.top = this._getItemPosition(index) + "px";
elem.style.position = "absolute";
if (elem == oldElem) return;
this.innerElem.replaceChild(elem, this._renderedRows.get(index));
this._renderedRows.set(index, elem);
}
/**
* Rerender items within the scrollbox. Call sparingly
*/
invalidate() {
// Removes any items out of view and adds the ones not in view
this.render();
// Rerender the rest
for (let index of Array.from(this._renderedRows.keys())) {
this.rerenderItem(index);
}
}
/**
* Render all items within the scrollbox and remove those no longer visible
*/
render() {
const {
renderItem,
innerElem,
} = this;
const [startIndex, stopIndex] = this._getRangeToRender();
if (stopIndex - startIndex > 0) {
for (let index = startIndex; index < stopIndex; index++) {
if (this._renderedRows.has(index)) continue;
let elem = renderItem(index);
elem.style.top = this._getItemPosition(index) + "px";
elem.style.position = "absolute";
innerElem.appendChild(elem);
this._renderedRows.set(index, elem);
}
}
for (let [index, elem] of this._renderedRows.entries()) {
if (index < startIndex || index >= stopIndex) {
elem.remove();
this._renderedRows.delete(index);
}
}
}
/**
* Use to update constructor params
* @param options (see constructor())
*/
update(options = {}) {
Object.assign(this, options);
const { itemHeight, targetElement, innerElem } = this;
const itemCount = this._getItemCount();
innerElem.style.position = 'relative';
innerElem.style.height = `${itemHeight * itemCount}px`;
this.scrollDirection = 0;
this.scrollOffset = targetElement.scrollTop;
}
/**
* Scroll the top of the scrollbox to a specified location
* @param scrollOffset {Integer} offset for the top of the tree
*/
scrollTo(scrollOffset) {
scrollOffset = Math.max(0, scrollOffset);
this.scrollOffset = scrollOffset;
this.targetElement.scrollTop = scrollOffset;
this.render();
}
/**
* Scroll the scrollbox to a specified item. No-op if already in view
* @param index
*/
scrollToRow(index) {
const { itemHeight, scrollOffset } = this;
const itemCount = this._getItemCount();
const height = this.getWindowHeight();
index = Math.max(0, Math.min(index, itemCount - 1));
let startPosition = this._getItemPosition(index);
let endPosition = startPosition + itemHeight;
if (startPosition < scrollOffset) {
this.scrollTo(startPosition);
}
else if (endPosition > scrollOffset + height) {
this.scrollTo(Math.min(endPosition - height, (itemCount * itemHeight) - height));
}
}
getFirstVisibleRow() {
return Math.ceil(this.scrollOffset / this.itemHeight);
}
getLastVisibleRow() {
const height = this.getWindowHeight();
return Math.max(1, Math.floor((this.scrollOffset + height + 1) / this.itemHeight)) - 1;
}
getWindowHeight() {
return this.targetElement.getBoundingClientRect().height;
}
getIndexByMouseEventPosition = (yOffset) => {
return Math.min(this._getItemCount()-1, Math.floor((yOffset - this.innerElem.getBoundingClientRect().top) / this.itemHeight));
}
getElementByIndex = index => this._renderedRows.get(index);
/**
* @returns {Integer} - the number of fully visible items in the scrollbox
*/
getPageLength() {
const height = this.getWindowHeight();
return Math.ceil(height / this.itemHeight);
}
_getItemPosition = (index) => {
return (this.itemHeight * index);
};
_getRangeToRender() {
const { itemHeight, overscanCount, scrollDirection, scrollOffset } = this;
const itemCount = this._getItemCount();
const height = this.getWindowHeight();
if (itemCount === 0) {
return [0, 0, 0, 0];
}
const startIndex = Math.floor(scrollOffset / itemHeight);
const stopIndex = Math.ceil((scrollOffset + height) / itemHeight + 1);
// Overscan by one item in each direction so that tab/focus works.
// If there isn't at least one extra item, tab loops back around.
const overscanBackward =
!scrollDirection || scrollDirection === -1
? Math.max(1, overscanCount)
: 1;
const overscanForward =
!scrollDirection || scrollDirection === 1
? Math.max(1, overscanCount)
: 1;
return [
Math.max(0, startIndex - overscanBackward),
Math.max(0, Math.min(itemCount, stopIndex + overscanForward)),
startIndex,
stopIndex,
];
}
_getItemCount() {
const itemCount = this.getItemCount();
if (this._lastItemCount != itemCount) {
this._lastItemCount = itemCount;
this.update();
this.invalidate();
}
return this._lastItemCount;
}
_handleScroll = (event) => {
const { scrollOffset: prevScrollOffset } = this;
const { clientHeight, scrollHeight, scrollTop } = event.currentTarget;
if (prevScrollOffset === scrollTop) {
// Scroll position may have been updated by cDM/cDU,
// In which case we don't need to trigger another render,
// And we don't want to update anything.
return;
}
// Prevent macOS elastic scrolling from causing visual shaking when scrolling past bounds.
const scrollOffset = Math.max(
0,
Math.min(scrollTop, scrollHeight - clientHeight)
);
this.scrollDirection = prevScrollOffset < scrollOffset ? 1 : -1;
this.scrollOffset = scrollOffset;
this._resetScrollDirection();
this.render();
};
_resetScrollDirection = Zotero.Utilities.debounce(() => this.scrollDirection = 0, 150);
};

View file

@ -1,5 +1,80 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2019 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
'use strict';
const noop = () => {};
function getDragTargetOrient(event, target) {
const elem = target || event.target;
const {y, height} = elem.getBoundingClientRect();
const ratio = (event.clientY - y) / height;
// first 1/6 of the elem ([x-----])
if (ratio <= 0.166) return -1;
// 2/6 to 5/6 of the elem ([-xxxx-])
else if (ratio <= 0.833) return 0;
// last 5/6 of the elem ([-----x])
else return 1;
}
function createDragHandler({ handleDrag, handleDragStop }) {
function onKeyDown(event) {
if (event.key == 'Escape') {
event.stopPropagation();
onDragStop(event);
}
}
function onDragStart() {
document.addEventListener('mousemove', handleDrag);
document.addEventListener('mouseup', onDragStop, { capture: true });
document.addEventListener('mouseleave', onDragStop);
window.addEventListener('blur', onDragStop);
// Register on first child because global bindings are bound
// on document and we need to stop the propagation in
// case we handle it here!
document.children[0].addEventListener('keydown', onKeyDown);
}
function onDragStop(event) {
document.removeEventListener('mousemove', handleDrag);
document.removeEventListener('mouseup', onDragStop, { capture: true });
document.removeEventListener('mouseleave', onDragStop);
window.removeEventListener('blur', onDragStop);
document.children[0].removeEventListener('keydown', onKeyDown);
handleDragStop(event, !event || event.type !== 'mouseup');
}
return {
start: onDragStart,
stop: onDragStop
}
}
export {
noop
noop, getDragTargetOrient, createDragHandler
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,289 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
*/
(function() {
const React = require('react');
const Icons = require('components/icons');
const COLUMNS = [
{
dataKey: "title",
primary: true,
defaultIn: new Set(["default", "feed"]),
label: "zotero.items.title_column",
ignoreInColumnPicker: "true",
flex: 4,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "firstCreator",
defaultIn: new Set(["default", "feed"]),
label: "zotero.items.creator_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "itemType",
label: "zotero.items.type_column",
width: "40",
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "date",
defaultIn: new Set(["feed"]),
label: "zotero.items.date_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "year",
disabledIn: "feed",
label: "zotero.items.year_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "publisher",
label: "zotero.items.publisher_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "publicationTitle",
disabledIn: "feed",
label: "zotero.items.publication_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "journalAbbreviation",
disabledIn: "feed",
submenu: true,
label: "zotero.items.journalAbbr_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "language",
submenu: true,
label: "zotero.items.language_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "accessDate",
disabledIn: "feed",
submenu: true,
label: "zotero.items.accessDate_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "libraryCatalog",
disabledIn: "feed",
submenu: true,
label: "zotero.items.libraryCatalog_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "callNumber",
disabledIn: "feed",
submenu: true,
label: "zotero.items.callNumber_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "rights",
submenu: true,
label: "zotero.items.rights_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "dateAdded",
disabledIn: "feed",
label: "zotero.items.dateAdded_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "dateModified",
disabledIn: "feed",
label: "zotero.items.dateModified_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "archive",
disabledIn: "feed",
submenu: true,
label: "zotero.items.archive_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "archiveLocation",
disabledIn: "feed",
submenu: true,
label: "zotero.items.archiveLocation_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "place",
disabledIn: "feed",
submenu: true,
label: "zotero.items.place_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "volume",
disabledIn: "feed",
submenu: true,
label: "zotero.items.volume_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "edition",
disabledIn: "feed",
submenu: true,
label: "zotero.items.edition_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "pages",
disabledIn: "feed",
submenu: true,
label: "zotero.items.pages_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "issue",
disabledIn: "feed",
submenu: true,
label: "zotero.items.issue_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "series",
disabledIn: "feed",
submenu: true,
label: "zotero.items.series_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "seriesTitle",
disabledIn: "feed",
submenu: true,
label: "zotero.items.seriesTitle_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "court",
disabledIn: "feed",
submenu: true,
label: "zotero.items.court_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "medium",
disabledIn: "feed",
submenu: true,
label: "zotero.items.medium_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "genre",
disabledIn: "feed",
submenu: true,
label: "zotero.items.genre_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "system",
disabledIn: "feed",
submenu: true,
label: "zotero.items.system_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "extra",
disabledIn: "feed",
label: "zotero.items.extra_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "hasAttachment",
defaultIn: new Set(["default"]),
disabledIn: "feed",
label: "zotero.tabs.attachments.label",
iconLabel: <Icons.IconAttachSmall />,
fixedWidth: true,
width: "14",
zoteroPersist: new Set(["hidden", "sortDirection"])
},
{
dataKey: "numNotes",
disabledIn: "feed",
label: "zotero.tabs.notes.label",
iconLabel: <Icons.IconTreeitemNoteSmall />,
width: "14",
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
}
];
let DATA_KEY_TO_COLUMN = {};
for (const column of COLUMNS) {
DATA_KEY_TO_COLUMN[column.dataKey] = column;
}
function getDefaultColumnByDataKey(dataKey) {
return Object.assign({}, DATA_KEY_TO_COLUMN[dataKey], {hidden: false});
}
function getDefaultColumnsByDataKeys(dataKeys) {
return COLUMNS.filter(column => dataKeys.includes(column.dataKey)).map(column => Object.assign({}, column, {hidden: false}));
}
module.exports = {
COLUMNS,
getDefaultColumnByDataKey,
getDefaultColumnsByDataKeys,
};
})();

View file

@ -0,0 +1,253 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
*/
const { TreeSelectionStub } = require('components/virtualized-table');
const React = require('react');
/**
* Common methods for Zotero.ItemTree and Zotero.CollectionTree
* @type {Zotero.LibraryTree}
*/
var LibraryTree = class LibraryTree extends React.Component {
constructor(props) {
super(props);
this._rows = [];
this._rowMap = {};
this.domEl = props.domEl;
this._ownerDocument = props.domEl.ownerDocument;
this.onSelect = this._createEventBinding('select');
this.onRefresh = this._createEventBinding('refresh');
}
get window() {
return this._ownerDocument.defaultView;
}
get selection() {
return this.tree ? this.tree.selection : TreeSelectionStub;
}
get rowCount() {
return this._rows.length;
}
waitForSelect() {
return this._waitForEvent('select');
}
componentDidCatch(error, info) {
// Async operations might attempt to update the react components
// after window close in tests, which will cause unnecessary crashing
// so we set an unintialized flag that we check in select functions
// like #notify
if (this._uninitialized) return;
Zotero.debug("ItemTree: React threw an error");
Zotero.logError(error);
Zotero.debug(info);
if (this.type == 'item') Zotero.Prefs.clear('lastViewedFolder');
Zotero.crash();
}
getParentIndex = (index) => {
var thisLevel = this.getLevel(index);
if (thisLevel == 0) return -1;
for (var i = index - 1; i >= 0; i--) {
if (this.getLevel(i) < thisLevel) {
return i;
}
}
return -1;
}
getLevel(index) {
return this._rows[index].level;
}
/**
* Return a reference to the tree row at a given row
*
* @return {TreeRow}
*/
getRow(index) {
return this._rows[index];
}
/**
* Return the index of the row with a given ID (e.g., "C123" for collection 123)
*
* @param {String} - Row id
* @return {Integer|false}
*/
getRowIndexByID(id) {
if (!(id in this._rowMap)) {
Zotero.debug(`${this.name}: Trying to access a row with invalid ID ${id}`)
return false;
}
return this._rowMap[id];
}
/**
* Add a tree row to the main array, update the row count, tell the treebox that the row
* count changed, and update the row map
*
* @param {TreeRow} treeRow
* @param {Number} [beforeRow] - Row index to insert new row before
*/
_addRow(treeRow, beforeRow, skipRowMapRefresh) {
this._rows.splice(beforeRow, 0, treeRow);
if (!skipRowMapRefresh) {
// Increment all rows in map at or above insertion point
for (let i in this._rowMap) {
if (this._rowMap[i] >= beforeRow) {
this._rowMap[i]++;
}
}
// Add new row to map
this._rowMap[treeRow.id] = beforeRow;
}
}
_removeRows(rows) {
rows = Zotero.Utilities.arrayUnique(rows);
rows.sort((a, b) => a - b);
for (let i = rows.length - 1; i >= 0; i--) {
this._removeRow(rows[i], true);
}
this._refreshRowMap();
}
/**
* Remove a row from the main array and parent row children arrays,
* delete the row from the map, and optionally update all rows above it in the map
*/
_removeRow(index, skipMapUpdate) {
var id = this.getRow(index).id;
let level = this.getLevel(index);
let moveSelect = index - 1;
if (index <= this.selection.pivot) {
while (moveSelect >= this._rows.length) {
moveSelect--;
}
this.selection.select(moveSelect);
}
this._rows.splice(index, 1);
if (index != 0
&& this.getLevel(index - 1) < level
&& (!this._rows[index] || this.getLevel(index) != level)) {
this._rows[index - 1].isOpen = false;
}
delete this._rowMap[id];
if (!skipMapUpdate) {
for (let i in this._rowMap) {
if (this._rowMap[i] > index) {
this._rowMap[i]--;
}
}
}
}
_refreshRowMap() {
var rowMap = {};
for (var i = 0; i < this.rowCount; i++) {
let row = this.getRow(i);
let id = row.id;
if (rowMap[id] !== undefined) {
Zotero.debug(`WARNING: _refreshRowMap(): ${this.type} row ${rowMap[id]} already found for item ${id} at ${i}`, 2);
Zotero.debug(new Error().stack, 2);
}
rowMap[id] = i;
}
this._rowMap = rowMap;
}
_onSelectionChange = () => {
if (!this._uninitialized) {
this.props.onSelectionChange && this.props.onSelectionChange(this.selection);
}
}
_onSelectionChangeDebounced = Zotero.Utilities.debounce(this._onSelectionChange, 100)
handleTwistyMouseUp = (event, index) => {
this.toggleOpenState(index);
event.stopPropagation();
this.tree.focus();
}
// The caller has to ensure the tree is redrawn
ensureRowIsVisible(index) {
this.tree && this.tree.scrollToRow(index);
}
_updateHeight = () => {
this.forceUpdate(() => {
if (this.tree) {
this.tree.rerender();
}
});
}
updateHeight = Zotero.Utilities.debounce(this._updateHeight, 200);
updateFontSize() {
this.tree.updateFontSize();
}
setDropEffect(event, effect) {
// On Windows (in Fx26), Firefox uses 'move' for unmodified drags
// and 'copy'/'link' for drags with system-default modifier keys
// as long as the actions are allowed by the initial effectAllowed set
// in onDragStart, regardless of the effectAllowed or dropEffect set
// in onDragOver. It doesn't seem to be possible to use 'copy' for
// the default and 'move' for modified, as we need to in the collections
// tree. To prevent inaccurate cursor feedback, we set effectAllowed to
// 'copy' in onDragStart, which locks the cursor at 'copy'. ('none' still
// changes the cursor, but 'move'/'link' do not.) It'd be better to use
// the unadorned 'move', but we use 'copy' instead because with 'move' text
// can't be dragged to some external programs (e.g., Chrome, Notepad++),
// which seems worse than always showing 'copy' feedback.
//
// However, since effectAllowed is enforced, leaving it at 'copy'
// would prevent our modified 'move' in the collections tree from working,
// so we also have to set effectAllowed here (called from onDragOver) to
// the same action as the dropEffect. This allows the dropEffect setting
// (which we use in the tree's canDrop() and drop() to determine the desired
// action) to be changed, even if the cursor doesn't reflect the new setting.
if (Zotero.isWin || Zotero.isLinux) {
event.dataTransfer.effectAllowed = effect;
}
event.dataTransfer.dropEffect = effect;
}
};
Zotero.Utilities.Internal.makeClassEventDispatcher(LibraryTree);
module.exports = LibraryTree;

View file

@ -29,7 +29,7 @@
(function () {
const React = require('react');
const ReactDOM = require('react-dom');
const ReactDom = require('react-dom');
const PropTypes = require('prop-types');
const { IntlProvider } = require('react-intl');
const TagSelector = require('components/tagSelector.js');
@ -74,6 +74,16 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
focusTextbox() {
this.searchBoxRef.current.focus();
}
componentDidCatch(error, info) {
// Async operations might attempt to update the react components
// after window close in tests, which will cause unnecessary crashing.
if (this._uninitialized) return;
Zotero.debug("TagSelectorContainer: React threw an error");
Zotero.logError(error);
Zotero.debug(info);
Zotero.crash();
}
componentDidUpdate(_prevProps, _prevState) {
Zotero.debug("Tag selector updated");
@ -756,13 +766,14 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
<TagSelectorContainer ref={c => ref = c } {...opts} />
</IntlProvider>
);
ReactDOM.render(elem, domEl);
ReactDom.render(elem, domEl);
ref.domEl = domEl;
return ref;
}
uninit() {
ReactDOM.unmountComponentAtNode(this.domEl);
this._uninitialized = true;
ReactDom.unmountComponentAtNode(this.domEl);
Zotero.Notifier.unregisterObserver(this._notifierID);
Zotero.Prefs.unregisterObserver(this._prefObserverID);
}

View file

@ -135,7 +135,7 @@ var Zotero_File_Interface = new function() {
*
* @return {Promise}
*/
this.exportFile = Zotero.Promise.method(function () {
this.exportFile = async function () {
var exporter = new Zotero_File_Exporter();
exporter.libraryID = ZoteroPane_Local.getSelectedLibraryID();
if (exporter.libraryID === false) {
@ -143,7 +143,7 @@ var Zotero_File_Interface = new function() {
}
exporter.name = Zotero.Libraries.getName(exporter.libraryID);
return exporter.save();
});
};
/*
* exports a collection or saved search

View file

@ -2,10 +2,10 @@
/* eslint-disable no-unused-vars */
var Zotero = Components.classes['@zotero.org/Zotero;1']
// Currently uses only nsISupports
//.getService(Components.interfaces.chnmIZoteroService).
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
// Currently uses only nsISupports
//.getService(Components.interfaces.chnmIZoteroService).
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
// Components.utils.import('resource://zotero/require.js');
// Not using Cu.import here since we don't want the require module to be cached

View file

@ -319,7 +319,7 @@ var Zotero_Citation_Dialog = new function () {
_itemSelected(itemDataID);
// turn off highlight in item tree
_suppressNextTreeSelect = true;
document.getElementById("zotero-items-tree").view.selection.clearSelection();
itemsView.selection.clearSelection();
document.getElementById("remove").disabled = !itemDataID;
document.getElementById("add").disabled = true;
_configListPosition(!itemDataID, selectedListIndex);

View file

@ -28,6 +28,7 @@
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<?xml-stylesheet href="chrome://zotero/skin/integration.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
@ -43,6 +44,7 @@
ondialogcancel="Zotero_Citation_Dialog.cancel();"
onclose="Zotero_Citation_Dialog.cancel();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
persist="screenX screenY width height"
resizable="true"
buttons="extra1,extra2,accept,cancel"
@ -62,191 +64,13 @@
onkeypress="if(event.keyCode == event.DOM_VK_ESCAPE) { if (this.value == '') { cancelDialog(); return false; } this.value = ''; onSearch(); return false; } return true;"/>
</hbox>
<hbox flex="1" style="margin-top: 5px">
<tree id="zotero-collections-tree"
style="width: 200px;" hidecolumnpicker="true" seltype="cell"
onselect="onCollectionSelected();">
<treecols>
<treecol
id="zotero-collections-name-column"
flex="1"
primary="true"
hideheader="true"/>
</treecols>
<treechildren/>
</tree>
<deck id="zotero-items-pane-content" selectedIndex="0" flex="1">
<tree id="zotero-items-tree"
enableColumnDrag="true" flex="1" seltype="single"
onselect="onItemSelected(); Zotero_Citation_Dialog.treeItemSelected();">
<treecols id="zotero-items-columns-header">
<treecol
id="zotero-items-column-title" primary="true"
label="&zotero.items.title_column;"
flex="4" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-firstCreator"
label="&zotero.items.creator_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-itemType" hidden="true"
label="&zotero.items.type_column;"
width="40" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-date" hidden="true"
label="&zotero.items.date_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-year" hidden="true"
label="&zotero.items.year_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-publisher" hidden="true"
label="&zotero.items.publisher_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-publicationTitle" hidden="true"
label="&zotero.items.publication_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-journalAbbreviation" hidden="true"
label="&zotero.items.journalAbbr_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-language" hidden="true"
label="&zotero.items.language_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-accessDate" hidden="true"
label="&zotero.items.accessDate_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-libraryCatalog" hidden="true"
label="&zotero.items.libraryCatalog_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-callNumber" hidden="true"
submenu="true"
label="&zotero.items.callNumber_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-rights" hidden="true"
submenu="true"
label="&zotero.items.rights_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-dateAdded" hidden="true"
label="&zotero.items.dateAdded_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-dateModified" hidden="true"
label="&zotero.items.dateModified_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-archive" hidden="true"
submenu="true"
label="&zotero.items.archive_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-archiveLocation" hidden="true"
submenu="true"
label="&zotero.items.archiveLocation_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-place" hidden="true"
submenu="true"
label="&zotero.items.place_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-volume" hidden="true"
submenu="true"
label="&zotero.items.volume_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-edition" hidden="true"
submenu="true"
label="&zotero.items.edition_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-pages" hidden="true"
submenu="true"
label="&zotero.items.pages_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-issue" hidden="true"
submenu="true"
label="&zotero.items.issue_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-series" hidden="true"
submenu="true"
label="&zotero.items.series_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-seriesTitle" hidden="true"
submenu="true"
label="&zotero.items.seriesTitle_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-court" hidden="true"
submenu="true"
label="&zotero.items.court_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-medium" hidden="true"
submenu="true"
label="&zotero.items.medium_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-genre" hidden="true"
submenu="true"
label="&zotero.items.genre_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-system" hidden="true"
submenu="true"
label="&zotero.items.system_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-extra" hidden="true"
label="&zotero.items.extra_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
</treecols>
<treechildren/>
</tree>
<!-- Label for displaying messages when items pane is hidden
(e.g. "Advanced search mode — press Enter to search.")-->
<vbox id="zotero-items-pane-message-box" pack="center" align="center"/>
</deck>
<vbox id="zotero-collections-tree-container" class="virtualized-table-container" style="min-width: 200px">
<html:div id="zotero-collections-tree"></html:div>
</vbox>
<hbox id="zotero-items-pane-content" class="virtualized-table-container" flex="1">
<html:div id="zotero-items-tree"></html:div>
</hbox>
</hbox>
</vbox>

View file

@ -36,7 +36,7 @@ var Zotero_Bibliography_Dialog = new function () {
/**
* Initializes add citation dialog
*/
this.load = function() {
this.load = async function() {
bibEditInterface = window.arguments[0].wrappedJSObject;
_revertAllButton = document.documentElement.getButton("extra2");
@ -44,7 +44,6 @@ var Zotero_Bibliography_Dialog = new function () {
_addButton = document.getElementById("add");
_removeButton = document.getElementById("remove");
_itemList = document.getElementById("item-list");
_itemTree = document.getElementById("zotero-items-tree");
_revertAllButton.label = Zotero.getString("integration.revertAll.button");
_revertAllButton.disabled = bibEditInterface.isAnyEdited();
@ -54,7 +53,7 @@ var Zotero_Bibliography_Dialog = new function () {
document.getElementById('editor').format = "RTF";
// load (from selectItemsDialog.js)
doLoad();
await doLoad();
// load bibliography entries
_loadItems();
@ -119,7 +118,7 @@ var Zotero_Bibliography_Dialog = new function () {
if(_itemList.selectedItems.length) {
_suppressAllSelectEvents = true;
_itemTree.view.selection.clearSelection();
itemsView.selection.clearSelection();
_suppressAllSelectEvents = false;
// only show revert button if at least one selected item has been edited

View file

@ -27,6 +27,7 @@
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<?xml-stylesheet href="chrome://zotero/skin/integration.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
@ -60,51 +61,13 @@
<textbox id="zotero-tb-search" type="search" timeout="250" oncommand="onSearch()" dir="reverse" onkeypress="if(event.keyCode == event.DOM_VK_ESCAPE) { this.value = ''; this.doCommand('cmd_zotero_search'); return false; } return true;"/>
</hbox>
<hbox flex="1" style="margin-top: 5px">
<tree id="zotero-collections-tree"
style="width: 150px;" hidecolumnpicker="true" seltype="single"
onselect="onCollectionSelected();">
<treecols>
<treecol
id="zotero-collections-name-column"
flex="1"
primary="true"
hideheader="true"/>
</treecols>
<treechildren/>
</tree>
<deck id="zotero-items-pane-content" selectedIndex="0" flex="1">
<tree id="zotero-items-tree"
flex="1" hidecolumnpicker="true" seltype="multiple"
onselect="Zotero_Bibliography_Dialog.treeItemSelected();">
<treecols>
<treecol
id="zotero-items-column-title" primary="true"
label="&zotero.items.title_column;"
flex="4" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-firstCreator"
label="&zotero.items.creator_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-dateAdded" hidden="true"
label="&zotero.items.dateAdded_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-dateModified" hidden="true"
label="&zotero.items.dateModified_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
</treecols>
<treechildren/>
</tree>
<!-- Label for displaying messages when items pane is hidden
(e.g. "Advanced search mode — press Enter to search.")-->
<vbox id="zotero-items-pane-message-box" pack="center" align="center"/>
</deck>
<vbox id="zotero-collections-tree-container" class="virtualized-table-container" style="min-width: 150px">
<html:div id="zotero-collections-tree" class="edit-bibl"></html:div>
</vbox>
<hbox id="zotero-items-pane-content" class="virtualized-table-container" flex="1">
<html:div id="zotero-items-tree"></html:div>
</hbox>
</hbox>
</vbox>

View file

@ -24,7 +24,7 @@
*/
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDom from 'react-dom';
import TagsBoxContainer from 'containers/tagsBoxContainer';
var ZoteroItemPane = new function() {
@ -182,7 +182,7 @@ var ZoteroItemPane = new function() {
return;
}
else if (index == 2) {
ReactDOM.render(
ReactDom.render(
<TagsBoxContainer
key={"tagsBox-" + item.id}
item={item}
@ -224,6 +224,9 @@ var ZoteroItemPane = new function() {
yield this.viewItem(_lastItem, null, 1);
}
}
if (viewBox.selectedIndex == 0 && action == 'refresh' && _lastItem) {
yield this.viewItem(_lastItem, null, 0);
}
});

View file

@ -69,7 +69,7 @@
</groupbox>
<!-- Regular item -->
<tabbox id="zotero-view-tabbox" class="zotero-view-tabbox" flex="1" onselect="if (!ZoteroPane_Local.collectionsView.selection || event.originalTarget.localName != 'tabpanels') { return; }; ZoteroItemPane.viewItem(ZoteroPane_Local.getSelectedItems()[0], ZoteroPane_Local.collectionsView.editable ? 'edit' : 'view', this.selectedIndex)">
<tabbox id="zotero-view-tabbox" class="zotero-view-tabbox" flex="1" onselect="if (!ZoteroPane_Local.getCollectionTreeRow() || event.originalTarget.localName != 'tabpanels') { return; }; ZoteroItemPane.viewItem(ZoteroPane_Local.getSelectedItems()[0], ZoteroPane_Local.collectionsView.editable ? 'edit' : 'view', this.selectedIndex)">
<tabs id="zotero-editpane-tabs" class="zotero-editpane-tabs">
<tab id="zotero-editpane-info-tab" label="&zotero.tabs.info.label;"/>
<tab id="zotero-editpane-notes-tab" label="&zotero.tabs.notes.label;"/>

View file

@ -0,0 +1,141 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
import VirtualizedTable from 'components/virtualized-table';
const { IntlProvider } = require('react-intl');
import React from 'react';
import ReactDom from 'react-dom';
var tree;
var engines;
const columns = [
{ dataKey: 'visible', type: 'checkbox', fixedWidth: true, width: 28 },
{ dataKey: 'name', label: "zotero.preferences.locate.name" },
{ dataKey: 'description', label: "zotero.preferences.locate.description" },
];
function init() {
engines = Zotero.LocateManager.getEngines();
const domEl = document.querySelector('#locateManager-tree');
let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
<VirtualizedTable
getRowCount={() => engines.length}
id="locateManager-table"
ref={ref => tree = ref}
renderItem={VirtualizedTable.makeRowRenderer(getRowData)}
showHeader={true}
multiSelect={true}
columns={columns}
onActivate={handleActivate}
/>
</IntlProvider>
);
return new Promise(resolve => ReactDom.render(elem, domEl, resolve));
}
function getRowData(index) {
var data = {};
columns.forEach((column) => {
if (column.dataKey == 'visible') {
var value = !engines[index].hidden;
}
else {
value = engines[index][column.dataKey];
}
data[column.dataKey] = value;
});
return data;
}
/**
* Refreshes the list of locate engines in the locate pane
* @param {String} name of locate engine to select
*/
function updateTree() {
if (!tree) return;
tree.forceUpdate(tree.invalidate);
}
function handleActivate(event, indices) {
// Ignore Enter, only run on dblclick
if (event.key) return;
indices.forEach(index => engines[index].hidden = !engines[index].hidden)
updateTree();
}
/**
* Adds a new Locate Engine to the locate pane
**/
/*
function addLocateEngine() {
// alert(Zotero.LocateManager.activeLocateEngines.join(" || "));
var textbox = document.getElementById('locate-add-textbox');
Zotero.LocateManager.addLocateEngine(textbox.value);
refreshLocateEnginesList();
}
*/
function toggleLocateEngines() {
if (!tree) return;
const numSelected = tree.selection.count;
const numVisible = engines.filter((_, index) => tree.selection.isSelected(index))
.reduce((acc, engine) => acc + (engine.hidden ? 0 : 1), 0);
// Make all visible, unless all selected are already visible
var hideAll = numVisible == numSelected;
engines.forEach((engine, index) => {
if (tree.selection.isSelected(index)) {
engine.hidden = hideAll;
}
});
updateTree();
}
/**
* Deletes selected Locate Engines from the locate pane
**/
function deleteLocateEngine() {
engines.forEach((engine, index) => {
if (tree.selection.isSelected(index)) {
Zotero.LocateManager.removeLocateEngine(engine);
}
});
tree.selection.clearSelection();
updateTree();
}
/**
* Restores Default Locate Engines
**/
function restoreDefaultLocateEngines() {
Zotero.LocateManager.restoreDefaultEngines();
engines = Zotero.LocateManager.getEngines();
updateTree();
}

View file

@ -29,6 +29,7 @@
<?xml-stylesheet href="chrome://zotero-platform/content/preferences.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
<?xml-stylesheet href="chrome://zotero/skin/preferences.css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!--
@ -41,12 +42,17 @@ To add a new preference:
in Zotero.Prefs.observe()
-->
<prefwindow id="zotero-locate-manager-prefs" title="&zotero.preferences.title;" onload="refreshLocateEnginesList()"
windowtype="zotero:pref" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<prefwindow
id="zotero-locate-manager-prefs"
title="&zotero.preferences.title;" onload="init()"
windowtype="zotero:pref"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<prefpane id="zotero-prefpane-locate"
label="&zotero.preferences.prefpane.locate;"
image="chrome://zotero/skin/prefs-styles.png">
image="chrome://zotero/skin/prefs-styles.png"
flex="1">
<!-- TODO: pic for locate pane -->
<!-- TODO: create labels in dtd -->
@ -63,17 +69,8 @@ To add a new preference:
<separator class="thin"/>
<hbox>
<tree flex="1" id="locateManager" hidecolumnpicker="true" rows="10"
onselect="document.getElementById('locateManager-delete').disabled = undefined"
editable="false">
<treecols>
<treecol type="checkbox" id="locateManager-checkbox" editable="true" flex="0.5"/>
<treecol id="locateManager-name" label="&zotero.preferences.locate.name;" flex="1"/>
<treecol id="locateManager-description" label="&zotero.preferences.locate.description;" flex="2"/>
</treecols>
<treechildren id="locateManager-rows"/>
</tree>
<hbox class="virtualized-table-container" flex="1" height="100">
<html:div id="locateManager-tree"/>
</hbox>
<separator class="thin"/>
@ -87,11 +84,10 @@ To add a new preference:
<button disabled="true" id="locateManager-delete" label="-" onclick="deleteLocateEngine()" flex="0.5"/>
</hbox>
</hbox>
<separator class="thin"/>
<!--
TODO: Restore a way to add these
<separator class="thin"/>
<label id="addLocateEngineDescription" style="font-size: 10px; width: 45em; height: 6em">
&zotero.preferences.locate.addDescription;
</label>
@ -99,183 +95,6 @@ To add a new preference:
</groupbox>
</prefpane>
<script src="chrome://zotero/content/include.js"></script>
<script type="application/javascript">
<![CDATA[
function treeClick(event) {
// We only care about primary button double and triple clicks
if (!event || (event.detail != 2 && event.detail != 3) || event.button != 0) {
return;
}
var t = event.originalTarget;
if (t.localName != 'treechildren') {
return;
}
var tree = t.parentNode;
var row = {}, col = {}, obj = {};
var cell = tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
var treechildren = document.getElementById('locateManager-rows');
var treeitem = treechildren.children[row.value];
treeitem.engine.hidden = !treeitem.engine.hidden;
refreshLocateEnginesList()
}
/**
* Refreshes the list of locate engines in the locate pane
* @param {String} name of locate engine to select
*/
function refreshLocateEnginesList() {
var tree = document.getElementById('locateManager');
var treechildren = document.getElementById('locateManager-rows');
// add click listener
tree.addEventListener("click", treeClick, false);
// store ranges
var nRanges = tree.view.selection.getRangeCount();
var start = {};
var end = {};
var ranges = [];
for(var i=0; i<nRanges; i++) {
tree.view.selection.getRangeAt(i, start, end);
ranges.push([start.value, end.value]);
}
// clear tree
while (treechildren.hasChildNodes()) {
treechildren.removeChild(treechildren.firstChild);
}
// repopulate tree with available engines
var engines = Zotero.LocateManager.getEngines();
var i = 0;
for (let engine of engines) {
var treeitem = document.createElement('treeitem');
var treerow = document.createElement('treerow');
var checkboxCell = document.createElement('treecell');
var nameCell = document.createElement('treecell');
var descriptionCell = document.createElement('treecell');
treeitem.engine = engine;
nameCell.setAttribute('label', engine.name);
descriptionCell.setAttribute('label', engine.description);
if( !engine.hidden ) {
checkboxCell.setAttribute('value', 'true');
}
treerow.appendChild(checkboxCell);
treerow.appendChild(nameCell);
treerow.appendChild(descriptionCell);
treeitem.appendChild(treerow);
treechildren.appendChild(treeitem);
i++;
}
// restore ranges
for (let range of ranges) {
if(range[1] <= engines.length-1) {
tree.view.selection.rangedSelect(range[0], range[1], true);
}
}
}
/**
* Adds a new Locate Engine to the locate pane
**/
/*
function addLocateEngine() {
// alert(Zotero.LocateManager.activeLocateEngines.join(" || "));
var textbox = document.getElementById('locate-add-textbox');
Zotero.LocateManager.addLocateEngine(textbox.value);
refreshLocateEnginesList();
}
*/
function toggleLocateEngines() {
// get selected engines names
var tree = document.getElementById('locateManager');
var treeItems = tree.lastChild.childNodes;
var engineNames = [];
var start = {};
var end = {};
var nRanges = tree.view.selection.getRangeCount();
var numStatuses = 0;
var engineStatusesSum = 0;
for(var i=0; i<nRanges; i++) {
tree.view.selection.getRangeAt(i, start, end);
for(var j=start.value; j<=end.value; j++) {
var engineStatus = treeItems[j].engine.hidden ? 0 : 1;
numStatuses += 1;
engineStatusesSum += engineStatus;
}
}
var hidden;
switch( engineStatusesSum ) {
case 0:
// all off, turn all on
hidden = false;
break;
case numStatuses:
// all on, turn all off
hidden = true;
break;
default:
// some on, some off. turn all on
hidden = false;
}
Zotero.LocateManager.getEngines().forEach(engine => engine.hidden = hidden);
refreshLocateEnginesList();
}
/**
* Deletes selected Locate Engines from the locate pane
**/
function deleteLocateEngine() {
// get selected engines names
var tree = document.getElementById('locateManager');
var treeItems = tree.lastChild.childNodes;
var engineNames = [];
var start = {};
var end = {};
var nRanges = tree.view.selection.getRangeCount();
for(var i=0; i<nRanges; i++) {
tree.view.selection.getRangeAt(i, start, end);
for(var j=start.value; j<=end.value; j++) {
Zotero.LocateManager.removeEngine(treeItems[j].engine);
}
}
for(var i=0; i<engineNames.length; i++) {
Zotero.LocateManager.removeLocateEngine(engineNames[i]);
}
tree.view.selection.clearSelection();
refreshLocateEnginesList();
}
/**
* Restores Default Locate Engines
**/
function restoreDefaultLocateEngines() {
Zotero.LocateManager.restoreDefaultEngines();
refreshLocateEnginesList();
}
]]>
</script>
<script src="include.js"></script>
<script src="locateManager.js"/>
</prefwindow>

View file

@ -60,7 +60,7 @@ var Zotero_LocateMenu = new function() {
}
// add separator at end if necessary
if(locateMenu.lastChild.tagName !== "menuseparator") {
if(locateMenu.lastChild && locateMenu.lastChild.tagName !== "menuseparator") {
locateMenu.appendChild(document.createElement("menuseparator"));
}

View file

@ -26,6 +26,7 @@
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/preferences.css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!DOCTYPE window [
@ -36,6 +37,7 @@
]>
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="" buttons="accept"
id="zotero-librariesToSync"
onload="Zotero_Preferences.Sync.initLibrariesToSync(); sizeToContent()" >
@ -46,17 +48,17 @@
<groupbox>
<caption label="&zotero.preferences.sync.librariesToSync;"/>
<tree id="libraries-to-sync-tree" flex="1" width="415" hidecolumnpicker="true" rows="10" seltype="single" editable="true"
ondblclick="Zotero_Preferences.Sync.dblClickLibraryToSync(event);"
onclick="Zotero_Preferences.Sync.clickLibraryToSync(event)"
onkeyup="if (event.keyCode == event.DOM_VK_SPACE) { Zotero_Preferences.Sync.toggleLibraryToSync(this.currentIndex); }">
<treecols>
<treecol editable="false" id="libraries-to-sync-checked" label="&zotero.preferences.sync.librariesToSync.sync;"/>
<treecol editable="false" id="libraries-to-sync-name" flex="1" label="&zotero.preferences.sync.librariesToSync.library;"/>
</treecols>
<treechildren id="libraries-to-sync-rows"/>
</tree>
<hbox class="virtualized-table-container" flex="1" height="200" width="600">
<html:div id="libraries-to-sync-tree"/>
</hbox>
<separator class="thin"/>
<hbox align="center">
<hbox pack="start" flex="1">
<button label="&zotero.preferences.sync.toggle;" onclick="Zotero_Preferences.Sync.toggleLibraryToSync()"/>
</hbox>
</hbox>
</groupbox>
<script>

View file

@ -32,6 +32,7 @@
<?xml-stylesheet href="chrome://zotero/skin/zotero.css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css"?>
<?xml-stylesheet href="chrome://zotero-platform-version/content/style.css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!--
To add an observer for a preference change, add an appropriate case in

View file

@ -27,6 +27,12 @@
import FilePicker from 'zotero/filePicker';
var React = require('react');
var ReactDom = require('react-dom');
var VirtualizedTable = require('components/virtualized-table');
var { IntlProvider } = require('react-intl');
var { makeRowRenderer } = VirtualizedTable;
Zotero_Preferences.Cite = {
wordPluginIDs: new Set([
'zoteroOpenOfficeIntegration@zotero.org',
@ -78,44 +84,59 @@ Zotero_Preferences.Cite = {
* @param {String} cslID Style to select
* @return {Promise}
*/
refreshStylesList: Zotero.Promise.coroutine(function* (cslID) {
refreshStylesList: async function (cslID) {
Zotero.debug("Refreshing styles list");
var treechildren = document.getElementById('styleManager-rows');
while (treechildren.hasChildNodes()) {
treechildren.removeChild(treechildren.firstChild);
}
await Zotero.Styles.init();
yield Zotero.Styles.init();
var styles = Zotero.Styles.getVisible();
var selectIndex = false;
styles.forEach(function (style, i) {
var treeitem = document.createElement('treeitem');
var treerow = document.createElement('treerow');
var titleCell = document.createElement('treecell');
var updatedCell = document.createElement('treecell');
if (style.updated) {
var updatedDate = Zotero.Date.formatDate(Zotero.Date.strToDate(style.updated), true);
if (!this._tree) {
const columns = [
{
dataKey: "title",
label: "zotero.preferences.cite.styles.styleManager.title",
},
{
dataKey: "updated",
label: "zotero.preferences.cite.styles.styleManager.updated",
fixedWidth: true,
width: 150
}
];
var handleKeyDown = (event) => {
if (event.key == 'Delete' || Zotero.isMac && event.key == 'Backspace') {
Zotero_Preferences.Cite.deleteStyle();
return false;
}
};
let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
<VirtualizedTable
getRowCount={() => Zotero.Styles.getVisible().length}
id="styleManager-table"
ref={ref => this._tree = ref}
renderItem={makeRowRenderer(index => Zotero.Styles.getVisible()[index])}
showHeader={true}
multiSelect={true}
columns={columns}
staticColumns={true}
onSelectionChange={() => document.getElementById('styleManager-delete').disabled = undefined}
onKeyDown={handleKeyDown}
/>
</IntlProvider>
);
await new Promise(resolve => ReactDom.render(elem, document.getElementById("styleManager"), resolve));
}
else {
this._tree.invalidate();
}
if (cslID) {
var styles = Zotero.Styles.getVisible();
var index = styles.findIndex(style => style.styleID == cslID);
if (index != -1) {
this._tree.selection.select(index);
}
else {
var updatedDate = '';
}
treeitem.setAttribute('id', 'zotero-csl-' + style.styleID);
titleCell.setAttribute('label', style.title);
updatedCell.setAttribute('label', updatedDate);
treerow.appendChild(titleCell);
treerow.appendChild(updatedCell);
treeitem.appendChild(treerow);
treechildren.appendChild(treeitem);
if (cslID == style.styleID) {
document.getElementById('styleManager').view.selection.select(i);
}
});
}),
}
},
openStylesPage: function () {
@ -168,17 +189,10 @@ Zotero_Preferences.Cite = {
**/
deleteStyle: Zotero.Promise.coroutine(function* () {
// get selected cslIDs
var tree = document.getElementById('styleManager');
var treeItems = tree.lastChild.childNodes;
var styles = Zotero.Styles.getVisible();
var cslIDs = [];
var start = {};
var end = {};
var nRanges = tree.view.selection.getRangeCount();
for(var i=0; i<nRanges; i++) {
tree.view.selection.getRangeAt(i, start, end);
for(var j=start.value; j<=end.value; j++) {
cslIDs.push(treeItems[j].getAttribute('id').substr(11));
}
for (let index of this._tree.selection.selected.keys()) {
cslIDs.push(styles[index].styleID);
}
if(cslIDs.length == 0) {

View file

@ -31,6 +31,7 @@
]>
<overlay id="zotero-prefpane-cite-overlay"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<prefpane id="zotero-prefpane-cite"
@ -52,15 +53,9 @@
<groupbox flex="1">
<caption label="&zotero.preferences.cite.styles.styleManager;"/>
<tree flex="1" id="styleManager" hidecolumnpicker="true" rows="6"
onselect="document.getElementById('styleManager-delete').disabled = undefined"
onkeypress="if (event.keyCode == event.DOM_VK_DELETE) { Zotero_Preferences.Cite.deleteSelectedStyle(); }">
<treecols>
<treecol id="styleManager-title" label="&zotero.preferences.cite.styles.styleManager.title;" flex="1"/>
<treecol id="styleManager-updated" label="&zotero.preferences.cite.styles.styleManager.updated;"/>
</treecols>
<treechildren id="styleManager-rows"/>
</tree>
<hbox class="virtualized-table-container" flex="1" height="300px">
<html:div id="styleManager"/>
</hbox>
<separator class="thin"/>
<hbox align="center" flex="1" height="40">
<label class="zotero-text-link"

View file

@ -25,6 +25,12 @@
"use strict";
var React = require('react');
var ReactDom = require('react-dom');
var VirtualizedTable = require('components/virtualized-table');
var { IntlProvider } = require('react-intl');
var { makeRowRenderer } = VirtualizedTable;
Zotero_Preferences.Export = {
init: Zotero.Promise.coroutine(function* () {
this.updateQuickCopyInstructions();
@ -191,29 +197,28 @@ Zotero_Preferences.Export = {
document.getElementById('quickCopy-delete').disabled = false;
},
showQuickCopySiteEditor: Zotero.Promise.coroutine(function* (index) {
var treechildren = document.getElementById('quickCopy-siteSettings-rows');
var formattedName = document.getElementById('zotero-quickCopy-menu').label;
showQuickCopySiteEditor: async function () {
const index = this._tree.selection.focused;
var formattedName = document.getElementById('zotero-quickCopy-menu').label;
var locale = this._lastSelectedLocale;
var asHTML = document.getElementById('zotero-quickCopy-copyAsHTML').checked;
if (index !== undefined && index > -1 && index < treechildren.childNodes.length) {
var treerow = treechildren.childNodes[index].firstChild;
var domain = treerow.childNodes[0].getAttribute('label');
formattedName = treerow.childNodes[1].getAttribute('label');
locale = treerow.childNodes[2].getAttribute('label');
asHTML = treerow.childNodes[3].getAttribute('label') !== '';
if (index !== undefined && index > -1 && index < this._rows.length) {
var row = this._rows[index];
var domain = row.domain;
formattedName = row.format;
locale = row.locale;
asHTML = row.copyAsHTML;
}
var format = yield Zotero.QuickCopy.getSettingFromFormattedName(formattedName);
var format = await Zotero.QuickCopy.getSettingFromFormattedName(formattedName);
if (asHTML) {
format = format.replace('bibliography=', 'bibliography/html=');
}
var styles = Zotero.Styles.getVisible();
var translation = new Zotero.Translate("export");
var translators = yield translation.getTranslators();
var translators = await translation.getTranslators();
var io = { domain, format, locale, asHTML, ok: false, styles, translators };
window.openDialog('chrome://zotero/content/preferences/quickCopySiteEditor.xul',
@ -224,63 +229,95 @@ Zotero_Preferences.Export = {
}
if (domain && domain != io.domain) {
yield Zotero.DB.queryAsync("DELETE FROM settings WHERE setting='quickCopySite' AND key=?", [domain]);
await Zotero.DB.queryAsync("DELETE FROM settings WHERE setting='quickCopySite' AND key=?", [domain]);
}
var quickCopysetting = Zotero.QuickCopy.unserializeSetting(io.format);
quickCopysetting.locale = io.locale;
yield Zotero.DB.queryAsync("REPLACE INTO settings VALUES ('quickCopySite', ?, ?)", [io.domain, JSON.stringify(quickCopysetting)]);
await Zotero.DB.queryAsync("REPLACE INTO settings VALUES ('quickCopySite', ?, ?)", [io.domain, JSON.stringify(quickCopysetting)]);
yield Zotero.QuickCopy.loadSiteSettings();
await Zotero.QuickCopy.loadSiteSettings();
yield this.refreshQuickCopySiteList();
}),
await this.refreshQuickCopySiteList();
},
refreshQuickCopySiteList: Zotero.Promise.coroutine(function* () {
var treechildren = document.getElementById('quickCopy-siteSettings-rows');
while (treechildren.hasChildNodes()) {
treechildren.removeChild(treechildren.firstChild);
}
refreshQuickCopySiteList: async function () {
var sql = "SELECT key AS domainPath, value AS format FROM settings "
+ "WHERE setting='quickCopySite' ORDER BY domainPath COLLATE NOCASE";
var siteData = yield Zotero.DB.queryAsync(sql);
var siteData = await Zotero.DB.queryAsync(sql);
for (var i=0; i<siteData.length; i++) {
var treeitem = document.createElement('treeitem');
var treerow = document.createElement('treerow');
var domainCell = document.createElement('treecell');
var formatCell = document.createElement('treecell');
var localeCell = document.createElement('treecell');
var htmlCell = document.createElement('treecell');
domainCell.setAttribute('label', siteData[i].domainPath);
var formattedName = yield Zotero.QuickCopy.getFormattedNameFromSetting(siteData[i].format);
formatCell.setAttribute('label', formattedName);
var format = Zotero.QuickCopy.unserializeSetting(siteData[i].format);
localeCell.setAttribute('label', format.locale);
htmlCell.setAttribute('label', format.contentType == 'html' ? ' ✓ ' : '');
treerow.appendChild(domainCell);
treerow.appendChild(formatCell);
treerow.appendChild(localeCell);
treerow.appendChild(htmlCell);
treeitem.appendChild(treerow);
treechildren.appendChild(treeitem);
this._rows = [];
for (let row of siteData) {
var formattedName = await Zotero.QuickCopy.getFormattedNameFromSetting(row.format);
var format = Zotero.QuickCopy.unserializeSetting(row.format);
this._rows.push({
domain: row.domainPath,
format: formattedName,
locale: format.locale,
copyAsHTML: format.contentType == 'html',
});
}
if (!this._tree) {
const columns = [
{
dataKey: "domain",
label: "zotero.preferences.quickCopy.siteEditor.domainPath",
flex: 2
},
{
dataKey: "format",
label: "zotero.preferences.quickCopy.siteEditor.format",
flex: 4
},
{
dataKey: "locale",
label: "zotero.preferences.quickCopy.siteEditor.locale",
flex: 1
},
{
dataKey: "copyAsHTML",
label: "HTML",
type: 'checkbox',
fixedWidth: true,
width: 55,
}
];
var handleKeyDown = (event) => {
if (event.key == 'Delete' || Zotero.isMac && event.key == 'Backspace') {
Zotero_Preferences.Export.deleteSelectedQuickCopySite();
}
};
let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
<VirtualizedTable
getRowCount={() => this._rows.length}
id="quickCopy-siteSettings-table"
ref={ref => this._tree = ref}
renderItem={makeRowRenderer(index => this._rows[index])}
showHeader={true}
columns={columns}
staticColumns={true}
onSelectionChange={() => Zotero_Preferences.Export.enableQuickCopySiteButtons()}
onKeyDown={handleKeyDown}
onActivate={(event, indices) => Zotero_Preferences.Export.showQuickCopySiteEditor()}
/>
</IntlProvider>
);
await new Promise(resolve => ReactDom.render(elem, document.getElementById("quickCopy-siteSettings"), resolve));
} else {
this._tree.invalidate();
}
this.disableQuickCopySiteButtons();
}),
},
deleteSelectedQuickCopySite: Zotero.Promise.coroutine(function* () {
var tree = document.getElementById('quickCopy-siteSettings');
var treeitem = tree.lastChild.childNodes[tree.currentIndex];
var domainPath = treeitem.firstChild.firstChild.getAttribute('label');
var domainPath = this._rows[this._tree.selection.focused].domain;
yield Zotero.DB.queryAsync("DELETE FROM settings WHERE setting='quickCopySite' AND key=?", [domainPath]);
yield Zotero.QuickCopy.loadSiteSettings();
yield this.refreshQuickCopySiteList();

View file

@ -30,7 +30,9 @@
%common;
]>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<overlay
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<prefpane id="zotero-prefpane-export"
onpaneload="Zotero_Preferences.Export.init()"
helpTopic="export">
@ -67,18 +69,9 @@
<separator/>
<label value="&zotero.preferences.quickCopy.siteEditor.setings;" control="quickCopy-siteSettings"/>
<tree flex="1" id="quickCopy-siteSettings" hidecolumnpicker="true" rows="6" seltype="single"
ondblclick="Zotero_Preferences.Export.showQuickCopySiteEditor(this.currentIndex)"
onselect="Zotero_Preferences.Export.enableQuickCopySiteButtons()"
onkeypress="if (event.keyCode == event.DOM_VK_DELETE) { Zotero_Preferences.Export.deleteSelectedQuickCopySite(); }">
<treecols>
<treecol id="quickCopy-urlColumn" label="&zotero.preferences.quickCopy.siteEditor.domainPath;" flex="1"/>
<treecol id="quickCopy-formatColumn" label="&zotero.preferences.quickCopy.siteEditor.format;" flex="2"/>
<treecol id="quickCopy-localeColumn" label="&zotero.preferences.quickCopy.siteEditor.locale;"/>
<treecol id="quickCopy-copyAsHTML" label="HTML"/>
</treecols>
<treechildren id="quickCopy-siteSettings-rows"/>
</tree>
<hbox class="virtualized-table-container" flex="1" height="300px">
<html:div id="quickCopy-siteSettings"/>
</hbox>
<separator class="thin"/>
<hbox>
<button disabled="true" id="quickCopy-edit" label="&zotero.general.edit;"

View file

@ -1,95 +0,0 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 20062013 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
-->
<!DOCTYPE prefwindow [
<!ENTITY % preferencesDTD SYSTEM "chrome://zotero/locale/preferences.dtd"> %preferencesDTD;
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd"> %zoteroDTD;
]>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
<?xml-stylesheet href="chrome://zotero/skin/preferences.css"?>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<prefwindow id="zotero-prefs">
<prefpane id="zotero-prefpane-proxies"
label="&zotero.preferences.prefpane.proxies;"
image="chrome://zotero/skin/prefs-proxies.png"
onpaneload="Zotero_Preferences.Proxies.init()"
position="6"
helpTopic="proxies">
<description width="45em" style="font-size: 12px">
&zotero.preferences.proxies.desc_before_link;
<label class="zotero-text-link" href="http://www.zotero.org/support/proxies"
value="&zotero.preferences.proxies.desc_link;"/>
&zotero.preferences.proxies.desc_after_link;
</description>
<command id="zotero-proxies-update" oncommand="Zotero_Preferences.Proxies.updateProxyPrefs()"/>
<checkbox id="zotero-proxies-transparent" label="&zotero.preferences.proxies.transparent;"
command="zotero-proxies-update"/>
<vbox style="margin-left: 1em">
<checkbox id="zotero-proxies-autoRecognize" label="&zotero.preferences.proxies.autoRecognize;"
command="zotero-proxies-update"/>
<checkbox id="zotero-proxies-showRedirectNotification" label="&zotero.preferences.proxies.showRedirectNotification;"
command="zotero-proxies-update"/>
<hbox style="display: block; line-height: 1.75em">
<checkbox id="zotero-proxies-disableByDomain-checkbox"
label="&zotero.preferences.proxies.disableByDomain;"
command="zotero-proxies-update"/>
<textbox id="zotero-proxies-disableByDomain-textbox"
onchange="Zotero_Preferences.Proxies.updateProxyPrefs()"
flex="1" style="max-width: 11.75em"/>
</hbox>
</vbox>
<groupbox flex="1" id="proxyGroup">
<caption label="&zotero.preferences.proxies.configured;"/>
<tree id="proxyTree" hidecolumnpicker="true" rows="6" seltype="single"
ondblclick="Zotero_Preferences.Proxies.showProxyEditor(this.currentIndex)" onselect="Zotero_Preferences.Proxies.enableProxyButtons()"
onkeypress="if (event.keyCode == event.DOM_VK_DELETE) { Zotero_Preferences.Proxies.deleteProxy(); }">
<treecols>
<treecol id="proxyTree-hostname" label="&zotero.preferences.proxies.hostname;" flex="1"/>
<treecol id="proxyTree-scheme" label="&zotero.preferences.proxies.scheme;" flex="3"/>
</treecols>
<treechildren id="proxyTree-rows"/>
</tree>
<separator class="thin"/>
<hbox>
<button disabled="true" id="proxyTree-edit" label="&zotero.general.edit;" onclick="Zotero_Preferences.Proxies.showProxyEditor(document.getElementById('proxyTree').currentIndex)"/>
<spacer flex="1"/>
<button disabled="true" id="proxyTree-delete" label="-" onclick="Zotero_Preferences.Proxies.deleteProxy()"/>
<button label="+" id="proxyTree-add" onclick="Zotero_Preferences.Proxies.showProxyEditor()"/>
</hbox>
</groupbox>
<separator/>
<separator/>
</prefpane>
<script src="preferences_proxies.js" type="application/javascript"/>
</prefwindow>
</overlay>

View file

@ -1,169 +0,0 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 20062013 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
"use strict";
Zotero_Preferences.Proxies = {
_proxies: null,
init: function () {
this.refreshProxyList();
this.updateCheckboxState();
},
/**
* Updates proxy autoRecognize and transparent settings based on checkboxes
*/
updateProxyPrefs: function () {
var transparent = document.getElementById('zotero-proxies-transparent').checked;
Zotero.Prefs.set("proxies.transparent", transparent);
Zotero.Prefs.set("proxies.autoRecognize", document.getElementById('zotero-proxies-autoRecognize').checked);
Zotero.Prefs.set("proxies.showRedirectNotification", document.getElementById('zotero-proxies-showRedirectNotification').checked);
Zotero.Prefs.set("proxies.disableByDomainString", document.getElementById('zotero-proxies-disableByDomain-textbox').value);
Zotero.Prefs.set("proxies.disableByDomain", document.getElementById('zotero-proxies-disableByDomain-checkbox').checked &&
document.getElementById('zotero-proxies-disableByDomain-textbox').value != "");
Zotero.Proxies.init();
this.updateCheckboxState();
},
updateCheckboxState: function() {
var transparent = document.getElementById('zotero-proxies-transparent').checked;
document.getElementById('proxyTree-add').disabled =
document.getElementById('proxyTree-delete').disabled =
document.getElementById('proxyTree').disabled =
document.getElementById('zotero-proxies-autoRecognize').disabled =
document.getElementById('zotero-proxies-showRedirectNotification').disabled =
document.getElementById('zotero-proxies-disableByDomain-checkbox').disabled =
document.getElementById('zotero-proxies-disableByDomain-textbox').disabled =
!transparent;
},
/**
* Enables UI buttons when proxy is selected
*/
enableProxyButtons: function () {
document.getElementById('proxyTree-edit').disabled = false;
document.getElementById('proxyTree-delete').disabled = false;
},
/**
* Adds a proxy to the proxy pane
*/
showProxyEditor: function (index) {
if(index == -1) return;
window.openDialog('chrome://zotero/content/preferences/proxyEditor.xul',
"zotero-preferences-proxyEditor", "chrome,modal,centerscreen",
index !== undefined ? this._proxies[index] : null);
this.refreshProxyList();
},
/**
* Deletes the currently selected proxy
*/
deleteProxy: function () {
if(document.getElementById('proxyTree').currentIndex == -1) return;
this._proxies[document.getElementById('proxyTree').currentIndex].erase();
this.refreshProxyList();
document.getElementById('proxyTree-delete').disabled = true;
},
/**
* Refreshes the proxy pane
*/
refreshProxyList: function () {
if(!document.getElementById("zotero-prefpane-proxies")) return;
// get and sort proxies
this._proxies = Zotero.Proxies.proxies.slice();
for(var i=0; i<this._proxies.length; i++) {
if(!this._proxies[i].proxyID) {
this._proxies.splice(i, 1);
i--;
}
}
this._proxies = this._proxies.sort(function(a, b) {
if(a.multiHost) {
if(b.multiHost) {
if(a.hosts[0] < b.hosts[0]) {
return -1;
} else {
return 1;
}
} else {
return -1;
}
} else if(b.multiHost) {
return 1;
}
if(a.scheme < b.scheme) {
return -1;
} else if(b.scheme > a.scheme) {
return 1;
}
return 0;
});
// erase old children
var treechildren = document.getElementById('proxyTree-rows');
while (treechildren.hasChildNodes()) {
treechildren.removeChild(treechildren.firstChild);
}
// add proxies to list
for (var i=0; i<this._proxies.length; i++) {
var treeitem = document.createElement('treeitem');
var treerow = document.createElement('treerow');
var hostnameCell = document.createElement('treecell');
var schemeCell = document.createElement('treecell');
hostnameCell.setAttribute('label', this._proxies[i].multiHost ? Zotero.getString("proxies.multiSite") : this._proxies[i].hosts[0]);
schemeCell.setAttribute('label', this._proxies[i].scheme);
treerow.appendChild(hostnameCell);
treerow.appendChild(schemeCell);
treeitem.appendChild(treerow);
treechildren.appendChild(treeitem);
}
document.getElementById('proxyTree').currentIndex = -1;
document.getElementById('proxyTree-edit').disabled = true;
document.getElementById('proxyTree-delete').disabled = true;
document.getElementById('zotero-proxies-transparent').checked = Zotero.Prefs.get("proxies.transparent");
document.getElementById('zotero-proxies-autoRecognize').checked = Zotero.Prefs.get("proxies.autoRecognize");
document.getElementById('zotero-proxies-showRedirectNotification').checked = Zotero.Prefs.get("proxies.showRedirectNotification");
document.getElementById('zotero-proxies-disableByDomain-checkbox').checked = Zotero.Prefs.get("proxies.disableByDomain");
document.getElementById('zotero-proxies-disableByDomain-textbox').value = Zotero.Prefs.get("proxies.disableByDomainString");
}
};

View file

@ -28,6 +28,13 @@ Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm");
Components.utils.import("resource://zotero/config.js");
var React = require('react');
var ReactDom = require('react-dom');
var VirtualizedTable = require('components/virtualized-table');
var { getDomElement } = require('components/icons');
var { IntlProvider } = require('react-intl');
var { renderCell } = VirtualizedTable;
Zotero_Preferences.Sync = {
checkmarkChar: '\u2705',
noChar: '\uD83D\uDEAB',
@ -227,102 +234,106 @@ Zotero_Preferences.Sync = {
},
dblClickLibraryToSync: function (event) {
var tree = document.getElementById("libraries-to-sync-tree");
var row = {}, col = {}, child = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, child);
// Below the list or on checkmark column
if (!col.value || col.value.element.id == 'libraries-to-sync-checked') {
return;
}
// if dblclicked anywhere but the checkbox update pref
return this.toggleLibraryToSync(row.value);
},
clickLibraryToSync: function (event) {
var tree = document.getElementById("libraries-to-sync-tree");
var row = {}, col = {}, child = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, child);
// Below the list or not on checkmark column
if (!col.value || col.value.element.id != 'libraries-to-sync-checked') {
return;
}
// if clicked on checkbox update pref
return this.toggleLibraryToSync(row.value);
},
toggleLibraryToSync: function (index) {
var treechildren = document.getElementById('libraries-to-sync-rows');
if (index >= treechildren.childNodes.length) {
return;
}
var row = treechildren.childNodes[index];
var val = row.firstChild.childNodes[1].getAttribute('value');
if (!val) {
return
}
toggleLibraryToSync: function () {
const index = this._tree.selection.focused;
if (index == -1 || !this._rows[index].editable) return;
const row = this._rows[index];
this._rows[index].checked = !this._rows[index].checked;
this._tree.invalidateRow(index);
var librariesToSkip = JSON.parse(Zotero.Prefs.get('sync.librariesToSkip') || '[]');
var indexOfId = librariesToSkip.indexOf(val);
var indexOfId = librariesToSkip.indexOf(row.id);
if (indexOfId == -1) {
librariesToSkip.push(val);
} else {
librariesToSkip.push(row.id);
}
else {
librariesToSkip.splice(indexOfId, 1);
}
Zotero.Prefs.set('sync.librariesToSkip', JSON.stringify(librariesToSkip));
var cell = row.firstChild.firstChild;
var spacing = Zotero.isWin ? ' ' : ' ';
cell.setAttribute('label', spacing + (indexOfId != -1 ? this.checkmarkChar : this.noChar));
cell.setAttribute('value', indexOfId != -1);
},
initLibrariesToSync: Zotero.Promise.coroutine(function* () {
var tree = document.getElementById("libraries-to-sync-tree");
var treechildren = document.getElementById('libraries-to-sync-rows');
while (treechildren.hasChildNodes()) {
treechildren.removeChild(treechildren.firstChild);
initLibrariesToSync: async function () {
const columns = [
{
dataKey: "checked",
label: "zotero.preferences.sync.librariesToSync.sync",
fixedWidth: true,
width: '60'
},
{
dataKey: "name",
label: "zotero.preferences.sync.librariesToSync.library"
}
];
this._rows = [];
function renderItem(index, selection, oldDiv=null, columns) {
const row = this._rows[index];
let div;
if (oldDiv) {
div = oldDiv;
div.innerHTML = "";
}
else {
div = document.createElementNS("http://www.w3.org/1999/xhtml", 'div');
div.className = "row";
}
div.classList.toggle('selected', selection.isSelected(index));
for (let column of columns) {
if (column.dataKey === 'checked') {
let span = document.createElementNS("http://www.w3.org/1999/xhtml", 'span');
span.className = `cell ${column.className}`;
if (row.id != 'loading') {
span.innerText = row.checked ? this.checkmarkChar : this.noChar;
span.style.textAlign = 'center';
}
div.appendChild(span);
}
else {
div.appendChild(renderCell(index, row[column.dataKey], column));
}
}
return div;
}
let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
<VirtualizedTable
getRowCount={() => this._rows.length}
id="librariesToSync-table"
ref={ref => this._tree = ref}
renderItem={renderItem.bind(this)}
showHeader={true}
columns={columns}
staticColumns={true}
onActivate={Zotero_Preferences.Sync.toggleLibraryToSync.bind(this)}
/>
</IntlProvider>
);
ReactDom.render(elem, document.getElementById("libraries-to-sync-tree"));
var addRow = function (libraryName, id, checked=false, editable=true) {
var treeitem = document.createElement('treeitem');
var treerow = document.createElement('treerow');
var checkboxCell = document.createElement('treecell');
var nameCell = document.createElement('treecell');
nameCell.setAttribute('label', libraryName);
nameCell.setAttribute('value', id);
nameCell.setAttribute('editable', false);
var spacing = Zotero.isWin ? ' ' : ' ';
checkboxCell.setAttribute(
'label',
id == 'loading' ? '' : (spacing + (checked ? this.checkmarkChar : this.noChar))
);
checkboxCell.setAttribute('value', checked);
checkboxCell.setAttribute('editable', false);
treerow.appendChild(checkboxCell);
treerow.appendChild(nameCell);
treeitem.appendChild(treerow);
treechildren.appendChild(treeitem);
this._rows.push({
name: libraryName,
id,
checked,
editable
});
this._tree.invalidate();
}.bind(this);
// Add loading row while we're loading a group list
var loadingLabel = Zotero.getString("zotero.preferences.sync.librariesToSync.loadingLibraries");
addRow(loadingLabel, "loading", false, false);
var apiKey = yield Zotero.Sync.Data.Local.getAPIKey();
var client = Zotero.Sync.Runner.getAPIClient({apiKey});
var apiKey = await Zotero.Sync.Data.Local.getAPIKey();
var client = Zotero.Sync.Runner.getAPIClient({ apiKey });
var groups = [];
try {
// Load up remote groups
var keyInfo = yield Zotero.Sync.Runner.checkAccess(client, {timeout: 5000});
groups = yield client.getGroups(keyInfo.userID);
var keyInfo = await Zotero.Sync.Runner.checkAccess(client, {timeout: 5000});
groups = await client.getGroups(keyInfo.userID);
}
catch (e) {
// Connection problems
@ -342,11 +353,12 @@ Zotero_Preferences.Sync = {
}
// Remove the loading row
treechildren.removeChild(treechildren.firstChild);
this._rows = [];
this._tree.invalidate();
var librariesToSkip = JSON.parse(Zotero.Prefs.get('sync.librariesToSkip') || '[]');
// Add default rows
addRow(Zotero.getString("pane.collections.libraryAndFeeds"), "L" + Zotero.Libraries.userLibraryID,
addRow(Zotero.getString("pane.collections.libraryAndFeeds"), "L" + Zotero.Libraries.userLibraryID,
librariesToSkip.indexOf("L" + Zotero.Libraries.userLibraryID) == -1);
// Sort groups
@ -356,8 +368,7 @@ Zotero_Preferences.Sync = {
for (let group of groups) {
addRow(group.data.name, "G" + group.id, librariesToSkip.indexOf("G" + group.id) == -1);
}
}),
},
updateStorageSettingsUI: Zotero.Promise.coroutine(function* () {
this.unverifyStorageServer();

View file

@ -1,146 +0,0 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
*/
var Zotero_ProxyEditor = new function() {
var treechildren;
var tree;
var treecol;
var multiSite;
/**
* Called when this window is first opened. Sets values if necessary
*/
this.load = function() {
treechildren = document.getElementById("zotero-proxies-hostname-multiSite-tree-children");
tree = document.getElementById("zotero-proxies-hostname-multiSite-tree");
multiSite = document.getElementById("zotero-proxies-multiSite");
if(window.arguments && window.arguments[0]) {
var proxy = window.arguments[0];
document.getElementById("zotero-proxies-scheme").value = proxy.scheme;
document.getElementById("zotero-proxies-multiSite").checked = !!proxy.multiHost;
if(proxy.hosts) {
if(proxy.multiHost) {
this.multiSiteChanged();
for (var i=0; i<proxy.hosts.length; i++) {
_addTreeElement(proxy.hosts[i]);
}
document.getElementById("zotero-proxies-autoAssociate").checked = proxy.autoAssociate;
} else {
document.getElementById("zotero-proxies-hostname-text").value = proxy.hosts[0];
}
}
}
window.sizeToContent();
}
/**
* Called when a user checks/unchecks the Multi-Site checkbox. Shows or hides multi-site
* hostname specification box as necessary.
*/
this.multiSiteChanged = function() {
document.getElementById("zotero-proxies-hostname-multiSite").hidden = !multiSite.checked;
document.getElementById("zotero-proxies-hostname-multiSite-description").hidden = !multiSite.checked;
document.getElementById("zotero-proxies-hostname").hidden = multiSite.checked;
window.sizeToContent();
}
/**
* Called when a row is selected
*/
this.select = function() {
document.getElementById("zotero-proxies-delete").disabled = tree.selectedIndex == -1;
}
/**
* Adds a host when in multi-host mode
*/
this.addHost = function() {
_addTreeElement();
tree.startEditing(treechildren.childNodes.length-1, tree.columns.getFirstColumn());
}
/**
* Deletes a host when in multi-host mode
*/
this.deleteHost = function() {
if(tree.currentIndex == -1) return;
treechildren.removeChild(treechildren.childNodes[tree.currentIndex]);
document.getElementById("zotero-proxies-delete").disabled = true;
}
/**
* Called when the user clicks "OK." Updates proxy for Zotero.Proxy.
*/
this.accept = function() {
var proxy = window.arguments && window.arguments[0] ? window.arguments[0] : new Zotero.Proxy();
proxy.scheme = document.getElementById("zotero-proxies-scheme").value;
proxy.multiHost = multiSite.checked;
if(proxy.multiHost) {
proxy.hosts = [];
var treecol = tree.columns.getFirstColumn();
for(var i=0; i<tree.view.rowCount; i++) {
var host = tree.view.getCellText(i, treecol);
if(host) proxy.hosts.push(host);
}
proxy.autoAssociate = document.getElementById("zotero-proxies-autoAssociate").checked;
} else {
proxy.hosts = [document.getElementById("zotero-proxies-hostname-text").value];
}
var hasErrors = proxy.validate();
if(hasErrors) {
error = hasErrors.shift();
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
promptService.alert(
window, Zotero.getString("proxies.error"),
Zotero.getString("proxies.error." + error, hasErrors)
);
if(window.arguments && window.arguments[0]) proxy.revert(); // async
return false;
}
proxy.save(true);
return true;
}
/**
* Adds an element to the tree
*/
function _addTreeElement(label) {
var treeitem = document.createElement('treeitem');
var treerow = document.createElement('treerow');
var treecell = document.createElement('treecell');
if(label) treecell.setAttribute('label', label);
treerow.appendChild(treecell);
treeitem.appendChild(treerow);
treechildren.appendChild(treeitem);
}
}

View file

@ -1,71 +0,0 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
-->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/preferences.css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/preferences.dtd">
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="" buttons="cancel,accept"
id="zotero-proxyEditor"
onload="Zotero_ProxyEditor.load();"
ondialogaccept="return Zotero_ProxyEditor.accept();">
<script src="chrome://zotero/content/include.js"/>
<script src="proxyEditor.js"/>
<checkbox id="zotero-proxies-multiSite" label="&zotero.preferences.proxies.multiSite;"
oncommand="Zotero_ProxyEditor.multiSiteChanged()"/>
<separator class="thin"/>
<vbox id="zotero-proxies-hostname-multiSite" hidden="true">
<checkbox id="zotero-proxies-autoAssociate" label="&zotero.preferences.proxies.autoAssociate;"/>
<tree flex="1" id="zotero-proxies-hostname-multiSite-tree" hidecolumnpicker="true" editable="true" rows="6"
onkeypress="if (event.keyCode == event.DOM_VK_DELETE) { Zotero_ProxyEditor.remove(); }"
onselect="Zotero_ProxyEditor.select();">
<treecols>
<treecol label="&zotero.preferences.proxies.hostname;" id="zotero-proxies-hostname-multiSite-tree-col" flex="1"/>
</treecols>
<treechildren id="zotero-proxies-hostname-multiSite-tree-children"/>
</tree>
<hbox pack="end">
<button id="zotero-proxies-delete" label="-" onclick="Zotero_ProxyEditor.deleteHost()" disabled="true"/>
<button id="zotero-proxies-add" label="+" onclick="Zotero_ProxyEditor.addHost()"/>
</hbox>
</vbox>
<vbox id="zotero-proxies-hostname">
<label value="&zotero.preferences.proxies.hostname;:" control="zotero-proxies-hostname-text"/>
<textbox id="zotero-proxies-hostname-text"/>
</vbox>
<separator class="thin"/>
<label value="&zotero.preferences.proxies.scheme;:" control="zotero-proxies-scheme"/>
<textbox id="zotero-proxies-scheme"/>
<label value="&zotero.preferences.proxies.variables;"/>
<label value="&zotero.preferences.proxies.h_variable;" id="zotero-proxies-hostname-multiSite-description" hidden="true"/>
<label value="&zotero.preferences.proxies.p_variable;"/>
<label value="&zotero.preferences.proxies.d_variable;"/>
<label value="&zotero.preferences.proxies.f_variable;"/>
<label value="&zotero.preferences.proxies.a_variable;"/>
</dialog>

View file

@ -0,0 +1,134 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2018 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
*/
Components.utils.import("resource://gre/modules/Services.jsm");
import React from 'react';
import ReactDom from 'react-dom';
import VirtualizedTable from 'components/virtualized-table';
const { IntlProvider } = require('react-intl');
const { getDomElement } = require('components/icons');
const { renderCell } = VirtualizedTable;
let _progressIndicator = null;
let _progressQueue;
let _tree;
function _getImageByStatus(status) {
if (status === Zotero.ProgressQueue.ROW_PROCESSING) {
return getDomElement('IconArrowRefresh');
}
else if (status === Zotero.ProgressQueue.ROW_FAILED) {
return getDomElement('IconCross');
}
else if (status === Zotero.ProgressQueue.ROW_SUCCEEDED) {
return getDomElement('IconTick');
}
return document.createElementNS("http://www.w3.org/1999/xhtml", 'span');
}
function _rowToTreeItem(index, selection, oldDiv=null, columns) {
let rows = _progressQueue.getRows();
let row = rows[index];
let div;
if (oldDiv) {
div = oldDiv;
div.innerHTML = "";
}
else {
div = document.createElementNS("http://www.w3.org/1999/xhtml", 'div');
div.className = "row";
}
div.classList.toggle('selected', selection.isSelected(index));
for (let column of columns) {
if (column.dataKey === 'success') {
let span = document.createElementNS("http://www.w3.org/1999/xhtml", 'span');
span.className = `cell icon ${column.className}`;
span.appendChild(_getImageByStatus(row.status));
div.appendChild(span);
}
else {
div.appendChild(renderCell(index, row[column.dataKey], column));
}
}
return div;
}
function _init() {
var io = window.arguments[0];
_progressQueue = io.progressQueue;
document.title = Zotero.getString(_progressQueue.getTitle());
let columns = _progressQueue.getColumns();
const tableColumns = [
{ dataKey: 'success', fixedWidth: true, width: "26" },
{ dataKey: 'fileName', label: Zotero.getString(columns[0]) },
{ dataKey: 'message', label: Zotero.getString(columns[1]) },
];
const domEl = document.querySelector('#tree');
let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
<VirtualizedTable
getRowCount={() => _progressQueue.getRows().length}
id="locateManager-table"
ref={ref => io.tree = _tree = ref}
renderItem={_rowToTreeItem}
showHeader={true}
columns={tableColumns}
onActivate={_handleActivate}
/>
</IntlProvider>
);
ReactDom.render(elem, domEl);
}
/**
* Focus items in Zotero library when double-clicking them in the Retrieve
* metadata window.
* @param {Event} event
* @param {Number[]} indices to activate
* @private
*/
async function _handleActivate(event, indices) {
if (event && event.type === 'dblclick') {
let itemID = _progressQueue.getRows()[indices[0]].id;
if (!itemID) return;
let item = await Zotero.Items.getAsync(itemID);
if (!item) return;
if (item.parentItemID) itemID = item.parentItemID;
let win = Services.wm.getMostRecentWindow("navigator:browser");
if (win) {
win.ZoteroPane.selectItem(itemID, false, true);
win.focus();
}
}
}

View file

@ -1,10 +1,15 @@
<?xml version="1.0" ?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
width="550" height="230"
onload="_init()"
id="zotero-progress">
<script src="include.js"></script>
<script src="progressQueueDialog.js"/>
<vbox style="padding:10px" flex="1">
<label id="label" control="progress-indicator" value=""/>
<hbox align="center">
@ -13,15 +18,8 @@
<button id="minimize-button" label="&zotero.general.minimize;"/>
<button id="close-button" label="&zotero.general.close;"/>
</hbox>
<tree flex="1" id="tree" hidecolumnpicker="true">
<treecols>
<treecol id="success-col" style="width:20px;"/>
<splitter class="tree-splitter" hidden="true"/>
<treecol id="col1" flex="1"/>
<splitter class="tree-splitter"/>
<treecol id="col2" flex="2"/>
</treecols>
<treechildren id="treechildren"/>
</tree>
<hbox class="virtualized-table-container" flex="1" height="200px">
<html:div id="tree"/>
</hbox>
</vbox>
</window>

View file

@ -28,6 +28,11 @@
*/
import FilePicker from 'zotero/filePicker';
import React from 'react';
import ReactDom from 'react-dom';
import VirtualizedTable from 'components/virtualized-table';
import { IntlProvider } from 'react-intl';
import { getDomElement } from 'components/icons';
/**
* Front end for recognizing PDFs
@ -37,11 +42,24 @@ var Zotero_RTFScan = new function() {
const ACCEPT_ICON = "chrome://zotero/skin/rtfscan-accept.png";
const LINK_ICON = "chrome://zotero/skin/rtfscan-link.png";
const BIBLIOGRAPHY_PLACEHOLDER = "\\{Bibliography\\}";
const columns = [
{ dataKey: 'rtf', label: "zotero.rtfScan.citation.label", primary: true, flex: 4 },
{ dataKey: 'item', label: "zotero.rtfScan.itemName.label", flex: 5 },
{ dataKey: 'action', label: "", fixedWidth: true, width: "26px" },
];
var ids = 0;
var tree;
this._rows = [
{ id: 'unmapped', rtf: Zotero.Intl.strings['zotero.rtfScan.unmappedCitations.label'], collapsed: false },
{ id: 'ambiguous', rtf: Zotero.Intl.strings['zotero.rtfScan.ambiguousCitations.label'], collapsed: false },
{ id: 'mapped', rtf: Zotero.Intl.strings['zotero.rtfScan.mappedCitations.label'], collapsed: false },
];
this._rowMap = {};
this._rows.forEach((row, index) => this._rowMap[row.id] = index);
var inputFile = null, outputFile = null;
var unmappedCitationsItem, ambiguousCitationsItem, mappedCitationsItem;
var unmappedCitationsChildren, ambiguousCitationsChildren, mappedCitationsChildren;
var citations, citationItemIDs, allCitedItemIDs, contents;
var citations, citationItemIDs, contents;
/** INTRO PAGE UI **/
@ -127,28 +145,31 @@ var Zotero_RTFScan = new function() {
/**
* Called when second page is shown.
*/
this.scanPageShowing = function() {
this.scanPageShowing = async function () {
// can't advance
document.documentElement.canAdvance = false;
// wait a ms so that UI thread gets updated
window.setTimeout(function() { _scanRTF() }, 1);
}
try {
await this._scanRTF();
}
catch (e) {
Zotero.logError(e);
Zotero.debug(e);
}
};
/**
* Scans file for citations, then proceeds to next wizard page.
*/
var _scanRTF = Zotero.Promise.coroutine(function* () {
this._scanRTF = async () => {
// set up globals
citations = [];
citationItemIDs = {};
unmappedCitationsItem = document.getElementById("unmapped-citations-item");
ambiguousCitationsItem = document.getElementById("ambiguous-citations-item");
mappedCitationsItem = document.getElementById("mapped-citations-item");
unmappedCitationsChildren = document.getElementById("unmapped-citations-children");
ambiguousCitationsChildren = document.getElementById("ambiguous-citations-children");
mappedCitationsChildren = document.getElementById("mapped-citations-children");
let unmappedRow = this._rows[this._rowMap['unmapped']];
let ambiguousRow = this._rows[this._rowMap['ambiguous']];
let mappedRow = this._rows[this._rowMap['mapped']];
// set up regular expressions
// this assumes that names are >=2 chars or only capital initials and that there are no
@ -165,57 +186,60 @@ var Zotero_RTFScan = new function() {
contents = Zotero.File.getContents(inputFile).replace(/([^\\\r])\r?\n/, "$1 ").replace("\\'92", "'", "g").replace("\\rquote ", "");
var m;
var lastCitation = false;
while((m = citationRe.exec(contents))) {
while ((m = citationRe.exec(contents))) {
// determine whether suppressed or standard regular expression was used
if(m[2]) { // standard parenthetical
if (m[2]) { // standard parenthetical
var citationString = m[2];
var creators = m[3];
var etAl = !!m[4];
var title = m[5];
var date = m[6];
var pages = m[7];
var start = citationRe.lastIndex-m[0].length;
var end = citationRe.lastIndex+2;
} else { // suppressed
var citationString = m[8];
var creators = m[9];
var etAl = !!m[10];
var title = false;
var date = m[12];
var pages = false;
var start = citationRe.lastIndex-m[11].length;
var end = citationRe.lastIndex;
var start = citationRe.lastIndex - m[0].length;
var end = citationRe.lastIndex + 2;
}
else { // suppressed
citationString = m[8];
creators = m[9];
etAl = !!m[10];
title = false;
date = m[12];
pages = false;
start = citationRe.lastIndex - m[11].length;
end = citationRe.lastIndex;
}
citationString = citationString.replace("\\{", "{", "g").replace("\\}", "}", "g");
var suppressAuthor = !m[2];
if(lastCitation && lastCitation.end >= start) {
if (lastCitation && lastCitation.end >= start) {
// if this citation is just an extension of the last, add items to it
lastCitation.citationStrings.push(citationString);
lastCitation.pages.push(pages);
lastCitation.end = end;
} else {
}
else {
// otherwise, add another citation
var lastCitation = {"citationStrings":[citationString], "pages":[pages], "start":start,
"end":end, "suppressAuthor":suppressAuthor};
lastCitation = { citationStrings: [citationString], pages: [pages],
start, end, suppressAuthor };
citations.push(lastCitation);
}
// only add each citation once
if(citationItemIDs[citationString]) continue;
Zotero.debug("Found citation "+citationString);
if (citationItemIDs[citationString]) continue;
Zotero.debug("Found citation " + citationString);
// for each individual match, look for an item in the database
var s = new Zotero.Search;
creators = creators.replace(".", "");
creators = creators.replace(".", "");
// TODO: localize "et al." term
creators = creators.split(creatorSplitRe);
for(var i=0; i<creators.length; i++) {
if(!creators[i]) {
if(i == creators.length-1) {
for (let i = 0; i < creators.length; i++) {
if (!creators[i]) {
if (i == creators.length - 1) {
break;
} else {
}
else {
creators.splice(i, 1);
}
}
@ -224,73 +248,66 @@ var Zotero_RTFScan = new function() {
var lastName = spaceIndex == -1 ? creators[i] : creators[i].substr(spaceIndex+1);
s.addCondition("lastName", "contains", lastName);
}
if(title) s.addCondition("title", "contains", title);
if (title) s.addCondition("title", "contains", title);
s.addCondition("date", "is", date);
var ids = yield s.search();
Zotero.debug("Mapped to "+ids);
var ids = await s.search();
Zotero.debug("Mapped to " + ids);
citationItemIDs[citationString] = ids;
if(!ids) { // no mapping found
unmappedCitationsChildren.appendChild(_generateItem(citationString, ""));
unmappedCitationsItem.hidden = undefined;
} else { // some mapping found
var items = yield Zotero.Items.getAsync(ids);
if(items.length > 1) {
if (!ids) { // no mapping found
let row = _generateItem(citationString, "");
row.parent = unmappedRow;
this._insertRows(row, this._rowMap.ambiguous);
}
else { // some mapping found
var items = await Zotero.Items.getAsync(ids);
if (items.length > 1) {
// check to see how well the author list matches the citation
var matchedItems = [];
for(var i=0; i<items.length; i++) {
yield items[i].loadDataType('creators');
if(_matchesItemCreators(creators, items[i])) matchedItems.push(items[i]);
for (let item of items) {
await item.loadAllData();
if (_matchesItemCreators(creators, item)) matchedItems.push(item);
}
if(matchedItems.length != 0) items = matchedItems;
if (matchedItems.length != 0) items = matchedItems;
}
if(items.length == 1) { // only one mapping
mappedCitationsChildren.appendChild(_generateItem(citationString, items[0].getField("title")));
if (items.length == 1) { // only one mapping
await items[0].loadAllData();
let row = _generateItem(citationString, items[0].getField("title"));
row.parent = mappedRow;
this._insertRows(row, this._rows.length);
citationItemIDs[citationString] = [items[0].id];
mappedCitationsItem.hidden = undefined;
} else { // ambiguous mapping
var treeitem = _generateItem(citationString, "");
}
else { // ambiguous mapping
let row = _generateItem(citationString, "");
row.parent = ambiguousRow;
this._insertRows(row, this._rowMap.mapped);
// generate child items
var treeitemChildren = document.createElement('treechildren');
treeitem.appendChild(treeitemChildren);
for(var i=0; i<items.length; i++) {
treeitemChildren.appendChild(_generateItem("", items[i].getField("title"), true));
let children = [];
for (let item of items) {
let childRow = _generateItem("", item.getField("title"), true);
childRow.parent = row;
children.push(childRow);
}
treeitem.setAttribute("container", "true");
treeitem.setAttribute("open", "true");
ambiguousCitationsChildren.appendChild(treeitem);
ambiguousCitationsItem.hidden = undefined;
this._insertRows(children, this._rowMap[row.id] + 1);
}
}
}
tree.invalidate();
// when scanning is complete, go to citations page
document.documentElement.canAdvance = true;
document.documentElement.advance();
});
};
function _generateItem(citationString, itemName, accept) {
var treeitem = document.createElement('treeitem');
var treerow = document.createElement('treerow');
var treecell = document.createElement('treecell');
treecell.setAttribute("label", citationString);
treerow.appendChild(treecell);
var treecell = document.createElement('treecell');
treecell.setAttribute("label", itemName);
treerow.appendChild(treecell);
var treecell = document.createElement('treecell');
treecell.setAttribute("src", accept ? ACCEPT_ICON : LINK_ICON);
treerow.appendChild(treecell);
treeitem.appendChild(treerow);
return treeitem;
function _generateItem(citationString, itemName, action) {
return {
rtf: citationString,
item: itemName,
action
};
}
function _matchesItemCreators(creators, item, etAl) {
@ -366,28 +383,23 @@ var Zotero_RTFScan = new function() {
* Called when the citations page is rewound. Removes all citations from the list, clears
* globals, and returns to intro page.
*/
this.citationsPageRewound = function() {
this.citationsPageRewound = function () {
// skip back to intro page
document.documentElement.currentPage = document.getElementById('intro-page');
// remove children from tree
while(unmappedCitationsChildren.hasChildNodes()) {
unmappedCitationsChildren.removeChild(unmappedCitationsChildren.firstChild);
}
while(ambiguousCitationsChildren.hasChildNodes()) {
ambiguousCitationsChildren.removeChild(ambiguousCitationsChildren.firstChild);
}
while(mappedCitationsChildren.hasChildNodes()) {
mappedCitationsChildren.removeChild(mappedCitationsChildren.firstChild);
}
// hide headings
unmappedCitationsItem.hidden = ambiguousCitationsItem.hidden = mappedCitationsItem.hidden = true;
this._rows = [
{ id: 'unmapped', rtf: Zotero.Intl.strings['zotero.rtfScan.unmappedCitations.label'], collapsed: false },
{ id: 'ambiguous', rtf: Zotero.Intl.strings['zotero.rtfScan.ambiguousCitations.label'], collapsed: false },
{ id: 'mapped', rtf: Zotero.Intl.strings['zotero.rtfScan.mappedCitations.label'], collapsed: false },
];
this._rowMap = {};
this._rows.forEach((row, index) => this._rowMap[row.id] = index);
return false;
}
/**
* Called when a tree item is clicked to remap a citation, or accept a suggestion for an
* Called when a tree item is clicked to remap a citation, or accept a suggestion for an
* ambiguous citation
*/
this.treeClick = function(event) {
@ -400,53 +412,7 @@ var Zotero_RTFScan = new function() {
// figure out which item this corresponds to
row = row.value;
var level = tree.view.getLevel(row);
if(col.value.index == 2 && level > 0) {
var iconColumn = col.value;
var itemNameColumn = iconColumn.getPrevious();
var citationColumn = itemNameColumn.getPrevious();
if(level == 2) { // ambiguous citation item
// get relevant information
var parentIndex = tree.view.getParentIndex(row);
var citation = tree.view.getCellText(parentIndex, citationColumn);
var itemName = tree.view.getCellText(row, itemNameColumn);
// update item name on parent and delete children
tree.view.setCellText(parentIndex, itemNameColumn, itemName);
var treeitem = tree.view.getItemAtIndex(row);
treeitem.parentNode.parentNode.removeChild(treeitem.parentNode);
// update array
citationItemIDs[citation] = [citationItemIDs[citation][row-parentIndex-1]];
} else { // mapped or unmapped citation, or ambiguous citation parent
var citation = tree.view.getCellText(row, citationColumn);
var io = {singleSelection:true};
if(citationItemIDs[citation] && citationItemIDs[citation].length == 1) { // mapped citation
// specify that item should be selected in window
io.select = citationItemIDs[citation][0];
}
window.openDialog('chrome://zotero/content/selectItemsDialog.xul', '', 'chrome,modal', io);
if(io.dataOut && io.dataOut.length) {
var selectedItemID = io.dataOut[0];
var selectedItem = Zotero.Items.get(selectedItemID);
var treeitem = tree.view.getItemAtIndex(row);
// remove any children (if ambiguous)
var children = treeitem.getElementsByTagName("treechildren");
if(children.length) treeitem.removeChild(children[0]);
// update item name
tree.view.setCellText(row, itemNameColumn, selectedItem.getField("title"));
// update array
citationItemIDs[citation] = [selectedItemID];
}
}
}
_refreshCanAdvance();
}
/**
@ -471,7 +437,8 @@ var Zotero_RTFScan = new function() {
/**
* Called when style page is shown to add styles to listbox.
*/
this.stylePageShowing = function() {
this.stylePageShowing = async function() {
await Zotero.Styles.init();
Zotero_File_Interface_Bibliography.init({
supportedNotes: ['footnotes', 'endnotes']
});
@ -607,4 +574,210 @@ var Zotero_RTFScan = new function() {
document.documentElement.canAdvance = true;
document.documentElement.advance();
}
this._onTwistyMouseUp = (event, index) => {
const row = this._rows[index];
if (!row.collapsed) {
// Store children rows on the parent when collapsing
row.children = [];
const depth = this._getRowLevel(index);
for (let childIndex = index + 1; childIndex < this._rows.length && this._getRowLevel(this._rows[childIndex]) > depth; childIndex++) {
row.children.push(this._rows[childIndex]);
}
// And then remove them
this._removeRows(row.children.map((_, childIndex) => index + 1 + childIndex));
}
else {
// Insert children rows from the ones stored on the parent
this._insertRows(row.children, index + 1);
delete row.children;
}
row.collapsed = !row.collapsed;
tree.invalidate();
};
this._onActionMouseUp = (event, index) => {
let row = this._rows[index];
if (!row.parent) return;
let level = this._getRowLevel(row);
if (level == 2) { // ambiguous citation item
let parentIndex = this._rowMap[row.parent.id];
// Update parent item
row.parent.item = row.item;
// Remove children
let children = [];
for (let childIndex = parentIndex + 1; childIndex < this._rows.length && this._getRowLevel(this._rows[childIndex]) >= level; childIndex++) {
children.push(this._rows[childIndex]);
}
this._removeRows(children.map((_, childIndex) => parentIndex + 1 + childIndex));
// Move citation to mapped rows
row.parent.parent = this._rows[this._rowMap.mapped];
this._removeRows(parentIndex);
this._insertRows(row.parent, this._rows.length);
// update array
citationItemIDs[row.parent.rtf] = [citationItemIDs[row.parent.rtf][index-parentIndex-1]];
}
else { // mapped or unmapped citation, or ambiguous citation parent
var citation = row.rtf;
var io = { singleSelection: true };
if (citationItemIDs[citation] && citationItemIDs[citation].length == 1) { // mapped citation
// specify that item should be selected in window
io.select = citationItemIDs[citation][0];
}
window.openDialog('chrome://zotero/content/selectItemsDialog.xul', '', 'chrome,modal', io);
if (io.dataOut && io.dataOut.length) {
var selectedItemID = io.dataOut[0];
var selectedItem = Zotero.Items.get(selectedItemID);
// update item name
row.item = selectedItem.getField("title");
// Remove children
let children = [];
for (let childIndex = index + 1; childIndex < this._rows.length && this._getRowLevel(this._rows[childIndex]) > level; childIndex++) {
children.push(this._rows[childIndex]);
}
this._removeRows(children.map((_, childIndex) => index + 1 + childIndex));
if (row.parent.id != 'mapped') {
// Move citation to mapped rows
row.parent = this._rows[this._rowMap.mapped];
this._removeRows(index);
this._insertRows(row, this._rows.length);
}
// update array
citationItemIDs[citation] = [selectedItemID];
}
}
tree.invalidate();
_refreshCanAdvance();
};
this._insertRows = (rows, beforeRow) => {
if (!Array.isArray(rows)) {
rows = [rows];
}
this._rows.splice(beforeRow, 0, ...rows);
rows.forEach(row => row.id = ids++);
for (let row of rows) {
row.id = ids++;
}
// Refresh the row map
this._rowMap = {};
this._rows.forEach((row, index) => this._rowMap[row.id] = index);
};
this._removeRows = (indices) => {
if (!Array.isArray(indices)) {
indices = [indices];
}
// Reverse sort so we can safely splice out the entries from the rows array
indices.sort((a, b) => b - a);
for (const index of indices) {
this._rows.splice(index, 1);
}
// Refresh the row map
this._rowMap = {};
this._rows.forEach((row, index) => this._rowMap[row.id] = index);
};
this._getRowLevel = (row, depth=0) => {
if (typeof row == 'number') {
row = this._rows[row];
}
if (!row.parent) {
return depth;
}
return this._getRowLevel(row.parent, depth+1);
}
this._renderItem = (index, selection, oldDiv=null, columns) => {
const row = this._rows[index];
let div;
if (oldDiv) {
div = oldDiv;
div.innerHTML = "";
}
else {
div = document.createElementNS("http://www.w3.org/1999/xhtml", 'div');
div.className = "row";
}
for (const column of columns) {
if (column.primary) {
let twisty;
if (row.children || (this._rows[index + 1] && this._rows[index + 1].parent == row)) {
twisty = getDomElement("IconTwisty");
twisty.classList.add('twisty');
if (!row.collapsed) {
twisty.classList.add('open');
}
twisty.style.pointerEvents = 'auto';
twisty.addEventListener('mousedown', event => event.stopPropagation());
twisty.addEventListener('mouseup', event => this._onTwistyMouseUp(event, index),
{ passive: true });
}
else {
twisty = document.createElementNS("http://www.w3.org/1999/xhtml", 'span');
twisty.classList.add("spacer-twisty");
}
let textSpan = document.createElementNS("http://www.w3.org/1999/xhtml", 'span');
textSpan.className = "cell-text";
textSpan.innerText = row[column.dataKey] || "";
let span = document.createElementNS("http://www.w3.org/1999/xhtml", 'span');
span.className = `cell primary ${column.className}`;
span.appendChild(twisty);
span.appendChild(textSpan);
span.style.paddingLeft = (5 + 20 * this._getRowLevel(row)) + 'px';
div.appendChild(span);
}
else if (column.dataKey == 'action') {
let span = document.createElementNS("http://www.w3.org/1999/xhtml", 'span');
span.className = `cell action ${column.className}`;
if (row.parent) {
if (row.action) {
span.appendChild(getDomElement('IconRTFScanAccept'));
}
else {
span.appendChild(getDomElement('IconRTFScanLink'));
}
span.addEventListener('mouseup', e => this._onActionMouseUp(e, index), { passive: true });
span.style.pointerEvents = 'auto';
}
div.appendChild(span);
}
else {
let span = document.createElementNS("http://www.w3.org/1999/xhtml", 'span');
span.className = `cell ${column.className}`;
span.innerText = row[column.dataKey] || "";
div.appendChild(span);
}
}
return div;
};
this._initCitationTree = function () {
const domEl = document.querySelector('#tree');
const elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
<VirtualizedTable
getRowCount={() => this._rows.length}
id="rtfScan-table"
ref={ref => tree = ref}
renderItem={this._renderItem}
showHeader={true}
columns={columns}
/>
</IntlProvider>
);
return new Promise(resolve => ReactDom.render(elem, domEl, resolve));
};
}

View file

@ -2,10 +2,13 @@
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/upgrade.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/bibliography.css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="&zotero.rtfScan.title;" width="700" height="550"
onload="Zotero_RTFScan._initCitationTree()"
id="zotero-rtfScan">
<script src="include.js"/>
@ -52,34 +55,10 @@
onpageshow="Zotero_RTFScan.citationsPageShowing()"
onpagerewound="return Zotero_RTFScan.citationsPageRewound();">
<description width="700">&zotero.rtfScan.citationsPage.description;</description>
<tree flex="1" id="tree" hidecolumnpicker="true" onclick="Zotero_RTFScan.treeClick(event)">
<treecols>
<treecol label="&zotero.rtfScan.citation.label;" id="pdf-col" flex="1" primary="true"/>
<splitter class="tree-splitter"/>
<treecol label="&zotero.rtfScan.itemName.label;" id="item-col" flex="2"/>
<treecol id="action-col" style="width:40px"/>
</treecols>
<treechildren id="treechildren">
<treeitem id="unmapped-citations-item" container="true" open="true" hidden="true">
<treerow>
<treecell label="&zotero.rtfScan.unmappedCitations.label;"/>
</treerow>
<treechildren id="unmapped-citations-children"/>
</treeitem>
<treeitem id="ambiguous-citations-item" container="true" open="true" hidden="true">
<treerow>
<treecell label="&zotero.rtfScan.ambiguousCitations.label;"/>
</treerow>
<treechildren id="ambiguous-citations-children"/>
</treeitem>
<treeitem id="mapped-citations-item" container="true" open="true" hidden="true">
<treerow>
<treecell label="&zotero.rtfScan.mappedCitations.label;"/>
</treerow>
<treechildren id="mapped-citations-children"/>
</treeitem>
</treechildren>
</tree>
<hbox class="virtualized-table-container" flex="1" height="500">
<html:div id="tree"/>
</hbox>
</wizardpage>
<wizardpage id="style-page" label="&zotero.rtfScan.stylePage.label;"

View file

@ -23,17 +23,20 @@
***** END LICENSE BLOCK *****
*/
import CollectionTree from 'containers/collectionTree';
import ItemTree from 'containers/itemTree';
var itemsView;
var collectionsView;
var io;
var connectionSelectedDeferred;
const isEditBibliographyDialog = !!document.querySelector('#zotero-edit-bibliography-dialog');
const isAddEditItemsDialog = !!document.querySelector('#zotero-add-citation-dialog');
/*
* window takes two arguments:
* io - used for input/output (dataOut is list of item IDs)
* sourcesOnly - whether only sources should be shown in the window
*/
var doLoad = Zotero.Promise.coroutine(function* () {
var doLoad = async function () {
// Set font size from pref
var sbc = document.getElementById('zotero-select-items-container');
Zotero.setFontSize(sbc);
@ -43,67 +46,75 @@ var doLoad = Zotero.Promise.coroutine(function* () {
if(io.addBorder) document.getElementsByTagName("dialog")[0].style.border = "1px solid black";
if(io.singleSelection) document.getElementById("zotero-items-tree").setAttribute("seltype", "single");
setItemsPaneMessage(Zotero.getString('pane.items.loading'));
collectionsView = new Zotero.CollectionTreeView();
itemsView = await ItemTree.init(document.getElementById('zotero-items-tree'), {
onSelectionChange: () => {
if (isEditBibliographyDialog) {
Zotero_Bibliography_Dialog.treeItemSelected();
}
else if (isAddEditItemsDialog) {
onItemSelected();
Zotero_Citation_Dialog.treeItemSelected();
}
else {
onItemSelected();
}
},
id: "select-items-dialog",
dragAndDrop: false,
persistColumns: false,
columnPicker: true,
emptyMessage: Zotero.getString('pane.items.loading')
});
itemsView.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
collectionsView = await CollectionTree.init(document.getElementById('zotero-collections-tree'), {
onSelectionChange: Zotero.Utilities.debounce(() => onCollectionSelected(), 100),
});
collectionsView.hideSources = ['duplicates', 'trash', 'feeds'];
document.getElementById('zotero-collections-tree').view = collectionsView;
yield collectionsView.waitForLoad();
connectionSelectedDeferred = Zotero.Promise.defer();
yield connectionSelectedDeferred.promise;
await collectionsView.makeVisible();
if (io.select) {
yield collectionsView.selectItem(io.select);
await collectionsView.selectItem(io.select);
}
Zotero.updateQuickSearchBox(document);
});
};
function doUnload()
{
collectionsView.unregister();
if(itemsView)
itemsView.unregister();
io.deferred && io.deferred.resolve();
}
var onCollectionSelected = Zotero.Promise.coroutine(function* ()
{
if(itemsView)
itemsView.unregister();
if(collectionsView.selection.count == 1 && collectionsView.selection.currentIndex != -1)
{
var collectionTreeRow = collectionsView.getRow(collectionsView.selection.currentIndex);
collectionTreeRow.setSearch('');
Zotero.Prefs.set('lastViewedFolder', collectionTreeRow.id);
setItemsPaneMessage(Zotero.getString('pane.items.loading'));
// Load library data if necessary
var library = Zotero.Libraries.get(collectionTreeRow.ref.libraryID);
if (!library.getDataLoaded('item')) {
Zotero.debug("Waiting for items to load for library " + library.libraryID);
yield library.waitForDataLoad('item');
}
// Create items list and wait for it to load
itemsView = new Zotero.ItemTreeView(collectionTreeRow);
itemsView.sourcesOnly = !!window.arguments[1];
document.getElementById('zotero-items-tree').view = itemsView;
yield itemsView.waitForLoad();
clearItemsPaneMessage();
connectionSelectedDeferred.resolve();
collectionsView.runListeners('select');
var onCollectionSelected = async function () {
if (!collectionsView.selection.count) return;
var collectionTreeRow = collectionsView.getRow(collectionsView.selection.focused);
collectionTreeRow.setSearch('');
Zotero.Prefs.set('lastViewedFolder', collectionTreeRow.id);
itemsView.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
// Load library data if necessary
var library = Zotero.Libraries.get(collectionTreeRow.ref.libraryID);
if (!library.getDataLoaded('item')) {
Zotero.debug("Waiting for items to load for library " + library.libraryID);
await library.waitForDataLoad('item');
}
});
await itemsView.changeCollectionTreeRow(collectionTreeRow);
itemsView.clearItemsPaneMessage();
collectionsView.runListeners('select');
};
function onSearch()
{
if(itemsView)
if (itemsView)
{
var searchVal = document.getElementById('zotero-tb-search').value;
itemsView.setFilter('search', searchVal);
@ -115,30 +126,6 @@ function onItemSelected()
itemsView.runListeners('select');
}
function setItemsPaneMessage(content) {
var elem = document.getElementById('zotero-items-pane-message-box');
elem.textContent = '';
if (typeof content == 'string') {
let contentParts = content.split("\n\n");
for (let part of contentParts) {
var desc = document.createElement('description');
desc.appendChild(document.createTextNode(part));
elem.appendChild(desc);
}
}
else {
elem.appendChild(content);
}
document.getElementById('zotero-items-pane-content').selectedIndex = 1;
}
function clearItemsPaneMessage() {
var box = document.getElementById('zotero-items-pane-message-box');
document.getElementById('zotero-items-pane-content').selectedIndex = 0;
}
function doAccept()
{
function doAccept() {
io.dataOut = itemsView.getSelectedItems(true);
}

View file

@ -27,6 +27,7 @@
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<dialog
@ -40,6 +41,7 @@
onload="doLoad();"
onunload="doUnload();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
style="padding:2em"
persist="screenX screenY width height">
@ -54,203 +56,13 @@
</hbox>
<hbox flex="1">
<tree id="zotero-collections-tree"
style="width: 200px;" hidecolumnpicker="true" seltype="cell"
onselect="onCollectionSelected();">
<treecols>
<treecol
id="zotero-collections-name-column"
flex="1"
primary="true"
hideheader="true"/>
</treecols>
<treechildren/>
</tree>
<deck id="zotero-items-pane-content" selectedIndex="0" flex="1">
<tree id="zotero-items-tree"
enableColumnDrag="true" flex="1" seltype="multiple"
onselect="onItemSelected();">
<treecols id="zotero-items-columns-header">
<treecol
id="zotero-items-column-title" primary="true"
label="&zotero.items.title_column;"
flex="4" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-firstCreator"
label="&zotero.items.creator_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-itemType" hidden="true"
label="&zotero.items.type_column;"
width="40" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-date" hidden="true"
label="&zotero.items.date_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-year" hidden="true"
label="&zotero.items.year_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-publisher" hidden="true"
label="&zotero.items.publisher_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-publicationTitle" hidden="true"
label="&zotero.items.publication_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-journalAbbreviation" hidden="true"
label="&zotero.items.journalAbbr_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-language" hidden="true"
label="&zotero.items.language_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-accessDate" hidden="true"
label="&zotero.items.accessDate_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-libraryCatalog" hidden="true"
label="&zotero.items.libraryCatalog_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-callNumber" hidden="true"
submenu="true"
label="&zotero.items.callNumber_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-rights" hidden="true"
submenu="true"
label="&zotero.items.rights_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-dateAdded" hidden="true"
label="&zotero.items.dateAdded_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-dateModified" hidden="true"
label="&zotero.items.dateModified_column;"
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-archive" hidden="true"
submenu="true"
label="&zotero.items.archive_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-archiveLocation" hidden="true"
submenu="true"
label="&zotero.items.archiveLocation_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-place" hidden="true"
submenu="true"
label="&zotero.items.place_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-volume" hidden="true"
submenu="true"
label="&zotero.items.volume_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-edition" hidden="true"
submenu="true"
label="&zotero.items.edition_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-pages" hidden="true"
submenu="true"
label="&zotero.items.pages_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-issue" hidden="true"
submenu="true"
label="&zotero.items.issue_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-series" hidden="true"
submenu="true"
label="&zotero.items.series_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-seriesTitle" hidden="true"
submenu="true"
label="&zotero.items.seriesTitle_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-court" hidden="true"
submenu="true"
label="&zotero.items.court_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-medium" hidden="true"
submenu="true"
label="&zotero.items.medium_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-genre" hidden="true"
submenu="true"
label="&zotero.items.genre_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-system" hidden="true"
submenu="true"
label="&zotero.items.system_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-extra" hidden="true"
label="&zotero.items.extra_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-hasAttachment" hidden="true"
class="treecol-image"
label="&zotero.tabs.attachments.label;"
zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-numNotes" hidden="true"
class="treecol-image"
label="&zotero.tabs.notes.label;"
zotero-persist="width ordinal hidden sortActive sortDirection"/>
</treecols>
<treechildren/>
</tree>
<!-- Label for displaying messages when items pane is hidden
(e.g. "Advanced search mode — press Enter to search.")-->
<vbox id="zotero-items-pane-message-box" pack="center" align="center"/>
</deck>
<vbox id="zotero-collections-tree-container" class="virtualized-table-container" style="min-width: 200px">
<html:div id="zotero-collections-tree"></html:div>
</vbox>
<hbox id="zotero-items-pane-content" class="virtualized-table-container" flex="1">
<html:div id="zotero-items-tree"></html:div>
</hbox>
</hbox>
</vbox>

View file

@ -24,11 +24,11 @@
*/
const React = require('react');
const ReactDOM = require('react-dom');
const ReactDom = require('react-dom');
function init() {
let div = document.querySelector('div');
ReactDOM.render(<DataGeneratorForm/>, div);
ReactDom.render(<DataGeneratorForm/>, div);
}
class DataGeneratorForm extends React.Component {

View file

@ -29,11 +29,13 @@ Zotero.CollectionTreeRow = function (collectionTreeView, type, ref, level, isOpe
this.view = collectionTreeView;
this.type = type;
this.ref = ref;
this.level = level || 0
this.level = level || 0;
this.isOpen = isOpen || false;
this.onUnload = null;
}
Zotero.CollectionTreeRow.IDCounter = 0;
Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () {
switch (this.type) {
@ -73,7 +75,10 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () {
break;
}
return '';
if (!this._id) {
this._id = 'I' + Zotero.CollectionTreeRow.IDCounter++;
}
return this._id;
});
Zotero.CollectionTreeRow.prototype.isLibrary = function (includeGlobal)
@ -142,6 +147,10 @@ Zotero.CollectionTreeRow.prototype.isShare = function()
return this.type == 'share';
}
Zotero.CollectionTreeRow.prototype.isContainer = function() {
return this.isLibrary(true) || this.isCollection() || this.isPublications() || this.isBucket();
}
// Special
@ -439,3 +448,26 @@ Zotero.CollectionTreeRow.prototype.isSearchMode = function() {
return true;
}
}
Zotero.CollectionTreeCache = {
"lastTreeRow":null,
"lastTempTable":null,
"lastSearch":null,
"lastResults":null,
"clear": function () {
this.lastTreeRow = null;
this.lastSearch = null;
if (this.lastTempTable) {
let tableName = this.lastTempTable;
let id = Zotero.DB.addCallback('commit', async function () {
await Zotero.DB.queryAsync(
"DROP TABLE IF EXISTS " + tableName, false, { noCache: true }
);
Zotero.DB.removeCallback('commit', id);
});
}
this.lastTempTable = null;
this.lastResults = null;
}
}

File diff suppressed because it is too large Load diff

View file

@ -62,7 +62,7 @@ Zotero.Server.Connector = {
if (!editable && !allowReadOnly) {
let userLibrary = Zotero.Libraries.userLibrary;
if (userLibrary && userLibrary.editable) {
Zotero.debug("Save target isn't editable -- switching to My Library");
Zotero.debug("Save target isn't editable -- switching lastViewedFolder to My Library");
let treeViewID = userLibrary.treeViewID;
Zotero.Prefs.set('lastViewedFolder', treeViewID);
({ library, collection, editable } = this.resolveTarget(treeViewID));

View file

@ -66,7 +66,7 @@ Zotero.Feed = function(params = {}) {
// Return a proxy so that we can disable the object once it's deleted
return new Proxy(this, {
get: function(obj, prop) {
if (obj._disabled && !(prop == 'libraryID' || prop == 'id' || prop == 'treeViewID')) {
if (obj._disabled && !(prop == 'libraryID' || prop == 'id' || prop == 'treeViewID' || prop == 'name')) {
throw new Error("Feed " + obj.libraryID + " has been disabled");
}
return obj[prop];

View file

@ -35,7 +35,7 @@ Zotero.Group = function (params = {}) {
// Return a proxy so that we can disable the object once it's deleted
return new Proxy(this, {
get: function(obj, prop) {
if (obj._disabled && !(prop == 'libraryID' || prop == 'id')) {
if (obj._disabled && !(prop == 'libraryID' || prop == 'id' || prop == 'name')) {
throw new Error("Group (" + obj.libraryID + ") has been disabled");
}
return obj[prop];

View file

@ -4253,38 +4253,20 @@ Zotero.Item.prototype.getImageSrc = function() {
}
Zotero.Item.prototype.getImageSrcWithTags = Zotero.Promise.coroutine(function* () {
//Zotero.debug("Generating tree image for item " + this.id);
var uri = this.getImageSrc();
var retracted = Zotero.Retractions.isRetracted(this);
Zotero.Item.prototype.getTagColors = function () {
var tags = this.getTags();
if (!tags.length && !retracted) {
return uri;
}
if (!tags.length) return [];
var colorData = [];
if (tags.length) {
let tagColors = Zotero.Tags.getColors(this.libraryID);
for (let tag of tags) {
let data = tagColors.get(tag.tag);
if (data) {
colorData.push(data);
}
let colorData = [];
let tagColors = Zotero.Tags.getColors(this.libraryID);
for (let tag of tags) {
let data = tagColors.get(tag.tag);
if (data) {
colorData.push(data);
}
if (!colorData.length && !retracted) {
return uri;
}
colorData.sort(function (a, b) {
return a.position - b.position;
});
}
var colors = colorData.map(val => val.color);
return Zotero.Tags.generateItemsListImage(colors, uri, retracted);
});
return colorData.sort((a, b) => a.position - b.position).map(val => val.color);
};

View file

@ -55,7 +55,7 @@ Zotero.Library = function(params = {}) {
// Return a proxy so that we can disable the object once it's deleted
return new Proxy(this, {
get: function(obj, prop) {
if (obj._disabled && !(prop == 'libraryID' || prop == 'id')) {
if (obj._disabled && !(prop == 'libraryID' || prop == 'id' || prop == 'name')) {
throw new Error("Library (" + obj.libraryID + ") has been disabled");
}
return obj[prop];

View file

@ -238,6 +238,9 @@ Zotero.Debug = new function () {
});
});
this._setLevel = function(level) {
_level = level;
}
this.addListener = function (listener) {
this.enabled = true;

View file

@ -0,0 +1,245 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
*/
// Implements nsIFlavorDataProvider for dragging attachment files to OS
//
// Not used on Windows in Firefox 3 or higher
Zotero.FileDragDataProvider = function (itemIDs) {
this._itemIDs = itemIDs;
};
Zotero.FileDragDataProvider.prototype = {
QueryInterface : function(iid) {
if (iid.equals(Components.interfaces.nsIFlavorDataProvider) ||
iid.equals(Components.interfaces.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
},
getFlavorData : function(transferable, flavor, data, dataLen) {
Zotero.debug("Getting flavor data for " + flavor);
if (flavor == "application/x-moz-file-promise") {
// On platforms other than OS X, the only directory we know of here
// is the system temp directory, and we pass the nsIFile of the file
// copied there in data.value below
var useTemp = !Zotero.isMac;
// Get the destination directory
var dirPrimitive = {};
var dataSize = {};
transferable.getTransferData("application/x-moz-file-promise-dir", dirPrimitive, dataSize);
var destDir = dirPrimitive.value.QueryInterface(Components.interfaces.nsIFile);
var draggedItems = Zotero.Items.get(this._itemIDs);
var items = [];
// Make sure files exist
var notFoundNames = [];
for (var i=0; i<draggedItems.length; i++) {
// TODO create URL?
if (!draggedItems[i].isAttachment() ||
draggedItems[i].getAttachmentLinkMode() == Zotero.Attachments.LINK_MODE_LINKED_URL) {
continue;
}
if (draggedItems[i].getFile()) {
items.push(draggedItems[i]);
}
else {
notFoundNames.push(draggedItems[i].getField('title'));
}
}
// If using the temp directory, create a directory to store multiple
// files, since we can (it seems) only pass one nsIFile in data.value
if (useTemp && items.length > 1) {
var tmpDirName = 'Zotero Dragged Files';
destDir.append(tmpDirName);
if (destDir.exists()) {
destDir.remove(true);
}
destDir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o755);
}
var copiedFiles = [];
var existingItems = [];
var existingFileNames = [];
for (var i=0; i<items.length; i++) {
// TODO create URL?
if (!items[i].isAttachment() ||
items[i].attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
continue;
}
var file = items[i].getFile();
// Determine if we need to copy multiple files for this item
// (web page snapshots)
if (items[i].attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_FILE) {
var parentDir = file.parent;
var files = parentDir.directoryEntries;
var numFiles = 0;
while (files.hasMoreElements()) {
var f = files.getNext();
f.QueryInterface(Components.interfaces.nsIFile);
if (f.leafName.indexOf('.') != 0) {
numFiles++;
}
}
}
// Create folder if multiple files
if (numFiles > 1) {
var dirName = Zotero.Attachments.getFileBaseNameFromItem(items[i]);
try {
if (useTemp) {
var copiedFile = destDir.clone();
copiedFile.append(dirName);
if (copiedFile.exists()) {
// If item directory already exists in the temp dir,
// delete it
if (items.length == 1) {
copiedFile.remove(true);
}
// If item directory exists in the container
// directory, it's a duplicate, so give this one
// a different name
else {
copiedFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o644);
var newName = copiedFile.leafName;
copiedFile.remove(null);
}
}
}
parentDir.copyToFollowingLinks(destDir, newName ? newName : dirName);
// Store nsIFile
if (useTemp) {
copiedFiles.push(copiedFile);
}
}
catch (e) {
if (e.name == 'NS_ERROR_FILE_ALREADY_EXISTS') {
// Keep track of items that already existed
existingItems.push(items[i].id);
existingFileNames.push(dirName);
}
else {
throw (e);
}
}
}
// Otherwise just copy
else {
try {
if (useTemp) {
var copiedFile = destDir.clone();
copiedFile.append(file.leafName);
if (copiedFile.exists()) {
// If file exists in the temp directory,
// delete it
if (items.length == 1) {
copiedFile.remove(true);
}
// If file exists in the container directory,
// it's a duplicate, so give this one a different
// name
else {
copiedFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o644);
var newName = copiedFile.leafName;
copiedFile.remove(null);
}
}
}
file.copyToFollowingLinks(destDir, newName ? newName : null);
// Store nsIFile
if (useTemp) {
copiedFiles.push(copiedFile);
}
}
catch (e) {
if (e.name == 'NS_ERROR_FILE_ALREADY_EXISTS') {
existingItems.push(items[i].id);
existingFileNames.push(items[i].getFile().leafName);
}
else {
throw (e);
}
}
}
}
// Files passed via data.value will be automatically moved
// from the temp directory to the destination directory
if (useTemp && copiedFiles.length) {
if (items.length > 1) {
data.value = destDir.QueryInterface(Components.interfaces.nsISupports);
}
else {
data.value = copiedFiles[0].QueryInterface(Components.interfaces.nsISupports);
}
dataLen.value = 4;
}
if (notFoundNames.length || existingItems.length) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
}
// Display alert if files were not found
if (notFoundNames.length > 0) {
// On platforms that use a temporary directory, an alert here
// would interrupt the dragging process, so we just log a
// warning to the console
if (useTemp) {
for (let name of notFoundNames) {
var msg = "Attachment file for dragged item '" + name + "' not found";
Zotero.log(msg, 'warning',
'chrome://zotero/content/xpcom/itemTreeView.js');
}
}
else {
promptService.alert(null, Zotero.getString('general.warning'),
Zotero.getString('dragAndDrop.filesNotFound') + "\n\n"
+ notFoundNames.join("\n"));
}
}
// Display alert if existing files were skipped
if (existingItems.length > 0) {
promptService.alert(null, Zotero.getString('general.warning'),
Zotero.getString('dragAndDrop.existingFiles') + "\n\n"
+ existingFileNames.join("\n"));
}
}
}
}

View file

@ -51,7 +51,7 @@ Zotero.Intl = new function () {
Zotero.Utilities.Internal.quitZotero(true);
return;
}
}
}
Components.utils.import("resource://gre/modules/PluralForm.jsm");
@ -76,7 +76,7 @@ Zotero.Intl = new function () {
Zotero.rtl = (Zotero.dir === 'rtl');
this.strings = {};
const intlFiles = ['zotero.dtd', 'mozilla/editMenuOverlay.dtd'];
const intlFiles = ['zotero.dtd', 'preferences.dtd', 'mozilla/editMenuOverlay.dtd'];
for (let intlFile of intlFiles) {
let localeXML = Zotero.File.getContentsFromURL(`chrome://zotero/locale/${intlFile}`);
let regexp = /<!ENTITY ([^\s]+)\s+"([^"]+)/g;

File diff suppressed because it is too large Load diff

View file

@ -1,545 +0,0 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2013 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
*/
Zotero.LibraryTreeView = function () {
this._initialized = false;
this._listeners = {};
this._rows = [];
this._rowMap = {};
this.id = Zotero.Utilities.randomString();
Zotero.debug("Creating " + this.type + "s view with id " + this.id);
//
// Create .on(Load|Select|Refresh).addListener() methods
//
var _createEventBinding = function (event, alwaysOnce) {
return alwaysOnce
? {
addListener: listener => this._addListener(event, listener, true)
}
: {
addListener: (listener, once) => this._addListener(event, listener, once)
};
}.bind(this);
this.onLoad = _createEventBinding('load', true);
this.onSelect = _createEventBinding('select');
this.onRefresh = _createEventBinding('refresh');
};
Zotero.LibraryTreeView.prototype = {
get initialized() {
return this._initialized;
},
addEventListener: function (event, listener) {
Zotero.logError("Zotero.LibraryTreeView::addEventListener() is deprecated");
this.addListener(event, listener);
},
waitForLoad: function () {
return this._waitForEvent('load');
},
waitForSelect: function () {
return this._waitForEvent('select');
},
runListeners: Zotero.Promise.coroutine(function* (event) {
//Zotero.debug(`Calling ${event} listeners on ${this.type} tree ${this.id}`);
if (!this._listeners[event]) return;
for (let [listener, once] of this._listeners[event].entries()) {
yield Zotero.Promise.resolve(listener.call(this));
if (once) {
this._listeners[event].delete(listener);
}
}
}),
_addListener: function(event, listener, once) {
// If already initialized run now
if (event == 'load' && this._initialized) {
listener.call(this);
}
else {
if (!this._listeners[event]) {
this._listeners[event] = new Map();
}
this._listeners[event].set(listener, once);
}
},
_waitForEvent: Zotero.Promise.coroutine(function* (event) {
if (event == 'load' && this._initialized) {
return;
}
return new Zotero.Promise((resolve, reject) => {
this._addListener(event, () => resolve(), true);
});
}),
/**
* Return a reference to the tree row at a given row
*
* @return {Zotero.CollectionTreeRow|Zotero.ItemTreeRow}
*/
getRow: function(row) {
return this._rows[row];
},
/**
* Return the index of the row with a given ID (e.g., "C123" for collection 123)
*
* @param {String} - Row id
* @return {Integer|false}
*/
getRowIndexByID: function (id) {
var type = "";
if (this.type != 'item') {
var type = id[0];
id = ('' + id).substr(1);
}
return this._rowMap[type + id] !== undefined ? this._rowMap[type + id] : false;
},
getSelectedRowIndexes: function () {
var rows = [];
var start = {};
var end = {};
for (let i = 0, len = this.selection.getRangeCount(); i < len; i++) {
this.selection.getRangeAt(i, start, end);
for (let j = start.value; j <= end.value; j++) {
rows.push(j);
}
}
return rows;
},
/**
* Return an object describing the current scroll position to restore after changes
*
* @return {Object|Boolean} - Object with .id (a treeViewID) and .offset, or false if no rows
*/
_saveScrollPosition: function() {
var treebox = this._treebox;
var first = treebox.getFirstVisibleRow();
if (!first) {
return false;
}
var last = treebox.getLastVisibleRow();
var firstSelected = null;
for (let i = first; i <= last; i++) {
// If an object is selected, keep the first selected one in position
if (this.selection.isSelected(i)) {
return {
id: this.getRow(i).ref.treeViewID,
offset: i - first
};
}
}
// Otherwise keep the first visible row in position
return {
id: this.getRow(first).ref.treeViewID,
offset: 0
};
},
/**
* Restore a scroll position returned from _saveScrollPosition()
*/
_rememberScrollPosition: function (scrollPosition) {
if (!scrollPosition || !scrollPosition.id) {
return;
}
var row = this.getRowIndexByID(scrollPosition.id);
if (row === false) {
return;
}
this._treebox.scrollToRow(Math.max(row - scrollPosition.offset, 0));
},
runSelectListeners: function () {
return this._runListeners('select');
},
/**
* Add a tree row to the main array, update the row count, tell the treebox that the row
* count changed, and update the row map
*
* @param {Array} newRows - Array to operate on
* @param {Zotero.ItemTreeRow} itemTreeRow
* @param {Number} [beforeRow] - Row index to insert new row before
*/
_addRow: function (treeRow, beforeRow, skipRowMapRefresh) {
this._addRowToArray(this._rows, treeRow, beforeRow);
this.rowCount++;
this._treebox.rowCountChanged(beforeRow, 1);
if (!skipRowMapRefresh) {
// Increment all rows in map at or above insertion point
for (let i in this._rowMap) {
if (this._rowMap[i] >= beforeRow) {
this._rowMap[i]++
}
}
// Add new row to map
this._rowMap[treeRow.id] = beforeRow;
}
},
/**
* Add a tree row into a given array
*
* @param {Array} array - Array to operate on
* @param {Zotero.CollectionTreeRow|ItemTreeRow} treeRow
* @param {Number} beforeRow - Row index to insert new row before
*/
_addRowToArray: function (array, treeRow, beforeRow) {
array.splice(beforeRow, 0, treeRow);
},
/**
* Remove a row from the main array, decrement the row count, tell the treebox that the row
* count changed, update the parent isOpen if necessary, delete the row from the map, and
* optionally update all rows above it in the map
*/
_removeRow: function (row, skipMapUpdate) {
var id = this._rows[row].id;
var level = this.getLevel(row);
var lastRow = row == this.rowCount - 1;
if (lastRow && this.selection.isSelected(row)) {
// Deselect removed row
this.selection.toggleSelect(row);
// If no other rows selected, select first selectable row before
if (this.selection.count == 0 && row !== 0) {
let previous = row;
while (true) {
previous--;
// Should ever happen
if (previous < 0) {
break;
}
if (!this.isSelectable(previous)) {
continue;
}
this.selection.toggleSelect(previous);
break;
}
}
}
this._rows.splice(row, 1);
this.rowCount--;
// According to the example on https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsITreeBoxObject#rowCountChanged
// this should start at row + 1 ("rowCountChanged(rowIndex+1, -1);"), but that appears to
// just be wrong. A negative count indicates removed rows, but the index should still
// start at the place where the removals begin, not after it going backward.
this._treebox.rowCountChanged(row, -1);
// Update isOpen if parent and no siblings
if (row != 0
&& this.getLevel(row - 1) < level
&& (!this._rows[row] || this.getLevel(row) != level)) {
this._rows[row - 1].isOpen = false;
this._treebox.invalidateRow(row - 1);
}
delete this._rowMap[id];
if (!skipMapUpdate) {
for (let i in this._rowMap) {
if (this._rowMap[i] > row) {
this._rowMap[i]--;
}
}
}
},
_removeRows: function (rows) {
rows = Zotero.Utilities.arrayUnique(rows);
rows.sort((a, b) => a - b);
for (let i = rows.length - 1; i >= 0; i--) {
this._removeRow(rows[i]);
}
},
getLevel: function (row) {
return this._rows[row].level;
},
isContainerOpen: function(row) {
return this._rows[row].isOpen;
},
/**
* Called while a drag is over the tree
*/
canDrop: function(row, orient, dataTransfer) {
// onDragOver() calls the view's canDropCheck() and sets the
// dropEffect, which we check here. Setting the dropEffect on the
// dataTransfer here seems to have no effect.
// ondragover doesn't have access to the orientation on its own,
// so we stuff it in Zotero.DragDrop
Zotero.DragDrop.currentOrientation = orient;
return dataTransfer.dropEffect && dataTransfer.dropEffect != "none";
},
/*
* Called by HTML 5 Drag and Drop when dragging over the tree
*/
onDragEnter: function (event) {
Zotero.DragDrop.currentEvent = event;
return false;
},
/**
* Called by HTML 5 Drag and Drop when dragging over the tree
*
* We use this to set the drag action, which is used by view.canDrop(),
* based on the view's canDropCheck() and modifier keys.
*/
onDragOver: function (event) {
// Prevent modifier keys from doing their normal things
event.preventDefault();
Zotero.DragDrop.currentEvent = event;
var target = event.target;
if (target.tagName != 'treechildren') {
let doc = target.ownerDocument;
// Consider a drop on the items pane message box (e.g., when showing the welcome text)
// a drop on the items tree
let msgBox = doc.getElementById('zotero-items-pane-message-box');
if (msgBox.contains(target) && msgBox.firstChild.hasAttribute('allowdrop')) {
target = doc.querySelector('#zotero-items-tree treechildren');
}
else {
this._setDropEffect(event, "none");
return false;
}
}
var tree = target.parentNode;
let row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
if (tree.id == 'zotero-collections-tree') {
var view = tree.ownerDocument.defaultView.ZoteroPane.collectionsView;
}
else if (tree.id == 'zotero-items-tree') {
var view = tree.ownerDocument.defaultView.ZoteroPane.itemsView;
}
else {
throw new Error("Invalid tree id '" + tree.id + "'");
}
if (!view.canDropCheck(row.value, Zotero.DragDrop.currentOrientation, event.dataTransfer)) {
this._setDropEffect(event, "none");
return;
}
if (event.dataTransfer.getData("zotero/item")) {
var sourceCollectionTreeRow = Zotero.DragDrop.getDragSource(event.dataTransfer);
if (sourceCollectionTreeRow) {
if (this.type == 'collection') {
var targetCollectionTreeRow = Zotero.DragDrop.getDragTarget(event);
}
else if (this.type == 'item') {
var targetCollectionTreeRow = this.collectionTreeRow;
}
else {
throw new Error("Invalid type '" + this.type + "'");
}
if (!targetCollectionTreeRow) {
this._setDropEffect(event, "none");
return false;
}
if (sourceCollectionTreeRow.id == targetCollectionTreeRow.id) {
// Ignore drag into the same collection
if (this.type == 'collection') {
this._setDropEffect(event, "none");
}
// If dragging from the same source, do a move
else {
this._setDropEffect(event, "move");
}
return false;
}
// If the source isn't a collection, the action has to be a copy
if (!sourceCollectionTreeRow.isCollection()) {
this._setDropEffect(event, "copy");
return false;
}
// For now, all cross-library drags are copies
if (sourceCollectionTreeRow.ref.libraryID != targetCollectionTreeRow.ref.libraryID) {
this._setDropEffect(event, "copy");
return false;
}
}
if ((Zotero.isMac && event.metaKey) || (!Zotero.isMac && event.shiftKey)) {
this._setDropEffect(event, "move");
}
else {
this._setDropEffect(event, "copy");
}
}
else if (event.dataTransfer.getData("zotero/collection")) {
let collectionID = Zotero.DragDrop.getDataFromDataTransfer(event.dataTransfer).data[0];
let { libraryID: sourceLibraryID } = Zotero.Collections.getLibraryAndKeyFromID(collectionID);
if (this.type == 'collection') {
var targetCollectionTreeRow = Zotero.DragDrop.getDragTarget(event);
}
else {
throw new Error("Invalid type '" + this.type + "'");
}
// For now, all cross-library drags are copies
if (sourceLibraryID != targetCollectionTreeRow.ref.libraryID) {
/*if ((Zotero.isMac && event.metaKey) || (!Zotero.isMac && event.shiftKey)) {
this._setDropEffect(event, "move");
}
else {
this._setDropEffect(event, "copy");
}*/
this._setDropEffect(event, "copy");
return false;
}
// And everything else is a move
this._setDropEffect(event, "move");
}
else if (event.dataTransfer.types.contains("application/x-moz-file")) {
// As of Aug. 2013 nightlies:
//
// - Setting the dropEffect only works on Linux and OS X.
//
// - Modifier keys don't show up in the drag event on OS X until the
// drop (https://bugzilla.mozilla.org/show_bug.cgi?id=911918),
// so since we can't show a correct effect, we leave it at
// the default 'move', the least misleading option, and set it
// below in onDrop().
//
// - The cursor effect gets set by the system on Windows 7 and can't
// be overridden.
if (!Zotero.isMac) {
if (event.shiftKey) {
if (event.ctrlKey) {
event.dataTransfer.dropEffect = "link";
}
else {
event.dataTransfer.dropEffect = "move";
}
}
else {
event.dataTransfer.dropEffect = "copy";
}
}
}
return false;
},
/*
* Called by HTML 5 Drag and Drop when dropping onto the tree
*/
onDrop: function (event) {
// See note above
if (event.dataTransfer.types.contains("application/x-moz-file")) {
if (Zotero.isMac) {
Zotero.DragDrop.currentEvent = event;
if (event.metaKey) {
if (event.altKey) {
event.dataTransfer.dropEffect = 'link';
}
else {
event.dataTransfer.dropEffect = 'move';
}
}
else {
event.dataTransfer.dropEffect = 'copy';
}
}
}
return false;
},
onDragExit: function (event) {
//Zotero.debug("Clearing drag data");
Zotero.DragDrop.currentEvent = null;
},
_setDropEffect: function (event, effect) {
// On Windows (in Fx26), Firefox uses 'move' for unmodified drags
// and 'copy'/'link' for drags with system-default modifier keys
// as long as the actions are allowed by the initial effectAllowed set
// in onDragStart, regardless of the effectAllowed or dropEffect set
// in onDragOver. It doesn't seem to be possible to use 'copy' for
// the default and 'move' for modified, as we need to in the collections
// tree. To prevent inaccurate cursor feedback, we set effectAllowed to
// 'copy' in onDragStart, which locks the cursor at 'copy'. ('none' still
// changes the cursor, but 'move'/'link' do not.) It'd be better to use
// the unadorned 'move', but we use 'copy' instead because with 'move' text
// can't be dragged to some external programs (e.g., Chrome, Notepad++),
// which seems worse than always showing 'copy' feedback.
//
// However, since effectAllowed is enforced, leaving it at 'copy'
// would prevent our modified 'move' in the collections tree from working,
// so we also have to set effectAllowed here (called from onDragOver) to
// the same action as the dropEffect. This allows the dropEffect setting
// (which we use in the tree's canDrop() and drop() to determine the desired
// action) to be changed, even if the cursor doesn't reflect the new setting.
if (Zotero.isWin || Zotero.isLinux) {
event.dataTransfer.effectAllowed = effect;
}
event.dataTransfer.dropEffect = effect;
}
};

View file

@ -227,6 +227,8 @@ Zotero.Prefs = new function(){
Zotero.setFontSize(
Zotero.getActiveZoteroPane().document.getElementById('zotero-pane')
);
Zotero.getActiveZoteroPane().collectionsView && Zotero.getActiveZoteroPane().collectionsView.updateFontSize();
Zotero.getActiveZoteroPane().itemsView && Zotero.getActiveZoteroPane().itemsView.updateFontSize();
}],
[ "layout", function(val) {
Zotero.getActiveZoteroPane().updateLayout();
@ -466,4 +468,59 @@ Zotero.Prefs = new function(){
});
});
}
this.getVirtualCollectionState = function (type) {
const prefKeys = {
duplicates: 'duplicateLibraries',
unfiled: 'unfiledLibraries',
retracted: 'retractedLibraries'
};
let prefKey = prefKeys[type];
if (!prefKey) {
throw new Error("Invalid virtual collection type '" + type + "'");
}
var libraries;
try {
libraries = JSON.parse(Zotero.Prefs.get(prefKey) || '{}');
if (typeof libraries != 'object') {
throw true;
}
}
// Ignore old/incorrect formats
catch (e) {
Zotero.Prefs.clear(prefKey);
libraries = {};
}
return libraries;
};
this.getVirtualCollectionStateForLibrary = function (libraryID, type) {
return this.getVirtualCollectionState(type)[libraryID] !== false;
};
this.setVirtualCollectionStateForLibrary = function (libraryID, type, show) {
const prefKeys = {
duplicates: 'duplicateLibraries',
unfiled: 'unfiledLibraries',
retracted: 'retractedLibraries'
};
let prefKey = prefKeys[type];
if (!prefKey) {
throw new Error("Invalid virtual collection type '" + type + "'");
}
var libraries = this.getVirtualCollectionState(type);
// Update current library
libraries[libraryID] = !!show;
// Remove libraries that don't exist or that are set to true
for (let id of Object.keys(libraries).filter(id => libraries[id] || !Zotero.Libraries.exists(id))) {
delete libraries[id];
}
Zotero.Prefs.set(prefKey, JSON.stringify(libraries));
};
}

View file

@ -163,6 +163,7 @@ Zotero.ProgressQueue = function (options) {
* @param {String} message
*/
this.updateRow = function(itemID, status, message) {
Zotero.debug(`ProgressQueue: updating row ${itemID}, ${status}, ${message}`);
for (let row of _rows) {
if (row.id === itemID) {
row.status = status;

View file

@ -32,7 +32,7 @@ Zotero.ProgressQueueDialog = function (progressQueue) {
let _progressWindow = null;
let _progressIndicator = null;
let _rowIDs = [];
let _io = { progressQueue: _progressQueue };
let _status = null;
let _showMinimize = true;
@ -45,11 +45,11 @@ Zotero.ProgressQueueDialog = function (progressQueue) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
if (win) {
_progressWindow = win.openDialog("chrome://zotero/content/progressQueueDialog.xul",
"", "chrome,close=yes,resizable=yes,dependent,dialog,centerscreen");
"", "chrome,close=yes,resizable=yes,dependent,dialog,centerscreen", _io);
}
else {
_progressWindow = Services.ww.openWindow(null, "chrome://zotero/content/progressQueueDialog.xul",
"", "chrome,close=yes,resizable=yes,dependent,dialog,centerscreen", null);
"", "chrome,close=yes,resizable=yes,dependent,dialog,centerscreen", _io);
}
_progressWindow.addEventListener('pageshow', _onWindowLoaded.bind(this), false);
@ -82,70 +82,9 @@ Zotero.ProgressQueueDialog = function (progressQueue) {
_progressQueue.cancel();
};
function _getImageByStatus(status) {
if (status === Zotero.ProgressQueue.ROW_PROCESSING) {
return LOADING_IMAGE;
}
else if (status === Zotero.ProgressQueue.ROW_FAILED) {
return FAILURE_IMAGE;
}
else if (status === Zotero.ProgressQueue.ROW_SUCCEEDED) {
return SUCCESS_IMAGE;
}
return '';
}
function _rowToTreeItem(row) {
let treeitem = _progressWindow.document.createElement('treeitem');
treeitem.setAttribute('id', 'item-' + row.id);
let treerow = _progressWindow.document.createElement('treerow');
let treecell = _progressWindow.document.createElement('treecell');
treecell.setAttribute('id', 'item-' + row.id + '-icon');
treecell.setAttribute('src', _getImageByStatus(row.status));
treerow.appendChild(treecell);
treecell = _progressWindow.document.createElement('treecell');
treecell.setAttribute('label', row.fileName);
treerow.appendChild(treecell);
treecell = _progressWindow.document.createElement('treecell');
treecell.setAttribute('id', 'item-' + row.id + '-title');
treecell.setAttribute('label', row.message);
treerow.appendChild(treecell);
treeitem.appendChild(treerow);
return treeitem;
}
function _onWindowLoaded() {
let rows = _progressQueue.getRows();
_rowIDs = [];
_progressWindow.document.title = Zotero.getString(_progressQueue.getTitle());
let col1 = _progressWindow.document.getElementById('col1');
let col2 = _progressWindow.document.getElementById('col2');
let columns = _progressQueue.getColumns();
col1.setAttribute('label', Zotero.getString(columns[0]));
col2.setAttribute('label', Zotero.getString(columns[1]));
let treechildren = _progressWindow.document.getElementById('treechildren');
for (let row of rows) {
_rowIDs.push(row.id);
let treeitem = _rowToTreeItem(row);
treechildren.appendChild(treeitem);
}
_progressWindow.document.getElementById('tree').addEventListener('dblclick',
function (event) {
_onDblClick(event, this);
}
);
var rootElement = document.getElementById('zotero-progress');
Zotero.setFontSize(rootElement);
_progressIndicator = _progressWindow.document.getElementById('progress-indicator');
_progressWindow.document.getElementById('cancel-button')
@ -181,33 +120,24 @@ Zotero.ProgressQueueDialog = function (progressQueue) {
_progressIndicator = null;
_status = null;
_showMinimize = true;
_rowIDs = [];
});
_updateProgress();
_progressQueue.addListener('rowadded', function (row) {
_rowIDs.push(row.id);
let treeitem = _rowToTreeItem(row);
treechildren.appendChild(treeitem);
_io.tree.invalidate();
_updateProgress();
});
_progressQueue.addListener('rowupdated', function (row) {
let itemIcon = _progressWindow.document.getElementById('item-' + row.id + '-icon');
let itemTitle = _progressWindow.document.getElementById('item-' + row.id + '-title');
itemIcon.setAttribute('src', _getImageByStatus(row.status));
itemTitle.setAttribute('label', row.message);
_io.tree.invalidate();
_updateProgress();
});
_progressQueue.addListener('rowdeleted', function (row) {
_rowIDs.splice(_rowIDs.indexOf(row.id), 1);
let treeitem = _progressWindow.document.getElementById('item-' + row.id);
treeitem.parentNode.removeChild(treeitem);
_io.tree.invalidate();
_updateProgress();
});
_updateProgress();
}
function _updateProgress() {
@ -228,29 +158,4 @@ Zotero.ProgressQueueDialog = function (progressQueue) {
_progressWindow.document.getElementById("label").value = _status || Zotero.getString('general.processing');
}
}
/**
* Focus items in Zotero library when double-clicking them in the Retrieve
* metadata window.
* @param {Event} event
* @param {tree} tree XUL tree object
* @private
*/
async function _onDblClick(event, tree) {
if (event && tree && event.type === 'dblclick') {
let itemID = _rowIDs[tree.treeBoxObject.getRowAt(event.clientX, event.clientY)];
if (!itemID) return;
let item = await Zotero.Items.getAsync(itemID);
if (!item) return;
if (item.parentItemID) itemID = item.parentItemID;
let win = Services.wm.getMostRecentWindow("navigator:browser");
if (win) {
win.ZoteroPane.selectItem(itemID, false, true);
win.focus();
}
}
}
};

View file

@ -303,7 +303,7 @@ Zotero.Retractions = {
// Changed
&& (previous != current
// Explicitly hidden
|| (current && !Zotero.Utilities.Internal.getVirtualCollectionStateForLibrary(libraryID, 'retracted')))) {
|| (current && !Zotero.Prefs.getVirtualCollectionStateForLibrary(libraryID, 'retracted')))) {
let promises = [];
for (let zp of Zotero.getZoteroPanes()) {
promises.push(zp.setVirtual(libraryID, 'retracted', current));

@ -1 +1 @@
Subproject commit db52081e6eedfb0aa250dfc7f93ab2cf7ed6e468
Subproject commit 10ffb4a766ce7d43aac3b321863a4e585669be02

View file

@ -32,6 +32,71 @@
Zotero.Utilities.Internal = {
SNAPSHOT_SAVE_TIMEOUT: 30000,
makeClassEventDispatcher: function (cls) {
cls.prototype._events = null;
cls.prototype.runListeners = async function (event) {
// Zotero.debug(`Running ${event} listeners on ${cls.toString()}`);
if (!this._events) this._events = {};
if (!this._events[event]) {
this._events[event] = {
listeners: new Map(),
};
}
this._events[event].triggered = true;
// Array.from(entries) since entries() returns an iterator and we want a snapshot of the entries
// at the time of runListeners() call to prevent triggering listeners that are added right
// runListeners() invocation
for (let [listener, once] of Array.from(this._events[event].listeners.entries())) {
await Zotero.Promise.resolve(listener.call(this));
if (once) {
this._events[event].listeners.delete(listener);
}
}
};
/**
* @param event {String} name of the event
* @param alwaysOnce {Boolean} whether all event listeners on this event will only be triggered once
* @param immediateAfterTrigger {Boolean} whether the event listeners should be triggered immediately
* upon being added if the event had been triggered at least once
* @returns {Object} A listener object with an addListener(listener, once) method
* @private
*/
cls.prototype._createEventBinding = function (event, alwaysOnce, immediateAfterTrigger) {
if (!this._events) this._events = {};
this._events[event] = {
listeners: new Map(),
immediateAfterTrigger
};
return {
addListener: (listener, once) => {
this._addListener(event, listener, alwaysOnce || once, immediateAfterTrigger)
}
}
};
cls.prototype._addListener = function (event, listener, once, immediateAfterTrigger) {
if (!this._events) this._events = {};
let ev = this._events[event];
if (!ev) {
this._events[event] = {
listeners: new Map(),
immediateAfterTrigger
};
}
if ((immediateAfterTrigger || ev.immediateAfterTrigger) && ev.triggered) {
return listener.call(this);
}
this._events[event].listeners.set(listener, once);
};
cls.prototype._waitForEvent = async function (event) {
return new Zotero.Promise((resolve, reject) => {
this._addListener(event, () => resolve(), true);
});
};
},
/**
* Run a function on chunks of a given size of an array's elements.
*
@ -1797,77 +1862,6 @@ Zotero.Utilities.Internal = {
return menu;
},
// TODO: Move somewhere better
getVirtualCollectionState: function (type) {
switch (type) {
case 'duplicates':
var prefKey = 'duplicateLibraries';
break;
case 'unfiled':
var prefKey = 'unfiledLibraries';
break;
case 'retracted':
var prefKey = 'retractedLibraries';
break;
default:
throw new Error("Invalid virtual collection type '" + type + "'");
}
var libraries;
try {
libraries = JSON.parse(Zotero.Prefs.get(prefKey) || '{}');
if (typeof libraries != 'object') {
throw true;
}
}
// Ignore old/incorrect formats
catch (e) {
Zotero.Prefs.clear(prefKey);
libraries = {};
}
return libraries;
},
getVirtualCollectionStateForLibrary: function (libraryID, type) {
return this.getVirtualCollectionState(type)[libraryID] !== false;
},
setVirtualCollectionStateForLibrary: function (libraryID, type, show) {
switch (type) {
case 'duplicates':
var prefKey = 'duplicateLibraries';
break;
case 'unfiled':
var prefKey = 'unfiledLibraries';
break;
case 'retracted':
var prefKey = 'retractedLibraries';
break;
default:
throw new Error("Invalid virtual collection type '" + type + "'");
}
var libraries = this.getVirtualCollectionState(type);
// Update current library
libraries[libraryID] = !!show;
// Remove libraries that don't exist or that are set to true
for (let id of Object.keys(libraries).filter(id => libraries[id] || !Zotero.Libraries.exists(id))) {
delete libraries[id];
}
Zotero.Prefs.set(prefKey, JSON.stringify(libraries));
},
openPreferences: function (paneID, options = {}) {
if (typeof options == 'string') {
Zotero.debug("ZoteroPane.openPreferences() now takes an 'options' object -- update your code", 2);
@ -2055,14 +2049,20 @@ Zotero.Utilities.Internal = {
if (size <= 1) {
size = 'small';
}
else if (size <= 1.25) {
else if (size <= 1.15) {
size = 'medium';
}
else {
else if (size <= 1.3) {
size = 'large';
}
else {
size = 'x-large';
}
// Custom attribute -- allows for additional customizations in zotero.css
rootElement.setAttribute('zoteroFontSize', size);
if (Zotero.rtl) {
rootElement.setAttribute('dir', 'rtl');
}
},
getAncestorByTagName: function (elem, tagName){

View file

@ -1991,7 +1991,6 @@ Zotero.VersionHeader = {
Zotero.DragDrop = {
currentEvent: null,
currentOrientation: 0,
currentSourceNode: null,
getDataFromDataTransfer: function (dataTransfer, firstOnly) {
var dt = dataTransfer;
@ -2050,28 +2049,8 @@ Zotero.DragDrop = {
},
getDragSource: function (dataTransfer) {
if (!dataTransfer) {
//Zotero.debug("Drag data not available", 2);
return false;
}
// For items, the drag source is the CollectionTreeRow of the parent window
// of the source tree
if (dataTransfer.types.contains("zotero/item")) {
let sourceNode = dataTransfer.mozSourceNode || this.currentSourceNode;
if (!sourceNode || sourceNode.tagName != 'treechildren'
|| sourceNode.parentElement.id != 'zotero-items-tree') {
return false;
}
var win = sourceNode.ownerDocument.defaultView;
if (win.document.documentElement.getAttribute('windowtype') == 'zotero:search') {
return win.ZoteroAdvancedSearch.itemsView.collectionTreeRow;
}
return win.ZoteroPane.collectionsView.selectedTreeRow;
}
return false;
getDragSource: function () {
return this.currentDragSource;
},

File diff suppressed because it is too large Load diff

View file

@ -330,32 +330,17 @@
<box id="zotero-collections-tree-shim"/>
<!-- This extra vbox prevents the toolbar from getting compressed when resizing
the tag selector to max height -->
<tree id="zotero-collections-tree"
hidecolumnpicker="true"
oncontextmenu="ZoteroPane.onCollectionsContextMenuOpen(event)"
onmouseover="ZoteroPane_Local.collectionsView.setHighlightedRows();"
onselect="ZoteroPane_Local.onCollectionSelected();"
seltype="cell" flex="1" editable="true">
<treecols>
<treecol
id="zotero-collections-name-column"
flex="1"
primary="true"
hideheader="true"/>
</treecols>
<treechildren ondragstart="ZoteroPane_Local.collectionsView.onDragStart(event)"
ondragenter="return ZoteroPane_Local.collectionsView.onDragEnter(event)"
ondragover="return ZoteroPane_Local.collectionsView.onDragOver(event)"
ondrop="return ZoteroPane_Local.collectionsView.onDrop(event)"/>
</tree>
<vbox id="zotero-collections-tree-container" class="virtualized-table-container" flex="1">
<html:div id="zotero-collections-tree"></html:div>
</vbox>
<splitter
id="zotero-tags-splitter"
orient="vertical"
collapse="after"
zotero-persist="state"
onmousemove="if (this.getAttribute('state') == 'dragging') { ZoteroPane.handleTagSelectorResize(); }"
id="zotero-tags-splitter"
orient="vertical"
collapse="after"
zotero-persist="state"
onmousemove="if (this.getAttribute('state') == 'dragging') { ZoteroPane.handleTagSelectorResize(); }"
>
<grippy oncommand="ZoteroPane_Local.toggleTagSelector()"/>
<grippy oncommand="ZoteroPane.toggleTagSelector()"/>
</splitter>
<!-- 'collapsed' is no longer necessary here due to the persisted 'state' on
zotero-tags-splitter, but without this an old-style entry for 'collapsed'
@ -376,214 +361,9 @@
</splitter>
<box id="zotero-layout-switcher" orient="horizontal" zotero-persist="orient" flex="1">
<vbox id="zotero-items-pane" zotero-persist="width height" flex="1">
<deck id="zotero-items-pane-content" selectedIndex="0" flex="1">
<!-- Key navigation is handled by listener in itemTreeView.js -->
<tree
id="zotero-items-tree"
enableColumnDrag="true"
disableKeyNavigation="true"
onfocus="if (ZoteroPane_Local.itemsView.rowCount &amp;&amp; !ZoteroPane_Local.itemsView.selection.count) { ZoteroPane_Local.itemsView.selection.select(0); }"
onkeydown="ZoteroPane_Local.handleKeyDown(event, this.id)"
onselect="ZoteroPane_Local.itemSelected(event)"
oncommand="ZoteroPane_Local.serializePersist()"
oncontextmenu="ZoteroPane.onItemsContextMenuOpen(event)"
flex="1"
hidden="true"
zotero-persist="current-view-group">
<treecols id="zotero-items-columns-header">
<treecol
id="zotero-items-column-title" primary="true" default-in="default feed"
label="&zotero.items.title_column;" ignoreincolumnpicker="true"
flex="4" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-firstCreator" default-in="default feed"
label="&zotero.items.creator_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-itemType"
label="&zotero.items.type_column;"
width="40" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-date" default-in="feed"
label="&zotero.items.date_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-year" disabled-in="feed"
label="&zotero.items.year_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-publisher"
label="&zotero.items.publisher_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-publicationTitle" disabled-in="feed"
label="&zotero.items.publication_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-journalAbbreviation" disabled-in="feed"
submenu="true"
label="&zotero.items.journalAbbr_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-language"
submenu="true"
label="&zotero.items.language_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-accessDate" disabled-in="feed"
submenu="true"
label="&zotero.items.accessDate_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-libraryCatalog" disabled-in="feed"
submenu="true"
label="&zotero.items.libraryCatalog_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-callNumber" disabled-in="feed"
submenu="true"
label="&zotero.items.callNumber_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-rights"
submenu="true"
label="&zotero.items.rights_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-dateAdded" disabled-in="feed"
label="&zotero.items.dateAdded_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-dateModified" disabled-in="feed"
label="&zotero.items.dateModified_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-archive" disabled-in="feed"
submenu="true"
label="&zotero.items.archive_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-archiveLocation" disabled-in="feed"
submenu="true"
label="&zotero.items.archiveLocation_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-place" disabled-in="feed"
submenu="true"
label="&zotero.items.place_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-volume" disabled-in="feed"
submenu="true"
label="&zotero.items.volume_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-edition" disabled-in="feed"
submenu="true"
label="&zotero.items.edition_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-pages" disabled-in="feed"
submenu="true"
label="&zotero.items.pages_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-issue" disabled-in="feed"
submenu="true"
label="&zotero.items.issue_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-series" disabled-in="feed"
submenu="true"
label="&zotero.items.series_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-seriesTitle" disabled-in="feed"
submenu="true"
label="&zotero.items.seriesTitle_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-court" disabled-in="feed"
submenu="true"
label="&zotero.items.court_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-medium" disabled-in="feed"
submenu="true"
label="&zotero.items.medium_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-genre" disabled-in="feed"
submenu="true"
label="&zotero.items.genre_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-system" disabled-in="feed"
submenu="true"
label="&zotero.items.system_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-extra" disabled-in="feed"
label="&zotero.items.extra_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-hasAttachment" default-in="default" disabled-in="feed"
class="treecol-image"
label="&zotero.tabs.attachments.label;"
fixed="true"
zotero-persist="ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-numNotes" disabled-in="feed"
class="treecol-image"
label="&zotero.tabs.notes.label;"
zotero-persist="width ordinal hidden sortActive sortDirection"/>
</treecols>
<treechildren ondragstart="ZoteroPane_Local.itemsView.onDragStart(event)"
ondragenter="return ZoteroPane_Local.itemsView.onDragEnter(event)"
ondragover="return ZoteroPane_Local.itemsView.onDragOver(event)"
ondrop="return ZoteroPane_Local.itemsView.onDrop(event)"
ondragend="ZoteroPane_Local.itemsView.onDragEnd(event)"/>
</tree>
<!-- Label for displaying messages when items pane is hidden
(e.g. "Advanced search mode — press Enter to search.")-->
<vbox id="zotero-items-pane-message-box" pack="center" align="center"
ondragenter="return ZoteroPane.itemsView.onDragEnter(event)"
ondragover="return ZoteroPane.itemsView.onDragOver(event)"
ondrop="ZoteroPane.itemsView.onDrop(event); ZoteroPane.itemsView.drop(-1, -1, event.dataTransfer)"/>
</deck>
</vbox>
<hbox id="zotero-items-pane" class="virtualized-table-container" zotero-persist="width height" flex="1">
<html:div id="zotero-items-tree"></html:div>
</hbox>
<splitter id="zotero-items-splitter" resizebefore="closest" resizeafter="closest" collapse="after" orient="horizontal" zotero-persist="state orient"
onmousemove="ZoteroPane.updateToolbarPosition(); ZoteroPane.updateTagsBoxSize()"
@ -592,7 +372,7 @@
</splitter>
<!-- itemPane.xul -->
<vbox id="zotero-item-pane"/>
<vbox id="zotero-item-pane" zotero-persist="width height"/>
</box>
</hbox>
</vbox>

View file

@ -83,6 +83,7 @@
<!ENTITY zotero.preferences.sync.reset.resetFileSyncHistory.desc "Compare all attachment files with the storage service">
<!ENTITY zotero.preferences.sync.reset "Reset">
<!ENTITY zotero.preferences.sync.reset.button "Reset…">
<!ENTITY zotero.preferences.sync.toggle "Toggle">
<!ENTITY zotero.preferences.prefpane.search "Search">

View file

@ -228,6 +228,7 @@ date.relative.daysAgo.multiple = %S days ago
date.relative.yearsAgo.one = 1 year ago
date.relative.yearsAgo.multiple = %S years ago
pane.collections.title = Collections
pane.collections.delete.title = Delete Collection
pane.collections.delete = Are you sure you want to delete the selected collection?
pane.collections.delete.keepItems = Items within this collection will not be deleted.
@ -299,6 +300,7 @@ pane.items.intro.text1 = Welcome to %S!
pane.items.intro.text2 = View the [Quick Start Guide] to learn how to begin building your library, and be sure to [install a %S] so you can add items to %S as you browse the web.
pane.items.intro.text3 = Already using %S on another computer? [Set up syncing] to pick up right where you left off.
pane.items.title = Items
pane.items.loading = Loading items…
pane.items.loadError = Error loading items list
pane.items.columnChooser.moreColumns = More Columns

View file

@ -4,83 +4,12 @@
overflow: hidden;
}
/* Why is this necessary? */
#zotero-collections-tree treechildren::-moz-tree-image,
#zotero-items-tree treechildren::-moz-tree-image {
margin-right: 5px;
}
#zotero-collections-pane
{
min-width: 150px;
width: 250px;
}
#zotero-collections-tree {
min-height: 5.2em;
}
#zotero-collections-tree treechildren::-moz-tree-row {
height: 1.7em;
}
/* As of Fx37, the tree doesn't scale HiDPI images properly on Windows and Linux */
#zotero-collections-tree treechildren::-moz-tree-image,
#zotero-items-tree treechildren::-moz-tree-image {
height: 16px;
}
#zotero-collections-tree treechildren::-moz-tree-image(primary)
{
margin-right: 5px;
}
#zotero-collections-tree treechildren::-moz-tree-separator {
border: none;
}
#zotero-collections-tree treechildren::-moz-tree-twisty(notwisty),
#zotero-collections-tree treechildren::-moz-tree-twisty(header) {
width: 0;
}
/* Set by setHighlightedRows() and getRowProperties() in collectionTreeView.js) */
#zotero-collections-tree treechildren::-moz-tree-row(highlighted)
{
background: #FFFF99 !important;
}
#zotero-items-column-hasAttachment, #zotero-items-column-numNotes {
min-width: 21px;
}
#zotero-items-column-hasAttachment {
list-style-image: url(chrome://zotero/skin/attach-small.png);
}
#zotero-items-column-hasAttachment .treecol-icon {
width: 13px;
}
#zotero-items-column-numNotes {
list-style-image: url(chrome://zotero/skin/treeitem-note-small.png);
}
#zotero-items-column-numNotes .treecol-icon {
width: 12px;
}
@media (min-resolution: 1.25dppx) {
.tree-columnpicker-icon {
list-style-image: url(chrome://zotero/skin/firefox/columnpicker@2x.gif);
width: 14px;
}
}
#zotero-items-column-numNotes {
text-align: center;
}
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie)
{
margin: 1px 0 0;
@ -219,75 +148,37 @@
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie63) { -moz-image-region: rect(32px, 2016px, 64px, 1984px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie64) { -moz-image-region: rect(32px, 2048px, 64px, 2016px); }
/* Style search results, display non-matches in gray */
#zotero-items-tree treechildren::-moz-tree-cell-text(contextRow) {
color: gray;
}
#zotero-items-tree treechildren::-moz-tree-cell-text(contextRow, selected, focus) {
/* This is the default color, not the (platform-specific) highlight color, but at least it
helps to differentiate when clicking on a context row. */
color: inherit;
}
/* Style unread items/collections in bold */
#zotero-items-tree treechildren::-moz-tree-cell-text(unread),
#zotero-collections-tree treechildren::-moz-tree-cell-text(unread) {
font-weight: bold;
}
#zotero-items-pane
{
min-width: 290px;
min-height: 150px;
}
/* Used for intro text */
#zotero-items-pane-message-box {
overflow-y: auto;
cursor: default;
}
#zotero-items-pane-message-box div {
.items-tree-message div {
padding: 0 35px;
}
#zotero-items-pane-message-box p {
.items-tree-message p {
max-width: 800px;
font-size: 1.45em;
line-height: 1.7em;
text-align: left;
}
#zotero-items-pane-message-box div.publications p {
.items-tree-message div.publications p {
font-size: 1.2em;
}
/* Increase size when window is wider */
#zotero-pane.width-1000 #zotero-items-pane-message-box div.publications p {
#zotero-pane.width-1000 .items-tree-message div.publications p {
font-size: 1.35em;
}
#zotero-items-pane-message-box span.text-link {
.items-tree-message span.text-link {
color: rgb(0, 149, 221);
cursor: pointer;
text-decoration: underline;
}
#zotero-items-pane-message-box {
-moz-appearance: listbox;
text-align: center;
padding: 20px;
}
#zotero-items-pane-message-box description:not(:first-child) {
margin-top: .75em;
}
#zotero-item-pane
{
width: 338px;
min-width: 338px;
min-height: 200px;
overflow-y: hidden;
}

View file

@ -9,12 +9,12 @@
height: 1.5em;
}
*[zoteroFontSize=large] treechildren::-moz-tree-row
*[zoteroFontSize=large] treechildren::-moz-tree-row, *[zoteroFontSize=x-large] treechildren::-moz-tree-row
{
height: 1.5em;
}
*[zoteroFontSize=large] .treecol-text
*[zoteroFontSize=large] .treecol-text, *[zoteroFontSize=x-large] .treecol-text
{
margin:0;
padding:0;

View file

@ -65,8 +65,6 @@ const xpcomFilesAll = [
/** XPCOM files to be loaded only for local translation and DB access **/
const xpcomFilesLocal = [
'libraryTreeView',
'collectionTreeView',
'collectionTreeRow',
'annotations',
'api',
@ -102,10 +100,10 @@ const xpcomFilesLocal = [
'duplicates',
'editorInstance',
'feedReader',
'fileDragDataProvider',
'fulltext',
'id',
'integration',
'itemTreeView',
'locale',
'locateManager',
'mime',

1
resource/react-dom-server.js vendored Symbolic link
View file

@ -0,0 +1 @@
../node_modules/react-dom/umd/react-dom-server.browser.development.js

View file

@ -61,13 +61,15 @@ var require = (function() {
}
function getZotero() {
if (win.Zotero) Zotero = win.Zotero;
if (typeof Zotero === 'undefined') {
try {
Zotero = Components.classes["@zotero.org/Zotero;1"]
.getService(Components.interfaces.nsISupports).wrappedJSObject;
} catch (e) {}
}
return Zotero || {};
return Zotero || {};
}
var cons;
@ -87,6 +89,8 @@ var require = (function() {
navigator: typeof win.navigator !== 'undefined' && win.navigator || {},
setTimeout: win.setTimeout,
clearTimeout: win.clearTimeout,
requestAnimationFrame: win.setTimeout,
cancelAnimationFrame: win.clearTimeout
};
Object.defineProperty(globals, 'Zotero', { get: getZotero });
var loader = Loader({
@ -101,5 +105,5 @@ var require = (function() {
globals
});
let require = Require(loader, requirer);
return require
return require;
})();

View file

@ -37,3 +37,6 @@
@import "components/tabBar";
@import "components/tagsBox";
@import "components/tagSelector";
@import "components/collection-tree";
@import "components/virtualized-table";
@import "components/item-tree";

View file

@ -33,7 +33,6 @@
span.menu-marker {
-moz-appearance: toolbarbutton-dropdown;
display: inline-block;
vertical-align: middle;
margin-right: -5px;
}
}

View file

@ -0,0 +1,45 @@
#zotero-collections-tree-container {
height: 5.2em;
}
#zotero-collections-tree {
width: 100%;
.virtualized-table {
overflow-y: auto;
flex: 1 0;
text-overflow: ellipsis;
}
.cell.primary {
display: flex;
align-items: center;
:not(.cell-text) {
flex-shrink: 0
}
.cell-text {
flex-shrink: 1;
text-overflow: ellipsis;
overflow: hidden;
margin-left: 6px;
}
input.cell-text {
border: 1px highlight solid;
padding: 1px 2px;
margin-right: 5px;
width: 100%;
font-size: inherit;
}
.cell-icon {
min-width: 16px;
}
}
.row.editing .cell {
pointer-events: auto;
}
}

View file

@ -2,6 +2,20 @@
width: 16px;
}
.icon.icon-downchevron > img {
width: 7px;
.icon-bg {
width: 16px;
height: 16px;
display: inline-block;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
vertical-align: middle;
}
.icon.icon-downchevron {
width: 7px !important;
}
.icon {
-moz-appearance: none !important;
}

View file

@ -0,0 +1,47 @@
#zotero-items-pane {
min-width: 290px;
min-height: 150px;
height: 150px;
width: 290px;
.items-tree-message {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
}
#zotero-items-tree {
.virtualized-table-header .icon {
width: 13px;
height: 13px;
}
.cell.primary {
.retracted {
width: 12px;
margin-inline-start: 3px;
}
.tag-swatch {
display: inline-block;
min-width: 8px;
min-height: 8px;
margin-inline-start: 3px;
border-radius: 1px;
}
}
.cell.hasAttachment {
box-sizing: content-box;
padding: 0 4px;
}
.cell.numNotes {
text-align: center;
}
}

View file

@ -75,6 +75,7 @@
text-overflow: ellipsis;
white-space: pre;
padding: 1px 4px 3px; // See also TagSelectorList.jsx
background-color: $tag-selector-bg;
&.colored {
font-weight: bold;

View file

@ -0,0 +1,251 @@
//
// Virtualized table
// --------------------------------------------------
/**
<hbox class="virtualized-table-container" flex="1">
<html:div id="virtualized-table-div"/>
</hbox>
*/
.virtualized-table-container {
display: flex;
height: 0;
flex-direction: column;
> div {
display: flex;
flex: 1;
background-color: -moz-field;
overflow: hidden;
position: relative;
}
}
.virtualized-table, .drag-image-container {
width: 100%;
display: flex;
flex-direction: column;
position: relative;
&:focus {
outline: none;
}
&.resizing {
cursor: col-resize;
.cell {
cursor: col-resize;
}
}
.cell {
min-width: 30px;
cursor: default;
white-space: nowrap;
flex-grow: 1;
flex-shrink: 1;
box-sizing: border-box;
&.primary {
display: flex;
align-items: center;
:not(.cell-text) {
flex-shrink: 0
}
.cell-text {
flex-shrink: 1;
text-overflow: ellipsis;
overflow: hidden;
margin-inline-start: 6px;
}
.twisty + .cell-text, .spacer-twisty + .cell-text {
margin-inline-start: 0;
}
}
.cell-icon {
min-width: 16px;
}
}
.row {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
box-sizing: border-box;
&.drop {
color: $shade-0 !important;
background: $shade-5 !important;
* {
pointer-events: none !important;
}
}
span.drop-before, span.drop-after {
position: absolute;
width: 20%;
height: 1px;
background-color: $shade-5;
pointer-events: none;
}
span.drop-before {
top: 0;
}
span.drop-after {
bottom: -1px;
}
&.selected:not(.highlighted) {
background-color: highlight;
color: highlighttext;
}
&.highlighted {
background: #FFFF99;
}
&.unread {
font-weight: bold;
}
&.context-row {
color: gray;
}
}
.column-drag-marker {
z-index: 99999;
position: absolute;
top: 0;
height: 100%;
width: 2px;
background-color: #ccc;
}
}
.virtualized-table-header {
display: flex;
flex-direction: row;
align-items: center;
width: calc(100% - 11px);
border-bottom: 1px solid #ccc;
background: #f6f6f6;
height: 14px;
overflow: hidden;
border-inline-end: 1px solid #ddd;
&.static-columns {
pointer-events: none;
}
.column-picker {
text-align: center;
}
.cell {
display: flex;
position: relative;
height: 100%;
align-items: center;
&:hover {
background: #fff;
}
&.dragging {
background: #e9e9e9;
}
.resizer {
background: linear-gradient(#ddd, #ddd) no-repeat center/1px 80%;
cursor: col-resize;
height: 100%;
content: "\00a0";
display: block;
position: absolute;
left: -5px;
min-width: 10px;
}
.label {
margin-inline-start: 10px;
overflow: hidden;
text-overflow: ellipsis;
}
&.cell-icon {
> .label {
margin-inline-start: 0;
}
justify-content: center;
}
.sort-indicator {
-moz-appearance: toolbarbutton-dropdown;
display: block;
position: absolute;
right: 10px;
&.ascending {
transform: rotate(180deg);
}
}
}
}
.virtualized-table-body, .drag-image-container{
flex: 1 0;
max-width: 100%;
overflow: auto;
.row {
padding-inline-start: 6px;
}
.cell {
padding: 2px 5px;
text-overflow: ellipsis;
overflow: hidden;
pointer-events: none;
min-height: 90%;
}
}
.spacer-twisty {
display: inline-block;
min-width: 8px;
}
.twisty {
margin-inline-end: 0 !important;
display: flex;
align-items: center;
svg {
fill: #444;
transition: transform 0.125s ease;
transform: rotate(-90deg);
}
&.open svg {
transform: rotate(0deg) !important;
}
}
*[dir=rtl] {
.virtualized-table-header {
.cell .sort-indicator {
left: 10px;
right: initial;
}
.resizer {
right: -5px;
left: initial;
}
}
.twisty svg {
transform: rotate(90deg);
}
}

View file

@ -0,0 +1,8 @@
#zotero-collections-tree-container {
margin-bottom: -1px;
}
#zotero-collections-tree .virtualized-table .row {
height: 1.333em;
}

View file

@ -0,0 +1,5 @@
#zotero-items-tree {
.cell.hasAttachment, .cell.numNotes {
padding: 0 8px;
}
}

View file

@ -0,0 +1,28 @@
.virtualized-table {
border: 1px solid #ccc;
}
.virtualized-table, .drag-image-container {
.twisty {
padding-inline-end: 3px;
svg {
width: 5px;
}
}
}
.virtualized-table-header {
height: 28px;
background-image: linear-gradient(#fff, #fafafa);
&.dragging {
color: #666;
background: #f8f8f8;
}
.cell .sort-indicator {
transform: scale(1.25);
&.ascending {
transform: scale(1.25) rotate(180deg);
}
}
}

View file

@ -0,0 +1,54 @@
#zotero-collections-tree {
.virtualized-table {
background-color: #d2d8e2;
.row {
height: 1.818em;
&.selected:not(.highlighted) {
background: -moz-linear-gradient(top, #6494D4, #2559AC) repeat-x;
border-top: 1px solid #5382C5;
font-weight: bold !important;
color: #ffffff !important;
height: calc(1.818em - 1px);
padding-bottom: 1px;
}
}
&:not(:focus) .row.selected:not(.highlighted) {
background: -moz-linear-gradient(top, #A0B0CF, #7386AB) repeat-x;
border-top: 1px solid #94A1C0;
}
&:-moz-window-inactive {
background-color: rgb(232, 232, 232);
.row.selected:not(.highlighted) {
background: -moz-linear-gradient(top, #B4B4B4, #8A8A8A) repeat-x;
border-top: 1px solid #979797;
}
}
}
}
// IDK why, but these heights are all over the place on macOS (not a f(x)=x)
*[zoteroFontSize=medium] #zotero-collections-tree .virtualized-table .row {
height: 1.739em;
&.focused:not(.highlighted) {
height: calc(1.738em - 1px);
}
}
*[zoteroFontSize=large] #zotero-collections-tree .virtualized-table .row {
height: 1.68em;
&.focused:not(.highlighted) {
height: calc(1.68em - 1px);
}
}
*[zoteroFontSize=x-large] #zotero-collections-tree .virtualized-table .row {
height: 1.697em;
&.focused:not(.highlighted) {
height: calc(1.697em - 1px);
}
}

9
scss/mac/_item-tree.scss Normal file
View file

@ -0,0 +1,9 @@
#zotero-items-tree {
// Selected rows when the tree is not the focused element
.virtualized-table:not(:focus) {
.row.selected {
background: #dcdcdc;
color: initial;
}
}
}

View file

@ -0,0 +1,24 @@
.virtualized-table, .drag-image-container {
.twisty {
width: 19px;
svg {
fill: #888;
width: 16px;
}
}
.focused:not(.highlighted) .twisty svg {
fill: #fff;
}
.spacer-twisty {
min-width: 19px;
}
}
.virtualized-table-body, .drag-image-container{
.cell:not(:first-child) {
border-inline-start: 1px solid #ddd;
}
}

33
scss/win/_item-tree.scss Normal file
View file

@ -0,0 +1,33 @@
#zotero-items-tree .virtualized-table {
.row {
padding-inline-end: 1px;
&.selected {
background-color: #e5f3ff;
border: 1px solid #7bc3ff;
color: inherit;
padding-inline-start: 1px;
padding-inline-end: 0;
}
&:hover {
background-color: #e5f3ff;
}
}
}
#zotero-items-tree .virtualized-table:not(:focus) {
.row {
&.selected {
color: inherit;
background: #f0f0f0;
border: none;
padding-inline-start: 2px;
padding-inline-end: 1px;
}
&.selected:hover {
background-color: #e5f3ff;
border: 1px solid #7bc3ff;
padding-inline-start: 1px;
padding-inline-end: 0;
}
}
}

View file

@ -0,0 +1,50 @@
.spacer-twisty {
min-width: 16px;
}
.virtualized-table {
&:not(:focus) .row.selected:not(.highlighted) {
color: inherit;
background: #f0f0f0;
}
.row {
padding-inline-start: 2px;
.twisty svg {
fill: #b6b6b6;
width: 16px;
}
.twisty.open svg {
fill: #636363;
}
&:hover .twisty svg {
fill: #4ed0f9;
}
&.drop {
background-color: highlight;
color: highlighttext;
}
}
}
.virtualized-table-header {
height: 24px;
background: #fff;
.cell {
&:hover {
background: #d9ebf9;
}
&:active {
background: #bcdcf4;
}
&.dragging {
background: #d9ebf9 !important;
color: #6d6d6d;
}
}
}

View file

@ -11,3 +11,6 @@
@import "mac/search";
@import "mac/tabBar";
@import "mac/tag-selector";
@import "mac/virtualized-table";
@import "mac/collection-tree";
@import "mac/item-tree";

View file

@ -10,3 +10,6 @@
@import "linux/search";
@import "linux/tagsBox";
@import "linux/about";
@import "linux/virtualized-table";
@import "linux/item-tree";
@import "linux/collection-tree";

View file

@ -7,3 +7,5 @@
@import "win/createParent";
@import "win/search";
@import "win/tag-selector";
@import "win/item-tree";
@import "win/virtualized-table";

View file

@ -257,6 +257,11 @@ var waitForTagSelector = function (win, numUpdates = 1) {
return deferred.promise;
};
var waitForCollectionTree = function(win) {
let cv = win.ZoteroPane.collectionsView;
return cv._waitForEvent('refresh');
}
/**
* Waits for a single item event. Returns a promise for the item ID(s).
*/
@ -324,29 +329,10 @@ function waitForCallback(cb, interval, timeout) {
}
function clickOnItemsRow(itemsView, row, button = 0) {
var x = {};
var y = {};
var width = {};
var height = {};
itemsView._treebox.getCoordsForCellItem(
row,
itemsView._treebox.columns.getNamedColumn('zotero-items-column-title'),
'text',
x, y, width, height
);
// Select row to trigger multi-select
var tree = itemsView._treebox.treeBody;
var rect = tree.getBoundingClientRect();
var x = rect.left + x.value;
var y = rect.top + y.value;
tree.dispatchEvent(new MouseEvent("mousedown", {
clientX: x,
clientY: y,
button,
detail: 1
}));
function clickOnItemsRow(win, itemsView, row) {
itemsView._treebox.scrollToRow(row);
let elem = win.document.querySelector(`#${itemsView.id}-row-${row}`);
elem.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, button: 0 }));
}

View file

@ -3,8 +3,8 @@
describe("Create Bibliography Dialog", function () {
var win, zp;
before(function* () {
win = yield loadZoteroPane();
before(async function () {
win = await loadZoteroPane();
zp = win.ZoteroPane;
});
@ -12,19 +12,19 @@ describe("Create Bibliography Dialog", function () {
win.close();
});
it("should perform a search", function* () {
yield Zotero.Styles.init();
var item = yield createDataObject('item');
it("should perform a search", async function () {
await Zotero.Styles.init();
var item = await createDataObject('item');
var deferred = Zotero.Promise.defer();
var called = false;
waitForWindow("chrome://zotero/content/bibliography.xul", function (dialog) {
waitForWindow("chrome://zotero/content/preferences/preferences.xul", function (window) {
// Wait for pane switch
Zotero.Promise.coroutine(function* () {
(async function () {
do {
Zotero.debug("Checking for pane");
yield Zotero.Promise.delay(5);
await Zotero.Promise.delay(5);
}
while (window.document.documentElement.currentPane.id != 'zotero-prefpane-cite');
let pane = window.document.documentElement.currentPane;
@ -37,8 +37,8 @@ describe("Create Bibliography Dialog", function () {
});
dialog.document.getElementById('manage-styles').click();
});
win.Zotero_File_Interface.bibliographyFromItems();
yield deferred.promise;
await win.Zotero_File_Interface.bibliographyFromItems();
await deferred.promise;
assert.ok(called);
});

View file

@ -1,6 +1,6 @@
"use strict";
describe("Zotero.CollectionTreeView", function() {
describe("Zotero.CollectionTree", function() {
var win, zp, cv, userLibraryID;
before(function* () {
@ -51,8 +51,10 @@ describe("Zotero.CollectionTreeView", function() {
if (cv.isContainerOpen(group2Row)) {
yield cv.toggleOpenState(group2Row);
}
// Don't wait for delayed save
cv._saveOpenStates();
// #_saveOpenStates is debounced
yield Zotero.Promise.delay(500);
group1Row = cv.getRowIndexByID(group1.treeViewID);
group2Row = cv.getRowIndexByID(group2.treeViewID);
@ -79,30 +81,22 @@ describe("Zotero.CollectionTreeView", function() {
describe("collapse/expand", function () {
it("should close and open My Library repeatedly", function* () {
yield cv.selectLibrary(userLibraryID);
var row = cv.selection.currentIndex;
var row = cv.selection.focused;
cv.collapseLibrary(userLibraryID);
var nextRow = cv.getRow(row + 1);
assert.equal(cv.selection.currentIndex, row);
assert.ok(nextRow.isSeparator());
assert.equal(cv.selection.focused, row);
assert.isFalse(cv.isContainerOpen(row));
yield cv.expandLibrary(userLibraryID);
nextRow = cv.getRow(row + 1);
assert.equal(cv.selection.currentIndex, row);
assert.ok(!nextRow.isSeparator());
assert.equal(cv.selection.focused, row);
assert.ok(cv.isContainerOpen(row));
cv.collapseLibrary(userLibraryID);
nextRow = cv.getRow(row + 1);
assert.equal(cv.selection.currentIndex, row);
assert.ok(nextRow.isSeparator());
assert.equal(cv.selection.focused, row);
assert.isFalse(cv.isContainerOpen(row));
yield cv.expandLibrary(userLibraryID);
nextRow = cv.getRow(row + 1);
assert.equal(cv.selection.currentIndex, row);
assert.ok(!nextRow.isSeparator());
assert.equal(cv.selection.focused, row);
assert.ok(cv.isContainerOpen(row));
})
})
@ -112,7 +106,7 @@ describe("Zotero.CollectionTreeView", function() {
before(function* () {
yield cv.selectLibrary(userLibraryID);
libraryRow = cv.selection.currentIndex;
libraryRow = cv.selection.focused;
});
beforeEach(function* () {
@ -128,28 +122,31 @@ describe("Zotero.CollectionTreeView", function() {
it("should open a library and respect stored container state", function* () {
// Collapse B
yield cv.toggleOpenState(cv.getRowIndexByID(col2.treeViewID));
yield cv._saveOpenStates();
cv._saveOpenStates();
// #_saveOpenStates is debounced
yield Zotero.Promise.delay(500);
// Close and reopen library
yield cv.toggleOpenState(libraryRow);
yield cv.expandLibrary(userLibraryID);
assert.ok(cv.getRowIndexByID(col1.treeViewID))
assert.ok(cv.getRowIndexByID(col2.treeViewID))
assert.isFalse(cv.getRowIndexByID(col3.treeViewID))
assert.isTrue(cv.isContainerOpen(libraryRow));
assert.isTrue(cv.isContainerOpen(cv.getRowIndexByID(col1.treeViewID)));
assert.isFalse(cv.isContainerOpen(cv.getRowIndexByID(col2.treeViewID)));
});
it("should open a library and all subcollections in recursive mode", function* () {
yield cv.toggleOpenState(cv.getRowIndexByID(col2.treeViewID));
yield cv._saveOpenStates();
cv._saveOpenStates();
// #_saveOpenStates is debounced
yield Zotero.Promise.delay(500);
// Close and reopen library
yield cv.toggleOpenState(libraryRow);
yield cv.expandLibrary(userLibraryID, true);
assert.ok(cv.getRowIndexByID(col1.treeViewID))
assert.ok(cv.getRowIndexByID(col2.treeViewID))
assert.ok(cv.getRowIndexByID(col3.treeViewID))
assert.isTrue(cv.isContainerOpen(cv.getRowIndexByID(col1.treeViewID)));
assert.isTrue(cv.isContainerOpen(cv.getRowIndexByID(col2.treeViewID)));
});
it("should open a group and show top-level collections", function* () {
@ -162,9 +159,11 @@ describe("Zotero.CollectionTreeView", function() {
var col5 = yield createDataObject('collection', { libraryID, parentID: col4.id });
// Close everything
[col4, col1, group].forEach(o => cv._closeContainer(cv.getRowIndexByID(o.treeViewID)));
yield Zotero.Promise.all([col4, col1, group]
.map(o => cv.toggleOpenState(cv.getRowIndexByID(o.treeViewID), false)));
yield cv.expandLibrary(libraryID);
assert.isNumber(cv.getRowIndexByID(col1.treeViewID));
assert.isNumber(cv.getRowIndexByID(col2.treeViewID));
assert.isNumber(cv.getRowIndexByID(col3.treeViewID));
@ -185,6 +184,7 @@ describe("Zotero.CollectionTreeView", function() {
assert.isFalse(cv.isContainerOpen(row));
yield cv.expandToCollection(collection2.id);
cv.forceUpdate();
// Make sure parent row position hasn't changed
assert.equal(cv.getRowIndexByID("C" + collection1.id), row);
@ -196,7 +196,7 @@ describe("Zotero.CollectionTreeView", function() {
describe("#selectByID()", function () {
it("should select the trash", function* () {
yield cv.selectByID("T1");
var row = cv.selection.currentIndex;
var row = cv.selection.focused;
var treeRow = cv.getRow(row);
assert.ok(treeRow.isTrash());
assert.equal(treeRow.ref.libraryID, userLibraryID);
@ -206,7 +206,7 @@ describe("Zotero.CollectionTreeView", function() {
describe("#selectWait()", function () {
it("shouldn't hang if row is already selected", function* () {
var row = cv.getRowIndexByID("T" + userLibraryID);
cv.selection.select(row);
yield cv.selectWait(row);
yield Zotero.Promise.delay(50);
yield cv.selectWait(row);
})
@ -288,14 +288,14 @@ describe("Zotero.CollectionTreeView", function() {
yield cv.selectLibrary(group.libraryID);
yield waitForItemsLoad(win);
assert.isFalse(cv.selectedTreeRow.editable);
assert.isFalse(zp.getCollectionTreeRow().editable);
var cmd = win.document.getElementById('cmd_zotero_newStandaloneNote');
assert.isTrue(cmd.getAttribute('disabled') == 'true');
group.editable = true;
yield group.saveTx();
assert.isTrue(cv.selectedTreeRow.editable);
assert.isTrue(zp.getCollectionTreeRow().editable);
assert.isFalse(cmd.getAttribute('disabled') == 'true');
});
@ -375,7 +375,7 @@ describe("Zotero.CollectionTreeView", function() {
assert.isAbove(aRow, 0);
assert.isAbove(bRow, 0);
// skipSelect is implied for multiple collections, so library should still be selected
assert.equal(cv.selection.currentIndex, 0);
assert.equal(cv.selection.focused, 0);
});
@ -432,7 +432,7 @@ describe("Zotero.CollectionTreeView", function() {
// since otherwise they'll interfere with the count
yield getGroup();
var originalRowCount = cv.rowCount;
var originalRowCount = cv._rows.length;
var group = yield createGroup();
yield createDataObject('collection', { libraryID: group.libraryID });
@ -442,7 +442,7 @@ describe("Zotero.CollectionTreeView", function() {
yield createDataObject('collection', { libraryID: group.libraryID });
// Group, collections, Duplicates, Unfiled, and trash
assert.equal(cv.rowCount, originalRowCount + 9);
assert.equal(cv._rows.length, originalRowCount + 9);
// Select group
yield cv.selectLibrary(group.libraryID);
@ -452,7 +452,7 @@ describe("Zotero.CollectionTreeView", function() {
try {
yield group.eraseTx();
assert.equal(cv.rowCount, originalRowCount);
assert.equal(cv._rows.length, originalRowCount);
// Make sure the tree wasn't refreshed
sinon.assert.notCalled(spy);
}
@ -482,7 +482,9 @@ describe("Zotero.CollectionTreeView", function() {
yield cv.selectLibrary(feed.libraryID);
waitForDialog();
var id = feed.treeViewID;
let promise = waitForCollectionTree(win);
yield win.ZoteroPane.deleteSelectedCollection();
yield promise;
assert.isFalse(cv.getRowIndexByID(id))
})
});
@ -491,10 +493,11 @@ describe("Zotero.CollectionTreeView", function() {
it("should switch to library root if item isn't in collection", async function () {
var item = await createDataObject('item');
var collection = await createDataObject('collection');
Zotero.debug(zp.itemsView._rows);
await cv.selectItem(item.id);
await waitForItemsLoad(win);
assert.equal(cv.selection.currentIndex, 0);
assert.sameMembers(zp.itemsView.getSelectedItems(true), [item.id]);
assert.equal(cv.selection.focused, 0);
assert.sameMembers(zp.itemsView.getSelectedItems(), [item]);
});
});
@ -505,12 +508,12 @@ describe("Zotero.CollectionTreeView", function() {
var item2 = await createDataObject('item');
await cv.selectItems([item1.id, item2.id]);
await waitForItemsLoad(win);
assert.equal(cv.selection.currentIndex, 0);
assert.equal(cv.selection.focused, 0);
assert.sameMembers(zp.itemsView.getSelectedItems(true), [item1.id, item2.id]);
});
});
describe("#drop()", function () {
describe("#onDrop()", function () {
/**
* Simulate a drag and drop
*
@ -521,7 +524,7 @@ describe("Zotero.CollectionTreeView", function() {
* value returned after the drag. Otherwise, an 'add' event will be waited for, and
* an object with 'ids' and 'extraData' will be returned.
*/
var drop = Zotero.Promise.coroutine(function* (objectType, targetRow, ids, promise, action = 'copy') {
var onDrop = Zotero.Promise.coroutine(function* (objectType, targetRow, ids, promise, action = 'copy') {
if (typeof targetRow == 'string') {
var row = cv.getRowIndexByID(targetRow);
var orient = 0;
@ -530,30 +533,33 @@ describe("Zotero.CollectionTreeView", function() {
var { row, orient } = targetRow;
}
var stub = sinon.stub(Zotero.DragDrop, "getDragTarget");
stub.returns(cv.getRow(row));
Zotero.DragDrop.currentDragSource = objectType == "item" && zp.itemsView.collectionTreeRow;
if (!promise) {
promise = waitForNotifierEvent("add", objectType);
}
yield cv.drop(row, orient, {
dropEffect: action,
effectAllowed: action,
mozSourceNode: win.document.getElementById(`zotero-${objectType}s-tree`).treeBoxObject.treeBody,
types: {
contains: function (type) {
return type == `zotero/${objectType}`;
}
},
getData: function (type) {
if (type == `zotero/${objectType}`) {
return ids.join(",");
yield cv.onDrop({
persist: () => 0,
target: {ownerDocument: {defaultView: win}},
dataTransfer: {
dropEffect: action,
effectAllowed: action,
types: {
contains: function (type) {
return type == `zotero/${objectType}`;
}
},
getData: function (type) {
if (type == `zotero/${objectType}`) {
return ids.join(",");
}
}
}
});
}, row);
// Add observer to wait for add
var result = yield promise;
stub.restore();
Zotero.DragDrop.currentDragSource = null;
return result;
});
@ -561,12 +567,9 @@ describe("Zotero.CollectionTreeView", function() {
var canDrop = Zotero.Promise.coroutine(function* (type, targetRowID, ids) {
var row = cv.getRowIndexByID(targetRowID);
var stub = sinon.stub(Zotero.DragDrop, "getDragTarget");
stub.returns(cv.getRow(row));
var dt = {
dropEffect: 'copy',
effectAllowed: 'copy',
mozSourceNode: win.document.getElementById(`zotero-${type}s-tree`),
types: {
contains: function (type) {
return type == `zotero/${type}`;
@ -582,7 +585,6 @@ describe("Zotero.CollectionTreeView", function() {
if (canDrop) {
canDrop = yield cv.canDropCheckAsync(row, 0, dt);
}
stub.restore();
return canDrop;
});
@ -604,14 +606,14 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'collection-item', 'test');
yield drop('item', 'C' + collection.id, [item.id], deferred.promise);
yield onDrop('item', 'C' + collection.id, [item.id], deferred.promise);
Zotero.Notifier.unregisterObserver(observerID);
yield cv.selectCollection(collection.id);
yield waitForItemsLoad(win);
var itemsView = win.ZoteroPane.itemsView
var itemsView = win.ZoteroPane.itemsView;
assert.equal(itemsView.rowCount, 1);
var treeRow = itemsView.getRow(0);
assert.equal(treeRow.ref.id, item.id);
@ -636,7 +638,9 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'collection-item', 'test');
yield drop('item', 'C' + collection2.id, [item.id], deferred.promise, 'move');
let promise = zp.itemsView.waitForSelect();
yield onDrop('item', 'C' + collection2.id, [item.id], deferred.promise, 'move');
yield promise;
Zotero.Notifier.unregisterObserver(observerID);
@ -683,7 +687,7 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'item', 'test');
yield drop('item', 'P' + libraryID, [item.id], deferred.promise);
yield onDrop('item', 'P' + libraryID, [item.id], deferred.promise);
Zotero.Notifier.unregisterObserver(observerID);
stub.restore();
@ -721,7 +725,7 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'item', 'test');
yield drop('item', 'P' + libraryID, [item.id], deferred.promise);
yield onDrop('item', 'P' + libraryID, [item.id], deferred.promise);
Zotero.Notifier.unregisterObserver(observerID);
stub.restore();
@ -760,7 +764,7 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'item', 'test');
yield drop('item', 'P' + libraryID, [item.id], deferred.promise);
yield onDrop('item', 'P' + libraryID, [item.id], deferred.promise);
Zotero.Notifier.unregisterObserver(observerID);
stub.restore();
@ -799,7 +803,7 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'item', 'test');
yield drop('item', 'P' + libraryID, [item.id], deferred.promise);
yield onDrop('item', 'P' + libraryID, [item.id], deferred.promise);
Zotero.Notifier.unregisterObserver(observerID);
stub.restore();
@ -821,7 +825,7 @@ describe("Zotero.CollectionTreeView", function() {
parentItemID: item.id
});
var ids = (yield drop('item', 'L' + group.libraryID, [item.id])).ids;
var ids = (yield onDrop('item', 'L' + group.libraryID, [item.id])).ids;
yield cv.selectLibrary(group.libraryID);
yield waitForItemsLoad(win);
@ -864,7 +868,7 @@ describe("Zotero.CollectionTreeView", function() {
attachment.setField('title', attachmentTitle);
yield attachment.saveTx();
yield drop('item', 'L' + group.libraryID, [item.id]);
yield onDrop('item', 'L' + group.libraryID, [item.id]);
assert.isFalse(yield canDrop('item', 'L' + group.libraryID, [item.id]));
})
@ -878,7 +882,7 @@ describe("Zotero.CollectionTreeView", function() {
await cv.selectLibrary(group1.libraryID);
await waitForItemsLoad(win);
await drop('item', 'L' + group2.libraryID, [item.id]);
await onDrop('item', 'L' + group2.libraryID, [item.id]);
assert.isFalse(await item.getLinkedItem(group2.libraryID));
// New collection should link back to original
@ -893,14 +897,14 @@ describe("Zotero.CollectionTreeView", function() {
var collection = await createDataObject('collection', { libraryID: group.libraryID });
var item = await createDataObject('item', false, { skipSelect: true });
await drop('item', 'L' + group.libraryID, [item.id]);
await onDrop('item', 'L' + group.libraryID, [item.id]);
var droppedItem = await item.getLinkedItem(group.libraryID);
droppedItem.setCollections([collection.id]);
droppedItem.deleted = true;
await droppedItem.saveTx();
await drop('item', 'L' + group.libraryID, [item.id]);
await onDrop('item', 'L' + group.libraryID, [item.id]);
var linkedItem = await item.getLinkedItem(group.libraryID);
assert.notEqual(linkedItem, droppedItem);
@ -934,7 +938,7 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'collection', 'test');
yield drop(
yield onDrop(
'collection',
{
row: 0,
@ -990,7 +994,7 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'collection', 'test');
yield drop(
yield onDrop(
'collection',
{
row: colIndexE,
@ -1054,7 +1058,7 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'collection', 'test');
yield drop(
yield onDrop(
'collection',
{
row: colIndexD,
@ -1107,7 +1111,7 @@ describe("Zotero.CollectionTreeView", function() {
}
}, 'collection', 'test');
await drop(
await onDrop(
'collection',
'L' + group.libraryID,
[collectionA.id],
@ -1144,7 +1148,7 @@ describe("Zotero.CollectionTreeView", function() {
await cv.selectCollection(collection.id);
await waitForItemsLoad(win);
await drop('collection', 'L' + group2.libraryID, [collection.id]);
await onDrop('collection', 'L' + group2.libraryID, [collection.id]);
assert.isFalse(await collection.getLinkedCollection(group2.libraryID));
// New collection should link back to original
@ -1174,7 +1178,7 @@ describe("Zotero.CollectionTreeView", function() {
var deferred = Zotero.Promise.defer();
var itemIds;
var ids = (yield drop('item', 'C' + collection.id, [feedItem.id])).ids;
var ids = (yield onDrop('item', 'C' + collection.id, [feedItem.id])).ids;
// Check that the translated item was the one that was created after drag
var item;

View file

@ -38,8 +38,10 @@ describe("Duplicate Items", function () {
var iv = zp.itemsView;
var row = iv.getRowIndexByID(item1.id);
assert.isNumber(row);
clickOnItemsRow(iv, row);
var promise = iv.waitForSelect();
clickOnItemsRow(win, iv, row);
assert.equal(iv.selection.count, 2);
yield promise;
// Click merge button
var button = win.document.getElementById('zotero-duplicates-merge-button');
@ -75,7 +77,9 @@ describe("Duplicate Items", function () {
// Select the first item, which should select both
var iv = zp.itemsView;
var row = iv.getRowIndexByID(item1.id);
clickOnItemsRow(iv, row);
var promise = iv.waitForSelect();
clickOnItemsRow(win, iv, row);
yield promise;
// Click merge button
var button = win.document.getElementById('zotero-duplicates-merge-button');

View file

@ -1,36 +1,35 @@
"use strict";
describe("Zotero.ItemTreeView", function() {
describe("Zotero.ItemTree", function() {
var win, zp, cv, itemsView;
var userLibraryID;
var existingItemID;
var existingItemID2;
// Load Zotero pane and select library
before(function* () {
win = yield loadZoteroPane();
before(async function () {
win = await loadZoteroPane();
zp = win.ZoteroPane;
cv = zp.collectionsView;
userLibraryID = Zotero.Libraries.userLibraryID;
var item1 = yield createDataObject('item', { setTitle: true });
var item1 = await createDataObject('item', { setTitle: true });
existingItemID = item1.id;
var item2 = yield createDataObject('item');
var item2 = await createDataObject('item');
existingItemID2 = item2.id;
});
beforeEach(function* () {
yield selectLibrary(win);
beforeEach(async function () {
await selectLibrary(win);
itemsView = zp.itemsView;
})
itemsView._columnsId = null;
});
after(function () {
win.close();
});
it("shouldn't show items in trash in library root", function* () {
var item = yield createDataObject('item', { title: "foo" });
it("shouldn't show items in trash in library root", async function () {
var item = await createDataObject('item', { title: "foo" });
var itemID = item.id;
item.deleted = true;
yield item.saveTx();
await item.saveTx();
assert.isFalse(itemsView.getRowIndexByID(itemID));
})
@ -83,7 +82,7 @@ describe("Zotero.ItemTreeView", function() {
var toSelect = [note1.id, note2.id, note3.id];
itemsView.collapseAllRows();
var numSelected = await itemsView.selectItems(toSelect);
assert.equal(numSelected, 3);
var selected = itemsView.getSelectedItems(true);
@ -106,9 +105,9 @@ describe("Zotero.ItemTreeView", function() {
var str = Zotero.Utilities.randomString();
var item = yield createDataObject('item', { title: str });
var row = itemsView.getRowIndexByID(item.id);
assert.equal(itemsView.getCellText(row, { id: 'zotero-items-column-title' }), str);
assert.equal(itemsView.getCellText(row, 'title'), str);
yield modifyDataObject(item);
assert.notEqual(itemsView.getCellText(row, { id: 'zotero-items-column-title' }), str);
assert.notEqual(itemsView.getCellText(row, 'title'), str);
})
})
@ -121,15 +120,17 @@ describe("Zotero.ItemTreeView", function() {
win.ZoteroPane.itemSelected.restore();
})
it("should select a new item", function* () {
it("should select a new item", async function () {
let selectPromise = itemsView.waitForSelect();
itemsView.selection.clearSelection();
assert.lengthOf(itemsView.getSelectedItems(), 0);
await selectPromise;
assert.equal(win.ZoteroPane.itemSelected.callCount, 1);
// Create item
var item = new Zotero.Item('book');
var id = yield item.saveTx();
var id = await item.saveTx();
// New item should be selected
var selected = itemsView.getSelectedItems();
@ -138,7 +139,7 @@ describe("Zotero.ItemTreeView", function() {
// Item should have been selected once
assert.equal(win.ZoteroPane.itemSelected.callCount, 2);
assert.ok(win.ZoteroPane.itemSelected.returnValues[1].value());
await assert.eventually.ok(win.ZoteroPane.itemSelected.returnValues[1]);
});
it("shouldn't select a new item if skipNotifier is passed", function* () {
@ -166,9 +167,9 @@ describe("Zotero.ItemTreeView", function() {
assert.equal(selected[0], existingItemID);
});
it("shouldn't select a new item if skipSelect is passed", function* () {
it("shouldn't select a new item if skipSelect is passed", async function () {
// Select existing item
yield itemsView.selectItem(existingItemID);
await itemsView.selectItem(existingItemID);
var selected = itemsView.getSelectedItems(true);
assert.lengthOf(selected, 1);
assert.equal(selected[0], existingItemID);
@ -178,14 +179,12 @@ describe("Zotero.ItemTreeView", function() {
// Create item with skipSelect flag
var item = new Zotero.Item('book');
var id = yield item.saveTx({
var id = await item.saveTx({
skipSelect: true
});
// itemSelected should have been called once (from 'selectEventsSuppressed = false'
// in notify()) as a no-op
assert.equal(win.ZoteroPane.itemSelected.callCount, 1);
assert.isFalse(win.ZoteroPane.itemSelected.returnValues[0].value());
// No select events should have occurred
assert.equal(win.ZoteroPane.itemSelected.callCount, 0);
// Existing item should still be selected
selected = itemsView.getSelectedItems(true);
@ -244,56 +243,6 @@ describe("Zotero.ItemTreeView", function() {
yield itemsView._refreshPromise;
});
it.skip("shouldn't clear quicksearch in Unfiled Items when adding selected item to collection", async function () {
var spy = sinon.spy(win.ZoteroPane, 'search');
var collection = await createDataObject('collection');
var title1 = Zotero.Utilities.randomString();
var title2 = Zotero.Utilities.randomString();
var item1 = await createDataObject('item', { title: title1 });
var item2 = await createDataObject('item', { title: title1 });
var item3 = await createDataObject('item', { title: title2 });
await zp.setVirtual(userLibraryID, 'unfiled', true, true);
itemsView = zp.itemsView;
assert.equal(cv.selectedTreeRow.id, 'U' + userLibraryID);
var searchString = title1;
var quicksearch = win.document.getElementById('zotero-tb-search');
quicksearch.value = searchString;
quicksearch.doCommand();
while (!spy.called) {
Zotero.debug("Waiting for search");
await Zotero.Promise.delay(50);
}
await spy.returnValues[0];
spy.resetHistory();
assert.equal(itemsView.rowCount, 2);
await itemsView.selectItem(item1.id);
// Move item1 to collection
item1.setCollections([collection.id]);
await item1.saveTx({
skipSelect: true
});
assert.equal(itemsView.rowCount, 1);
assert.equal(quicksearch.value, searchString);
// Clear search
quicksearch.value = "";
quicksearch.doCommand();
while (!spy.called) {
Zotero.debug("Waiting for search");
await Zotero.Promise.delay(50);
}
await spy.returnValues[0];
spy.restore();
});
it("shouldn't change selection outside of trash if new trashed item is created with skipSelect", function* () {
yield selectLibrary(win);
yield waitForItemsLoad(win);
@ -325,10 +274,8 @@ describe("Zotero.ItemTreeView", function() {
item.setField('title', 'no select on modify');
yield item.saveTx();
// itemSelected should have been called once (from 'selectEventsSuppressed = false'
// in notify()) as a no-op
assert.equal(win.ZoteroPane.itemSelected.callCount, 1);
assert.isFalse(win.ZoteroPane.itemSelected.returnValues[0].value());
// No select events should have occurred
assert.equal(win.ZoteroPane.itemSelected.callCount, 0);
// Modified item should not be selected
assert.lengthOf(itemsView.getSelectedItems(), 0);
@ -387,14 +334,14 @@ describe("Zotero.ItemTreeView", function() {
}.bind(this));
// Selection should stay on third row
assert.equal(itemsView.selection.currentIndex, 2);
assert.equal(itemsView.selection.focused, 2);
// Delete item
var treeRow = itemsView.getRow(2);
yield treeRow.ref.eraseTx();
// Selection should stay on third row
assert.equal(itemsView.selection.currentIndex, 2);
assert.equal(itemsView.selection.focused, 2);
yield Zotero.Items.erase(items.map(item => item.id));
});
@ -581,33 +528,31 @@ describe("Zotero.ItemTreeView", function() {
assert.equal(zp.itemsView.getRowIndexByID(item.id), 0);
});
it("should re-sort search results when an item is modified", function* () {
var search = yield createDataObject('search');
it("should re-sort search results when an item is modified", async function () {
var search = await createDataObject('search');
itemsView = zp.itemsView;
var title = search.getConditions()[0].value;
var item1 = yield createDataObject('item', { title: title + " 1" });
var item2 = yield createDataObject('item', { title: title + " 3" });
var item3 = yield createDataObject('item', { title: title + " 5" });
var item4 = yield createDataObject('item', { title: title + " 7" });
var col = itemsView._treebox.columns.getNamedColumn('zotero-items-column-title');
col.element.click();
if (col.element.getAttribute('sortDirection') == 'ascending') {
col.element.click();
}
var item1 = await createDataObject('item', { title: title + " 1" });
var item2 = await createDataObject('item', { title: title + " 3" });
var item3 = await createDataObject('item', { title: title + " 5" });
var item4 = await createDataObject('item', { title: title + " 7" });
const colIndex = itemsView.tree._getColumns().findIndex(column => column.dataKey == 'title');
await itemsView.tree._columns.toggleSort(colIndex);
// Check initial sort order
assert.equal(itemsView.getRow(0).ref.getField('title'), title + " 7");
assert.equal(itemsView.getRow(3).ref.getField('title'), title + " 1");
assert.equal(itemsView.getRow(0).ref.getField('title'), title + " 1");
assert.equal(itemsView.getRow(3).ref.getField('title'), title + " 7");
// Set first row to title that should be sorted in the middle
itemsView.getRow(0).ref.setField('title', title + " 4");
yield itemsView.getRow(0).ref.saveTx();
itemsView.getRow(3).ref.setField('title', title + " 4");
await itemsView.getRow(3).ref.saveTx();
assert.equal(itemsView.getRow(0).ref.getField('title'), title + " 5");
assert.equal(itemsView.getRow(1).ref.getField('title'), title + " 4");
assert.equal(itemsView.getRow(3).ref.getField('title'), title + " 1");
assert.equal(itemsView.getRow(0).ref.getField('title'), title + " 1");
assert.equal(itemsView.getRow(1).ref.getField('title'), title + " 3");
assert.equal(itemsView.getRow(2).ref.getField('title'), title + " 4");
assert.equal(itemsView.getRow(3).ref.getField('title'), title + " 5");
});
it("should update search results when search conditions are changed", function* () {
@ -643,16 +588,16 @@ describe("Zotero.ItemTreeView", function() {
assert.equal(zp.itemsView.rowCount, 1);
});
it("should remove items from Unfiled Items when added to a collection", function* () {
it("should remove items from Unfiled Items when added to a collection", async function () {
var userLibraryID = Zotero.Libraries.userLibraryID;
var collection = yield createDataObject('collection');
var item = yield createDataObject('item', { title: "Unfiled Item" });
yield zp.setVirtual(userLibraryID, 'unfiled', true, true);
assert.equal(cv.selectedTreeRow.id, 'U' + userLibraryID);
yield waitForItemsLoad(win);
var collection = await createDataObject('collection');
var item = await createDataObject('item', { title: "Unfiled Item" });
await zp.setVirtual(userLibraryID, 'unfiled', true, true);
assert.equal(zp.getCollectionTreeRow().id, 'U' + userLibraryID);
await waitForItemsLoad(win);
assert.isNumber(zp.itemsView.getRowIndexByID(item.id));
yield Zotero.DB.executeTransaction(function* () {
yield collection.addItem(item.id);
await Zotero.DB.executeTransaction(async function () {
await collection.addItem(item.id);
});
assert.isFalse(zp.itemsView.getRowIndexByID(item.id));
});
@ -675,38 +620,37 @@ describe("Zotero.ItemTreeView", function() {
});
describe("My Publications", function () {
before(function* () {
before(async function () {
var libraryID = Zotero.Libraries.userLibraryID;
var s = new Zotero.Search;
s.libraryID = libraryID;
s.addCondition('publications', 'true');
var ids = yield s.search();
var ids = await s.search();
yield Zotero.Items.erase(ids);
await Zotero.Items.erase(ids);
yield zp.collectionsView.selectByID("P" + libraryID);
yield waitForItemsLoad(win);
await zp.collectionsView.selectByID("P" + libraryID);
await waitForItemsLoad(win);
// Make sure we're showing the intro text
var deck = win.document.getElementById('zotero-items-pane-content');
assert.equal(deck.selectedIndex, 1);
var messageElem = win.document.querySelector('.items-tree-message');
assert.notEqual(messageElem.style.display, 'none');
});
it("should replace My Publications intro text with items list on item add", function* () {
var item = yield createDataObject('item');
it("should replace My Publications intro text with items list on item add", async function () {
var item = await createDataObject('item');
yield zp.collectionsView.selectByID("P" + item.libraryID);
yield waitForItemsLoad(win);
var iv = zp.itemsView;
await zp.collectionsView.selectByID("P" + item.libraryID);
await waitForItemsLoad(win);
item.inPublications = true;
yield item.saveTx();
await item.saveTx();
var messageElem = win.document.querySelector('.items-tree-message');
assert.equal(messageElem.style.display, 'none');
var deck = win.document.getElementById('zotero-items-pane-content');
assert.equal(deck.selectedIndex, 0);
assert.isNumber(iv.getRowIndexByID(item.id));
assert.isNumber(itemsView.getRowIndexByID(item.id));
});
it("should add new item to My Publications items list", function* () {
@ -716,16 +660,15 @@ describe("Zotero.ItemTreeView", function() {
yield zp.collectionsView.selectByID("P" + item1.libraryID);
yield waitForItemsLoad(win);
var iv = zp.itemsView;
var deck = win.document.getElementById('zotero-items-pane-content');
assert.equal(deck.selectedIndex, 0);
var messageElem = win.document.querySelector('.items-tree-message');
assert.equal(messageElem.style.display, 'none');
var item2 = createUnsavedDataObject('item');
item2.inPublications = true;
yield item2.saveTx();
assert.isNumber(iv.getRowIndexByID(item2.id));
assert.isNumber(itemsView.getRowIndexByID(item2.id));
});
it("should add modified item to My Publications items list", function* () {
@ -736,17 +679,16 @@ describe("Zotero.ItemTreeView", function() {
yield zp.collectionsView.selectByID("P" + item1.libraryID);
yield waitForItemsLoad(win);
var iv = zp.itemsView;
var messageElem = win.document.querySelector('.items-tree-message');
assert.equal(messageElem.style.display, 'none');
var deck = win.document.getElementById('zotero-items-pane-content');
assert.equal(deck.selectedIndex, 0);
assert.isFalse(iv.getRowIndexByID(item2.id));
assert.isFalse(itemsView.getRowIndexByID(item2.id));
item2.inPublications = true;
yield item2.saveTx();
assert.isNumber(iv.getRowIndexByID(item2.id));
assert.isNumber(itemsView.getRowIndexByID(item2.id));
});
it("should show Show/Hide button for imported file attachment", function* () {
@ -755,9 +697,9 @@ describe("Zotero.ItemTreeView", function() {
yield zp.collectionsView.selectByID("P" + item.libraryID);
yield waitForItemsLoad(win);
var iv = zp.itemsView;
yield iv.selectItem(attachment.id);
yield itemsView.selectItem(attachment.id);
yield Zotero.Promise.delay();
var box = win.document.getElementById('zotero-item-pane-top-buttons-my-publications');
assert.isFalse(box.hidden);
@ -772,9 +714,8 @@ describe("Zotero.ItemTreeView", function() {
yield zp.collectionsView.selectByID("P" + item.libraryID);
yield waitForItemsLoad(win);
var iv = zp.itemsView;
yield iv.selectItem(attachment.id);
yield itemsView.selectItem(attachment.id);
var box = win.document.getElementById('zotero-item-pane-top-buttons-my-publications');
assert.isTrue(box.hidden);
@ -783,7 +724,7 @@ describe("Zotero.ItemTreeView", function() {
})
describe("#drop()", function () {
describe("#onDrop()", function () {
var httpd;
var port = 16213;
var baseURL = `http://localhost:${port}/`;
@ -791,6 +732,11 @@ describe("Zotero.ItemTreeView", function() {
var pdfURL = baseURL + pdfFilename;
var pdfPath;
function drop(index, orient, dataTransfer) {
Zotero.DragDrop.currentOrientation = orient;
return itemsView.onDrop({ dataTransfer: dataTransfer }, index);
}
// Serve a PDF to test URL dragging
before(function () {
Components.utils.import("resource://zotero-unit/httpd.js");
@ -826,12 +772,11 @@ describe("Zotero.ItemTreeView", function() {
var item2 = yield createDataObject('item', { title: "B", collections: [collection.id] });
var item3 = yield createDataObject('item', { itemType: 'note', parentID: item1.id });
let view = zp.itemsView;
yield view.selectItem(item3.id);
yield itemsView.selectItem(item3.id);
var promise = view.waitForSelect();
var promise = itemsView.waitForSelect();
view.drop(view.getRowIndexByID(item2.id), 0, {
drop(itemsView.getRowIndexByID(item2.id), 0, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -845,17 +790,17 @@ describe("Zotero.ItemTreeView", function() {
}
},
mozItemCount: 1
})
});
yield promise;
// Old parent should be empty
assert.isFalse(view.isContainerOpen(view.getRowIndexByID(item1.id)));
assert.isTrue(view.isContainerEmpty(view.getRowIndexByID(item1.id)));
assert.isFalse(itemsView.isContainerOpen(itemsView.getRowIndexByID(item1.id)));
assert.isTrue(itemsView.isContainerEmpty(itemsView.getRowIndexByID(item1.id)));
// New parent should be open
assert.isTrue(view.isContainerOpen(view.getRowIndexByID(item2.id)));
assert.isFalse(view.isContainerEmpty(view.getRowIndexByID(item2.id)));
assert.isTrue(itemsView.isContainerOpen(itemsView.getRowIndexByID(item2.id)));
assert.isFalse(itemsView.isContainerEmpty(itemsView.getRowIndexByID(item2.id)));
});
it("should move a child item from last item in list to another", function* () {
@ -865,12 +810,11 @@ describe("Zotero.ItemTreeView", function() {
var item2 = yield createDataObject('item', { title: "B", collections: [collection.id] });
var item3 = yield createDataObject('item', { itemType: 'note', parentID: item2.id });
let view = zp.itemsView;
yield view.selectItem(item3.id);
yield itemsView.selectItem(item3.id);
var promise = view.waitForSelect();
var promise = itemsView.waitForSelect();
view.drop(view.getRowIndexByID(item1.id), 0, {
drop(itemsView.getRowIndexByID(item1.id), 0, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -884,17 +828,17 @@ describe("Zotero.ItemTreeView", function() {
}
},
mozItemCount: 1
})
});
yield promise;
// Old parent should be empty
assert.isFalse(view.isContainerOpen(view.getRowIndexByID(item2.id)));
assert.isTrue(view.isContainerEmpty(view.getRowIndexByID(item2.id)));
assert.isFalse(itemsView.isContainerOpen(itemsView.getRowIndexByID(item2.id)));
assert.isTrue(itemsView.isContainerEmpty(itemsView.getRowIndexByID(item2.id)));
// New parent should be open
assert.isTrue(view.isContainerOpen(view.getRowIndexByID(item1.id)));
assert.isFalse(view.isContainerEmpty(view.getRowIndexByID(item1.id)));
assert.isTrue(itemsView.isContainerOpen(itemsView.getRowIndexByID(item1.id)));
assert.isFalse(itemsView.isContainerEmpty(itemsView.getRowIndexByID(item1.id)));
});
it("should create a stored top-level attachment when a file is dragged", function* () {
@ -903,7 +847,7 @@ describe("Zotero.ItemTreeView", function() {
var promise = itemsView.waitForSelect();
itemsView.drop(0, -1, {
drop(0, -1, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -920,6 +864,8 @@ describe("Zotero.ItemTreeView", function() {
})
yield promise;
// Attachment add triggers multiple notifications and multiple select events
yield itemsView.waitForSelect();
var items = itemsView.getSelectedItems();
var path = yield items[0].getFilePathAsync();
assert.equal(
@ -931,7 +877,7 @@ describe("Zotero.ItemTreeView", function() {
it("should create a stored top-level attachment when a URL is dragged", function* () {
var promise = itemsView.waitForSelect();
itemsView.drop(0, -1, {
drop(0, -1, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -946,7 +892,7 @@ describe("Zotero.ItemTreeView", function() {
},
mozItemCount: 1,
})
yield promise;
var item = itemsView.getSelectedItems()[0];
assert.equal(item.getField('url'), pdfURL);
@ -963,7 +909,7 @@ describe("Zotero.ItemTreeView", function() {
var promise = waitForItemEvent('add');
itemsView.drop(parentRow, 0, {
drop(parentRow, 0, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -1017,7 +963,7 @@ describe("Zotero.ItemTreeView", function() {
}
);
itemsView.drop(0, -1, {
drop(0, -1, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -1074,7 +1020,7 @@ describe("Zotero.ItemTreeView", function() {
}
);
itemsView.drop(0, -1, {
drop(0, -1, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -1118,7 +1064,7 @@ describe("Zotero.ItemTreeView", function() {
var promise = waitForItemEvent('add');
itemsView.drop(parentRow, 0, {
drop(parentRow, 0, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -1165,7 +1111,7 @@ describe("Zotero.ItemTreeView", function() {
var promise = waitForItemEvent('add');
itemsView.drop(parentRow, 0, {
drop(parentRow, 0, {
dropEffect: 'link',
effectAllowed: 'link',
types: {
@ -1212,7 +1158,7 @@ describe("Zotero.ItemTreeView", function() {
var promise = waitForItemEvent('add');
itemsView.drop(parentRow, 0, {
drop(parentRow, 0, {
dropEffect: 'link',
effectAllowed: 'link',
types: {
@ -1251,7 +1197,7 @@ describe("Zotero.ItemTreeView", function() {
var promise = waitForItemEvent('add');
itemsView.drop(parentRow, 0, {
drop(parentRow, 0, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -1293,7 +1239,7 @@ describe("Zotero.ItemTreeView", function() {
var promise = waitForItemEvent('add');
itemsView.drop(parentRow, 0, {
drop(parentRow, 0, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {
@ -1330,7 +1276,7 @@ describe("Zotero.ItemTreeView", function() {
var promise = waitForItemEvent('add');
itemsView.drop(parentRow, 0, {
drop(parentRow, 0, {
dropEffect: 'copy',
effectAllowed: 'copy',
types: {

View file

@ -235,12 +235,11 @@ describe("Zotero.Library", function() {
let library = new Zotero.Library();
yield assert.isRejected(library.eraseTx());
});
it("should throw when accessing erased library methods, except for #libraryID", function* () {
it("should throw when accessing erased library methods, except for #libraryID and #name", function* () {
let library = yield createGroup();
yield library.eraseTx();
assert.doesNotThrow(() => library.libraryID);
assert.throws(() => library.name, /^Group \(\d+\) has been disabled$/);
assert.throws(() => library.editable = false, /^Group \(\d+\) has been disabled$/);
});
it("should clear child items from caches and DB", function* () {

View file

@ -1,6 +1,6 @@
"use strict";
describe("Zotero.LibraryTreeView", function() {
describe("Zotero.LibraryTree", function() {
var win, zp, cv, itemsView;
// Load Zotero pane and select library

View file

@ -31,18 +31,16 @@ describe("Related Box", function () {
// wrappedJSObject isn't working on zotero-collections-tree for some reason, so
// just wait for the items tree to be created and select it directly
do {
var view = selectWin.document.getElementById('zotero-items-tree').view.wrappedJSObject;
var selectItemsView = selectWin.itemsView;
var selectCollectionsView = selectWin.collectionsView;
yield Zotero.Promise.delay(50);
}
while (!view);
yield view.waitForLoad();
while (!selectItemsView || !selectCollectionsView);
yield selectCollectionsView.waitForLoad();
yield selectItemsView.waitForLoad();
// Select the other item
for (let i = 0; i < view.rowCount; i++) {
if (view.getRow(i).ref.id == item1.id) {
view.selection.select(i);
}
}
yield selectItemsView.selectItem(item1.id);
selectWin.document.documentElement.acceptDialog();
// Wait for relations list to populate

View file

@ -1678,6 +1678,7 @@ describe("Connector Server", function () {
});
it("should move item saved via /saveItems to another library", async function () {
let addItemsSpy = sinon.spy(Zotero.Server.Connector.SaveSession.prototype, 'addItems');
var group = await createGroup({ editable: true, filesEditable: false });
await selectLibrary(win);
await waitForItemsLoad(win);
@ -1727,6 +1728,16 @@ describe("Connector Server", function () {
// Attachment
await waitForItemEvent('add');
// There's an additional addItems call in saveItems that is not async returned and runs
// after attachment notifier add event callbacks are run, so we have to do some
// hacky waiting here, otherwise we get some crazy race-conditions due to
// collection changing being debounced
let callCount = addItemsSpy.callCount;
while (addItemsSpy.callCount <= callCount) {
await Zotero.Promise.delay(50);
}
await addItemsSpy.lastCall.returnValue;
var req = await reqPromise;
assert.equal(req.status, 201);
@ -1779,6 +1790,8 @@ describe("Connector Server", function () {
assert.isFalse(Zotero.Items.exists(item2.id));
assert.equal(item3.libraryID, Zotero.Libraries.userLibraryID);
assert.equal(item3.numAttachments(), 1);
addItemsSpy.restore();
});
it("should move item saved via /saveSnapshot to another library", async function () {

Some files were not shown because too many files have changed in this diff Show more