Merge pull request #2862 from tnajdek/2252-bulk-folder-import-no-react-wizards-squashed
fx-compat: Wizards. Add folder import
This commit is contained in:
commit
8469a2485b
38 changed files with 2998 additions and 1963 deletions
|
@ -225,7 +225,7 @@ var Zotero_File_Interface_Bibliography = new function() {
|
|||
updateLocaleMenu(selectedStyleObj);
|
||||
|
||||
//
|
||||
// For integrationDocPrefs.xul and rtfScan.xul
|
||||
// For integrationDocPrefs.xul and rtfScan.xhtml
|
||||
//
|
||||
if (isDocPrefs) {
|
||||
// update status of displayAs box based on style class
|
||||
|
|
|
@ -27,7 +27,7 @@ import PropTypes from 'prop-types';
|
|||
import { getDOMElement } from 'components/icons';
|
||||
|
||||
import VirtualizedTable, { renderCell } from 'components/virtualized-table';
|
||||
import { noop } from './utils';
|
||||
import { nextHTMLID, noop } from './utils';
|
||||
|
||||
|
||||
function getImageByStatus(status) {
|
||||
|
@ -45,8 +45,9 @@ function getImageByStatus(status) {
|
|||
|
||||
const ProgressQueueTable = ({ onActivate = noop, progressQueue }) => {
|
||||
const treeRef = useRef(null);
|
||||
const htmlID = useRef(nextHTMLID());
|
||||
|
||||
const getRowCount = useCallback(() => progressQueue.getRows().length, [progressQueue]);
|
||||
const getRowCount = useCallback(() => progressQueue.getTotal(), [progressQueue]);
|
||||
|
||||
const rowToTreeItem = useCallback((index, selection, oldDiv = null, columns) => {
|
||||
let rows = progressQueue.getRows();
|
||||
|
@ -92,6 +93,7 @@ const ProgressQueueTable = ({ onActivate = noop, progressQueue }) => {
|
|||
progressQueue.addListener('rowadded', refreshTree);
|
||||
progressQueue.addListener('rowupdated', refreshTree);
|
||||
progressQueue.addListener('rowdeleted', refreshTree);
|
||||
|
||||
return () => {
|
||||
progressQueue.removeListener('rowadded', refreshTree);
|
||||
progressQueue.removeListener('rowupdated', refreshTree);
|
||||
|
@ -103,7 +105,7 @@ const ProgressQueueTable = ({ onActivate = noop, progressQueue }) => {
|
|||
<VirtualizedTable
|
||||
getRowCount={ getRowCount }
|
||||
ref={ treeRef }
|
||||
id="progress-queue-table"
|
||||
id={ htmlID.current + '-progress-queue-table' }
|
||||
renderItem={ rowToTreeItem }
|
||||
showHeader={ true }
|
||||
columns={ tableColumns }
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2019 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
Copyright © 2020 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://digitalscholar.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
|
@ -16,17 +16,16 @@
|
|||
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();
|
||||
|
@ -72,9 +71,30 @@ function createDragHandler({ handleDrag, handleDragStop }) {
|
|||
return {
|
||||
start: onDragStart,
|
||||
stop: onDragStop
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
noop, getDragTargetOrient, createDragHandler
|
||||
var _htmlID = 1;
|
||||
|
||||
const nextHTMLID = (prefix = 'id-') => prefix + _htmlID++;
|
||||
|
||||
const scrollIntoViewIfNeeded = (element, container, opts = {}) => {
|
||||
const containerTop = container.scrollTop;
|
||||
const containerBottom = containerTop + container.clientHeight;
|
||||
const elementTop = element.offsetTop;
|
||||
const elementBottom = elementTop + element.clientHeight;
|
||||
|
||||
if (elementTop < containerTop || elementBottom > containerBottom) {
|
||||
const before = container.scrollTop;
|
||||
element.scrollIntoView(opts);
|
||||
const after = container.scrollTop;
|
||||
return after - before;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const stopPropagation = ev => ev.stopPropagation();
|
||||
|
||||
export {
|
||||
nextHTMLID, noop, getDragTargetOrient, createDragHandler, scrollIntoViewIfNeeded, stopPropagation
|
||||
};
|
||||
|
|
|
@ -58,6 +58,11 @@ class XULElementBase extends XULElement {
|
|||
shadow.append(content);
|
||||
}
|
||||
|
||||
MozXULElement.insertFTLIfNeeded("zotero.ftl");
|
||||
if (document.l10n) {
|
||||
document.l10n.connectRoot(this.shadowRoot);
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
|
|
153
chrome/content/zotero/elements/publicationsLicenseInfo.js
Normal file
153
chrome/content/zotero/elements/publicationsLicenseInfo.js
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2022 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/* global XULElementBase: false */
|
||||
|
||||
{
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/base.js", this);
|
||||
|
||||
const links = {
|
||||
cc: 'https://wiki.creativecommons.org/Considerations_for_licensors_and_licensees',
|
||||
cc0: 'https://wiki.creativecommons.org/CC0_FAQ'
|
||||
};
|
||||
|
||||
const getLicenseData = (license) => {
|
||||
var name, img, url, id;
|
||||
|
||||
switch (license) {
|
||||
case 'reserved':
|
||||
url = null;
|
||||
name = 'All rights reserved';
|
||||
img = 'chrome://zotero/skin/licenses/reserved.png';
|
||||
id = null;
|
||||
break;
|
||||
case 'cc':
|
||||
url = 'https://creativecommons.org/';
|
||||
name = 'Creative Commons';
|
||||
img = 'chrome://zotero/skin/licenses/cc-srr.png';
|
||||
id = null;
|
||||
break;
|
||||
|
||||
case 'cc0':
|
||||
url = "https://creativecommons.org/publicdomain/zero/1.0/";
|
||||
name = null;
|
||||
img = 'chrome://zotero/skin/licenses/' + license + ".svg";
|
||||
id = 'licenses-cc-0';
|
||||
break;
|
||||
|
||||
default:
|
||||
url = 'https://creativecommons.org/licenses/' + license.replace(/^cc-/, '') + '/4.0/';
|
||||
name = null;
|
||||
img = 'chrome://zotero/skin/licenses/' + license + ".svg";
|
||||
id = `licenses-${license}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return { url, name, img, id };
|
||||
};
|
||||
|
||||
const makeLicenseInfo = (url, name, img, id) => {
|
||||
const licenseInfo = `<div class="license-icon"><img title="${url}" src="${img}" /></div>`
|
||||
+ (id ? `<div class="license-name" data-l10n-id="${id}" />` : `<div class="license-name">${name}</div>`);
|
||||
|
||||
return MozXULElement.parseXULToFragment(
|
||||
url
|
||||
? `<a xmlns="http://www.w3.org/1999/xhtml" class="license-info" href="${url}">${licenseInfo}</a>`
|
||||
: `<div xmlns="http://www.w3.org/1999/xhtml" class="license-info">${licenseInfo}</div>`
|
||||
);
|
||||
};
|
||||
|
||||
const makeLicenseMoreInfo = (license) => {
|
||||
const needsMoreInfo = license.startsWith('cc') && license !== 'cc';
|
||||
const ccType = license === 'cc0' ? 'cc0' : 'cc';
|
||||
|
||||
return MozXULElement.parseXULToFragment(needsMoreInfo
|
||||
? `<div xmlns="http://www.w3.org/1999/xhtml" class="license-more-info" data-l10n-id="licenses-${ccType}-more-info">
|
||||
<a href="${links[ccType]}" data-l10n-name="license-considerations" />
|
||||
</div>`
|
||||
: ''
|
||||
);
|
||||
};
|
||||
|
||||
class PublicationsLicenseInfo extends XULElementBase {
|
||||
get stylesheets() {
|
||||
return [
|
||||
'chrome://global/skin/global.css',
|
||||
'chrome://zotero/skin/elements/license-info.css'
|
||||
];
|
||||
}
|
||||
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<div id="license-info"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
>
|
||||
</div>
|
||||
`);
|
||||
|
||||
validLicenses = new Set(['cc', 'cc-by', 'cc-by-sa', 'cc-by-nd', 'cc-by-nc', 'cc-by-nc-sa', 'cc-by-nc-nd', 'cc0', 'reserved']);
|
||||
|
||||
get license() {
|
||||
return this._license;
|
||||
}
|
||||
|
||||
set license(val) {
|
||||
if (!this.validLicenses.has(val)) {
|
||||
throw new Zotero.Error(`"${val}" is invalid value for attribute "license" in <licenseinfo>`);
|
||||
}
|
||||
this._license = val;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get licenseName() {
|
||||
return this.shadowRoot.querySelector('.license-name').getAttribute('label')
|
||||
? this.shadowRoot.querySelector('.license-name').getAttribute('label')
|
||||
: this.shadowRoot.querySelector('.license-name').textContent;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.license = this.getAttribute('license');
|
||||
this.shadowRoot.getElementById('license-info').addEventListener('click', this.onURLInteract.bind(this));
|
||||
this.shadowRoot.getElementById('license-info').addEventListener('keydown', this.onURLInteract.bind(this));
|
||||
}
|
||||
|
||||
update() {
|
||||
const { url, name, img, id } = getLicenseData(this.license);
|
||||
const licenseInfoEl = makeLicenseInfo(url, name, img, id);
|
||||
const licenseMoreEl = makeLicenseMoreInfo(this.license);
|
||||
this.shadowRoot.getElementById('license-info').replaceChildren(licenseInfoEl, licenseMoreEl);
|
||||
}
|
||||
|
||||
onURLInteract(ev) {
|
||||
const aEl = ev.target.closest('[href]');
|
||||
if (aEl && (ev.type === 'click' || (ev.type === 'keydown' && ev.key === ' '))) {
|
||||
ev.preventDefault();
|
||||
Zotero.launchURL(aEl.getAttribute('href'));
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('publications-license-info', PublicationsLicenseInfo);
|
||||
}
|
262
chrome/content/zotero/elements/styleConfigurator.js
Normal file
262
chrome/content/zotero/elements/styleConfigurator.js
Normal file
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2022 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/* global XULElementBase: false */
|
||||
|
||||
{
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/base.js", this);
|
||||
|
||||
class StyleSelector extends XULElementBase {
|
||||
get stylesheets() {
|
||||
return [
|
||||
'chrome://global/skin/global.css',
|
||||
'chrome://zotero/skin/elements/style-configurator.css'
|
||||
];
|
||||
}
|
||||
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<div id="style-selector"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
>
|
||||
<div>
|
||||
<xul:richlistbox id="style-list" tabindex="0" />
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
set value(val) {
|
||||
this.shadowRoot.getElementById('style-list').value = val;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.shadowRoot.getElementById('style-list').value;
|
||||
}
|
||||
|
||||
async init() {
|
||||
await Zotero.Styles.init();
|
||||
const styleListEl = this.shadowRoot.getElementById('style-list');
|
||||
|
||||
Zotero.Styles.getVisible().forEach((so) => {
|
||||
const value = so.styleID;
|
||||
// Add acronyms to APA and ASA to avoid confusion
|
||||
// https://forums.zotero.org/discussion/comment/357135/#Comment_357135
|
||||
const label = so.title
|
||||
.replace(/^American Psychological Association/, "American Psychological Association (APA)")
|
||||
.replace(/^American Sociological Association/, "American Sociological Association (ASA)");
|
||||
|
||||
styleListEl.appendChild(MozXULElement.parseXULToFragment(`
|
||||
<richlistitem value="${value}">${label}</richlistitem>
|
||||
`));
|
||||
});
|
||||
this.value = this.getAttribute('value');
|
||||
this.shadowRoot.getElementById('style-list').addEventListener("select", () => {
|
||||
const event = document.createEvent("Events");
|
||||
event.initEvent("select", true, true);
|
||||
this.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class LocaleSelector extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<div id="locale-selector"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
>
|
||||
<div>
|
||||
<xul:menulist id="locale-list" tabindex="0" native="true">
|
||||
<xul:menupopup />
|
||||
</xul:menulist>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
get stylesheets() {
|
||||
return [
|
||||
'chrome://global/skin/global.css',
|
||||
'chrome://zotero/skin/elements/style-configurator.css'
|
||||
];
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.localeListEl.value;
|
||||
}
|
||||
|
||||
set value(val) {
|
||||
this._value = val;
|
||||
const styleData = this._style ? Zotero.Styles.get(this._style) : null;
|
||||
this.localeListEl.value = styleData && styleData.locale || this._value;
|
||||
}
|
||||
|
||||
get style() {
|
||||
return this.style;
|
||||
}
|
||||
|
||||
set style(style) {
|
||||
this._style = style;
|
||||
const styleData = style ? Zotero.Styles.get(style) : null;
|
||||
this.localeListEl.disabled = !style || !!styleData.locale;
|
||||
this.localeListEl.value = styleData && styleData.locale || this._value || this.fallbackLocale;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.localeListEl = this.shadowRoot.getElementById('locale-list');
|
||||
this.localePopupEl = this.shadowRoot.querySelector('#locale-list > menupopup');
|
||||
}
|
||||
|
||||
async init() {
|
||||
this._style = this.getAttribute('style');
|
||||
this._value = this.getAttribute('value');
|
||||
|
||||
await Zotero.Styles.init();
|
||||
this.fallbackLocale = Zotero.Styles?.primaryDialects[Zotero.locale] || Zotero.locale;
|
||||
|
||||
const menuLocales = Zotero.Utilities.deepCopy(Zotero.Styles.locales);
|
||||
const menuLocalesKeys = Object.keys(menuLocales).sort();
|
||||
|
||||
// Make sure that client locale is always available as a choice
|
||||
if (this.fallbackLocale && !(this.fallbackLocale in menuLocales)) {
|
||||
menuLocales[this.fallbackLocale] = this.fallbackLocale;
|
||||
menuLocalesKeys.unshift(this.fallbackLocale);
|
||||
}
|
||||
|
||||
menuLocalesKeys.forEach((key) => {
|
||||
const label = menuLocales[key];
|
||||
|
||||
this.localePopupEl.appendChild(MozXULElement.parseXULToFragment(`
|
||||
<menuitem value="${key}" label="${label}"/>
|
||||
`));
|
||||
});
|
||||
|
||||
this.value = this._value;
|
||||
this.style = this._style;
|
||||
|
||||
this.localeListEl.addEventListener("command", (_event) => {
|
||||
this._value = this.localeListEl.value;
|
||||
const event = document.createEvent("Events");
|
||||
event.initEvent("select", true, true);
|
||||
this.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class StyleConfigurator extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<div id="style-configurator"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
>
|
||||
<label for="style-selector" data-l10n-id="bibliography-style-label" />
|
||||
<div id="style-selector-wrapper">
|
||||
<xul:style-selector id="style-selector" value="${this.getAttribute('style') || Zotero.Prefs.get('export.lastStyle') || ''}" />
|
||||
</div>
|
||||
<div id="locale-selector-wrapper">
|
||||
<label for="locale-selector" class="file-input-label" data-l10n-id="bibliography-locale-label" />
|
||||
<xul:locale-selector
|
||||
id="locale-selector"
|
||||
value="${this.getAttribute('locale') || Zotero.Prefs.get('export.lastLocale') || ''}"
|
||||
style="${this.getAttribute('style') || Zotero.Prefs.get('export.lastStyle') || ''}"
|
||||
/>
|
||||
</div>
|
||||
<div id="display-as-wrapper">
|
||||
<xul:radiogroup id="display-as">
|
||||
<xul:radio value="footnotes" data-l10n-id="integration-prefs-footnotes" selected="true" />
|
||||
<xul:radio value="endnotes" data-l10n-id="integration-prefs-endnotes" />
|
||||
</xul:radiogroup>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
get stylesheets() {
|
||||
return [
|
||||
'chrome://global/skin/global.css',
|
||||
'chrome://zotero/skin/elements/style-configurator.css'
|
||||
];
|
||||
}
|
||||
|
||||
set style(val) {
|
||||
this.shadowRoot.getElementById('style-selector').value = val;
|
||||
this.handleStyleChanged(val);
|
||||
}
|
||||
|
||||
get style() {
|
||||
return this.shadowRoot.getElementById('style-selector').value;
|
||||
}
|
||||
|
||||
set locale(val) {
|
||||
this.shadowRoot.getElementById('locale-selector').value = val;
|
||||
}
|
||||
|
||||
get locale() {
|
||||
return this.shadowRoot.getElementById('locale-selector').value;
|
||||
}
|
||||
|
||||
set displayAs(val) {
|
||||
this.shadowRoot.getElementById('display-as').value = val;
|
||||
}
|
||||
|
||||
get displayAs() {
|
||||
return this.shadowRoot.getElementById('display-as').value;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.shadowRoot.getElementById('style-configurator').style.display = 'none';
|
||||
await Zotero.Styles.init();
|
||||
this.shadowRoot.getElementById('style-configurator').style.display = '';
|
||||
this.shadowRoot.getElementById('style-selector').addEventListener('select', (_event) => {
|
||||
this.handleStyleChanged(_event.target.value);
|
||||
|
||||
const event = document.createEvent("Events");
|
||||
event.initEvent("select", true, true);
|
||||
this.dispatchEvent(event);
|
||||
});
|
||||
|
||||
this.shadowRoot.getElementById('locale-selector').addEventListener('select', (_event) => {
|
||||
const event = document.createEvent("Events");
|
||||
event.initEvent("select", true, true);
|
||||
this.dispatchEvent(event);
|
||||
});
|
||||
|
||||
this.shadowRoot.getElementById('display-as').addEventListener('select', (_event) => {
|
||||
const event = document.createEvent("Events");
|
||||
event.initEvent("select", true, true);
|
||||
this.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
handleStyleChanged(style) {
|
||||
this.shadowRoot.getElementById('locale-selector').style = style;
|
||||
const styleData = style ? Zotero.Styles.get(style) : null;
|
||||
const isNoteStyle = (styleData || {}).class === 'note';
|
||||
this.shadowRoot.getElementById('display-as-wrapper').style.display = isNoteStyle ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('locale-selector', LocaleSelector);
|
||||
customElements.define('style-selector', StyleSelector);
|
||||
customElements.define('style-configurator', StyleConfigurator);
|
||||
}
|
|
@ -389,7 +389,7 @@ var Zotero_File_Interface = new function() {
|
|||
};
|
||||
args.wrappedJSObject = args;
|
||||
|
||||
Services.ww.openWindow(null, "chrome://zotero/content/import/importWizard.xul",
|
||||
Services.ww.openWindow(null, "chrome://zotero/content/import/importWizard.xhtml",
|
||||
"importFile", "chrome,dialog=yes,centerscreen,width=600,height=400,modal", args);
|
||||
};
|
||||
|
||||
|
@ -448,6 +448,15 @@ var Zotero_File_Interface = new function() {
|
|||
translation.createNewCollection = createNewCollection;
|
||||
translation.mendeleyCode = options.mendeleyCode;
|
||||
}
|
||||
else if (options.folder) {
|
||||
Components.utils.import("chrome://zotero/content/import/folderImport.js");
|
||||
translation = new Zotero_Import_Folder({
|
||||
folder: options.folder,
|
||||
recreateStructure: options.recreateStructure,
|
||||
fileTypes: options.fileTypes,
|
||||
mimeTypes: options.mimeTypes,
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Check if the file is an SQLite database
|
||||
var sample = yield Zotero.File.getSample(file.path);
|
||||
|
@ -976,71 +985,6 @@ var Zotero_File_Interface = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
this.authenticateMendeleyOnlinePoll = function (win) {
|
||||
if (win && win[0] && win[0].location) {
|
||||
const matchResult = win[0].location.toString().match(/mendeley_oauth_redirect.html(?:.*?)(?:\?|&)code=(.*?)(?:&|$)/i);
|
||||
if (matchResult) {
|
||||
const mendeleyCode = matchResult[1];
|
||||
Zotero.getMainWindow().setTimeout(() => this.showImportWizard({ mendeleyCode }), 0);
|
||||
|
||||
// Clear all cookies to remove access
|
||||
//
|
||||
// This includes unrelated cookies in the central cookie store, but that's fine for
|
||||
// the moment, since we're not purposely using cookies for anything else.
|
||||
//
|
||||
// TODO: Switch to removeAllSince() once >Fx60
|
||||
try {
|
||||
Cc["@mozilla.org/cookiemanager;1"]
|
||||
.getService(Ci.nsICookieManager)
|
||||
.removeAll();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
win.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (win && !win.closed) {
|
||||
Zotero.getMainWindow().setTimeout(this.authenticateMendeleyOnlinePoll.bind(this, win), 200);
|
||||
}
|
||||
};
|
||||
|
||||
this.authenticateMendeleyOnline = function () {
|
||||
const uri = `https://api.mendeley.com/oauth/authorize?client_id=5907&redirect_uri=https%3A%2F%2Fzotero-static.s3.amazonaws.com%2Fmendeley_oauth_redirect.html&response_type=code&state=&scope=all`;
|
||||
var win = Services.wm.getMostRecentWindow("zotero:basicViewer");
|
||||
if (win) {
|
||||
win.loadURI(uri);
|
||||
}
|
||||
else {
|
||||
const ww = Services.ww;
|
||||
const arg = Components.classes["@mozilla.org/supports-string;1"]
|
||||
.createInstance(Components.interfaces.nsISupportsString);
|
||||
arg.data = uri;
|
||||
win = ww.openWindow(null, "chrome://zotero/content/standalone/basicViewer.xhtml",
|
||||
"basicViewer", "chrome,dialog=yes,resizable,centerscreen,menubar,scrollbars", arg);
|
||||
}
|
||||
|
||||
let browser;
|
||||
let func = function () {
|
||||
win.removeEventListener("load", func);
|
||||
browser = win.document.documentElement.getElementsByTagName('browser')[0];
|
||||
browser.addEventListener("pageshow", innerFunc);
|
||||
};
|
||||
let innerFunc = function () {
|
||||
browser.removeEventListener("pageshow", innerFunc);
|
||||
win.outerWidth = Math.max(640, Math.min(1000, win.screen.availHeight));
|
||||
win.outerHeight = Math.max(480, Math.min(800, win.screen.availWidth));
|
||||
};
|
||||
|
||||
win.addEventListener("load", func);
|
||||
|
||||
// polling executed by the main window because current (wizard) window will be closed
|
||||
Zotero.getMainWindow().setTimeout(this.authenticateMendeleyOnlinePoll.bind(this, win), 200);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate an error string reporting a translation failure. Includes the
|
||||
* label of the running translator if available.
|
||||
|
|
247
chrome/content/zotero/import/folderImport.js
Normal file
247
chrome/content/zotero/import/folderImport.js
Normal file
|
@ -0,0 +1,247 @@
|
|||
var EXPORTED_SYMBOLS = ["Zotero_Import_Folder"]; // eslint-disable-line no-unused-vars
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/include.js");
|
||||
|
||||
// matches "*" and "?" wildcards of a glob pattern, case-insensitive
|
||||
function simpleGlobMatch(filename, patterns) {
|
||||
for (const pattern of patterns) {
|
||||
// Convert glob pattern to regex pattern
|
||||
const regexPattern = pattern
|
||||
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex characters
|
||||
.replace(/\*/g, '.*') // Replace * with regex equivalent
|
||||
.replace(/\?/g, '.'); // Replace ? with regex equivalent
|
||||
|
||||
if (new RegExp(`^${regexPattern}$`, 'i').test(filename)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const collectFilesRecursive = async (dirPath, parents = [], files = []) => {
|
||||
await Zotero.File.iterateDirectory(dirPath, async ({ isDir, _isSymlink, name, path }) => {
|
||||
if (isDir) {
|
||||
await collectFilesRecursive(path, [...parents, name], files);
|
||||
}
|
||||
// TODO: Also check for hidden file attribute on windows?
|
||||
else if (!name.startsWith('.')) {
|
||||
files.push({ parents, path, name });
|
||||
}
|
||||
});
|
||||
return files;
|
||||
};
|
||||
|
||||
const findCollection = (libraryID, parentCollectionID, collectionName) => {
|
||||
const collections = parentCollectionID
|
||||
? Zotero.Collections.getByParent(parentCollectionID)
|
||||
: Zotero.Collections.getByLibrary(libraryID);
|
||||
|
||||
return collections.find(c => c.name === collectionName);
|
||||
};
|
||||
|
||||
// @TODO
|
||||
const findItemByHash = async (libraryID, hash) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
class Zotero_Import_Folder { // eslint-disable-line camelcase,no-unused-vars
|
||||
constructor({ mimeTypes = ['application/pdf'], fileTypes, folder, libraryID, recreateStructure }) {
|
||||
this.folder = folder;
|
||||
this.libraryID = libraryID;
|
||||
this.newItems = [];
|
||||
this.recreateStructure = recreateStructure;
|
||||
this.fileTypes = fileTypes && fileTypes.length ? fileTypes.split(',').map(ft => ft.trim()) : [];
|
||||
this._progress = 0;
|
||||
this._progressMax = 0;
|
||||
this._itemDone = () => {};
|
||||
this.types = mimeTypes; // whitelist of mime types to process
|
||||
}
|
||||
|
||||
setLocation(folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
setHandler(name, handler) {
|
||||
switch (name) {
|
||||
case 'itemDone':
|
||||
this._itemDone = handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setTranslator() {}
|
||||
|
||||
getProgress() {
|
||||
return this._progress / this._progressMax * 100;
|
||||
}
|
||||
|
||||
async getTranslators() {
|
||||
return [{ label: 'Folder import' }];
|
||||
}
|
||||
|
||||
async translate({ collections = [], linkFiles = false } = {}) {
|
||||
// https://github.com/zotero/zotero/pull/2862#discussion_r1141324302
|
||||
throw new Error('Folder import is not supported yet');
|
||||
const libraryID = this.libraryID || Zotero.Libraries.userLibraryID;
|
||||
const files = await collectFilesRecursive(this.folder);
|
||||
|
||||
// import is done in four phases: sniff for mime type, calculate md5, import as attachment, recognize.
|
||||
// hence number of files is multiplied by 4 to determine max progress
|
||||
this._progressMax = files.length * 4;
|
||||
|
||||
const mimeTypes = await Promise.all(files.map(
|
||||
async ({ path }) => {
|
||||
const mimeType = Zotero.MIME.sniffForMIMEType(await Zotero.File.getSample(path));
|
||||
this._progress++;
|
||||
this._itemDone();
|
||||
return mimeType;
|
||||
}
|
||||
));
|
||||
|
||||
const fileHashes = await Promise.all(files.map(
|
||||
async ({ name, path }, index) => {
|
||||
const contentType = mimeTypes[index];
|
||||
this._progress++;
|
||||
if (!(this.types.includes(contentType) || simpleGlobMatch(name, this.fileTypes))) {
|
||||
// don't bother calculating a hash for file that will be ignored
|
||||
return null;
|
||||
}
|
||||
const md5Hash = await Zotero.Utilities.Internal.md5Async(path);
|
||||
this._itemDone();
|
||||
return md5Hash;
|
||||
}
|
||||
));
|
||||
|
||||
files.forEach((fileData, index) => {
|
||||
fileData.parentCollectionIDs = (collections && collections.length) ? [...collections] : [];
|
||||
fileData.mimeType = mimeTypes[index];
|
||||
});
|
||||
|
||||
if (this.recreateStructure) {
|
||||
for (const fileData of files) {
|
||||
const { parents } = fileData;
|
||||
let prevParentCollectionID = null;
|
||||
if (parents.length) {
|
||||
prevParentCollectionID = (collections && collections.length) ? collections[0] : null;
|
||||
for (const parentName of parents) {
|
||||
const parentCollection = findCollection(libraryID, prevParentCollectionID, parentName) || new Zotero.Collection;
|
||||
parentCollection.libraryID = libraryID;
|
||||
parentCollection.name = parentName;
|
||||
if (prevParentCollectionID) {
|
||||
parentCollection.parentID = prevParentCollectionID;
|
||||
}
|
||||
await parentCollection.saveTx({ skipSelect: true }); //eslint-disable-line no-await-in-loop
|
||||
prevParentCollectionID = parentCollection.id;
|
||||
}
|
||||
}
|
||||
if (prevParentCollectionID) {
|
||||
fileData.parentCollectionIDs = [prevParentCollectionID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// index files by hash to avoid importing duplicate files. Keep track of where duplicates were found so that
|
||||
// duplicate item is still added to one collection per folder
|
||||
const fileDataByHash = {};
|
||||
files.forEach((fileData, index) => {
|
||||
const hash = fileHashes[index];
|
||||
if (hash in fileDataByHash) {
|
||||
fileDataByHash[hash].parentCollectionIDs.push(...fileData.parentCollectionIDs);
|
||||
}
|
||||
else {
|
||||
fileDataByHash[hash] = fileData;
|
||||
}
|
||||
});
|
||||
|
||||
// advance progress to account for duplicates found within file structure
|
||||
// these files won't be imported nor recognized so advance 2 ticks per file
|
||||
this._progress += 2 * (files.length - Object.keys(fileDataByHash).length);
|
||||
this._itemDone();
|
||||
|
||||
const attachmentItemHashLookup = {};
|
||||
const attachmentItems = await Promise.all(Object.entries(fileDataByHash).map(
|
||||
async ([hash, { name, path, parentCollectionIDs, mimeType }]) => {
|
||||
const options = {
|
||||
collections: parentCollectionIDs,
|
||||
contentType: mimeType,
|
||||
file: path,
|
||||
libraryID,
|
||||
};
|
||||
|
||||
let attachmentItem = null;
|
||||
|
||||
if ((this.types.includes(mimeType) || simpleGlobMatch(name, this.fileTypes))) {
|
||||
const existingItem = await findItemByHash(libraryID, hash);
|
||||
|
||||
if (existingItem) {
|
||||
existingItem.setCollections([...existingItem.getCollections(), ...parentCollectionIDs]);
|
||||
await existingItem.saveTx({ skipSelect: true });
|
||||
}
|
||||
else {
|
||||
if (linkFiles) {
|
||||
attachmentItem = await Zotero.Attachments.linkFromFile(options);
|
||||
}
|
||||
else {
|
||||
attachmentItem = await Zotero.Attachments.importFromFile(options);
|
||||
}
|
||||
|
||||
this.newItems.push(attachmentItem);
|
||||
attachmentItemHashLookup[attachmentItem.id] = hash;
|
||||
}
|
||||
}
|
||||
|
||||
if (attachmentItem && !Zotero.RecognizePDF.canRecognize(attachmentItem)) {
|
||||
// @TODO: store hash of an item that cannot be recognized
|
||||
await attachmentItem.saveTx({ skipSelect: true });
|
||||
attachmentItem = null;
|
||||
}
|
||||
this._progress++;
|
||||
this._itemDone();
|
||||
return attachmentItem;
|
||||
}
|
||||
));
|
||||
|
||||
|
||||
// discard unrecognizable items, increase progress for discarded items
|
||||
const recognizableItems = attachmentItems.filter(item => item !== null);
|
||||
this._progress += attachmentItems.length - recognizableItems.length;
|
||||
this._itemDone();
|
||||
|
||||
const recognizeQueue = Zotero.ProgressQueues.get('recognize');
|
||||
const itemsToSavePostRecognize = [];
|
||||
|
||||
const processRecognizedItem = ({ status, id }) => {
|
||||
const updatedItem = recognizableItems.find(i => i.id === id);
|
||||
if (status === Zotero.ProgressQueue.ROW_SUCCEEDED) {
|
||||
const recognizedItem = updatedItem.parentItem;
|
||||
if (recognizedItem && id in attachmentItemHashLookup) {
|
||||
// @TODO: Store hash of an attachment (attachmentItemHashLookup[id]) for this recognized item
|
||||
itemsToSavePostRecognize.push(recognizedItem);
|
||||
}
|
||||
}
|
||||
if (status === Zotero.ProgressQueue.ROW_FAILED) {
|
||||
if (updatedItem && id in attachmentItemHashLookup) {
|
||||
// @TODO: Store hash of a file that failed to be recognized (attachmentItemHashLookup[id])
|
||||
itemsToSavePostRecognize.push(updatedItem);
|
||||
}
|
||||
}
|
||||
if ([Zotero.ProgressQueue.ROW_FAILED, Zotero.ProgressQueue.ROW_SUCCEEDED].includes(status)) {
|
||||
this._progress++;
|
||||
this._itemDone();
|
||||
}
|
||||
};
|
||||
|
||||
recognizeQueue.addListener('rowupdated', processRecognizedItem);
|
||||
try {
|
||||
await Zotero.RecognizePDF.recognizeItems(recognizableItems);
|
||||
}
|
||||
finally {
|
||||
recognizeQueue.removeListener('rowupdated', processRecognizedItem);
|
||||
}
|
||||
|
||||
for (const item of itemsToSavePostRecognize) {
|
||||
await item.saveTx({ skipSelect: true });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,211 +1,249 @@
|
|||
import FilePicker from 'zotero/modules/filePicker';
|
||||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2022 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
var Zotero_Import_Wizard = {
|
||||
_wizard: null,
|
||||
_dbs: null,
|
||||
_file: null,
|
||||
_translation: null,
|
||||
_mendeleyOnlineRedirectURLWithCode: null,
|
||||
_mendeleyCode: null,
|
||||
|
||||
|
||||
init: async function () {
|
||||
this._wizard = document.getElementById('import-wizard');
|
||||
var dbs = await Zotero_File_Interface.findMendeleyDatabases();
|
||||
if (dbs.length) {
|
||||
// Local import disabled
|
||||
//document.getElementById('radio-import-source-mendeley').hidden = false;
|
||||
import FilePicker from 'zotero/filePicker';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ProgressQueueTable from 'components/progressQueueTable';
|
||||
|
||||
/* eslint camelcase: ["error", {allow: ["Zotero_File_Interface", "Zotero_Import_Wizard"]} ] */
|
||||
/* global Zotero_File_Interface: false */
|
||||
|
||||
|
||||
const Zotero_Import_Wizard = { // eslint-disable-line no-unused-vars
|
||||
wizard: null,
|
||||
folder: null,
|
||||
file: null,
|
||||
mendeleyCode: null,
|
||||
libraryID: null,
|
||||
translation: null,
|
||||
|
||||
async getShouldCreateCollection() {
|
||||
const sql = "SELECT ROWID FROM collections WHERE libraryID=?1 "
|
||||
+ "UNION "
|
||||
+ "SELECT ROWID FROM items WHERE libraryID=?1 "
|
||||
// Not in trash
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
|
||||
// And not a child item (which doesn't necessarily show up in the trash)
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL) "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL) "
|
||||
+ "LIMIT 1";
|
||||
return Zotero.DB.valueQueryAsync(sql, this.libraryID);
|
||||
},
|
||||
|
||||
async init() {
|
||||
const { mendeleyCode, libraryID } = window.arguments[0].wrappedJSObject ?? {};
|
||||
|
||||
this.libraryID = libraryID;
|
||||
this.mendeleyCode = mendeleyCode;
|
||||
|
||||
this.wizard = document.getElementById('import-wizard');
|
||||
this.wizard.getPageById('page-start')
|
||||
.addEventListener('pageadvanced', this.onImportSourceAdvance.bind(this));
|
||||
this.wizard.getPageById('page-mendeley-online-intro')
|
||||
.addEventListener('pagerewound', this.goToStart.bind(this));
|
||||
this.wizard.getPageById('page-mendeley-online-intro')
|
||||
.addEventListener('pageadvanced', this.openMendeleyAuthWindow.bind(this));
|
||||
this.wizard.getPageById('page-options')
|
||||
.addEventListener('pageshow', this.onOptionsPageShow.bind(this));
|
||||
this.wizard.getPageById('page-options')
|
||||
.addEventListener('pageadvanced', this.startImport.bind(this));
|
||||
this.wizard.getPageById('page-progress')
|
||||
.addEventListener('pageshow', this.onProgressPageShow.bind(this));
|
||||
|
||||
document
|
||||
.getElementById('other-files')
|
||||
.addEventListener('keyup', (ev) => {
|
||||
document.getElementById('import-other').checked = ev.currentTarget.value.length > 0;
|
||||
});
|
||||
document
|
||||
.querySelector('#page-done-error-mendeley > a')
|
||||
.addEventListener('click', this.onURLInteract.bind(this));
|
||||
document
|
||||
.querySelector('#page-done-error-mendeley > a')
|
||||
.addEventListener('keydown', this.onURLInteract.bind(this));
|
||||
document
|
||||
.querySelector('#page-done-error > button')
|
||||
.addEventListener('click', this.onReportErrorInteract.bind(this));
|
||||
document
|
||||
.querySelector('#page-done-error > button')
|
||||
.addEventListener('keydown', this.onReportErrorInteract.bind(this));
|
||||
|
||||
this.wizard.addEventListener('pageshow', this.updateFocus.bind(this));
|
||||
this.wizard.addEventListener('wizardcancel', this.onCancel.bind(this));
|
||||
|
||||
const shouldCreateCollection = await this.getShouldCreateCollection();
|
||||
document.getElementById('create-collection').checked = shouldCreateCollection;
|
||||
|
||||
// wizard.shadowRoot content isn't exposed to our css
|
||||
this.wizard.shadowRoot
|
||||
.querySelector('.wizard-header-label').style.fontSize = '16px';
|
||||
|
||||
if (mendeleyCode) {
|
||||
this.wizard.goTo('page-options');
|
||||
}
|
||||
|
||||
// If no existing collections or non-trash items in the library, don't create a new
|
||||
// collection by default
|
||||
var args = window.arguments[0].wrappedJSObject;
|
||||
if (args && args.libraryID) {
|
||||
let sql = "SELECT ROWID FROM collections WHERE libraryID=?1 "
|
||||
+ "UNION "
|
||||
+ "SELECT ROWID FROM items WHERE libraryID=?1 "
|
||||
// Not in trash
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
|
||||
// And not a child item (which doesn't necessarily show up in the trash)
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL) "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL) "
|
||||
+ "LIMIT 1";
|
||||
if (!await Zotero.DB.valueQueryAsync(sql, args.libraryID)) {
|
||||
document.getElementById('create-collection-checkbox').removeAttribute('checked');
|
||||
},
|
||||
|
||||
skipToDonePage(label, description, showReportErrorButton = false, isMendeleyError = false) {
|
||||
this.wizard.getPageById('page-done').dataset.headerLabelId = label;
|
||||
|
||||
if (!isMendeleyError) {
|
||||
if (Array.isArray(description)) {
|
||||
document.getElementById('page-done-description').dataset.l10nId = description[0];
|
||||
document.getElementById('page-done-description').dataset.l10nArgs = JSON.stringify(description[1]);
|
||||
}
|
||||
else {
|
||||
document.getElementById('page-done-description').dataset.l10nId = description;
|
||||
}
|
||||
}
|
||||
|
||||
if (args && args.mendeleyCode) {
|
||||
this._mendeleyCode = args.mendeleyCode;
|
||||
this._wizard.goTo('page-options');
|
||||
}
|
||||
|
||||
// Update labels
|
||||
document.getElementById('radio-import-source-mendeley-online').label
|
||||
= `Mendeley Reference Manager (${Zotero.getString('import.onlineImport')})`;
|
||||
document.getElementById('radio-import-source-mendeley').label
|
||||
= `Mendeley Desktop (${Zotero.getString('import.localImport')})`;
|
||||
document.getElementById('file-handling-store').label = Zotero.getString(
|
||||
'import.fileHandling.store',
|
||||
Zotero.appName
|
||||
);
|
||||
document.getElementById('file-handling-link').label = Zotero.getString('import.fileHandling.link');
|
||||
document.getElementById('file-handling-description').textContent = Zotero.getString(
|
||||
'import.fileHandling.description',
|
||||
Zotero.appName
|
||||
);
|
||||
|
||||
Zotero.Translators.init(); // async
|
||||
},
|
||||
document.getElementById('page-done-error-mendeley').style.display = isMendeleyError ? 'block' : 'none';
|
||||
document.getElementById('page-done-error').style.display = showReportErrorButton ? 'block' : 'none';
|
||||
|
||||
onCancel: function () {
|
||||
if (this._translation && this._translation.interrupt) {
|
||||
this._translation.interrupt();
|
||||
}
|
||||
},
|
||||
|
||||
onModeChosen: async function () {
|
||||
var wizard = this._wizard;
|
||||
const doneQueueContainer = document.getElementById('done-queue-container');
|
||||
const doneQueue = document.getElementById('done-queue');
|
||||
|
||||
var mode = document.getElementById('import-source').selectedItem.id;
|
||||
try {
|
||||
switch (mode) {
|
||||
case 'radio-import-source-file':
|
||||
await this.chooseFile();
|
||||
break;
|
||||
|
||||
case 'radio-import-source-mendeley-online':
|
||||
wizard.goTo('mendeley-online-explanation');
|
||||
wizard.canRewind = true;
|
||||
break;
|
||||
|
||||
case 'radio-import-source-mendeley':
|
||||
this._dbs = await Zotero_File_Interface.findMendeleyDatabases();
|
||||
// This shouldn't happen, because we only show the wizard if there are databases
|
||||
if (!this._dbs.length) {
|
||||
throw new Error("No databases found");
|
||||
}
|
||||
this._populateFileList(this._dbs);
|
||||
document.getElementById('file-options-header').textContent
|
||||
= Zotero.getString('fileInterface.chooseAppDatabaseToImport', 'Mendeley')
|
||||
wizard.goTo('page-file-list');
|
||||
wizard.canRewind = true;
|
||||
this._enableCancel();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown mode ${mode}`);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this._onDone(
|
||||
Zotero.getString('general.error'),
|
||||
Zotero.getString('fileInterface.importError'),
|
||||
true
|
||||
if (this.folder && !showReportErrorButton) {
|
||||
doneQueueContainer.style.display = 'flex';
|
||||
ReactDOM.render(
|
||||
<ProgressQueueTable progressQueue={ Zotero.ProgressQueues.get('recognize') } />,
|
||||
doneQueue
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
else {
|
||||
doneQueueContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
this.wizard.goTo('page-done');
|
||||
this.wizard.canRewind = false;
|
||||
},
|
||||
|
||||
onMendeleyOnlineShow: async function () {
|
||||
document.getElementById('mendeley-online-description').textContent = Zotero.getString(
|
||||
'import.online.intro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley']
|
||||
);
|
||||
document.getElementById('mendeley-online-description2').textContent = Zotero.getString(
|
||||
'import.online.intro2', [Zotero.appName, 'Mendeley']
|
||||
);
|
||||
goToStart() {
|
||||
this.wizard.goTo('page-start');
|
||||
this.wizard.canAdvance = true;
|
||||
},
|
||||
|
||||
onMendeleyOnlineAdvance: function () {
|
||||
if (!this._mendeleyOnlineRedirectURLWithCode) {
|
||||
Zotero_File_Interface.authenticateMendeleyOnline();
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
|
||||
goToStart: function () {
|
||||
this._wizard.goTo('page-start');
|
||||
this._wizard.canAdvance = true;
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
chooseFile: async function (translation) {
|
||||
var translation = new Zotero.Translate.Import();
|
||||
var translators = await translation.getTranslators();
|
||||
var fp = new FilePicker();
|
||||
async chooseFile() {
|
||||
const translation = new Zotero.Translate.Import();
|
||||
const translators = await translation.getTranslators();
|
||||
const fp = new FilePicker();
|
||||
fp.init(window, Zotero.getString("fileInterface.import"), fp.modeOpen);
|
||||
|
||||
fp.appendFilters(fp.filterAll);
|
||||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
|
||||
|
||||
// Add Mendeley DB, which isn't a translator
|
||||
var mendeleyFilter = {
|
||||
const mendeleyFilter = {
|
||||
label: "Mendeley Database", // TODO: Localize
|
||||
target: "*.sqlite"
|
||||
};
|
||||
var filters = [...translators];
|
||||
const filters = [...translators];
|
||||
filters.push(mendeleyFilter);
|
||||
|
||||
|
||||
filters.sort((a, b) => collation.compareString(1, a.label, b.label));
|
||||
for (let filter of filters) {
|
||||
fp.appendFilter(filter.label, "*." + filter.target);
|
||||
}
|
||||
|
||||
var rv = await fp.show();
|
||||
|
||||
const rv = await fp.show();
|
||||
if (rv !== fp.returnOK && rv !== fp.returnReplace) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Zotero.debug(`File is ${fp.file}`);
|
||||
|
||||
this._file = fp.file;
|
||||
this._wizard.canAdvance = true;
|
||||
this._wizard.goTo('page-options');
|
||||
this.file = fp.file;
|
||||
this.wizard.canAdvance = true;
|
||||
this.wizard.goTo('page-options');
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* When a file is clicked on in the file list
|
||||
*/
|
||||
onFileSelected: async function () {
|
||||
var index = document.getElementById('file-list').selectedIndex;
|
||||
if (index != -1) {
|
||||
this._file = this._dbs[index].path;
|
||||
this._wizard.canAdvance = true;
|
||||
|
||||
async chooseFolder() {
|
||||
const fp = new FilePicker();
|
||||
fp.init(window, Zotero.getString('attachmentBasePath.selectDir'), fp.modeGetFolder);
|
||||
fp.appendFilters(fp.filterAll);
|
||||
|
||||
const rv = await fp.show();
|
||||
if (rv !== fp.returnOK && rv !== fp.returnReplace) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
this._file = null;
|
||||
this._wizard.canAdvance = false;
|
||||
|
||||
Zotero.debug(`Folder is ${fp.file}`);
|
||||
|
||||
this.folder = fp.file;
|
||||
this.wizard.canAdvance = true;
|
||||
this.wizard.goTo('page-options');
|
||||
},
|
||||
|
||||
async onImportSourceAdvance(ev) {
|
||||
const selectedMode = document.getElementById('import-source-group').selectedItem.value;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
try {
|
||||
switch (selectedMode) {
|
||||
case 'file':
|
||||
this.folder = null;
|
||||
await this.chooseFile();
|
||||
break;
|
||||
case 'folder':
|
||||
this.file = null;
|
||||
await this.chooseFolder();
|
||||
break;
|
||||
case 'mendeleyOnline':
|
||||
this.file = null;
|
||||
this.folder = null;
|
||||
this.wizard.goTo('page-mendeley-online-intro');
|
||||
this.wizard.canRewind = true;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown mode ${selectedMode}`);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.skipToDonePage('general-error', 'file-interface-import-error', true);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* When the user clicks "Other…" to choose a file not in the list
|
||||
*/
|
||||
chooseMendeleyDB: async function () {
|
||||
document.getElementById('file-list').selectedIndex = -1;
|
||||
var fp = new FilePicker();
|
||||
fp.init(window, Zotero.getString('fileInterface.import'), fp.modeOpen);
|
||||
fp.appendFilter("Mendeley Database", "*.sqlite"); // TODO: Localize
|
||||
var rv = await fp.show();
|
||||
if (rv != fp.returnOK) {
|
||||
return false;
|
||||
}
|
||||
this._file = fp.file;
|
||||
this._wizard.canAdvance = true;
|
||||
this._wizard.advance();
|
||||
|
||||
onOptionsPageShow() {
|
||||
document.getElementById('page-options-folder-import').style.display = this.folder ? 'block' : 'none';
|
||||
document.getElementById('page-options-file-handling').style.display = this.mendeleyCode ? 'none' : 'block';
|
||||
this.wizard.canRewind = false;
|
||||
},
|
||||
|
||||
|
||||
onOptionsShown: function () {
|
||||
document.getElementById('file-handling-options').hidden = !!this._mendeleyCode;
|
||||
|
||||
openMendeleyAuthWindow(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
const arg = Components.classes["@mozilla.org/supports-string;1"]
|
||||
.createInstance(Components.interfaces.nsISupportsString);
|
||||
arg.data = 'mendeleyImport';
|
||||
|
||||
window.close();
|
||||
|
||||
Services.ww.openWindow(null, "chrome://zotero/content/standalone/basicViewer.xhtml",
|
||||
"basicViewer", "chrome,dialog=yes,centerscreen,width=1000,height=700,modal", arg);
|
||||
},
|
||||
|
||||
|
||||
onBeforeImport: async function (translation) {
|
||||
|
||||
async onBeforeImport(translation) {
|
||||
// Unrecognized translator
|
||||
if (!translation) {
|
||||
// Allow error dialog to be displayed, and then close window
|
||||
|
@ -214,159 +252,112 @@ var Zotero_Import_Wizard = {
|
|||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._translation = translation;
|
||||
|
||||
|
||||
this.translation = translation;
|
||||
|
||||
// Switch to progress pane
|
||||
this._wizard.goTo('page-progress');
|
||||
var pm = document.getElementById('import-progressmeter');
|
||||
|
||||
translation.setHandler('itemDone', function () {
|
||||
pm.value = translation.getProgress();
|
||||
this.wizard.goTo('page-progress');
|
||||
translation.setHandler('itemDone', () => {
|
||||
document.getElementById('import-progress').value = translation.getProgress();
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
onImportStart: async function () {
|
||||
if (!this._file && !this._mendeleyCode) {
|
||||
let index = document.getElementById('file-list').selectedIndex;
|
||||
this._file = this._dbs[index].path;
|
||||
|
||||
async onProgressPageShow() {
|
||||
this.wizard.canAdvance = false;
|
||||
this.wizard.canRewind = false;
|
||||
const progressQueueContainer = document.getElementById('progress-queue-container');
|
||||
const progressQueue = document.getElementById('progress-queue');
|
||||
if (this.folder) {
|
||||
progressQueueContainer.style.display = 'flex';
|
||||
ReactDOM.render(
|
||||
<ProgressQueueTable progressQueue={Zotero.ProgressQueues.get('recognize')} />,
|
||||
progressQueue
|
||||
);
|
||||
}
|
||||
this._disableCancel();
|
||||
this._wizard.canRewind = false;
|
||||
this._wizard.canAdvance = false;
|
||||
else {
|
||||
progressQueueContainer.style.display = 'none';
|
||||
}
|
||||
},
|
||||
|
||||
onURLInteract(ev) {
|
||||
if (ev.type === 'click' || (ev.type === 'keydown' && ev.key === ' ')) {
|
||||
Zotero.launchURL(ev.currentTarget.getAttribute('href'));
|
||||
window.close();
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
onReportErrorInteract(ev) {
|
||||
if (ev.type === 'click' || (ev.type === 'keydown' && ev.key === ' ')) {
|
||||
Zotero.getActiveZoteroPane().reportErrors();
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
|
||||
onCancel() {
|
||||
if (this.translation && this.translation.interrupt) {
|
||||
this.translation.interrupt();
|
||||
}
|
||||
},
|
||||
|
||||
updateFocus() {
|
||||
(this.wizard.currentPage.querySelector('radiogroup:not([disabled]),checkbox:not([disabled])') ?? this.wizard.currentPage).focus();
|
||||
},
|
||||
|
||||
async startImport() {
|
||||
this.wizard.canAdvance = false;
|
||||
this.wizard.canRewind = false;
|
||||
|
||||
const linkFiles = document.getElementById('file-handling').selectedItem.id === 'link';
|
||||
const recreateStructure = document.getElementById('recreate-structure').checked;
|
||||
const shouldCreateCollection = document.getElementById('create-collection').checked;
|
||||
const mimeTypes = document.getElementById('import-pdf').checked
|
||||
? ['application/pdf']
|
||||
: [];
|
||||
const fileTypes = document.getElementById('import-other').checked
|
||||
? document.getElementById('other-files').value
|
||||
: null;
|
||||
|
||||
try {
|
||||
let result = await Zotero_File_Interface.importFile({
|
||||
file: this._file,
|
||||
const result = await Zotero_File_Interface.importFile({
|
||||
addToLibraryRoot: !shouldCreateCollection,
|
||||
file: this.file,
|
||||
fileTypes,
|
||||
folder: this.folder,
|
||||
linkFiles,
|
||||
mendeleyCode: this.mendeleyCode,
|
||||
mimeTypes,
|
||||
onBeforeImport: this.onBeforeImport.bind(this),
|
||||
addToLibraryRoot: !document.getElementById('create-collection-checkbox')
|
||||
.hasAttribute('checked'),
|
||||
linkFiles: document.getElementById('file-handling-radio').selectedIndex == 1,
|
||||
mendeleyCode: this._mendeleyCode
|
||||
recreateStructure
|
||||
});
|
||||
|
||||
|
||||
// Cancelled by user or due to error
|
||||
if (!result) {
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let numItems = this._translation.newItems.length;
|
||||
this._onDone(
|
||||
Zotero.getString('fileInterface.importComplete'),
|
||||
Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems)
|
||||
|
||||
const numItems = this.translation.newItems.length;
|
||||
this.skipToDonePage(
|
||||
'file-interface-import-complete',
|
||||
['file-interface-items-were-imported', { numItems }]
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
if (e.message == 'Encrypted Mendeley database') {
|
||||
let url = 'https://www.zotero.org/support/kb/mendeley_import';
|
||||
let elem = document.createElement('div');
|
||||
elem.innerHTML = `The selected Mendeley database cannot be read, likely because it `
|
||||
+ `is encrypted. See <a href="${url}" class="text-link">How do I import a `
|
||||
+ `Mendeley library into Zotero?</a> for more information.`
|
||||
this._onDone(Zotero.getString('general.error'), elem);
|
||||
this.skipToDonePage('general.error', [], false, true);
|
||||
}
|
||||
else {
|
||||
this._onDone(
|
||||
Zotero.getString('general.error'),
|
||||
Zotero_File_Interface.makeImportErrorString(this._translation),
|
||||
const translatorLabel = this.translation?.translator?.[0]?.label;
|
||||
this.skipToDonePage(
|
||||
'general.error',
|
||||
translatorLabel
|
||||
? ['file-interface-import-error-translator', { translator: translatorLabel }]
|
||||
: 'file-interface-import-error',
|
||||
true
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
reportError: function () {
|
||||
Zotero.getActiveZoteroPane().reportErrors();
|
||||
window.close();
|
||||
},
|
||||
|
||||
|
||||
_populateFileList: async function (files) {
|
||||
var listbox = document.getElementById('file-list');
|
||||
|
||||
// Remove existing entries
|
||||
var items = listbox.getElementsByTagName('listitem');
|
||||
for (let item of items) {
|
||||
listbox.removeChild(item);
|
||||
}
|
||||
|
||||
for (let file of files) {
|
||||
let li = document.createElement('listitem');
|
||||
|
||||
let name = document.createElement('listcell');
|
||||
// Simply filenames
|
||||
let nameStr = file.name
|
||||
.replace(/\.sqlite$/, '')
|
||||
.replace(/@www\.mendeley\.com$/, '');
|
||||
if (nameStr == 'online') {
|
||||
nameStr = Zotero.getString('dataDir.default', 'online.sqlite');
|
||||
}
|
||||
name.setAttribute('label', nameStr + ' ');
|
||||
li.appendChild(name);
|
||||
|
||||
let lastModified = document.createElement('listcell');
|
||||
lastModified.setAttribute('label', file.lastModified.toLocaleString() + ' ');
|
||||
li.appendChild(lastModified);
|
||||
|
||||
let size = document.createElement('listcell');
|
||||
size.setAttribute(
|
||||
'label',
|
||||
Zotero.getString('general.nMegabytes', (file.size / 1024 / 1024).toFixed(1)) + ' '
|
||||
);
|
||||
li.appendChild(size);
|
||||
|
||||
listbox.appendChild(li);
|
||||
}
|
||||
|
||||
if (files.length == 1) {
|
||||
listbox.selectedIndex = 0;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_enableCancel: function () {
|
||||
this._wizard.getButton('cancel').disabled = false;
|
||||
},
|
||||
|
||||
|
||||
_disableCancel: function () {
|
||||
this._wizard.getButton('cancel').disabled = true;
|
||||
},
|
||||
|
||||
|
||||
_onDone: function (label, description, showReportErrorButton) {
|
||||
var wizard = this._wizard;
|
||||
wizard.getPageById('page-done').setAttribute('label', label);
|
||||
|
||||
var xulElem = document.getElementById('result-description');
|
||||
var htmlElem = document.getElementById('result-description-html');
|
||||
|
||||
if (description instanceof HTMLElement) {
|
||||
htmlElem.appendChild(description);
|
||||
Zotero.Utilities.Internal.updateHTMLInXUL(htmlElem, { callback: () => window.close() });
|
||||
xulElem.hidden = true;
|
||||
htmlElem.setAttribute('display', 'block');
|
||||
}
|
||||
else {
|
||||
xulElem.textContent = description;
|
||||
xulElem.hidden = false;
|
||||
htmlElem.setAttribute('display', 'none');
|
||||
}
|
||||
document.getElementById('result-description');
|
||||
|
||||
if (showReportErrorButton) {
|
||||
let button = document.getElementById('result-report-error');
|
||||
button.setAttribute('label', Zotero.getString('errorReport.reportError'));
|
||||
button.hidden = false;
|
||||
}
|
||||
|
||||
// When done, move to last page and allow closing
|
||||
wizard.canAdvance = true;
|
||||
wizard.goTo('page-done');
|
||||
wizard.canRewind = false;
|
||||
}
|
||||
};
|
||||
|
|
94
chrome/content/zotero/import/importWizard.xhtml
Normal file
94
chrome/content/zotero/import/importWizard.xhtml
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?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"
|
||||
onload="Zotero_Import_Wizard.init()"
|
||||
>
|
||||
<linkset>
|
||||
<html:link rel="localization" href="zotero.ftl" />
|
||||
</linkset>
|
||||
|
||||
<script src="chrome://global/content/customElements.js"/>
|
||||
<wizard id="import-wizard" class="import-wizard" data-l10n-id="import-wizard" width="600" height="400">
|
||||
<wizardpage pageid="page-start" data-header-label-id="import-where-from">
|
||||
<radiogroup id="import-source-group" align="start">
|
||||
<radio value="file" data-l10n-id="import-source-file" />
|
||||
<!-- @TODO
|
||||
<radio value="folder" data-l10n-id="import-source-folder" />
|
||||
-->
|
||||
<radio value="mendeleyOnline" data-l10n-id="import-source-online"
|
||||
data-l10n-args='{"targetApp": "Mendeley Reference Manager"}' />
|
||||
</radiogroup>
|
||||
</wizardpage>
|
||||
<wizardpage
|
||||
pageid="page-mendeley-online-intro"
|
||||
data-header-label-id="import-online-intro-title"
|
||||
>
|
||||
<div
|
||||
class="mendeley-online-intro"
|
||||
data-l10n-id="import-online-intro"
|
||||
data-l10n-args='{"targetAppOnline": "Mendeley Reference Manager", "targetApp": "Mendeley"}'></div>
|
||||
<div
|
||||
class="mendeley-online-intro"
|
||||
data-l10n-id="import-online-intro2"
|
||||
data-l10n-args='{"targetApp": "Mendeley"}'></div>
|
||||
</wizardpage>
|
||||
<wizardpage
|
||||
pageid="page-options"
|
||||
data-header-label-id="import-options"
|
||||
>
|
||||
<div class="options-group" id="page-options-common">
|
||||
<checkbox native="true" id="create-collection" data-l10n-id="import-create-collection" />
|
||||
</div>
|
||||
<div class="options-group" id="page-options-folder-import">
|
||||
<checkbox native="true" id="recreate-structure" data-l10n-id="import-recreate-structure" checked="true" />
|
||||
<h2 class="import-file-types-header" data-l10n-id="import-fileTypes-header"></h2>
|
||||
<fieldset>
|
||||
<div class="page-options-file-type">
|
||||
<checkbox native="true" id="import-pdf" data-l10n-id="import-fileTypes-pdf" checked="true" />
|
||||
</div>
|
||||
<div class="page-options-file-type">
|
||||
<checkbox native="true" id="import-other" />
|
||||
<html:input type="text" id="other-files" data-l10n-id="import-fileTypes-other" />
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="options-group" id="page-options-file-handling">
|
||||
<h2 data-l10n-id="import-file-handling"></h2>
|
||||
<radiogroup id="file-handling" align="start">
|
||||
<radio id="store" data-l10n-id="import-file-handling-store" />
|
||||
<radio id="link" data-l10n-id="import-file-handling-link" />
|
||||
</radiogroup>
|
||||
<div class="page-options-file-handling-description" data-l10n-id="import-fileHandling-description"></div>
|
||||
</div>
|
||||
</wizardpage>
|
||||
<wizardpage pageid="page-progress" data-header-label-id="import-importing">
|
||||
<html:progress id="import-progress" max="100" value="0" />
|
||||
<div id="progress-queue-container" class="progress-queue table-container">
|
||||
<div id="progress-queue"></div>
|
||||
</div>
|
||||
</wizardpage>
|
||||
<wizardpage pageid="page-done">
|
||||
<div id="page-done-description"></div>
|
||||
<div id="page-done-error-mendeley" data-l10n-id="import-mendeley-encrypted">
|
||||
<html:a data-l10n-name="mendeley-import-kb" href="https://www.zotero.org/support/kb/mendeley_import" />
|
||||
</div>
|
||||
<div id="page-done-error">
|
||||
<button data-l10n-id="report-error" />
|
||||
</div>
|
||||
<div id="page-done-progress-queue"></div>
|
||||
<div id="done-queue-container" class="done-queue table-container">
|
||||
<div id="done-queue"></div>
|
||||
</div>
|
||||
</wizardpage>
|
||||
</wizard>
|
||||
|
||||
<script src="chrome://zotero/content/include.js"/>
|
||||
<script src="../fileInterface.js"/>
|
||||
<script src="./importWizard.js" />
|
||||
</window>
|
|
@ -1,98 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/importWizard.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<wizard id="import-wizard"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
title="&zotero.import;"
|
||||
onwizardcancel="Zotero_Import_Wizard.onCancel()"
|
||||
onload="Zotero_Import_Wizard.init()">
|
||||
|
||||
<script src="../include.js"/>
|
||||
<script src="../fileInterface.js"/>
|
||||
<script src="importWizard.js"/>
|
||||
|
||||
<wizardpage pageid="page-start"
|
||||
label="&zotero.import.whereToImportFrom;"
|
||||
next="page-options"
|
||||
onpageadvanced="Zotero_Import_Wizard.onModeChosen(); return false;">
|
||||
<radiogroup id="import-source">
|
||||
<radio id="radio-import-source-file" label="&zotero.import.source.file;"/>
|
||||
<radio id="radio-import-source-mendeley-online"/>
|
||||
<radio id="radio-import-source-mendeley" hidden="true"/>
|
||||
</radiogroup>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage
|
||||
next="page-options"
|
||||
pageid="mendeley-online-explanation"
|
||||
onpageshow="Zotero_Import_Wizard.onMendeleyOnlineShow()"
|
||||
onpageadvanced="Zotero_Import_Wizard.onMendeleyOnlineAdvance(); return false;"
|
||||
onpagerewound="return Zotero_Import_Wizard.goToStart()"
|
||||
>
|
||||
<description id="mendeley-online-description" />
|
||||
<description id="mendeley-online-description2" />
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-file-list"
|
||||
next="page-options"
|
||||
onpagerewound="return Zotero_Import_Wizard.goToStart()">
|
||||
<description id="file-options-header"/>
|
||||
<listbox id="file-list" onselect="Zotero_Import_Wizard.onFileSelected()">
|
||||
<listhead>
|
||||
<listheader label="&zotero.import.database;"/>
|
||||
<listheader label="&zotero.import.lastModified;"/>
|
||||
<listheader label="&zotero.import.size;"/>
|
||||
</listhead>
|
||||
|
||||
<listcols>
|
||||
<listcol flex="1"/>
|
||||
<listcol/>
|
||||
<listcol/>
|
||||
</listcols>
|
||||
</listbox>
|
||||
<hbox>
|
||||
<button label="&zotero.general.other;" oncommand="Zotero_Import_Wizard.chooseMendeleyDB()"/>
|
||||
</hbox>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-options"
|
||||
label="&zotero.general.options;"
|
||||
next="page-progress"
|
||||
onpageshow="Zotero_Import_Wizard.onOptionsShown()"
|
||||
onpagerewound="return Zotero_Import_Wizard.goToStart()"
|
||||
onpageadvanced="Zotero_Import_Wizard.onImportStart()">
|
||||
<checkbox id="create-collection-checkbox" label="&zotero.import.createCollection;" checked="true" />
|
||||
|
||||
<vbox id="file-handling-options">
|
||||
<label control="file-handling-radio" value="&zotero.import.fileHandling;"/>
|
||||
<radiogroup id="file-handling-radio">
|
||||
<radio id="file-handling-store" selected="true"/>
|
||||
<radio id="file-handling-link"/>
|
||||
</radiogroup>
|
||||
<description id="file-handling-description"/>
|
||||
</vbox>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-progress"
|
||||
label="&zotero.import.importing;"
|
||||
onpageshow="document.getElementById('import-wizard').canRewind = false;"
|
||||
next="page-done">
|
||||
<progressmeter id="import-progressmeter" mode="determined"/>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-done">
|
||||
<description id="result-description"/>
|
||||
<html:div id="result-description-html"/>
|
||||
<hbox>
|
||||
<button id="result-report-error"
|
||||
oncommand="Zotero_Import_Wizard.reportError()"
|
||||
hidden="true"/>
|
||||
</hbox>
|
||||
</wizardpage>
|
||||
</wizard>
|
47
chrome/content/zotero/import/mendeley/authViewer.js
Normal file
47
chrome/content/zotero/import/mendeley/authViewer.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
Services.scriptloader.loadSubScript("chrome://zotero/content/fileInterface.js", window);
|
||||
const { E10SUtils } = ChromeUtils.import("resource://gre/modules/E10SUtils.jsm");
|
||||
const URI = `https://api.mendeley.com/oauth/authorize?client_id=5907&redirect_uri=https%3A%2F%2Fzotero-static.s3.amazonaws.com%2Fmendeley_oauth_redirect.html&response_type=code&state=&scope=all`;
|
||||
var startTime;
|
||||
|
||||
const tryExtractAuthCode = browser => {
|
||||
const matchResult = browser.webNavigation.currentURI.spec
|
||||
.match(/mendeley_oauth_redirect.html(?:.*?)(?:\?|&)code=(.*?)(?:&|$)/i);
|
||||
return matchResult ? matchResult[1] : false;
|
||||
}
|
||||
|
||||
const clearCookies = since => {
|
||||
const cookieManager = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
|
||||
const sinceμs = since * 1000;
|
||||
const cookiesSince = cookieManager.getCookiesSince(sinceμs);
|
||||
Zotero.debug(`Deleting ${cookiesSince.length} cookies created during Mendeley Auth (last ${(Date.now() - since)}ms)`);
|
||||
cookieManager.removeAllSince(sinceμs);
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
clearCookies(startTime);
|
||||
});
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
// basicViewer uses remote="false" browser, we need to re-construct the browser so it's
|
||||
// capable of loading a mendeley auth website
|
||||
const browser = document.querySelector('browser');
|
||||
browser.setAttribute('remote', 'true');
|
||||
browser.changeRemoteness({ remoteType: E10SUtils.DEFAULT_REMOTE_TYPE });
|
||||
browser.construct();
|
||||
|
||||
startTime = Date.now();
|
||||
|
||||
browser.addEventListener("pagetitlechanged", event => {
|
||||
const mendeleyCode = tryExtractAuthCode(browser);
|
||||
if (mendeleyCode) {
|
||||
window.close();
|
||||
Zotero_File_Interface.showImportWizard({ mendeleyCode })
|
||||
}
|
||||
document.title = browser.contentTitle;
|
||||
});
|
||||
|
||||
browser.loadURI(URI, {
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
});
|
||||
}, false);
|
|
@ -1,234 +1,29 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2015 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2015 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
var Zotero_Publications_Dialog = new function () {
|
||||
var _initialized = false;
|
||||
var _io;
|
||||
var _hasFiles = false;
|
||||
var _hasNotes = false;
|
||||
var _hasRights = null;
|
||||
var _includeFiles = true;
|
||||
var _includeNotes = true;
|
||||
var _keepRights = true;
|
||||
var _shareSettings = {
|
||||
sharing: 'reserved', // 'reserved', 'cc', 'cc0'
|
||||
adaptations: 'no',
|
||||
commercial: 'no'
|
||||
};
|
||||
var _license = null;
|
||||
|
||||
function _init() {
|
||||
try {
|
||||
var wizard = document.getElementById('zotero-publications-wizard');
|
||||
wizard.getButton('finish').label =
|
||||
Zotero.getString('publications.buttons.addToMyPublications');
|
||||
|
||||
if (window.arguments && window.arguments.length) {
|
||||
_io = window.arguments[0];
|
||||
_hasFiles = _io.hasFiles;
|
||||
_hasNotes = _io.hasNotes;
|
||||
_hasRights = _io.hasRights;
|
||||
if (_hasRights == 'none') _keepRights = false;
|
||||
delete _io.hasFiles;
|
||||
delete _io.hasNotes;
|
||||
delete _io.hasRights;
|
||||
}
|
||||
_initialized = true;
|
||||
}
|
||||
catch (e) {
|
||||
window.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.updatePage = function () {
|
||||
if (!_initialized) {
|
||||
_init();
|
||||
this.updateInclude();
|
||||
}
|
||||
|
||||
var wizard = document.getElementById('zotero-publications-wizard');
|
||||
var currentPage = wizard.currentPage;
|
||||
var pageid = currentPage.pageid;
|
||||
|
||||
if (pageid == 'intro') {
|
||||
let str = 'publications.authorship.checkbox';
|
||||
let filesCheckbox = document.getElementById('include-files');
|
||||
let notesCheckbox = document.getElementById('include-notes')
|
||||
|
||||
// Enable the checkboxes only when relevant
|
||||
filesCheckbox.disabled = !_hasFiles;
|
||||
filesCheckbox.checked = _hasFiles && _includeFiles;
|
||||
notesCheckbox.disabled = !_hasNotes;
|
||||
notesCheckbox.checked = _hasNotes && _includeNotes;
|
||||
|
||||
// Adjust the checkbox text based on whether there are files or notes
|
||||
if (filesCheckbox.checked || notesCheckbox.checked) {
|
||||
if (filesCheckbox.checked && notesCheckbox.checked) {
|
||||
str += '.filesNotes';
|
||||
}
|
||||
else if (filesCheckbox.checked) {
|
||||
str += '.files';
|
||||
}
|
||||
else {
|
||||
str += '.notes';
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pageid == 'choose-sharing') {
|
||||
let keepRightsBox = document.getElementById('keep-rights');
|
||||
let keepRightsCheckbox = document.getElementById('keep-rights-checkbox');
|
||||
if (_hasRights == 'none') {
|
||||
keepRightsBox.hidden = true;
|
||||
document.getElementById('sharing-radiogroup').focus();
|
||||
}
|
||||
else {
|
||||
let str = 'publications.sharing.keepRightsField';
|
||||
if (_hasRights == 'some') {
|
||||
str += 'WhereAvailable';
|
||||
}
|
||||
keepRightsCheckbox.label = Zotero.getString(str);
|
||||
keepRightsCheckbox.checked = _keepRights;
|
||||
this.updateKeepRights(keepRightsCheckbox.checked);
|
||||
}
|
||||
}
|
||||
// Select appropriate radio button from current license
|
||||
else if (pageid == 'choose-license') {
|
||||
document.getElementById('adaptations-' + _shareSettings.adaptations).selected = true;
|
||||
document.getElementById('commercial-' + _shareSettings.commercial).selected = true;
|
||||
}
|
||||
|
||||
_updateLicense();
|
||||
this.updateNextButton();
|
||||
};
|
||||
|
||||
|
||||
this.updateNextButton = function () {
|
||||
var wizard = document.getElementById('zotero-publications-wizard');
|
||||
var currentPage = wizard.currentPage;
|
||||
var nextPage = wizard.wizardPages[wizard.pageIndex + 1];
|
||||
var nextButton = wizard.getButton('next');
|
||||
|
||||
// Require authorship checkbox on first page to be checked to advance
|
||||
wizard.canAdvance = document.getElementById('confirm-authorship-checkbox').checked;
|
||||
|
||||
if (!nextPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hasFiles
|
||||
&& _includeFiles
|
||||
&& (currentPage.pageid == 'intro' ||
|
||||
// If CC selected on sharing page and we're not using existing rights for all
|
||||
// items, go to license chooser next
|
||||
(currentPage.pageid == 'choose-sharing'
|
||||
&& _shareSettings.sharing == 'cc'
|
||||
&& !(_hasRights == 'all' && _keepRights)))) {
|
||||
this.lastPage = false;
|
||||
nextButton.label = Zotero.getString(
|
||||
'publications.buttons.next',
|
||||
Zotero.getString('publications.buttons.' + nextPage.pageid)
|
||||
);
|
||||
}
|
||||
// Otherwise this is the last page
|
||||
else {
|
||||
this.lastPage = true;
|
||||
// Due to issues with linux not handling finish button hiding correctly
|
||||
// we just set the next button label to be the one for the finish button
|
||||
// and leave visibility handling up to mr wizard
|
||||
nextButton.label = Zotero.getString('publications.buttons.addToMyPublications');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update files/notes settings from checkboxes
|
||||
*/
|
||||
this.updateInclude = function () {
|
||||
var filesCheckbox = document.getElementById('include-files');
|
||||
var notesCheckbox = document.getElementById('include-notes')
|
||||
var authorshipCheckbox = document.getElementById('confirm-authorship-checkbox');
|
||||
_includeFiles = filesCheckbox.checked;
|
||||
_includeNotes = notesCheckbox.checked;
|
||||
authorshipCheckbox.label = Zotero.getString(
|
||||
'publications.intro.authorship' + (_includeFiles ? '.files' : '')
|
||||
);
|
||||
this.updateNextButton();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update rights setting from checkbox and hide sharing setting if necessary
|
||||
*/
|
||||
this.updateKeepRights = function (keepRights) {
|
||||
_keepRights = keepRights;
|
||||
|
||||
// If all items have rights and we're using them, the sharing page is the last page
|
||||
document.getElementById('choose-sharing-options').hidden = _hasRights == 'all' && keepRights;
|
||||
this.updateNextButton();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update sharing and license settings
|
||||
*/
|
||||
this.updateSharing = function (id) {
|
||||
var matches = id.match(/^(sharing|adaptations|commercial)-(.+)$/);
|
||||
var setting = matches[1];
|
||||
var value = matches[2];
|
||||
_shareSettings[setting] = value;
|
||||
_updateLicense();
|
||||
this.updateNextButton();
|
||||
}
|
||||
|
||||
|
||||
this.onAdvance = function () {
|
||||
if (this.lastPage) {
|
||||
this.finish();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
this.onFinish = function () {
|
||||
_io.includeFiles = document.getElementById('include-files').checked;
|
||||
_io.includeNotes = document.getElementById('include-notes').checked;
|
||||
_io.keepRights = _keepRights;
|
||||
_io.license = _license;
|
||||
_io.licenseName = _getLicenseName(_license);
|
||||
}
|
||||
|
||||
this.finish = function () {
|
||||
this.onFinish();
|
||||
window.close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* Update the calculated license and image
|
||||
*
|
||||
* Possible licenses:
|
||||
|
@ -242,185 +37,180 @@ var Zotero_Publications_Dialog = new function () {
|
|||
* 'cc0'
|
||||
* 'reserved'
|
||||
*/
|
||||
function _updateLicense() {
|
||||
var s = _shareSettings.sharing;
|
||||
var a = _shareSettings.adaptations;
|
||||
var c = _shareSettings.commercial;
|
||||
|
||||
if (s == 'cc0' || s == 'reserved') {
|
||||
_license = s;
|
||||
}
|
||||
else {
|
||||
_license = 'cc-by';
|
||||
if (c == 'no') {
|
||||
_license += '-nc';
|
||||
}
|
||||
if (a == 'no') {
|
||||
_license += '-nd';
|
||||
}
|
||||
else if (a == 'sharealike') {
|
||||
_license += '-sa';
|
||||
}
|
||||
}
|
||||
_updateLicenseSummary();
|
||||
const getLicense = (sharing, adaptations, commercial, currentPage) => {
|
||||
if (sharing === 'cc0' || sharing === 'reserved') {
|
||||
return sharing;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function _updateLicenseSummary() {
|
||||
var wizard = document.getElementById('zotero-publications-wizard');
|
||||
var currentPage = wizard.currentPage;
|
||||
var groupbox = currentPage.getElementsByAttribute('class', 'license-info')[0];
|
||||
if (!groupbox) return;
|
||||
if (groupbox.hasChildNodes()) {
|
||||
let hbox = groupbox.lastChild;
|
||||
var icon = currentPage.getElementsByAttribute('class', 'license-icon')[0];
|
||||
var div = currentPage.getElementsByAttribute('class', 'license-description')[0];
|
||||
}
|
||||
else {
|
||||
let hbox = document.createXULElement('hbox');
|
||||
hbox.align = "center";
|
||||
groupbox.appendChild(hbox);
|
||||
|
||||
var icon = document.createXULElement('image');
|
||||
icon.className = 'license-icon';
|
||||
icon.setAttribute('style', 'width: 88px');
|
||||
hbox.appendChild(icon);
|
||||
|
||||
let sep = document.createXULElement('separator');
|
||||
sep.orient = 'vertical';
|
||||
sep.setAttribute('style', 'width: 10px');
|
||||
hbox.appendChild(sep);
|
||||
|
||||
var div = document.createElement('div');
|
||||
div.className = 'license-description';
|
||||
div.setAttribute('style', 'width: 400px');
|
||||
hbox.appendChild(div);
|
||||
}
|
||||
|
||||
// Show generic CC icon on sharing page
|
||||
if (currentPage.pageid == 'choose-sharing' && _shareSettings.sharing == 'cc') {
|
||||
var license = 'cc';
|
||||
}
|
||||
else {
|
||||
var license = _license;
|
||||
}
|
||||
|
||||
icon.src = _getLicenseImage(license);
|
||||
var url = _getLicenseURL(license);
|
||||
if (url) {
|
||||
icon.setAttribute('tooltiptext', url);
|
||||
icon.style.cursor = 'pointer';
|
||||
icon.onclick = function () {
|
||||
try {
|
||||
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
let win = wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane_Local.loadURI(url, { shiftKey: true })
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else {
|
||||
icon.removeAttribute('tooltiptext');
|
||||
icon.style.cursor = 'auto';
|
||||
}
|
||||
|
||||
div.innerHTML = _getLicenseHTML(license);
|
||||
Zotero.Utilities.Internal.updateHTMLInXUL(div, { linkEvent: { shiftKey: true } });
|
||||
|
||||
_updateLicenseMoreInfo();
|
||||
if (currentPage !== 'choose-license') {
|
||||
return 'cc';
|
||||
}
|
||||
|
||||
|
||||
function _getLicenseImage(license) {
|
||||
// Use generic "Some Rights Reserved" image
|
||||
if (license == 'cc') {
|
||||
return "chrome://zotero/skin/licenses/cc-srr.png";
|
||||
}
|
||||
else if (license == 'reserved') {
|
||||
return "chrome://zotero/skin/licenses/reserved.png";
|
||||
}
|
||||
return "chrome://zotero/skin/licenses/" + license + ".svg";
|
||||
|
||||
let license = 'cc-by';
|
||||
if (commercial === 'no') {
|
||||
license += '-nc';
|
||||
}
|
||||
|
||||
|
||||
function _getLicenseHTML(license) {
|
||||
switch (license) {
|
||||
case 'cc':
|
||||
return '<a href="' + _getLicenseURL(license) + '">Creative Commons</a>';
|
||||
|
||||
case 'reserved':
|
||||
return "All rights reserved";
|
||||
|
||||
case 'cc0':
|
||||
return '<a href="' + _getLicenseURL(license) + '">CC0 1.0 Universal Public Domain Dedication</a>';
|
||||
|
||||
default:
|
||||
return '<a href="' + _getLicenseURL(license) + '">'
|
||||
+ Zotero.getString('licenses.' + license) + "</a>";
|
||||
}
|
||||
if (adaptations === 'no') {
|
||||
license += '-nd';
|
||||
}
|
||||
|
||||
|
||||
function _getLicenseName(license) {
|
||||
switch (license) {
|
||||
case 'reserved':
|
||||
return "All rights reserved";
|
||||
|
||||
case 'cc0':
|
||||
return 'CC0 1.0 Universal Public Domain Dedication';
|
||||
|
||||
default:
|
||||
return Zotero.getString('licenses.' + license) + " (" + license.toUpperCase() + ")";
|
||||
}
|
||||
else if (adaptations === 'sharealike') {
|
||||
license += '-sa';
|
||||
}
|
||||
|
||||
|
||||
function _getLicenseURL(license) {
|
||||
switch (license) {
|
||||
case 'reserved':
|
||||
return "";
|
||||
return license;
|
||||
};
|
||||
|
||||
const id = document.getElementById.bind(document);
|
||||
|
||||
const Zotero_Publications_Dialog = { // eslint-disable-line no-unused-vars, camelcase
|
||||
async init() {
|
||||
this.io = window.arguments?.[0] ?? {};
|
||||
this.wizard = id('publications-dialog-wizard');
|
||||
|
||||
id('include-files')
|
||||
.addEventListener('CheckboxStateChange', this.onIntroPageCheckboxChange.bind(this));
|
||||
id('confirm-authorship-checkbox')
|
||||
.addEventListener('CheckboxStateChange', this.onIntroPageCheckboxChange.bind(this));
|
||||
id('sharing-radiogroup')
|
||||
.addEventListener('select', this.onLicenseAspectRadioChange.bind(this));
|
||||
id('choose-adaptations')
|
||||
.addEventListener('select', this.onLicenseAspectRadioChange.bind(this));
|
||||
id('choose-commercial')
|
||||
.addEventListener('select', this.onLicenseAspectRadioChange.bind(this));
|
||||
id('keep-rights-checkbox')
|
||||
.addEventListener('CheckboxStateChange', this.onKeepRightsCheckboxChange.bind(this));
|
||||
this.wizard.getPageById('intro')
|
||||
.addEventListener('pageshow', this.onIntroPageShow.bind(this));
|
||||
this.wizard.getPageById('choose-sharing')
|
||||
.addEventListener('pageshow', this.onSharingPageShow.bind(this));
|
||||
this.wizard.getPageById('choose-license')
|
||||
.addEventListener('pageshow', this.onLicensePageShow.bind(this));
|
||||
|
||||
this.wizard.addEventListener('wizardnext', this.onWizardNext.bind(this));
|
||||
this.wizard.addEventListener('wizardfinish', this.onFinish.bind(this));
|
||||
|
||||
// wizard.shadowRoot content isn't exposed to our css
|
||||
this.wizard.shadowRoot
|
||||
.querySelector('.wizard-header-label').style.fontSize = '16px';
|
||||
|
||||
case 'cc':
|
||||
return 'https://creativecommons.org/';
|
||||
|
||||
case 'cc0':
|
||||
return "https://creativecommons.org/publicdomain/zero/1.0/";
|
||||
|
||||
default:
|
||||
return "https://creativecommons.org/licenses/" + license.replace(/^cc-/, '') + "/4.0/"
|
||||
this.updateNextButton();
|
||||
this.updateIntroPage();
|
||||
},
|
||||
|
||||
onIntroPageShow() {
|
||||
this.updateNextButton();
|
||||
this.updateIntroPage();
|
||||
this.updateFocus();
|
||||
},
|
||||
|
||||
onSharingPageShow() {
|
||||
this.updateSharingPage();
|
||||
this.updateNextButton();
|
||||
this.updateLicense();
|
||||
this.updateFocus();
|
||||
},
|
||||
|
||||
onLicensePageShow() {
|
||||
this.updateNextButton();
|
||||
this.updateLicense();
|
||||
this.updateFocus();
|
||||
},
|
||||
|
||||
onWizardNext(ev) {
|
||||
if ((this.wizard.currentPage.pageid === 'intro' && !id('include-files').checked)
|
||||
|| (this.wizard.currentPage.pageid === 'choose-sharing'
|
||||
&& (id('sharing-radiogroup').selectedItem.value !== 'cc'
|
||||
|| (this.io.hasRights === 'all' && id('keep-rights-checkbox').checked)
|
||||
))
|
||||
) {
|
||||
ev.preventDefault();
|
||||
this.onFinish();
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _updateLicenseMoreInfo() {
|
||||
var wizard = document.getElementById('zotero-publications-wizard');
|
||||
var currentPage = wizard.currentPage;
|
||||
var s = _shareSettings.sharing;
|
||||
|
||||
var div = currentPage.getElementsByAttribute('class', 'license-more-info')[0];
|
||||
if (s == 'cc0' || currentPage.pageid == 'choose-license') {
|
||||
let links = {
|
||||
cc: 'https://wiki.creativecommons.org/Considerations_for_licensors_and_licensees',
|
||||
cc0: 'https://wiki.creativecommons.org/CC0_FAQ'
|
||||
};
|
||||
div.innerHTML = Zotero.getString(
|
||||
'publications.' + s + '.moreInfo.text',
|
||||
// Add link to localized string
|
||||
'<a href="' + links[s] + '">'
|
||||
+ Zotero.getString('publications.' + s + '.moreInfo.linkText')
|
||||
+ '</a>'
|
||||
},
|
||||
|
||||
onFinish() {
|
||||
this.io.includeFiles = id('include-files').checked;
|
||||
this.io.includeNotes = id('include-notes').checked;
|
||||
this.io.keepRights = id('keep-rights-checkbox').checked;
|
||||
if (this.wizard.currentPage.pageid !== 'intro') {
|
||||
this.io.license = getLicense(
|
||||
id('sharing-radiogroup').selectedItem.value,
|
||||
id('choose-adaptations').selectedItem.value,
|
||||
id('choose-commercial').selectedItem.value,
|
||||
this.wizard.currentPage.pageid
|
||||
);
|
||||
Zotero.Utilities.Internal.updateHTMLInXUL(div, { linkEvent: { shiftKey: true } });
|
||||
this.io.licenseName = id('final-license-info').licenseName;
|
||||
}
|
||||
else {
|
||||
div.innerHTML = "";
|
||||
},
|
||||
|
||||
onIntroPageCheckboxChange() {
|
||||
this.updateIntroPage();
|
||||
this.updateNextButton();
|
||||
},
|
||||
|
||||
onKeepRightsCheckboxChange() {
|
||||
this.updateSharingPage();
|
||||
this.updateNextButton();
|
||||
},
|
||||
|
||||
onLicenseAspectRadioChange() {
|
||||
this.updateNextButton();
|
||||
this.updateLicense();
|
||||
},
|
||||
|
||||
updateIntroPage() {
|
||||
id('include-files').disabled = !this.io.hasFiles;
|
||||
id('include-notes').disabled = !this.io.hasNotes;
|
||||
id('confirm-authorship-checkbox').dataset.l10nId = id('include-files').checked
|
||||
? 'publications-intro-authorship-files'
|
||||
: 'publications-intro-authorship';
|
||||
},
|
||||
|
||||
updateSharingPage() {
|
||||
id('keep-rights').style.display
|
||||
= this.io.hasRights === 'none' ? 'none' : '';
|
||||
|
||||
id('keep-rights-checkbox').disabled = this.io.hasRights === 'none';
|
||||
id('keep-rights-checkbox').dataset.l10nId
|
||||
= this.io.hasRights === 'some'
|
||||
? 'publications-sharing-keep-rights-field-where-available'
|
||||
: 'publications-sharing-keep-rights-field';
|
||||
id('choose-sharing-options').style.display
|
||||
= this.io.hasRights === 'all' && id('keep-rights-checkbox').checked ? 'none' : '';
|
||||
},
|
||||
|
||||
updateLicense() {
|
||||
const license = getLicense(
|
||||
id('sharing-radiogroup').selectedItem.value,
|
||||
id('choose-adaptations').selectedItem.value,
|
||||
id('choose-commercial').selectedItem.value,
|
||||
this.wizard.currentPage.pageid
|
||||
);
|
||||
id('sharing-license-info').license = license;
|
||||
id('final-license-info').license = license;
|
||||
},
|
||||
|
||||
updateFocus() {
|
||||
this.wizard.currentPage.querySelector('radiogroup:not([disabled]),checkbox:not([disabled])').focus();
|
||||
},
|
||||
|
||||
updateNextButton() {
|
||||
const nextButton = this.wizard.getButton('next');
|
||||
this.wizard.canAdvance = id('confirm-authorship-checkbox').checked;
|
||||
|
||||
if (this.io.hasRights === 'all' && id('keep-rights-checkbox').checked) {
|
||||
nextButton.dataset.l10nId = 'publications-buttons-add-to-my-publications';
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (this.wizard.currentPage.pageid === 'intro') {
|
||||
nextButton.dataset.l10nId = id('include-files').checked
|
||||
? 'publications-buttons-next-sharing'
|
||||
: 'publications-buttons-add-to-my-publications';
|
||||
}
|
||||
else if (this.wizard.currentPage.pageid === 'choose-sharing') {
|
||||
nextButton.dataset.l10nId
|
||||
= id('sharing-radiogroup').selectedItem.value === 'cc'
|
||||
? 'publications-buttons-next-choose-license'
|
||||
: 'publications-buttons-add-to-my-publications';
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
|
93
chrome/content/zotero/publicationsDialog.xhtml
Normal file
93
chrome/content/zotero/publicationsDialog.xhtml
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2022 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
-->
|
||||
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
onload="Zotero_Publications_Dialog.init()"
|
||||
>
|
||||
<linkset>
|
||||
<html:link rel="localization" href="zotero.ftl" />
|
||||
</linkset>
|
||||
|
||||
<script src="chrome://global/content/customElements.js" />
|
||||
<script src="chrome://zotero/content/elements/publicationsLicenseInfo.js" />
|
||||
|
||||
<wizard
|
||||
id="publications-dialog-wizard"
|
||||
class="publications-dialog-wizard"
|
||||
width="600" height="550"
|
||||
>
|
||||
<wizardpage pageid="intro" data-l10n-id="publications-intro-page">
|
||||
<p class="description" data-l10n-id="publications-intro" />
|
||||
<checkbox native="true" id="include-files" data-l10n-id="publications-include-checkbox-files" />
|
||||
<checkbox native="true" id="include-notes" data-l10n-id="publications-include-checkbox-notes" />
|
||||
<p class="description" data-l10n-id="publications-include-adjust-at-any-time" />
|
||||
<div class="confirm-authorship-checkbox">
|
||||
<checkbox native="true" id="confirm-authorship-checkbox" data-l10n-id="publications-intro-authorship" />
|
||||
</div>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="choose-sharing" data-l10n-id="publications-sharing-page">
|
||||
<div id="keep-rights">
|
||||
<checkbox native="true" id="keep-rights-checkbox" data-l10n-id="publications-sharing-keep-rights-field" />
|
||||
</div>
|
||||
<div id="choose-sharing-options">
|
||||
<p class="description" data-l10n-id="publications-sharing-text" />
|
||||
<p class="description" data-l10n-id="publications-sharing-prompt" />
|
||||
<radiogroup id="sharing-radiogroup" align="start">
|
||||
<radio value="reserved" data-l10n-id="publications-sharing-reserved" />
|
||||
<radio value="cc" data-l10n-id="publications-sharing-cc" />
|
||||
<radio value="cc0" data-l10n-id="publications-sharing-cc0" />
|
||||
</radiogroup>
|
||||
<publications-license-info license="reserved" id="sharing-license-info" />
|
||||
</div>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="choose-license" data-l10n-id="publications-license-page">
|
||||
<p class="description" data-l10n-id="publications-choose-license-text" />
|
||||
<h2 data-l10n-id="publications-choose-license-adaptations-prompt" />
|
||||
<radiogroup id="choose-adaptations" align="start">
|
||||
<radio value="no" data-l10n-id="publications-choose-license-no" />
|
||||
<radio value="sharealike" data-l10n-id="publications-choose-license-sharealike" />
|
||||
<radio value="yes" data-l10n-id="publications-choose-license-yes" />
|
||||
</radiogroup>
|
||||
|
||||
<h2 data-l10n-id="publications-choose-license-commercial-prompt" />
|
||||
<radiogroup id="choose-commercial" align="start">
|
||||
<radio value="no" data-l10n-id="publications-choose-license-no" />
|
||||
<radio value="yes" data-l10n-id="publications-choose-license-yes" />
|
||||
</radiogroup>
|
||||
<publications-license-info license="cc-by-nc-nd" id="final-license-info" />
|
||||
</wizardpage>
|
||||
</wizard>
|
||||
|
||||
<script src="include.js" />
|
||||
<script src="publicationsDialog.js" />
|
||||
</window>
|
|
@ -1,109 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2015 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
-->
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd"> %zoteroDTD;
|
||||
<!ENTITY % publicationsDTD SYSTEM "chrome://zotero/locale/publications.dtd"> %publicationsDTD;
|
||||
]>
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/publicationsDialog.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/publicationsDialog.css"?>
|
||||
|
||||
<wizard id="zotero-publications-wizard" title="&zotero.publications.my_publications;"
|
||||
width="600"
|
||||
height="550"
|
||||
onwizardnext="return Zotero_Publications_Dialog.onAdvance()"
|
||||
onwizardfinish="return Zotero_Publications_Dialog.onFinish()"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script src="include.js"/>
|
||||
<script src="publicationsDialog.js"/>
|
||||
|
||||
<!-- NOTES AND ATTACHMENTS? -->
|
||||
|
||||
<wizardpage pageid="intro" label="&zotero.publications.my_publications;"
|
||||
onpageshow="Zotero_Publications_Dialog.updatePage()">
|
||||
<description>&zotero.publications.intro;</description>
|
||||
<separator/>
|
||||
<checkbox id="include-files" label="&zotero.publications.include.checkbox.files;"
|
||||
oncommand="Zotero_Publications_Dialog.updateInclude()"/>
|
||||
<checkbox id="include-notes" label="&zotero.publications.include.checkbox.notes;"
|
||||
oncommand="Zotero_Publications_Dialog.updateInclude()"/>
|
||||
<separator/>
|
||||
<description>&zotero.publications.include.adjustAtAnyTime;</description>
|
||||
<separator/>
|
||||
<checkbox id="confirm-authorship-checkbox"
|
||||
oncommand="Zotero_Publications_Dialog.updateNextButton()"/>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="choose-sharing" label="&zotero.publications.sharing.title;"
|
||||
onpageshow="Zotero_Publications_Dialog.updatePage()">
|
||||
<vbox id="keep-rights">
|
||||
<checkbox id="keep-rights-checkbox"
|
||||
oncommand="Zotero_Publications_Dialog.updateKeepRights(this.checked)"/>
|
||||
<separator/>
|
||||
</vbox>
|
||||
<vbox id="choose-sharing-options">
|
||||
<description>&zotero.publications.sharing.text;</description>
|
||||
<separator/>
|
||||
<description>&zotero.publications.sharing.prompt;</description>
|
||||
<separator class="thin"/>
|
||||
<radiogroup id="sharing-radiogroup"
|
||||
oncommand="Zotero_Publications_Dialog.updateSharing(this.selectedItem.id)">
|
||||
<radio id="sharing-reserved" label="&zotero.publications.sharing.reserved;"/>
|
||||
<radio id="sharing-cc" label="&zotero.publications.sharing.cc;"/>
|
||||
<radio id="sharing-cc0" label="&zotero.publications.sharing.cc0;"/>
|
||||
</radiogroup>
|
||||
|
||||
<groupbox class="license-info"/>
|
||||
<separator/>
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" class="license-more-info"/>
|
||||
</vbox>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="choose-license" label="&zotero.publications.chooseLicense.title;"
|
||||
onpageshow="Zotero_Publications_Dialog.updatePage()">
|
||||
<description>&zotero.publications.chooseLicense.text;</description>
|
||||
<separator/>
|
||||
<label value="&zotero.publications.chooseLicense.adaptations.prompt;" control="choose-adaptations"/>
|
||||
<radiogroup id="choose-adaptations" oncommand="Zotero_Publications_Dialog.updateSharing(this.selectedItem.id)">
|
||||
<radio id="adaptations-no" label="&zotero.general.no;" accesskey="N"/>
|
||||
<radio id="adaptations-sharealike" label="&zotero.publications.chooseLicense.adaptations.sharealike;" accesskey="S"/>
|
||||
<radio id="adaptations-yes" label="&zotero.general.yes;" accesskey="Y"/>
|
||||
</radiogroup>
|
||||
<separator/>
|
||||
<label value="&zotero.publications.chooseLicense.commercial.prompt;" control="choose-commercial"/>
|
||||
<radiogroup id="choose-commercial" oncommand="Zotero_Publications_Dialog.updateSharing(this.selectedItem.id)">
|
||||
<radio id="commercial-no" label="&zotero.general.no;" accesskey="N"/>
|
||||
<radio id="commercial-yes" label="&zotero.general.yes;" accesskey="Y"/>
|
||||
</radiogroup>
|
||||
|
||||
<groupbox class="license-info"/>
|
||||
<separator/>
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" class="license-more-info"/>
|
||||
</wizardpage>
|
||||
</wizard>
|
758
chrome/content/zotero/rtfScan.js
Normal file
758
chrome/content/zotero/rtfScan.js
Normal file
|
@ -0,0 +1,758 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import FilePicker from 'zotero/modules/filePicker';
|
||||
import VirtualizedTable from 'components/virtualized-table';
|
||||
import { getDOMElement } from 'components/icons';
|
||||
|
||||
var { Services } = ChromeUtils.import('resource://gre/modules/Services.jsm');
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/styleConfigurator.js', this);
|
||||
|
||||
function _generateItem(citationString, itemName, action) {
|
||||
return {
|
||||
rtf: citationString,
|
||||
item: itemName,
|
||||
action
|
||||
};
|
||||
}
|
||||
|
||||
function _matchesItemCreators(creators, item, etAl) {
|
||||
var itemCreators = item.getCreators();
|
||||
var primaryCreators = [];
|
||||
var primaryCreatorTypeID = Zotero.CreatorTypes.getPrimaryIDForType(item.itemTypeID);
|
||||
|
||||
// use only primary creators if primary creators exist
|
||||
for (let i = 0; i < itemCreators.length; i++) {
|
||||
if (itemCreators[i].creatorTypeID == primaryCreatorTypeID) {
|
||||
primaryCreators.push(itemCreators[i]);
|
||||
}
|
||||
}
|
||||
// if primaryCreators matches the creator list length, or if et al is being used, use only
|
||||
// primary creators
|
||||
if (primaryCreators.length == creators.length || etAl) itemCreators = primaryCreators;
|
||||
|
||||
// for us to have an exact match, either the citation creator list length has to match the
|
||||
// item creator list length, or et al has to be used
|
||||
if (itemCreators.length == creators.length || (etAl && itemCreators.length > creators.length)) {
|
||||
var matched = true;
|
||||
for (let i = 0; i < creators.length; i++) {
|
||||
// check each item creator to see if it matches
|
||||
matched = matched && _matchesItemCreator(creators[i], itemCreators[i]);
|
||||
if (!matched) break;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _matchesItemCreator(creator, itemCreator) {
|
||||
// make sure last name matches
|
||||
var lowerLast = itemCreator.lastName.toLowerCase();
|
||||
if (lowerLast != creator.substr(-lowerLast.length).toLowerCase()) return false;
|
||||
|
||||
// make sure first name matches, if it exists
|
||||
if (creator.length > lowerLast.length) {
|
||||
var firstName = Zotero.Utilities.trim(creator.substr(0, creator.length - lowerLast.length));
|
||||
if (firstName.length) {
|
||||
// check to see whether the first name is all initials
|
||||
const initialRe = /^(?:[A-Z]\.? ?)+$/;
|
||||
var m = initialRe.exec(firstName);
|
||||
if (m) {
|
||||
var initials = firstName.replace(/[^A-Z]/g, "");
|
||||
var itemInitials = itemCreator.firstName.split(/ +/g)
|
||||
.map(name => name[0].toUpperCase())
|
||||
.join("");
|
||||
if (initials != itemInitials) return false;
|
||||
}
|
||||
else {
|
||||
// not all initials; verify that the first name matches
|
||||
var firstWord = firstName.substr(0, itemCreator.firstName).toLowerCase();
|
||||
var itemFirstWord = itemCreator.firstName.substr(0, itemCreator.firstName.indexOf(" ")).toLowerCase();
|
||||
if (firstWord != itemFirstWord) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
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" },
|
||||
];
|
||||
|
||||
const BIBLIOGRAPHY_PLACEHOLDER = "\\{Bibliography\\}";
|
||||
|
||||
const initialRows = [
|
||||
{ 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 },
|
||||
];
|
||||
Object.freeze(initialRows);
|
||||
|
||||
// const initialRowMap = {};
|
||||
// initialRows.forEach((row, index) => initialRowMap[row.id] = index);
|
||||
const initialRowMap = initialRows.reduce((aggr, row, index) => {
|
||||
aggr[row.id] = index;
|
||||
return aggr;
|
||||
}, {});
|
||||
Object.freeze(initialRowMap);
|
||||
|
||||
|
||||
const Zotero_RTFScan = { // eslint-disable-line no-unused-vars, camelcase
|
||||
wizard: null,
|
||||
inputFile: null,
|
||||
outputFile: null,
|
||||
contents: null,
|
||||
tree: null,
|
||||
styleConfig: null,
|
||||
citations: null,
|
||||
citationItemIDs: null,
|
||||
ids: 0,
|
||||
rows: [...initialRows],
|
||||
rowMap: { ...initialRowMap },
|
||||
|
||||
|
||||
async init() {
|
||||
this.wizard = document.getElementById('rtfscan-wizard');
|
||||
|
||||
this.wizard.getPageById('page-start')
|
||||
.addEventListener('pageshow', this.onIntroShow.bind(this));
|
||||
this.wizard.getPageById('page-start')
|
||||
.addEventListener('pageadvanced', this.onIntroAdvanced.bind(this));
|
||||
this.wizard.getPageById('scan-page')
|
||||
.addEventListener('pageshow', this.onScanPageShow.bind(this));
|
||||
this.wizard.getPageById('style-page')
|
||||
.addEventListener('pageadvanced', this.onStylePageAdvanced.bind(this));
|
||||
this.wizard.getPageById('style-page')
|
||||
.addEventListener('pagerewound', this.onStylePageRewound.bind(this));
|
||||
this.wizard.getPageById('format-page')
|
||||
.addEventListener('pageshow', this.onFormatPageShow.bind(this));
|
||||
this.wizard.getPageById('citations-page')
|
||||
.addEventListener('pageshow', this.onCitationsPageShow.bind(this));
|
||||
this.wizard.getPageById('citations-page')
|
||||
.addEventListener('pagerewound', this.onCitationsPageRewound.bind(this));
|
||||
this.wizard.getPageById('complete-page')
|
||||
.addEventListener('pageshow', this.onCompletePageShow.bind(this));
|
||||
|
||||
document
|
||||
.getElementById('choose-input-file')
|
||||
.addEventListener('click', this.onChooseInputFile.bind(this));
|
||||
document
|
||||
.getElementById('choose-output-file')
|
||||
.addEventListener('click', this.onChooseOutputFile.bind(this));
|
||||
|
||||
ReactDOM.render((
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this.rows.length}
|
||||
id="rtfScan-table"
|
||||
ref={ref => this.tree = ref}
|
||||
renderItem={this.renderItem.bind(this)}
|
||||
showHeader={true}
|
||||
columns={columns}
|
||||
containerWidth={document.getElementById('tree').clientWidth}
|
||||
disableFontSizeScaling={true}
|
||||
/>
|
||||
), document.getElementById('tree'));
|
||||
|
||||
const lastInputFile = Zotero.Prefs.get("rtfScan.lastInputFile");
|
||||
if (lastInputFile) {
|
||||
document.getElementById('input-path').value = lastInputFile;
|
||||
this.inputFile = Zotero.File.pathToFile(lastInputFile);
|
||||
}
|
||||
const lastOutputFile = Zotero.Prefs.get("rtfScan.lastOutputFile");
|
||||
if (lastOutputFile) {
|
||||
document.getElementById('output-path').value = lastOutputFile;
|
||||
this.outputFile = Zotero.File.pathToFile(lastOutputFile);
|
||||
}
|
||||
|
||||
// wizard.shadowRoot content isn't exposed to our css
|
||||
this.wizard.shadowRoot
|
||||
.querySelector('.wizard-header-label').style.fontSize = '16px';
|
||||
|
||||
this.updatePath();
|
||||
document.getElementById("choose-input-file").focus();
|
||||
},
|
||||
|
||||
async onChooseInputFile(ev) {
|
||||
if (ev.type === 'keydown' && ev.key !== ' ') {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const fp = new FilePicker();
|
||||
fp.init(window, Zotero.getString("rtfScan.openTitle"), fp.modeOpen);
|
||||
fp.appendFilters(fp.filterAll);
|
||||
fp.appendFilter(Zotero.getString("rtfScan.rtf"), "*.rtf");
|
||||
const rv = await fp.show();
|
||||
|
||||
if (rv == fp.returnOK || rv == fp.returnReplace) {
|
||||
this.inputFile = Zotero.File.pathToFile(fp.file);
|
||||
this.updatePath();
|
||||
}
|
||||
},
|
||||
|
||||
async onChooseOutputFile(ev) {
|
||||
if (ev.type === 'keydown' && ev.key !== ' ') {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const fp = new FilePicker();
|
||||
fp.init(window, Zotero.getString("rtfScan.saveTitle"), fp.modeSave);
|
||||
fp.appendFilter(Zotero.getString("rtfScan.rtf"), "*.rtf");
|
||||
if (this.inputFile) {
|
||||
let leafName = this.inputFile.leafName;
|
||||
let dotIndex = leafName.lastIndexOf(".");
|
||||
if (dotIndex !== -1) {
|
||||
leafName = leafName.substr(0, dotIndex);
|
||||
}
|
||||
fp.defaultString = leafName + " " + Zotero.getString("rtfScan.scannedFileSuffix") + ".rtf";
|
||||
}
|
||||
else {
|
||||
fp.defaultString = "Untitled.rtf";
|
||||
}
|
||||
|
||||
var rv = await fp.show();
|
||||
|
||||
if (rv == fp.returnOK || rv == fp.returnReplace) {
|
||||
this.outputFile = Zotero.File.pathToFile(fp.file);
|
||||
this.updatePath();
|
||||
}
|
||||
},
|
||||
|
||||
onIntroShow() {
|
||||
this.wizard.canRewind = false;
|
||||
this.updatePath();
|
||||
},
|
||||
|
||||
onIntroAdvanced() {
|
||||
Zotero.Prefs.set("rtfScan.lastInputFile", this.inputFile.path);
|
||||
Zotero.Prefs.set("rtfScan.lastOutputFile", this.outputFile.path);
|
||||
},
|
||||
|
||||
async onScanPageShow() {
|
||||
this.wizard.canRewind = false;
|
||||
this.wizard.canAdvance = false;
|
||||
|
||||
// wait a ms so that UI thread gets updated
|
||||
try {
|
||||
await this.scanRTF();
|
||||
this.tree.invalidate();
|
||||
this.wizard.canRewind = true;
|
||||
this.wizard.canAdvance = true;
|
||||
this.wizard.advance();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.debug(e);
|
||||
}
|
||||
},
|
||||
|
||||
onStylePageRewound(ev) {
|
||||
ev.preventDefault();
|
||||
this.rows = [...initialRows];
|
||||
this.rowMap = { ...initialRowMap };
|
||||
this.wizard.goTo('page-start');
|
||||
},
|
||||
|
||||
onStylePageAdvanced() {
|
||||
const styleConfigurator = document.getElementById('style-configurator');
|
||||
this.styleConfig = {
|
||||
style: styleConfigurator.style,
|
||||
locale: styleConfigurator.locale,
|
||||
displayAs: styleConfigurator.displayAs
|
||||
};
|
||||
Zotero.Prefs.set("export.lastStyle", this.styleConfig.style);
|
||||
},
|
||||
|
||||
onCitationsPageShow() {
|
||||
this.refreshCanAdvanceIfCitationsReady();
|
||||
},
|
||||
|
||||
onCitationsPageRewound(ev) {
|
||||
ev.preventDefault();
|
||||
this.rows = [...initialRows];
|
||||
this.rowMap = { ...initialRowMap };
|
||||
this.wizard.goTo('page-start');
|
||||
},
|
||||
|
||||
onFormatPageShow() {
|
||||
this.wizard.canAdvance = false;
|
||||
this.wizard.canRewind = false;
|
||||
|
||||
window.setTimeout(() => {
|
||||
this.formatRTF();
|
||||
this.wizard.canRewind = true;
|
||||
this.wizard.canAdvance = true;
|
||||
this.wizard.advance();
|
||||
}, 0);
|
||||
},
|
||||
|
||||
onCompletePageShow() {
|
||||
this.wizard.canRewind = false;
|
||||
},
|
||||
|
||||
onRowTwistyMouseUp(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;
|
||||
this.tree.invalidate();
|
||||
},
|
||||
|
||||
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
|
||||
this.citationItemIDs[row.parent.rtf] = [this.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 (this.citationItemIDs[citation] && this.citationItemIDs[citation].length == 1) { // mapped citation
|
||||
// specify that item should be selected in window
|
||||
io.select = this.citationItemIDs[citation][0];
|
||||
}
|
||||
|
||||
window.openDialog('chrome://zotero/content/selectItemsDialog.xhtml', '', '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
|
||||
this.citationItemIDs[citation] = [selectedItemID];
|
||||
}
|
||||
}
|
||||
this.tree.invalidate();
|
||||
this.refreshCanAdvanceIfCitationsReady();
|
||||
},
|
||||
|
||||
async scanRTF() {
|
||||
// set up globals
|
||||
this.citations = [];
|
||||
this.citationItemIDs = {};
|
||||
|
||||
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
|
||||
// more than 4 names
|
||||
const nameRe = "(?:[^ .,;]{2,} |[A-Z].? ?){0,3}[A-Z][^ .,;]+";
|
||||
const creatorRe = '((?:(?:' + nameRe + ', )*' + nameRe + '(?:,? and|,? \\&|,) )?' + nameRe + ')(,? et al\\.?)?';
|
||||
// TODO: localize "and" term
|
||||
const creatorSplitRe = /(?:,| *(?:and|&)) +/g;
|
||||
var citationRe = new RegExp('(\\\\\\{|; )(' + creatorRe + ',? (?:"([^"]+)(?:,"|",) )?([0-9]{4})[a-z]?)(?:,(?: pp?.?)? ([^ )]+))?(?=;|\\\\\\})|(([A-Z][^ .,;]+)(,? et al\\.?)? (\\\\\\{([0-9]{4})[a-z]?\\\\\\}))', "gm");
|
||||
|
||||
// read through RTF file and display items as they're found
|
||||
// we could read the file in chunks, but unless people start having memory issues, it's
|
||||
// probably faster and definitely simpler if we don't
|
||||
this.contents = Zotero.File.getContents(this.inputFile)
|
||||
.replace(/([^\\\r])\r?\n/, "$1 ")
|
||||
.replace("\\'92", "'", "g")
|
||||
.replace("\\rquote ", "’");
|
||||
var m;
|
||||
var lastCitation = false;
|
||||
while ((m = citationRe.exec(this.contents))) {
|
||||
// determine whether suppressed or standard regular expression was used
|
||||
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
|
||||
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 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 {
|
||||
// otherwise, add another citation
|
||||
lastCitation = {
|
||||
citationStrings: [citationString], pages: [pages],
|
||||
start, end, suppressAuthor
|
||||
};
|
||||
this.citations.push(lastCitation);
|
||||
}
|
||||
|
||||
// only add each citation once
|
||||
if (this.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(".", "");
|
||||
// TODO: localize "et al." term
|
||||
creators = creators.split(creatorSplitRe);
|
||||
|
||||
for (let i = 0; i < creators.length; i++) {
|
||||
if (!creators[i]) {
|
||||
if (i == creators.length - 1) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
creators.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
var spaceIndex = creators[i].lastIndexOf(" ");
|
||||
var lastName = spaceIndex == -1 ? creators[i] : creators[i].substr(spaceIndex + 1);
|
||||
s.addCondition("lastName", "contains", lastName);
|
||||
}
|
||||
if (title) s.addCondition("title", "contains", title);
|
||||
s.addCondition("date", "is", date);
|
||||
var ids = await s.search(); // eslint-disable-line no-await-in-loop
|
||||
Zotero.debug("Mapped to " + ids);
|
||||
this.citationItemIDs[citationString] = ids;
|
||||
|
||||
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); // eslint-disable-line no-await-in-loop
|
||||
if (items.length > 1) {
|
||||
// check to see how well the author list matches the citation
|
||||
var matchedItems = [];
|
||||
for (let item of items) {
|
||||
await item.loadAllData(); // eslint-disable-line no-await-in-loop
|
||||
if (_matchesItemCreators(creators, item)) matchedItems.push(item);
|
||||
}
|
||||
|
||||
if (matchedItems.length != 0) items = matchedItems;
|
||||
}
|
||||
|
||||
if (items.length == 1) { // only one mapping
|
||||
await items[0].loadAllData(); // eslint-disable-line no-await-in-loop
|
||||
let row = _generateItem(citationString, items[0].getField("title"));
|
||||
row.parent = mappedRow;
|
||||
this.insertRows(row, this.rows.length);
|
||||
this.citationItemIDs[citationString] = [items[0].id];
|
||||
}
|
||||
else { // ambiguous mapping
|
||||
let row = _generateItem(citationString, "");
|
||||
row.parent = ambiguousRow;
|
||||
this.insertRows(row, this.rowMap.mapped);
|
||||
|
||||
// generate child items
|
||||
let children = [];
|
||||
for (let item of items) {
|
||||
let childRow = _generateItem("", item.getField("title"), true);
|
||||
childRow.parent = row;
|
||||
children.push(childRow);
|
||||
}
|
||||
this.insertRows(children, this.rowMap[row.id] + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
formatRTF() {
|
||||
// load style and create ItemSet with all items
|
||||
var zStyle = Zotero.Styles.get(this.styleConfig.style);
|
||||
var cslEngine = zStyle.getCiteProc(this.styleConfig.locale, 'rtf');
|
||||
var isNote = zStyle.class == "note";
|
||||
|
||||
// create citations
|
||||
// var k = 0;
|
||||
var cslCitations = [];
|
||||
var itemIDs = {};
|
||||
// var shouldBeSubsequent = {};
|
||||
for (let i = 0; i < this.citations.length; i++) {
|
||||
let citation = this.citations[i];
|
||||
var cslCitation = { citationItems: [], properties: {} };
|
||||
if (isNote) {
|
||||
cslCitation.properties.noteIndex = i;
|
||||
}
|
||||
|
||||
// create citation items
|
||||
for (var j = 0; j < citation.citationStrings.length; j++) {
|
||||
var citationItem = {};
|
||||
citationItem.id = this.citationItemIDs[citation.citationStrings[j]][0];
|
||||
itemIDs[citationItem.id] = true;
|
||||
citationItem.locator = citation.pages[j];
|
||||
citationItem.label = "page";
|
||||
citationItem["suppress-author"] = citation.suppressAuthor && !isNote;
|
||||
cslCitation.citationItems.push(citationItem);
|
||||
}
|
||||
|
||||
cslCitations.push(cslCitation);
|
||||
}
|
||||
Zotero.debug(cslCitations);
|
||||
|
||||
itemIDs = Object.keys(itemIDs);
|
||||
Zotero.debug(itemIDs);
|
||||
|
||||
// prepare the list of rendered citations
|
||||
var citationResults = cslEngine.rebuildProcessorState(cslCitations, "rtf");
|
||||
|
||||
// format citations
|
||||
var contentArray = [];
|
||||
var lastEnd = 0;
|
||||
for (let i = 0; i < this.citations.length; i++) {
|
||||
let citation = citationResults[i][2];
|
||||
Zotero.debug("Formatted " + citation);
|
||||
|
||||
// if using notes, we might have to move the note after the punctuation
|
||||
if (isNote && this.citations[i].start != 0 && this.contents[this.citations[i].start - 1] == " ") {
|
||||
contentArray.push(this.contents.substring(lastEnd, this.citations[i].start - 1));
|
||||
}
|
||||
else {
|
||||
contentArray.push(this.contents.substring(lastEnd, this.citations[i].start));
|
||||
}
|
||||
|
||||
lastEnd = this.citations[i].end;
|
||||
if (isNote && this.citations[i].end < this.contents.length && ".,!?".indexOf(this.contents[this.citations[i].end]) !== -1) {
|
||||
contentArray.push(this.contents[this.citations[i].end]);
|
||||
lastEnd++;
|
||||
}
|
||||
|
||||
if (isNote) {
|
||||
if (this.styleConfig.displayAs === 'endnotes') {
|
||||
contentArray.push("{\\super\\chftn}\\ftnbj {\\footnote\\ftnalt {\\super\\chftn } " + citation + "}");
|
||||
}
|
||||
else { // footnotes
|
||||
contentArray.push("{\\super\\chftn}\\ftnbj {\\footnote {\\super\\chftn } " + citation + "}");
|
||||
}
|
||||
}
|
||||
else {
|
||||
contentArray.push(citation);
|
||||
}
|
||||
}
|
||||
contentArray.push(this.contents.substring(lastEnd));
|
||||
this.contents = contentArray.join("");
|
||||
|
||||
// add bibliography
|
||||
if (zStyle.hasBibliography) {
|
||||
var bibliography = Zotero.Cite.makeFormattedBibliography(cslEngine, "rtf");
|
||||
bibliography = bibliography.substring(5, bibliography.length - 1);
|
||||
// fix line breaks
|
||||
var linebreak = "\r\n";
|
||||
if (this.contents.indexOf("\r\n") == -1) {
|
||||
bibliography = bibliography.replace("\r\n", "\n", "g");
|
||||
linebreak = "\n";
|
||||
}
|
||||
|
||||
if (this.contents.indexOf(BIBLIOGRAPHY_PLACEHOLDER) !== -1) {
|
||||
this.contents = this.contents.replace(BIBLIOGRAPHY_PLACEHOLDER, bibliography);
|
||||
}
|
||||
else {
|
||||
// add two newlines before bibliography
|
||||
bibliography = linebreak + "\\" + linebreak + "\\" + linebreak + bibliography;
|
||||
|
||||
// add bibliography automatically inside last set of brackets closed
|
||||
const bracketRe = /^\{+/;
|
||||
var m = bracketRe.exec(this.contents);
|
||||
if (m) {
|
||||
var closeBracketRe = new RegExp("(\\}{" + m[0].length + "}\\s*)$");
|
||||
this.contents = this.contents.replace(closeBracketRe, bibliography + "$1");
|
||||
}
|
||||
else {
|
||||
this.contents += bibliography;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cslEngine.free();
|
||||
|
||||
Zotero.File.putContents(this.outputFile, this.contents);
|
||||
|
||||
// save locale
|
||||
if (!zStyle.locale && this.styleConfig.locale) {
|
||||
Zotero.Prefs.set("export.lastLocale", this.styleConfig.locale);
|
||||
}
|
||||
},
|
||||
|
||||
refreshCanAdvanceIfCitationsReady() {
|
||||
let newCanAdvance = true;
|
||||
for (let i in this.citationItemIDs) {
|
||||
let itemList = this.citationItemIDs[i];
|
||||
if (itemList.length !== 1) {
|
||||
newCanAdvance = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.wizard.canAdvance = newCanAdvance;
|
||||
},
|
||||
|
||||
updatePath() {
|
||||
this.wizard.canAdvance = this.inputFile && this.outputFile;
|
||||
document.getElementById('input-path').value = this.inputFile ? this.inputFile.path : '';
|
||||
document.getElementById('output-path').value = this.outputFile ? this.outputFile.path : '';
|
||||
},
|
||||
|
||||
insertRows(newRows, beforeRow) {
|
||||
if (!Array.isArray(newRows)) {
|
||||
newRows = [newRows];
|
||||
}
|
||||
this.rows.splice(beforeRow, 0, ...newRows);
|
||||
newRows.forEach(row => row.id = this.ids++);
|
||||
|
||||
// Refresh the row map
|
||||
this.rowMap = {};
|
||||
this.rows.forEach((row, index) => this.rowMap[row.id] = index);
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
getRowLevel(row, depth = 0) {
|
||||
if (typeof row == 'number') {
|
||||
row = this.rows[row];
|
||||
}
|
||||
if (!row.parent) {
|
||||
return depth;
|
||||
}
|
||||
return this.getRowLevel(row.parent, depth + 1);
|
||||
},
|
||||
|
||||
renderItem(index, selection, oldDiv = null, columns) {
|
||||
const row = this.rows[index];
|
||||
let div;
|
||||
if (oldDiv) {
|
||||
div = oldDiv;
|
||||
div.innerHTML = "";
|
||||
}
|
||||
else {
|
||||
div = document.createElement('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.onRowTwistyMouseUp(event, index),
|
||||
{ passive: true });
|
||||
}
|
||||
else {
|
||||
twisty = document.createElement('span');
|
||||
twisty.classList.add("spacer-twisty");
|
||||
}
|
||||
|
||||
let textSpan = document.createElement('span');
|
||||
textSpan.className = "cell-text";
|
||||
textSpan.innerText = row[column.dataKey] || "";
|
||||
textSpan.style.paddingLeft = (5 + 20 * this.getRowLevel(row)) + 'px';
|
||||
|
||||
let span = document.createElement('span');
|
||||
span.className = `cell primary ${column.className}`;
|
||||
span.appendChild(twisty);
|
||||
span.appendChild(textSpan);
|
||||
div.appendChild(span);
|
||||
}
|
||||
else if (column.dataKey == 'action') {
|
||||
let span = document.createElement('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.createElement('span');
|
||||
span.className = `cell ${column.className}`;
|
||||
span.innerText = row[column.dataKey] || "";
|
||||
div.appendChild(span);
|
||||
}
|
||||
}
|
||||
return div;
|
||||
},
|
||||
|
||||
};
|
|
@ -1,779 +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 *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Tools for automatically retrieving a citation for the given PDF
|
||||
*/
|
||||
|
||||
import FilePicker from 'zotero/modules/filePicker';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import VirtualizedTable from 'components/virtualized-table';
|
||||
import { getDOMElement } from 'components/icons';
|
||||
|
||||
/**
|
||||
* Front end for recognizing PDFs
|
||||
* @namespace
|
||||
*/
|
||||
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.getString('zotero.rtfScan.unmappedCitations.label'), collapsed: false },
|
||||
{ id: 'ambiguous', rtf: Zotero.getString('zotero.rtfScan.ambiguousCitations.label'), collapsed: false },
|
||||
{ id: 'mapped', rtf: Zotero.getString('zotero.rtfScan.mappedCitations.label'), collapsed: false },
|
||||
];
|
||||
this._rowMap = {};
|
||||
this._rows.forEach((row, index) => this._rowMap[row.id] = index);
|
||||
|
||||
var inputFile = null, outputFile = null;
|
||||
var citations, citationItemIDs, contents;
|
||||
|
||||
/** INTRO PAGE UI **/
|
||||
|
||||
/**
|
||||
* Called when the first page is shown; loads target file from preference, if one is set
|
||||
*/
|
||||
this.introPageShowing = function() {
|
||||
var path = Zotero.Prefs.get("rtfScan.lastInputFile");
|
||||
if(path) {
|
||||
inputFile = Zotero.File.pathToFile(path);
|
||||
}
|
||||
var path = Zotero.Prefs.get("rtfScan.lastOutputFile");
|
||||
if(path) {
|
||||
outputFile = Zotero.File.pathToFile(path);
|
||||
}
|
||||
_updatePath();
|
||||
document.getElementById("choose-input-file").focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the first page is hidden
|
||||
*/
|
||||
this.introPageAdvanced = function() {
|
||||
Zotero.Prefs.set("rtfScan.lastInputFile", inputFile.path);
|
||||
Zotero.Prefs.set("rtfScan.lastOutputFile", outputFile.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to select the file to be processed
|
||||
*/
|
||||
this.chooseInputFile = async function () {
|
||||
// display file picker
|
||||
var fp = new FilePicker();
|
||||
fp.init(window, Zotero.getString("rtfScan.openTitle"), fp.modeOpen);
|
||||
|
||||
fp.appendFilters(fp.filterAll);
|
||||
fp.appendFilter(Zotero.getString("rtfScan.rtf"), "*.rtf");
|
||||
|
||||
var rv = await fp.show();
|
||||
if (rv == fp.returnOK || rv == fp.returnReplace) {
|
||||
inputFile = Zotero.File.pathToFile(fp.file);
|
||||
_updatePath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to select the output file
|
||||
*/
|
||||
this.chooseOutputFile = async function () {
|
||||
var fp = new FilePicker();
|
||||
fp.init(window, Zotero.getString("rtfScan.saveTitle"), fp.modeSave);
|
||||
fp.appendFilter(Zotero.getString("rtfScan.rtf"), "*.rtf");
|
||||
if(inputFile) {
|
||||
var leafName = inputFile.leafName;
|
||||
var dotIndex = leafName.lastIndexOf(".");
|
||||
if(dotIndex != -1) {
|
||||
leafName = leafName.substr(0, dotIndex);
|
||||
}
|
||||
fp.defaultString = leafName+" "+Zotero.getString("rtfScan.scannedFileSuffix")+".rtf";
|
||||
} else {
|
||||
fp.defaultString = "Untitled.rtf";
|
||||
}
|
||||
|
||||
var rv = await fp.show();
|
||||
if (rv == fp.returnOK || rv == fp.returnReplace) {
|
||||
outputFile = Zotero.File.pathToFile(fp.file);
|
||||
_updatePath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to update the path label in the dialog box
|
||||
* @private
|
||||
*/
|
||||
function _updatePath() {
|
||||
document.documentElement.canAdvance = inputFile && outputFile;
|
||||
if(inputFile) document.getElementById("input-path").value = inputFile.path;
|
||||
if(outputFile) document.getElementById("output-path").value = outputFile.path;
|
||||
}
|
||||
|
||||
/** SCAN PAGE UI **/
|
||||
|
||||
/**
|
||||
* Called when second page is shown.
|
||||
*/
|
||||
this.scanPageShowing = async function () {
|
||||
// can't advance
|
||||
document.documentElement.canAdvance = false;
|
||||
|
||||
// wait a ms so that UI thread gets updated
|
||||
try {
|
||||
await this._scanRTF();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.debug(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Scans file for citations, then proceeds to next wizard page.
|
||||
*/
|
||||
this._scanRTF = async () => {
|
||||
// set up globals
|
||||
citations = [];
|
||||
citationItemIDs = {};
|
||||
|
||||
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
|
||||
// more than 4 names
|
||||
const nameRe = "(?:[^ .,;]{2,} |[A-Z].? ?){0,3}[A-Z][^ .,;]+";
|
||||
const creatorRe = '((?:(?:'+nameRe+', )*'+nameRe+'(?:,? and|,? \\&|,) )?'+nameRe+')(,? et al\\.?)?';
|
||||
// TODO: localize "and" term
|
||||
const creatorSplitRe = /(?:,| *(?:and|\&)) +/g;
|
||||
var citationRe = new RegExp('(\\\\\\{|; )('+creatorRe+',? (?:"([^"]+)(?:,"|",) )?([0-9]{4})[a-z]?)(?:,(?: pp?\.?)? ([^ )]+))?(?=;|\\\\\\})|(([A-Z][^ .,;]+)(,? et al\\.?)? (\\\\\\{([0-9]{4})[a-z]?\\\\\\}))', "gm");
|
||||
|
||||
// read through RTF file and display items as they're found
|
||||
// we could read the file in chunks, but unless people start having memory issues, it's
|
||||
// probably faster and definitely simpler if we don't
|
||||
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))) {
|
||||
// determine whether suppressed or standard regular expression was used
|
||||
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
|
||||
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 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 {
|
||||
// otherwise, add another citation
|
||||
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);
|
||||
|
||||
// for each individual match, look for an item in the database
|
||||
var s = new Zotero.Search;
|
||||
creators = creators.replace(".", "");
|
||||
// TODO: localize "et al." term
|
||||
creators = creators.split(creatorSplitRe);
|
||||
|
||||
for (let i = 0; i < creators.length; i++) {
|
||||
if (!creators[i]) {
|
||||
if (i == creators.length - 1) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
creators.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
var spaceIndex = creators[i].lastIndexOf(" ");
|
||||
var lastName = spaceIndex == -1 ? creators[i] : creators[i].substr(spaceIndex+1);
|
||||
s.addCondition("lastName", "contains", lastName);
|
||||
}
|
||||
if (title) s.addCondition("title", "contains", title);
|
||||
s.addCondition("date", "is", date);
|
||||
var ids = await s.search();
|
||||
Zotero.debug("Mapped to " + ids);
|
||||
citationItemIDs[citationString] = ids;
|
||||
|
||||
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 (let item of items) {
|
||||
await item.loadAllData();
|
||||
if (_matchesItemCreators(creators, item)) matchedItems.push(item);
|
||||
}
|
||||
|
||||
if (matchedItems.length != 0) items = matchedItems;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
else { // ambiguous mapping
|
||||
let row = _generateItem(citationString, "");
|
||||
row.parent = ambiguousRow;
|
||||
this._insertRows(row, this._rowMap.mapped);
|
||||
|
||||
// generate child items
|
||||
let children = [];
|
||||
for (let item of items) {
|
||||
let childRow = _generateItem("", item.getField("title"), true);
|
||||
childRow.parent = row;
|
||||
children.push(childRow);
|
||||
}
|
||||
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, action) {
|
||||
return {
|
||||
rtf: citationString,
|
||||
item: itemName,
|
||||
action
|
||||
};
|
||||
}
|
||||
|
||||
function _matchesItemCreators(creators, item, etAl) {
|
||||
var itemCreators = item.getCreators();
|
||||
var primaryCreators = [];
|
||||
var primaryCreatorTypeID = Zotero.CreatorTypes.getPrimaryIDForType(item.itemTypeID);
|
||||
|
||||
// use only primary creators if primary creators exist
|
||||
for(var i=0; i<itemCreators.length; i++) {
|
||||
if(itemCreators[i].creatorTypeID == primaryCreatorTypeID) {
|
||||
primaryCreators.push(itemCreators[i]);
|
||||
}
|
||||
}
|
||||
// if primaryCreators matches the creator list length, or if et al is being used, use only
|
||||
// primary creators
|
||||
if(primaryCreators.length == creators.length || etAl) itemCreators = primaryCreators;
|
||||
|
||||
// for us to have an exact match, either the citation creator list length has to match the
|
||||
// item creator list length, or et al has to be used
|
||||
if(itemCreators.length == creators.length || (etAl && itemCreators.length > creators.length)) {
|
||||
var matched = true;
|
||||
for(var i=0; i<creators.length; i++) {
|
||||
// check each item creator to see if it matches
|
||||
matched = matched && _matchesItemCreator(creators[i], itemCreators[i]);
|
||||
if(!matched) break;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _matchesItemCreator(creator, itemCreator) {
|
||||
// make sure last name matches
|
||||
var lowerLast = itemCreator.lastName.toLowerCase();
|
||||
if(lowerLast != creator.substr(-lowerLast.length).toLowerCase()) return false;
|
||||
|
||||
// make sure first name matches, if it exists
|
||||
if(creator.length > lowerLast.length) {
|
||||
var firstName = Zotero.Utilities.trim(creator.substr(0, creator.length-lowerLast.length));
|
||||
if(firstName.length) {
|
||||
// check to see whether the first name is all initials
|
||||
const initialRe = /^(?:[A-Z]\.? ?)+$/;
|
||||
var m = initialRe.exec(firstName);
|
||||
if(m) {
|
||||
var initials = firstName.replace(/[^A-Z]/g, "");
|
||||
var itemInitials = itemCreator.firstName.split(/ +/g)
|
||||
.map(name => name[0].toUpperCase())
|
||||
.join("");
|
||||
if(initials != itemInitials) return false;
|
||||
} else {
|
||||
// not all initials; verify that the first name matches
|
||||
var firstWord = firstName.substr(0, itemCreator.firstName).toLowerCase();
|
||||
var itemFirstWord = itemCreator.firstName.substr(0, itemCreator.firstName.indexOf(" ")).toLowerCase();
|
||||
if(firstWord != itemFirstWord) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** CITATIONS PAGE UI **/
|
||||
|
||||
/**
|
||||
* Called when citations page is shown to determine whether user can immediately advance.
|
||||
*/
|
||||
this.citationsPageShowing = function() {
|
||||
_refreshCanAdvance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the citations page is rewound. Removes all citations from the list, clears
|
||||
* globals, and returns to intro page.
|
||||
*/
|
||||
this.citationsPageRewound = function () {
|
||||
// skip back to intro page
|
||||
document.documentElement.currentPage = document.getElementById('intro-page');
|
||||
|
||||
this._rows = [
|
||||
{ id: 'unmapped', rtf: Zotero.getString('zotero.rtfScan.unmappedCitations.label'), collapsed: false },
|
||||
{ id: 'ambiguous', rtf: Zotero.getString('zotero.rtfScan.ambiguousCitations.label'), collapsed: false },
|
||||
{ id: 'mapped', rtf: Zotero.getString('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
|
||||
* ambiguous citation
|
||||
*/
|
||||
this.treeClick = function(event) {
|
||||
var tree = document.getElementById("tree");
|
||||
|
||||
// get clicked cell
|
||||
var { row, col } = tree.getCellAt(event.clientX, event.clientY);
|
||||
|
||||
// figure out which item this corresponds to
|
||||
var level = tree.view.getLevel(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the button to advance the wizard should be enabled or not based on whether
|
||||
* unmapped citations exist, and sets the status appropriately
|
||||
*/
|
||||
function _refreshCanAdvance() {
|
||||
var canAdvance = true;
|
||||
for (let i in citationItemIDs) {
|
||||
let itemList = citationItemIDs[i];
|
||||
if(itemList.length != 1) {
|
||||
canAdvance = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
document.documentElement.canAdvance = canAdvance;
|
||||
}
|
||||
|
||||
/** STYLE PAGE UI **/
|
||||
|
||||
/**
|
||||
* Called when style page is shown to add styles to listbox.
|
||||
*/
|
||||
this.stylePageShowing = async function() {
|
||||
await Zotero.Styles.init();
|
||||
Zotero_File_Interface_Bibliography.init({
|
||||
supportedNotes: ['footnotes', 'endnotes']
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when style page is hidden to save preferences.
|
||||
*/
|
||||
this.stylePageAdvanced = function() {
|
||||
Zotero.Prefs.set("export.lastStyle", document.getElementById("style-listbox").selectedItem.value);
|
||||
}
|
||||
|
||||
/** FORMAT PAGE UI **/
|
||||
|
||||
this.formatPageShowing = function() {
|
||||
// can't advance
|
||||
document.documentElement.canAdvance = false;
|
||||
|
||||
// wait a ms so that UI thread gets updated
|
||||
window.setTimeout(function() { _formatRTF() }, 1);
|
||||
}
|
||||
|
||||
function _formatRTF() {
|
||||
// load style and create ItemSet with all items
|
||||
var zStyle = Zotero.Styles.get(document.getElementById("style-listbox").value)
|
||||
var locale = document.getElementById("locale-menu").value;
|
||||
var cslEngine = zStyle.getCiteProc(locale, 'rtf');
|
||||
var isNote = zStyle.class == "note";
|
||||
|
||||
// create citations
|
||||
var k = 0;
|
||||
var cslCitations = [];
|
||||
var itemIDs = {};
|
||||
var shouldBeSubsequent = {};
|
||||
for(var i=0; i<citations.length; i++) {
|
||||
var citation = citations[i];
|
||||
var cslCitation = {"citationItems":[], "properties":{}};
|
||||
if(isNote) {
|
||||
cslCitation.properties.noteIndex = i;
|
||||
}
|
||||
|
||||
// create citation items
|
||||
for(var j=0; j<citation.citationStrings.length; j++) {
|
||||
var citationItem = {};
|
||||
citationItem.id = citationItemIDs[citation.citationStrings[j]][0];
|
||||
itemIDs[citationItem.id] = true;
|
||||
citationItem.locator = citation.pages[j];
|
||||
citationItem.label = "page";
|
||||
citationItem["suppress-author"] = citation.suppressAuthor && !isNote;
|
||||
cslCitation.citationItems.push(citationItem);
|
||||
}
|
||||
|
||||
cslCitations.push(cslCitation);
|
||||
}
|
||||
Zotero.debug(cslCitations);
|
||||
|
||||
itemIDs = Object.keys(itemIDs);
|
||||
Zotero.debug(itemIDs);
|
||||
|
||||
// prepare the list of rendered citations
|
||||
var citationResults = cslEngine.rebuildProcessorState(cslCitations, "rtf");
|
||||
|
||||
// format citations
|
||||
var contentArray = [];
|
||||
var lastEnd = 0;
|
||||
for(var i=0; i<citations.length; i++) {
|
||||
var citation = citationResults[i][2];
|
||||
Zotero.debug("Formatted "+citation);
|
||||
|
||||
// if using notes, we might have to move the note after the punctuation
|
||||
if(isNote && citations[i].start != 0 && contents[citations[i].start-1] == " ") {
|
||||
contentArray.push(contents.substring(lastEnd, citations[i].start-1));
|
||||
} else {
|
||||
contentArray.push(contents.substring(lastEnd, citations[i].start));
|
||||
}
|
||||
|
||||
lastEnd = citations[i].end;
|
||||
if(isNote && citations[i].end < contents.length && ".,!?".indexOf(contents[citations[i].end]) !== -1) {
|
||||
contentArray.push(contents[citations[i].end]);
|
||||
lastEnd++;
|
||||
}
|
||||
|
||||
if(isNote) {
|
||||
if(document.getElementById("displayAs").selectedIndex) { // endnotes
|
||||
contentArray.push("{\\super\\chftn}\\ftnbj {\\footnote\\ftnalt {\\super\\chftn } "+citation+"}");
|
||||
} else { // footnotes
|
||||
contentArray.push("{\\super\\chftn}\\ftnbj {\\footnote {\\super\\chftn } "+citation+"}");
|
||||
}
|
||||
} else {
|
||||
contentArray.push(citation);
|
||||
}
|
||||
}
|
||||
contentArray.push(contents.substring(lastEnd));
|
||||
contents = contentArray.join("");
|
||||
|
||||
// add bibliography
|
||||
if(zStyle.hasBibliography) {
|
||||
var bibliography = Zotero.Cite.makeFormattedBibliography(cslEngine, "rtf");
|
||||
bibliography = bibliography.substring(5, bibliography.length-1);
|
||||
// fix line breaks
|
||||
var linebreak = "\r\n";
|
||||
if(contents.indexOf("\r\n") == -1) {
|
||||
bibliography = bibliography.replace("\r\n", "\n", "g");
|
||||
linebreak = "\n";
|
||||
}
|
||||
|
||||
if(contents.indexOf(BIBLIOGRAPHY_PLACEHOLDER) !== -1) {
|
||||
contents = contents.replace(BIBLIOGRAPHY_PLACEHOLDER, bibliography);
|
||||
} else {
|
||||
// add two newlines before bibliography
|
||||
bibliography = linebreak+"\\"+linebreak+"\\"+linebreak+bibliography;
|
||||
|
||||
// add bibliography automatically inside last set of brackets closed
|
||||
const bracketRe = /^\{+/;
|
||||
var m = bracketRe.exec(contents);
|
||||
if(m) {
|
||||
var closeBracketRe = new RegExp("(\\}{"+m[0].length+"}\\s*)$");
|
||||
contents = contents.replace(closeBracketRe, bibliography+"$1");
|
||||
} else {
|
||||
contents += bibliography;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cslEngine.free();
|
||||
|
||||
Zotero.File.putContents(outputFile, contents);
|
||||
|
||||
// save locale
|
||||
if (!document.getElementById("locale-menu").disabled) {
|
||||
Zotero.Prefs.set("export.lastLocale", locale);
|
||||
}
|
||||
|
||||
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.createElement('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.createElement('span');
|
||||
twisty.classList.add("spacer-twisty");
|
||||
}
|
||||
|
||||
let textSpan = document.createElement('span');
|
||||
textSpan.className = "cell-text";
|
||||
textSpan.innerText = row[column.dataKey] || "";
|
||||
|
||||
let span = document.createElement('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.createElement('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.createElement('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 = (
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this._rows.length}
|
||||
id="rtfScan-table"
|
||||
ref={ref => tree = ref}
|
||||
renderItem={this._renderItem}
|
||||
showHeader={true}
|
||||
columns={columns}
|
||||
disableFontSizeScaling={true}
|
||||
/>
|
||||
);
|
||||
return new Promise(resolve => ReactDOM.render(elem, domEl, resolve));
|
||||
};
|
||||
}
|
72
chrome/content/zotero/rtfScan.xhtml
Normal file
72
chrome/content/zotero/rtfScan.xhtml
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?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"
|
||||
onload="Zotero_RTFScan.init()"
|
||||
>
|
||||
<linkset>
|
||||
<html:link rel="localization" href="zotero.ftl" />
|
||||
</linkset>
|
||||
|
||||
<script src="chrome://global/content/customElements.js" />
|
||||
|
||||
<wizard id="rtfscan-wizard" class="rtfscan-wizard" width="700" height="550" data-l10n-id="rtfScan-wizard">
|
||||
<wizardpage pageid="page-start" data-l10n-id="rtfScan-intro-page">
|
||||
<div>
|
||||
<span class="page-start-1" data-l10n-id="rtfScan-introPage-description" />
|
||||
<span class="example">{Smith, 2009}</span>
|
||||
<span class="example">Smith {2009}</span>
|
||||
<span class="example">{Smith et al., 2009}</span>
|
||||
<span class="example">{John Smith, 2009}</span>
|
||||
<span class="example">{Smith, 2009, 10-14}</span>
|
||||
<span class="example">{Smith, "Title," 2009}</span>
|
||||
<span class="example">{Jones, 2005; Smith, 2009}</span>
|
||||
<span class="page-start-2" data-l10n-id="rtfScan-introPage-description2" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="choose-input-file" class="file-input-label" data-l10n-id="rtfScan-input-file" />
|
||||
<div class="file-input-container">
|
||||
<html:input type="text" data-l10n-id="zotero-file-none-selected" id="input-path" readonly="true" />
|
||||
<button id="choose-input-file" data-l10n-id="zotero-file-choose" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="choose-output-file" class="file-input-label" data-l10n-id="rtfScan-output-file" />
|
||||
<div class="file-input-container">
|
||||
<html:input type="text" data-l10n-id="zotero-file-none-selected" id="output-path" readonly="true" />
|
||||
<button id="choose-output-file" data-l10n-id="zotero-file-choose" />
|
||||
</div>
|
||||
</div>
|
||||
</wizardpage>
|
||||
<wizardpage pageid="scan-page" data-l10n-id="rtfScan-scan-page" >
|
||||
<p data-l10n-id="rtfScan-scanPage-description" />
|
||||
<html:progress id="scan-progress" />
|
||||
</wizardpage>
|
||||
<wizardpage class="citations-page" pageid="citations-page" data-l10n-id="rtfScan-citations-page">
|
||||
<p class="citations-page-description" data-l10n-id="rtfScan-citations-page-description" />
|
||||
<div class="table-container">
|
||||
<div id="tree" />
|
||||
</div>
|
||||
</wizardpage>
|
||||
<wizardpage pageid="style-page" data-l10n-id="rtfScan-style-page">
|
||||
<div class="style-selector-container">
|
||||
<style-configurator id="style-configurator" />
|
||||
</div>
|
||||
</wizardpage>
|
||||
<wizardpage pageid="format-page" data-l10n-id="rtfScan-format-page">
|
||||
<p data-l10n-id="rtfScan-format-page-description" />
|
||||
<html:progress id="format-progress" />
|
||||
</wizardpage>
|
||||
<wizardpage pageid="complete-page" data-l10n-id="rtfScan-complete-page">
|
||||
<p data-l10n-id="rtfScan-complete-page-description" />
|
||||
</wizardpage>
|
||||
</wizard>
|
||||
<script src="include.js" />
|
||||
<script src="fileInterface.js" />
|
||||
<script src="rtfScan.js" />
|
||||
</window>
|
|
@ -1,95 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<?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"/>
|
||||
<script src="bibliography.js"/>
|
||||
<script src="rtfScan.js"/>
|
||||
|
||||
<wizardpage id="intro-page" label="&zotero.rtfScan.introPage.label;"
|
||||
onpageshow="Zotero_RTFScan.introPageShowing()"
|
||||
onpageadvanced="Zotero_RTFScan.introPageAdvanced()">
|
||||
<vbox>
|
||||
<description width="700">&zotero.rtfScan.introPage.description;</description>
|
||||
<label value="{Smith, 2009}"/>
|
||||
<label value="Smith {2009}"/>
|
||||
<label value="{Smith et al., 2009}"/>
|
||||
<label value="{John Smith, 2009}"/>
|
||||
<label value="{Smith, 2009, 10-14}"/>
|
||||
<label value="{Smith, "Title," 2009}"/>
|
||||
<label value="{Jones, 2005; Smith, 2009}"/>
|
||||
<description width="700" style="padding-top:1em">&zotero.rtfScan.introPage.description2;</description>
|
||||
</vbox>
|
||||
<groupbox>
|
||||
<caption label="&zotero.rtfScan.inputFile.label;"/>
|
||||
<hbox align="center">
|
||||
<textbox value="&zotero.file.noneSelected.label;" id="input-path" flex="1" readonly="true"/>
|
||||
<button id="choose-input-file" label="&zotero.file.choose.label;" onclick="Zotero_RTFScan.chooseInputFile()"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
<groupbox>
|
||||
<caption label="&zotero.rtfScan.outputFile.label;"/>
|
||||
<hbox align="center">
|
||||
<textbox value="&zotero.file.noneSelected.label;" id="output-path" flex="1" readonly="true"/>
|
||||
<button id="choose-output-file" label="&zotero.file.choose.label;" onclick="Zotero_RTFScan.chooseOutputFile()"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage id="scan-page" label="&zotero.rtfScan.scanPage.label;"
|
||||
onpageshow="Zotero_RTFScan.scanPageShowing()">
|
||||
<description width="700">&zotero.rtfScan.scanPage.description;</description>
|
||||
<progressmeter id="progress-indicator" mode="undetermined"/>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage id="citations-page" label="&zotero.rtfScan.citationsPage.label;"
|
||||
onpageshow="Zotero_RTFScan.citationsPageShowing()"
|
||||
onpagerewound="return Zotero_RTFScan.citationsPageRewound();">
|
||||
<description width="700">&zotero.rtfScan.citationsPage.description;</description>
|
||||
|
||||
<hbox class="virtualized-table-container" flex="1" height="500">
|
||||
<html:div id="tree"/>
|
||||
</hbox>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage id="style-page" label="&zotero.rtfScan.stylePage.label;"
|
||||
onpageadvanced="Zotero_RTFScan.stylePageAdvanced()"
|
||||
onpageshow="Zotero_RTFScan.stylePageShowing()">
|
||||
<groupbox flex="1">
|
||||
<caption label="&zotero.bibliography.style.label;"/>
|
||||
<listbox id="style-listbox" onselect="Zotero_File_Interface_Bibliography.styleChanged()" flex="1"/>
|
||||
</groupbox>
|
||||
<groupbox>
|
||||
<hbox align="center">
|
||||
<caption label="&zotero.bibliography.locale.label;"/>
|
||||
<menulist id="locale-menu" oncommand="Zotero_File_Interface_Bibliography.localeChanged(this.value)"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
<groupbox id="displayAs-groupbox">
|
||||
<caption label="&zotero.integration.prefs.displayAs.label;"/>
|
||||
<radiogroup id="displayAs" orient="horizontal">
|
||||
<radio id="footnotes" label="&zotero.integration.prefs.footnotes.label;" selected="true"/>
|
||||
<radio id="endnotes" label="&zotero.integration.prefs.endnotes.label;"/>
|
||||
</radiogroup>
|
||||
</groupbox>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage id="format-page" label="&zotero.rtfScan.formatPage.label;"
|
||||
onpageshow="Zotero_RTFScan.formatPageShowing()">
|
||||
<description width="700">&zotero.rtfScan.formatPage.description;</description>
|
||||
<progressmeter id="progress-indicator" mode="undetermined"/>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage id="complete-page" label="&zotero.rtfScan.completePage.label;">
|
||||
<description width="700">&zotero.rtfScan.completePage.description;</description>
|
||||
</wizardpage>
|
||||
</wizard>
|
|
@ -60,8 +60,13 @@
|
|||
if (Zotero.isMac) {
|
||||
Services.scriptloader.loadSubScript("chrome://global/content/macWindowMenu.js", this);
|
||||
}
|
||||
if(window.arguments?.[0] === 'mendeleyImport') {
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/authViewer.js", this);
|
||||
} else {
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/standalone/basicViewer.js", this);
|
||||
}
|
||||
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/standalone/basicViewer.js", this);
|
||||
</script>
|
||||
|
||||
<commandset id="mainCommandSet">
|
||||
|
@ -165,6 +170,13 @@
|
|||
</toolbox>
|
||||
|
||||
<hbox flex="1" id="browser">
|
||||
<vbox id="appcontent" flex="1"/>
|
||||
<vbox id="appcontent" flex="1">
|
||||
<browser
|
||||
type="content"
|
||||
flex="1"
|
||||
remote="false"
|
||||
disableglobalhistory="true"
|
||||
maychangeremoteness="true"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</window>
|
||||
|
|
|
@ -33,7 +33,8 @@ Zotero.Relations = new function () {
|
|||
this._namespaces = {
|
||||
dc: 'http://purl.org/dc/elements/1.1/',
|
||||
owl: 'http://www.w3.org/2002/07/owl#',
|
||||
mendeleyDB: 'http://zotero.org/namespaces/mendeleyDB#'
|
||||
mendeleyDB: 'http://zotero.org/namespaces/mendeleyDB#',
|
||||
zotero: 'http://zotero.org/namespaces/zotero'
|
||||
};
|
||||
|
||||
var _types = ['collection', 'item'];
|
||||
|
|
|
@ -4752,7 +4752,7 @@ var ZoteroPane = new function()
|
|||
}
|
||||
}
|
||||
io.hasRights = allItemsHaveRights ? 'all' : (noItemsHaveRights ? 'none' : 'some');
|
||||
window.openDialog('chrome://zotero/content/publicationsDialog.xul','','chrome,modal', io);
|
||||
window.openDialog('chrome://zotero/content/publicationsDialog.xhtml','','chrome,modal', io);
|
||||
return io.license ? io : false;
|
||||
};
|
||||
|
||||
|
|
|
@ -702,7 +702,7 @@
|
|||
oncommand="ZoteroPane_Local.copySelectedItemsToClipboard();"
|
||||
disabled="true"/>
|
||||
<command id="cmd_zotero_createTimeline" oncommand="Zotero_Timeline_Interface.loadTimeline();"/>
|
||||
<command id="cmd_zotero_rtfScan" oncommand="window.openDialog('chrome://zotero/content/rtfScan.xul', 'rtfScan', 'chrome,centerscreen')"/>
|
||||
<command id="cmd_zotero_rtfScan" oncommand="window.openDialog('chrome://zotero/content/rtfScan.xhtml', 'rtfScan', 'chrome,centerscreen')"/>
|
||||
<command id="cmd_zotero_newCollection" oncommand="ZoteroPane_Local.newCollection()"/>
|
||||
<command id="cmd_zotero_newFeed_fromURL" oncommand="ZoteroPane_Local.newFeedFromURL()"/>
|
||||
<command id="cmd_zotero_newSavedSearch" oncommand="ZoteroPane_Local.newSearch()"/>
|
||||
|
|
|
@ -180,6 +180,8 @@
|
|||
<!ENTITY zotero.import "Import">
|
||||
<!ENTITY zotero.import.whereToImportFrom "Where do you want to import from?">
|
||||
<!ENTITY zotero.import.source.file "A file (BibTeX, RIS, Zotero RDF, etc.)">
|
||||
<!ENTITY zotero.import.source.folder "A folder of PDFs or other files">
|
||||
<!ENTITY zotero.import.onlineImport "online import">
|
||||
<!ENTITY zotero.import.importing "Importing…">
|
||||
<!ENTITY zotero.import.database "Database">
|
||||
<!ENTITY zotero.import.lastModified "Last Modified">
|
||||
|
|
178
chrome/locale/en-US/zotero/zotero.ftl
Normal file
178
chrome/locale/en-US/zotero/zotero.ftl
Normal file
|
@ -0,0 +1,178 @@
|
|||
-app-name = Zotero
|
||||
|
||||
import-wizard =
|
||||
.title = "Import"
|
||||
|
||||
import-where-from = Where do you want to import from?
|
||||
import-online-intro-title = Introduction
|
||||
|
||||
import-source-file =
|
||||
.label = A file (BibTeX, RIS, Zotero RDF, etc.)
|
||||
|
||||
import-source-folder =
|
||||
.label = A folder of PDFs or other files
|
||||
|
||||
import-source-online =
|
||||
.label = { $targetApp } online import
|
||||
|
||||
import-options = Options
|
||||
import-importing = Importing…
|
||||
|
||||
import-create-collection =
|
||||
.label = Place imported collections and items into new collection
|
||||
|
||||
import-recreate-structure =
|
||||
.label = Recreate folder structure as collections
|
||||
|
||||
import-fileTypes-header = File Types to Import:
|
||||
|
||||
import-fileTypes-pdf =
|
||||
.label = PDFs
|
||||
|
||||
import-fileTypes-other =
|
||||
.placeholder = Other files by pattern, comma-separated (e.g., *.jpg,*.png)
|
||||
|
||||
import-file-handling = File Handling
|
||||
import-file-handling-store =
|
||||
.label = Copy files to the { -app-name } storage folder
|
||||
import-file-handling-link =
|
||||
.label = Link to files in original location
|
||||
import-fileHandling-description = Linked files cannot be synced by { -app-name }.
|
||||
|
||||
general-error = Error
|
||||
file-interface-import-error = An error occurred while trying to import the selected file. Please ensure that the file is valid and try again.
|
||||
file-interface-import-complete = Import Complete
|
||||
file-interface-items-were-imported = { $numItems ->
|
||||
[one] item was imported
|
||||
*[other] { $numItems } items were imported
|
||||
}
|
||||
|
||||
import-mendeley-encrypted = The selected Mendeley database cannot be read, likely because it is encrypted.
|
||||
See <a data-l10n-name="mendeley-import-kb">How do I import a Mendeley library into Zotero?</a> for more information.
|
||||
|
||||
file-interface-import-error-translator = An error occurred importing the selected file with “{ $translator }”. Please ensure that the file is valid and try again.
|
||||
|
||||
# Variables:
|
||||
# $targetAppOnline (String)
|
||||
# $targetApp (String)
|
||||
import-online-intro=In the next step you will be asked to log in to { $targetAppOnline } and grant { -app-name } access. This is necessary to import your { $targetApp } library into { -app-name }.
|
||||
import-online-intro2={ -app-name } will never see or store your { $targetApp } password.
|
||||
|
||||
report-error =
|
||||
.label = Report Error…
|
||||
|
||||
rtfScan-wizard =
|
||||
.title = RTF Scan
|
||||
|
||||
rtfScan-introPage-description = Zotero can automatically extract and reformat citations and insert a bibliography into RTF files. To get started, choose an RTF file below.
|
||||
rtfScan-introPage-description2 = To get started, select an RTF input file and an output file below:
|
||||
|
||||
rtfScan-input-file = Input File
|
||||
rtfScan-output-file = Output File
|
||||
|
||||
zotero-file-none-selected =
|
||||
.value = No file selected
|
||||
|
||||
zotero-file-choose =
|
||||
.label = Choose File…
|
||||
|
||||
rtfScan-intro-page =
|
||||
.label = Introduction
|
||||
|
||||
rtfScan-scan-page =
|
||||
.label = Scanning for Citations
|
||||
|
||||
rtfScan-scanPage-description = Zotero is scanning your document for citations. Please be patient.
|
||||
|
||||
rtfScan-citations-page =
|
||||
.label = Verify Cited Items
|
||||
|
||||
rtfScan-citations-page-description = Please review the list of recognized citations below to ensure that Zotero has selected the corresponding items correctly. Any unmapped or ambiguous citations must be resolved before proceeding to the next step.
|
||||
|
||||
rtfScan-style-page =
|
||||
.label = Document Formatting
|
||||
|
||||
rtfScan-format-page =
|
||||
.label = Formatting Citations
|
||||
|
||||
rtfScan-format-page-description = Zotero is processing and formatting your RTF file. Please be patient.
|
||||
|
||||
rtfScan-complete-page =
|
||||
.label = RTF Scan Complete
|
||||
|
||||
rtfScan-complete-page-description = Your document has now been scanned and processed. Please ensure that it is formatted correctly.
|
||||
|
||||
|
||||
bibliography-style-label = Citation Style:
|
||||
bibliography-locale-label = Language:
|
||||
|
||||
integration-prefs-displayAs-label = Display Citations As:
|
||||
integration-prefs-footnotes =
|
||||
.label = Footnotes
|
||||
integration-prefs-endnotes =
|
||||
.label = Endnotes
|
||||
|
||||
|
||||
publications-intro-page =
|
||||
.label = My Publications
|
||||
|
||||
publications-intro = Items you add to My Publications will be shown on your profile page on zotero.org. If you choose to include attached files, they will be made publicly available under the license you specify. Only add work you yourself have created, and only include files if you have the rights to distribute them and wish to do so.
|
||||
publications-include-checkbox-files =
|
||||
.label = Include files
|
||||
publications-include-checkbox-notes =
|
||||
.label = Include notes
|
||||
|
||||
publications-include-adjust-at-any-time = You can adjust what to show at any time from the My Publications collection.
|
||||
publications-intro-authorship =
|
||||
.label = I created this work.
|
||||
publications-intro-authorship-files =
|
||||
.label = I created this work and have the rights to distribute included files.
|
||||
|
||||
publications-sharing-page =
|
||||
.label = Choose how your work may be shared
|
||||
|
||||
publications-sharing-keep-rights-field =
|
||||
.label = Keep the existing Rights field
|
||||
publications-sharing-keep-rights-field-where-available =
|
||||
.label = Keep the existing Rights field where available
|
||||
publications-sharing-text = You can reserve all rights to your work, license it under a Creative Commons license, or dedicate it to the public domain. In all cases, the work will be made publicly available via zotero.org.
|
||||
publications-sharing-prompt = Would you like to allow your work to be shared by others?
|
||||
publications-sharing-reserved =
|
||||
.label = No, only publish my work on zotero.org
|
||||
publications-sharing-cc =
|
||||
.label = Yes, under a Creative Commons license
|
||||
publications-sharing-cc0 =
|
||||
.label = Yes, and place my work in the public domain
|
||||
|
||||
publications-license-page =
|
||||
.label = Choose a Creative Commons license
|
||||
publications-choose-license-text = A Creative Commons license allows others to copy and redistribute your work as long as they give appropriate credit, provide a link to the license, and indicate if changes were made. Additional conditions can be specified below.
|
||||
publications-choose-license-adaptations-prompt = Allow adaptations of your work to be shared?
|
||||
|
||||
publications-choose-license-yes =
|
||||
.label = Yes
|
||||
.accesskey = Y
|
||||
publications-choose-license-no =
|
||||
.label = No
|
||||
.accesskey = N
|
||||
publications-choose-license-sharealike =
|
||||
.label = Yes, as long as others share alike
|
||||
.accesskey = S
|
||||
|
||||
publications-choose-license-commercial-prompt = Allow commercial uses of your work?
|
||||
publications-buttons-add-to-my-publications =
|
||||
.label = Add to My Publications
|
||||
publications-buttons-next-sharing =
|
||||
.label = Next: Sharing
|
||||
publications-buttons-next-choose-license =
|
||||
.label = Choose a License
|
||||
|
||||
licenses-cc-0 = CC0 1.0 Universal Public Domain Dedication
|
||||
licenses-cc-by = Creative Commons Attribution 4.0 International License
|
||||
licenses-cc-by-nd = Creative Commons Attribution-NoDerivatives 4.0 International License
|
||||
licenses-cc-by-sa = Creative Commons Attribution-ShareAlike 4.0 International License
|
||||
licenses-cc-by-nc = Creative Commons Attribution-NonCommercial 4.0 International License
|
||||
licenses-cc-by-nc-nd = Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License
|
||||
licenses-cc-by-nc-sa = Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
|
||||
licenses-cc-more-info = Be sure you have read the Creative Commons <a data-l10n-name="license-considerations">Considerations for licensors</a> before placing your work under a CC license. Note that the license you apply cannot be revoked, even if you later choose different terms or cease publishing the work.
|
||||
licenses-cc0-more-info = Be sure you have read the Creative Commons <a data-l10n-name="license-considerations">CC0 FAQ</a> before applying CC0 to your work. Please note that dedicating your work to the public domain is irreversible, even if you later choose different terms or cease publishing the work.
|
|
@ -808,14 +808,6 @@ fileInterface.exportError = An error occurred while trying to export the selecte
|
|||
fileInterface.importOPML = Import Feeds from OPML
|
||||
fileInterface.OPMLFeedFilter = OPML Feed List
|
||||
|
||||
import.onlineImport = online import
|
||||
import.localImport = local import
|
||||
import.fileHandling.store = Copy files to the %S storage folder
|
||||
import.fileHandling.link = Link to files in original location
|
||||
import.fileHandling.description = Linked files cannot be synced by %S.
|
||||
import.online.intro = In the next step you will be asked to log in to %2$S and grant %1$S access. This is necessary to import your %3$S library into %1$S.
|
||||
import.online.intro2 = %1$S will never see or store your %2$S password.
|
||||
|
||||
quickCopy.copyAs = Copy as %S
|
||||
|
||||
quickSearch.mode.titleCreatorYear = Title, Creator, Year
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
.wizard-header-label {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Start */
|
||||
wizard[currentpageid="page-start"] .wizard-header-label {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
wizard[currentpageid="page-start"] .wizard-page-box {
|
||||
margin-top: -2px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
radiogroup {
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
radio {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
/* File list */
|
||||
wizard[currentpageid="page-file-list"] .wizard-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-options-header {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
#file-handling-options {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
#file-handling-options > label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#file-handling-options radiogroup {
|
||||
font-size: 13px;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
#file-handling-options description {
|
||||
margin-top: .6em;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
listbox, #result-description, #result-description-html {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#result-description-html {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#mendeley-online-description, #mendeley-online-description2 {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#mendeley-online-description2 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#result-description-html a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
button, checkbox {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#result-report-error {
|
||||
margin-top: 13px;
|
||||
margin-left: 0;
|
||||
}
|
|
@ -44,6 +44,7 @@
|
|||
"colors": "^1.4.0",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.4",
|
||||
"fs-extra": "^3.0.1",
|
||||
"globby": "^6.1.0",
|
||||
"jspath": "^0.4.0",
|
||||
|
|
|
@ -33,8 +33,9 @@ var ZOTERO_CONFIG = {
|
|||
PLUGINS_URL: 'https://www.zotero.org/support/plugins',
|
||||
};
|
||||
|
||||
if (typeof process === 'object' && process + '' === '[object process]'){
|
||||
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
||||
module.exports = ZOTERO_CONFIG;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
var EXPORTED_SYMBOLS = ["ZOTERO_CONFIG"];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,14 @@ const browserifyConfigs = [
|
|||
config: {
|
||||
standalone: 'chaiAsPromised'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
src: 'node_modules/multimatch/index.js',
|
||||
dest: 'resource/multimatch.js',
|
||||
config: {
|
||||
standalone: 'multimatch'
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
// exclude mask used for js, copy, symlink and sass tasks
|
||||
|
|
|
@ -31,11 +31,14 @@
|
|||
@import "components/editable";
|
||||
@import "components/exportOptions";
|
||||
@import "components/icons";
|
||||
@import "components/import-wizard";
|
||||
@import "components/item-tree";
|
||||
@import "components/longTagFixer";
|
||||
@import "components/mainWindow";
|
||||
@import "components/notesList";
|
||||
@import "components/progressMeter";
|
||||
@import "components/publications-dialog.scss";
|
||||
@import "components/rtfScan.scss";
|
||||
@import "components/search";
|
||||
@import "components/syncButtonTooltip";
|
||||
@import "components/tabBar";
|
||||
|
|
142
scss/components/_import-wizard.scss
Normal file
142
scss/components/_import-wizard.scss
Normal file
|
@ -0,0 +1,142 @@
|
|||
.import-wizard {
|
||||
font-size: 12px;
|
||||
|
||||
radiogroup {
|
||||
radio {
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
wizardpage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> div {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline;
|
||||
color: -moz-nativehyperlinktext;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
outline:none;
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
}
|
||||
|
||||
checkbox {
|
||||
margin-left: 4px; // indent required for correct focus ring rendering
|
||||
}
|
||||
|
||||
button {
|
||||
font: menu;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin-bottom: 6px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#other-files {
|
||||
margin-left: -1px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
|
||||
#page-start {
|
||||
radiogroup {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
#page-options-folder-import {
|
||||
fieldset {
|
||||
display: block;
|
||||
border: none;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
#recreate-structure {
|
||||
margin-top: .25em;
|
||||
}
|
||||
|
||||
.import-file-types-header {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.page-options-file-type {
|
||||
display: flex;
|
||||
margin-top: .25em;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#page-options-file-handling {
|
||||
padding-top: 1.5em;
|
||||
|
||||
.radioset {
|
||||
// font-size: 13px;
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.page-options-file-handling-description {
|
||||
margin-top: .6em;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#page-done-error {
|
||||
margin-top: 2em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.mendeley-online-intro {
|
||||
// font-size: 13px;
|
||||
|
||||
& +.mendeley-online-intro {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
#import-progress {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#page-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// TODO: deduplicate with rtfscan
|
||||
.table-container {
|
||||
display: flex;
|
||||
height: 0;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
margin-top: 1.5em;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
background-color: -moz-field;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.virtualized-table-body {
|
||||
display: flex;
|
||||
|
||||
.windowed-list {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
scss/components/_publications-dialog.scss
Normal file
46
scss/components/_publications-dialog.scss
Normal file
|
@ -0,0 +1,46 @@
|
|||
.publications-dialog-wizard {
|
||||
font-size: 12px;
|
||||
|
||||
div {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 1em 0 0;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h2 + radiogroup {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p.description {
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
checkbox {
|
||||
margin-left: 4px; // indent required for correct focus ring rendering
|
||||
}
|
||||
|
||||
radiogroup {
|
||||
font-size: 12px;
|
||||
margin-top: 1em;
|
||||
|
||||
radio:first-child {
|
||||
margin-top: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
wizardpage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#include-files,
|
||||
#include-notes {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
85
scss/components/_rtfScan.scss
Normal file
85
scss/components/_rtfScan.scss
Normal file
|
@ -0,0 +1,85 @@
|
|||
.rtfscan-wizard {
|
||||
wizardpage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> div {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.example, .page-start-1, .page-start-2, .file-input-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.example {
|
||||
line-height: 1.5em
|
||||
}
|
||||
|
||||
.page-start-1 {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.page-start-2 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.file-input-label {
|
||||
margin: 1em $space-xs $space-min;
|
||||
}
|
||||
|
||||
.file-input-container {
|
||||
background-color: $input-group-background-color;
|
||||
border-radius: $border-radius-base;
|
||||
border: 1px solid $input-group-border-color;
|
||||
display: flex;
|
||||
margin: auto $space-min auto;
|
||||
padding: .5em;
|
||||
|
||||
> input {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
> button {
|
||||
flex: 0 1 auto;
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.citations-page-description {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.citations-page > .wizard-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
display: flex;
|
||||
height: 0;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
margin-top: 1.5em;
|
||||
|
||||
>div {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
background-color: -moz-field;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.virtualized-table-body {
|
||||
display: flex;
|
||||
|
||||
.windowed-list {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
scss/elements/license-info.scss
Normal file
50
scss/elements/license-info.scss
Normal file
|
@ -0,0 +1,50 @@
|
|||
@import "../abstracts/variables";
|
||||
@import "../abstracts/functions";
|
||||
@import "../abstracts/mixins";
|
||||
@import "../abstracts/placeholders";
|
||||
@import "../abstracts/utilities";
|
||||
@import "../themes/light";
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.license-info {
|
||||
display: flex;
|
||||
padding: var(--license-info-padding, 0 20px);
|
||||
margin: var(--license-info-margin, 1em 0 0 0);
|
||||
|
||||
.license-icon {
|
||||
flex: 0 1 auto;
|
||||
|
||||
> img {
|
||||
max-width: var(--license-icon-max-width, 88px);
|
||||
}
|
||||
}
|
||||
|
||||
.license-name {
|
||||
margin-left: var(--license-info-name-margin-left, 12px);
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.license-more-info {
|
||||
margin: var(--license-more-info-margin, 1.5em 0 0 0);
|
||||
font-size: var(--license-more-info-font-size, 11px);
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline;
|
||||
color: var(--license-info-link-color, -moz-nativehyperlinktext);
|
||||
text-decoration: var(--license-info-link-decoration, none);
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
text-decoration: var(--license-info-link-decoration-hover, $link-hover-decoration);
|
||||
}
|
||||
}
|
58
scss/elements/style-configurator.scss
Normal file
58
scss/elements/style-configurator.scss
Normal file
|
@ -0,0 +1,58 @@
|
|||
@import "../abstracts/variables";
|
||||
@import "../abstracts/functions";
|
||||
@import "../abstracts/mixins";
|
||||
@import "../abstracts/placeholders";
|
||||
@import "../abstracts/utilities";
|
||||
@import "../themes/light";
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
richlistbox {
|
||||
padding: var(--style-configurator-richlistbox-padding, 2px);
|
||||
max-height: var(--style-configurator-richlistitem-max-height, 260px);
|
||||
overflow: var(--style-configurator-richlistitem-overflow, auto scroll);
|
||||
|
||||
richlistitem {
|
||||
line-height: var(--style-configurator-richlistitem-line-height, 1.5em);
|
||||
}
|
||||
|
||||
@media (-moz-platform: macos) {
|
||||
&:not(:focus) richlistitem[selected="true"] {
|
||||
background-color: -moz-mac-secondaryhighlight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#locale-selector-wrapper,
|
||||
#style-selector-wrapper,
|
||||
#display-as-wrapper {
|
||||
background-color: var(--style-configurator-field-wrapper-background-color, $input-group-background-color);
|
||||
border-radius: var(--style-configurator-field-wrapper-border-radius, $border-radius-base);
|
||||
border: var(--style-configurator-field-wrapper-border, 1px solid $input-group-border-color);
|
||||
margin: var(--style-configurator-field-margin, 1.5em 0 0 0);
|
||||
padding: var(--style-configurator-field-padding, $space-xs);
|
||||
}
|
||||
|
||||
label[for="style-selector"] {
|
||||
margin: var(--style-configurator-label-margin, 1.5em 0 0 0);
|
||||
font-size: var(--style-configurator-label-font-size, 13px);
|
||||
}
|
||||
|
||||
#style-selector-wrapper {
|
||||
margin: var(--style-configurator-style-field-margin, 0);
|
||||
}
|
||||
|
||||
#locale-selector-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#display-as-wrapper {
|
||||
radiogroup {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
|
@ -156,6 +156,8 @@ $input-bg: $body-bg;
|
|||
$input-border-color: $shade-3;
|
||||
$input-focus-color: $secondary;
|
||||
$placeholder-color: $shade-5;
|
||||
$input-group-background-color: #e5e5e5;
|
||||
$input-group-border-color: darken($input-group-background-color, 5%);
|
||||
|
||||
// Editable
|
||||
$editable-color: $text-color;
|
||||
|
|
140
test/tests/folderImportTest.js
Normal file
140
test/tests/folderImportTest.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
/* global Zotero_Import_Folder: false */
|
||||
|
||||
describe('Zotero_Import_Folder', function () {
|
||||
var tmpDir;
|
||||
const uc = name => 'Zotero_Import_Folder_' + name;
|
||||
|
||||
before(async () => {
|
||||
tmpDir = await getTempDirectory();
|
||||
|
||||
await OS.File.makeDir(OS.Path.join(tmpDir, uc('dir1')));
|
||||
await OS.File.makeDir(OS.Path.join(tmpDir, uc('dir1'), uc('subdir1')));
|
||||
await OS.File.makeDir(OS.Path.join(tmpDir, uc('dir2')));
|
||||
|
||||
await OS.File.copy(
|
||||
OS.Path.join(getTestDataDirectory().path, 'recognizePDF_test_title.pdf'),
|
||||
OS.Path.join(tmpDir, 'recognizePDF_test_title.pdf')
|
||||
);
|
||||
await OS.File.copy(
|
||||
OS.Path.join(getTestDataDirectory().path, 'recognizePDF_test_title.pdf'),
|
||||
OS.Path.join(tmpDir, uc('dir1'), 'recognizePDF_test_title.pdf')
|
||||
);
|
||||
await OS.File.copy(
|
||||
OS.Path.join(getTestDataDirectory().path, 'recognizePDF_test_arXiv.pdf'),
|
||||
OS.Path.join(tmpDir, uc('dir1'), uc('subdir1'), 'recognizePDF_test_arXiv.pdf')
|
||||
);
|
||||
await OS.File.copy(
|
||||
OS.Path.join(getTestDataDirectory().path, 'recognizePDF_test_title.pdf'),
|
||||
OS.Path.join(tmpDir, uc('dir2'), 'recognizePDF_test_title.pdf')
|
||||
);
|
||||
await OS.File.copy(
|
||||
OS.Path.join(getTestDataDirectory().path, 'test.png'),
|
||||
OS.Path.join(tmpDir, uc('dir2'), 'test.png')
|
||||
);
|
||||
await OS.File.copy(
|
||||
OS.Path.join(getTestDataDirectory().path, 'test.html'),
|
||||
OS.Path.join(tmpDir, uc('dir2'), 'test.html')
|
||||
);
|
||||
await OS.File.copy(
|
||||
OS.Path.join(getTestDataDirectory().path, 'test.txt'),
|
||||
OS.Path.join(tmpDir, uc('dir2'), 'test.txt')
|
||||
);
|
||||
|
||||
Components.utils.import('chrome://zotero/content/import/folderImport.js');
|
||||
});
|
||||
|
||||
describe('#import', () => {
|
||||
it('should import PDFs from a folder and recreate structure without creating duplicates', async function () {
|
||||
// @TODO: re-enable when folder import is ready
|
||||
this.skip();
|
||||
this.timeout(30000);
|
||||
if (Zotero.automatedTest) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
const importer = new Zotero_Import_Folder({
|
||||
folder: tmpDir,
|
||||
recreateStructure: true,
|
||||
});
|
||||
|
||||
await importer.translate({
|
||||
libraryID: Zotero.Libraries.userLibraryID,
|
||||
linkFiles: true,
|
||||
});
|
||||
|
||||
assert.equal(importer.newItems.length, 2);
|
||||
|
||||
const firstPDFAttachment = importer.newItems.find(ni => ni.getField('title') === 'recognizePDF_test_arXiv.pdf');
|
||||
const firstPDFItem = await Zotero.Items.getAsync(firstPDFAttachment.parentID);
|
||||
const firstPDFCollections = await Zotero.Collections.getAsync(firstPDFItem.getCollections());
|
||||
assert.equal(firstPDFItem.getField('title'), 'Scaling study of an improved fermion action on quenched lattices');
|
||||
assert.equal(firstPDFCollections.length, 1);
|
||||
assert.equal(firstPDFCollections[0].name, uc('subdir1'));
|
||||
assert.equal((await Zotero.Collections.getAsync(firstPDFCollections[0].parentID)).name, uc('dir1'));
|
||||
|
||||
const secondPDFAttachment = importer.newItems.find(ni => ni.getField('title') === 'recognizePDF_test_title.pdf');
|
||||
const secondPDFItem = await Zotero.Items.getAsync(secondPDFAttachment.parentID);
|
||||
const secondPDFCollections = await Zotero.Collections.getAsync(secondPDFItem.getCollections());
|
||||
assert.equal(secondPDFItem.getField('title'), 'Bitcoin: A Peer-to-Peer Electronic Cash System');
|
||||
assert.equal(secondPDFCollections.length, 2);
|
||||
assert.sameMembers(secondPDFCollections.map(c => c.name), [uc('dir1'), uc('dir2')]);
|
||||
|
||||
assert.sameMembers(
|
||||
Zotero.Collections.getByLibrary(Zotero.Libraries.userLibraryID, true)
|
||||
.map(c => c.name)
|
||||
.filter(c => c.startsWith('Zotero_Import_Folder')),
|
||||
[uc('dir1'), uc('dir2'), uc('subdir1')]
|
||||
);
|
||||
|
||||
const importer2 = new Zotero_Import_Folder({
|
||||
folder: tmpDir,
|
||||
recreateStructure: true,
|
||||
});
|
||||
|
||||
await importer2.translate({
|
||||
libraryID: Zotero.Libraries.userLibraryID,
|
||||
linkFiles: true,
|
||||
});
|
||||
|
||||
assert.lengthOf(importer2.newItems, 0);
|
||||
assert.sameMembers(
|
||||
Zotero.Collections.getByLibrary(Zotero.Libraries.userLibraryID, true)
|
||||
.map(c => c.name)
|
||||
.filter(c => c.startsWith('Zotero_Import_Folder')),
|
||||
[uc('dir1'), uc('dir2'), uc('subdir1')]
|
||||
);
|
||||
});
|
||||
|
||||
it('should only import specified file types from a folder', async function () {
|
||||
// @TODO: re-enable when folder import is ready
|
||||
this.skip();
|
||||
this.timeout(30000);
|
||||
if (Zotero.automatedTest) {
|
||||
this.skip();
|
||||
}
|
||||
const importer = new Zotero_Import_Folder({
|
||||
folder: tmpDir,
|
||||
recreateStructure: false,
|
||||
fileTypes: '*.png,*.TXT', // should match case-insensitively
|
||||
mimeTypes: []
|
||||
});
|
||||
|
||||
await importer.translate({
|
||||
libraryID: Zotero.Libraries.userLibraryID,
|
||||
linkFiles: true,
|
||||
});
|
||||
|
||||
assert.equal(importer.newItems.length, 2);
|
||||
const pngItem = importer.newItems.find(ni => ni.getField('title') === 'test.png');
|
||||
assert.isDefined(pngItem);
|
||||
assert.isFalse(pngItem.parentID);
|
||||
|
||||
const txtItem = importer.newItems.find(ni => ni.getField('title') === 'test.txt');
|
||||
assert.isDefined(txtItem);
|
||||
assert.isFalse(txtItem.parentID);
|
||||
|
||||
const htmlItem = importer.newItems.find(ni => ni.getField('title') === 'test.html');
|
||||
assert.isUndefined(htmlItem);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue