Merge pull request #2029 from zotero/html-trees

This commit is contained in:
Dan Stillman 2021-08-21 07:18:43 -04:00 committed by GitHub
commit 0c01d68425
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
108 changed files with 11306 additions and 9905 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,20 +25,22 @@
Components.utils.import("resource://gre/modules/Services.jsm");
import ItemTree from 'zotero/itemTree';
import { getDefaultColumnsByDataKeys } from 'zotero/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;
function onLoad() {
async function onLoad() {
_searchBox = document.getElementById('zotero-search-box');
// Set font size from pref
@ -52,8 +54,37 @@ var ZoteroAdvancedSearch = new function() {
.then(function () {
_searchBox.search = io.dataIn.search;
});
var elem = document.getElementById('zotero-items-tree');
this.itemsView = await ItemTree.init(elem, {
id: "advanced-search",
dragAndDrop: true,
onActivate: this.onItemActivate.bind(this),
columns: getDefaultColumnsByDataKeys(['title', 'firstCreator']),
});
// A minimal implementation of Zotero.CollectionTreeRow
var collectionTreeRow = {
view: {},
ref: _searchBox.search,
isSearchMode: () => true,
getItems: async () => [],
isLibrary: () => false,
isCollection: () => false,
isSearch: () => true,
isPublications: () => false,
isDuplicates: () => false,
isFeed: () => false,
isShare: () => false,
isTrash: () => false
};
this.itemsView.changeCollectionTreeRow(collectionTreeRow);
}
this.onUnload = function () {
this.itemsView.unregister();
}
function search() {
_searchBox.updateSearch();
@ -63,41 +94,34 @@ var ZoteroAdvancedSearch = new function() {
var collectionTreeRow = {
view: {},
ref: _searchBox.search,
isSearchMode: function() { return true; },
getItems: Zotero.Promise.coroutine(function* () {
isSearchMode: () => true,
getItems: async function () {
var search = _searchBox.search.clone();
search.libraryID = _libraryID;
var ids = yield search.search();
var ids = await search.search();
return Zotero.Items.get(ids);
}),
isLibrary: function () { return false; },
isCollection: function () { return false; },
isSearch: function () { return true; },
},
isLibrary: () => false,
isCollection: () => false,
isSearch: () => true,
isPublications: () => false,
isDuplicates: () => false,
isFeed: () => false,
isShare: function () { return false; },
isTrash: function () { return false; }
}
isShare: () => false,
isTrash: () => 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;
}
@ -118,26 +142,24 @@ var ZoteroAdvancedSearch = new function() {
searches.map(s => s.name).filter(n => n.startsWith(prefix))
);
var name = { value: name };
name = { value: name };
var result = promptService.prompt(window,
Zotero.getString('pane.collections.newSavedSeach'),
Zotero.getString('pane.collections.savedSearchName'), name, "", {});
if (!result)
{
if (!result) {
return;
}
if (!name.value)
{
name.value = untitled;
if (!name.value) {
name.value = 'untitled';
}
var s = _searchBox.search.clone();
s.name = name.value;
yield s.save();
window.close()
window.close();
});
@ -149,42 +171,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;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,121 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2019 Corporation for Digital Scholarship
Vienna, 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

@ -27,7 +27,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { noop } from '../utils';
import { pickKeys } from '@zotero/immutable';
import { pickKeys } from 'zotero/modules/immutable';
//import AutoResizer from './auto-resizer';
import Autosuggest from 'react-autosuggest';

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

@ -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

View file

@ -0,0 +1,289 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2019 Corporation for Digital Scholarship
Vienna, 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 windowed-list
*/
constructor(options) {
for (let option of requiredOptions) {
if (!options.hasOwnProperty(option)) {
throw new Error('Attempted to initialize windowed-list without a required option: ' + option);
}
}
this.scrollDirection = 0;
this.scrollOffset = 0;
this.overscanCount = 2;
this._lastItemCount = null;
Object.assign(this, options);
this._renderedRows = new Map();
}
/**
* Call once to add the windowed-list DOM element to the container
*/
initialize() {
const { targetElement } = this;
this.innerElem = document.createElementNS("http://www.w3.org/1999/xhtml", 'div');
this.innerElem.className = "windowed-list";
targetElement.appendChild(this.innerElem);
targetElement.addEventListener('scroll', this._handleScroll);
this.update();
}
/**
* Call to remove the windowed-list 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) {
const maxOffset = this.itemHeight * this._getItemCount() - this.getWindowHeight();
scrollOffset = Math.min(Math.max(0, scrollOffset), maxOffset);
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

@ -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");
@ -762,6 +772,7 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
}
uninit() {
this._uninitialized = true;
ReactDOM.unmountComponentAtNode(this.domEl);
Zotero.Notifier.unregisterObserver(this._notifierID);
Zotero.Prefs.unregisterObserver(this._prefObserverID);

View file

@ -25,7 +25,7 @@
Components.utils.import("resource://gre/modules/osfile.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
import FilePicker from 'zotero/filePicker';
import FilePicker from 'zotero/modules/filePicker';
/****Zotero_File_Exporter****
**
@ -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

@ -1,4 +1,4 @@
import FilePicker from 'zotero/filePicker';
import FilePicker from 'zotero/modules/filePicker';
var Zotero_Import_Wizard = {
_wizard: null,

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

@ -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;"/>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,290 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Corporation for Digital Scholarship
Vienna, 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,
inMenu: false,
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,249 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Corporation for Digital Scholarship
Vienna, 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);
if (index <= this.selection.focused) {
this.selection.select(this.selection.focused - 1);
}
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

@ -0,0 +1,141 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Corporation for Digital Scholarship
Vienna, 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

@ -24,7 +24,7 @@
*/
Components.utils.import("resource://gre/modules/Services.jsm");
import FilePicker from 'zotero/filePicker';
import FilePicker from 'zotero/modules/filePicker';
Zotero_Preferences.Advanced = {
DEFAULT_OPENURL_RESOLVER: 'https://www.worldcat.org/registry/gateway',

View file

@ -25,7 +25,13 @@
"use strict";
import FilePicker from 'zotero/filePicker';
import FilePicker from 'zotero/modules/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([
@ -78,44 +84,66 @@ 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: 100
}
];
var handleKeyDown = (event) => {
if (event.key == 'Delete' || Zotero.isMac && event.key == 'Backspace') {
Zotero_Preferences.Cite.deleteStyle();
return false;
}
};
let styles = Zotero.Styles.getVisible()
.map((style) => {
return {
title: style.title,
updated: Zotero.Date.sqlToDate(style.updated, true).toLocaleDateString()
};
});
let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
<VirtualizedTable
getRowCount={() => styles.length}
id="styleManager-table"
ref={ref => this._tree = ref}
renderItem={makeRowRenderer(index => styles[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 +196,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

@ -27,7 +27,7 @@
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm");
import FilePicker from 'zotero/filePicker';
import FilePicker from 'zotero/modules/filePicker';
Zotero_Preferences.General = {
init: function () {

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

@ -27,7 +27,12 @@
* @fileOverview Tools for automatically retrieving a citation for the given PDF
*/
import FilePicker from 'zotero/filePicker';
import FilePicker from 'zotero/modules/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 'zotero/collectionTree';
import ItemTree from 'zotero/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

@ -7,7 +7,7 @@
<p id="result"></p>
<script src="../include.js"></script>
<script type="text/javascript">
import FilePicker from 'zotero/filePicker';
import FilePicker from 'zotero/modules/filePicker';
(async function () {
// Create schema

View file

@ -23,7 +23,7 @@
***** END LICENSE BLOCK *****
*/
import FilePicker from 'zotero/filePicker';
import FilePicker from 'zotero/modules/filePicker';
var Zotero_CSL_Editor = new function() {
this.init = init;

View file

@ -23,6 +23,9 @@
***** END LICENSE BLOCK *****
*/
// Run in RunJS:
// window.open('chrome://zotero/content/tools/data_generator.html', '_blank', 'chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar')
const React = require('react');
const ReactDOM = require('react-dom');

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

@ -25,7 +25,7 @@
"use strict";
import FilePicker from 'zotero/filePicker';
import FilePicker from 'zotero/modules/filePicker';
Zotero.DataDirectory = {
MIGRATION_MARKER: 'migrate-dir',

View file

@ -0,0 +1,245 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Corporation for Digital Scholarship
Vienna, 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 = _progressWindow.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

@ -31,7 +31,7 @@
*/
Zotero.Utilities.Internal = {
SNAPSHOT_SAVE_TIMEOUT: 30000,
/**
* Run a function on chunks of a given size of an array's elements.
*
@ -1797,77 +1797,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 +1984,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){
@ -2159,7 +2094,88 @@ Zotero.Utilities.Internal = {
return Zotero.ItemTypes.getImageSrc(attachment.mimeType === "application/pdf"
? "attachment-pdf" : "attachment-snapshot");
},
/**
* Pass a class into this to add generic methods for creating event listeners
* (and running those events).
*
* ```
* var MyClass = Zotero.Utilities.Internal.makeClassEventDispatcher(class {
* constructor: () => {
* this.onFoo = this.createEventBinding('foo');
* }
* foo: () => this.runListeners('foo');
* });
* let object = new MyClass();
* object.onFoo.addListener(() => console.log('foo ran in object of MyClass'));
* object.foo();
* ```
* @param cls
*/
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);
});
};
}
}
/**

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,50 @@
#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;
.items-tree-message {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
font-size: .7em;
}
#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({
@ -95,11 +99,10 @@ var require = (function() {
'': 'resource://zotero/',
'containers/': 'chrome://zotero/content/containers/',
'components/': 'chrome://zotero/content/components/',
'zotero/': 'chrome://zotero/content/modules/',
'@zotero/': 'chrome://zotero/content/modules/'
'zotero/': 'chrome://zotero/content/',
},
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,37 @@
#zotero-items-pane {
min-width: 290px;
min-height: 150px;
height: 150px;
width: 290px;
}
#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,265 @@
//
// 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: 1.8em;
overflow: hidden;
border-inline-end: 1px solid #ddd;
padding-inline-start: 6px;
&.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;
}
&:first-child .label {
margin-inline-start: 4px;
}
&.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: 13px;
}
.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,27 @@
.virtualized-table {
border: 1px solid #ccc;
}
.virtualized-table, .drag-image-container {
.twisty {
padding-inline-end: 3px;
svg {
width: 10px;
}
}
}
.virtualized-table-header {
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,28 @@
.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;
}
.spacer-header {
min-width: 6px;
}
}
.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,49 @@
.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 {
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;

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