diff --git a/chrome/content/zotero/elements/itemPaneHeader.js b/chrome/content/zotero/elements/itemPaneHeader.js
index 3da017133a..330355ef21 100644
--- a/chrome/content/zotero/elements/itemPaneHeader.js
+++ b/chrome/content/zotero/elements/itemPaneHeader.js
@@ -398,13 +398,16 @@
Zotero.Utilities.Internal.copyTextToClipboard(this.creatorYear.textContent);
}
else if (selectedMode === 'bibEntry') {
- Zotero_File_Interface.copyItemsToClipboard(
+ let content = Zotero.QuickCopy.getContentFromItems(
[this._item],
- Zotero.Prefs.get(PREF_BIB_ENTRY_STYLE),
- Zotero.Prefs.get(PREF_BIB_ENTRY_LOCALE),
- false,
- false
+ {
+ mode: 'bibliography',
+ id: Zotero.Prefs.get(PREF_BIB_ENTRY_STYLE),
+ contentType: '',
+ locale: Zotero.Prefs.get(PREF_BIB_ENTRY_LOCALE)
+ }
);
+ Zotero_File_Interface.writeToClipboard(content);
}
}
diff --git a/chrome/content/zotero/fileInterface.js b/chrome/content/zotero/fileInterface.js
index 7c0c9cdc5f..857e9cfa09 100644
--- a/chrome/content/zotero/fileInterface.js
+++ b/chrome/content/zotero/fileInterface.js
@@ -179,7 +179,6 @@ var Zotero_File_Interface = new function () {
var _unlock;
this.exportCollection = exportCollection;
- this.exportItemsToClipboard = exportItemsToClipboard;
this.exportItems = exportItems;
this.bibliographyFromItems = bibliographyFromItems;
@@ -238,85 +237,42 @@ var Zotero_File_Interface = new function () {
}
- /*
- * exports items to clipboard
+ /**
+ * Write QuickCopy content to the system clipboard.
+ *
+ * Accepts the `{ text, html? }` shape returned by getContentFromItems().
+ * Each present flavor is added; consumers (plain vs. rich-text targets)
+ * each pick what they support.
+ *
+ * @param {{text: String, html?: String}} content
*/
- function exportItemsToClipboard(items, format) {
- function _translate(items, format, callback) {
- let translation = new Zotero.Translate.Export();
- translation.setItems(items.slice());
- translation.setTranslator(format.id);
- if (format.options) {
- translation.setDisplayOptions(format.options);
- }
- translation.setHandler("done", callback);
- translation.translate();
+ this.writeToClipboard = function (content) {
+ if (!content) return;
+ let { text, html } = content;
+
+ let transferable = Components.classes['@mozilla.org/widget/transferable;1']
+ .createInstance(Components.interfaces.nsITransferable);
+ let clipboardService = Components.classes['@mozilla.org/widget/clipboard;1']
+ .getService(Components.interfaces.nsIClipboard);
+ transferable.init(null);
+
+ if (html) {
+ let str = Components.classes['@mozilla.org/supports-string;1']
+ .createInstance(Components.interfaces.nsISupportsString);
+ str.data = html;
+ transferable.addDataFlavor('text/html');
+ transferable.setTransferData('text/html', str, html.length * 2);
}
-
- // If translating with virtual "Markdown + Rich Text" translator, use Note Markdown and
- // Note HTML instead
- if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
- let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN, options: format.markdownOptions };
- let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML, options: format.htmlOptions };
- _translate(items, markdownFormat, (obj, worked) => {
- if (!worked) {
- Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
- return;
- }
- _translate(items, htmlFormat, (obj2, worked) => {
- if (!worked) {
- Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
- return;
- }
-
- let text = obj.string.replace(/\r\n/g, '\n');
- let html = obj2.string.replace(/\r\n/g, '\n');
-
- // copy to clipboard
- let transferable = Components.classes['@mozilla.org/widget/transferable;1']
- .createInstance(Components.interfaces.nsITransferable);
- let clipboardService = Components.classes['@mozilla.org/widget/clipboard;1']
- .getService(Components.interfaces.nsIClipboard);
-
- // Add Text
- let str = Components.classes['@mozilla.org/supports-string;1']
- .createInstance(Components.interfaces.nsISupportsString);
- str.data = text;
- transferable.addDataFlavor('text/plain');
- transferable.setTransferData('text/plain', str, text.length * 2);
-
- // Add HTML
- str = Components.classes['@mozilla.org/supports-string;1']
- .createInstance(Components.interfaces.nsISupportsString);
- str.data = html;
- transferable.addDataFlavor('text/html');
- transferable.setTransferData('text/html', str, html.length * 2);
-
- clipboardService.setData(
- transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard
- );
- });
- });
+ if (text) {
+ let str = Components.classes['@mozilla.org/supports-string;1']
+ .createInstance(Components.interfaces.nsISupportsString);
+ str.data = text;
+ transferable.addDataFlavor('text/plain');
+ transferable.setTransferData('text/plain', str, text.length * 2);
}
- else {
- _translate(items, format, (obj, worked) => {
- if (!worked) {
- Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
- return;
- }
- let text = obj.string;
- // For Note HTML translator use body content only
- if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
- let parser = new DOMParser();
- let doc = parser.parseFromString(text, 'text/html');
- text = doc.body.innerHTML;
- }
- Components.classes['@mozilla.org/widget/clipboardhelper;1']
- .getService(Components.interfaces.nsIClipboardHelper)
- .copyString(text.replace(/\r\n/g, '\n'));
- });
- }
- }
+
+ clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard);
+ };
this.getMendeleyDirectory = function () {
@@ -755,70 +711,6 @@ var Zotero_File_Interface = new function () {
}
- /**
- * Copies HTML and text citations or bibliography entries for passed items in given style
- *
- * Does not check that items are actual references (and not notes or attachments)
- *
- * @param {Zotero.Item[]} items
- * @param {String} style - Style id string (e.g., 'http://www.zotero.org/styles/apa')
- * @param {String} locale - Locale (e.g., 'en-US')
- * @param {Boolean} [asHTML=false] - Use HTML source for plain-text data
- * @param {Boolean} [asCitations=false] - Copy citation cluster instead of bibliography
- */
- this.copyItemsToClipboard = function (items, style, locale, asHTML, asCitations) {
- var d = new Date();
-
- // copy to clipboard
- var transferable = Components.classes["@mozilla.org/widget/transferable;1"].
- createInstance(Components.interfaces.nsITransferable);
- var clipboardService = Components.classes["@mozilla.org/widget/clipboard;1"].
- getService(Components.interfaces.nsIClipboard);
- style = Zotero.Styles.get(style);
- var cslEngine = style.getCiteProc(locale, 'html', { cache: true });
-
- if (asCitations) {
- cslEngine.updateItems(items.map(item => item.id));
- var citation = {
- citationItems: items.map(item => ({ id: item.id })),
- properties: {}
- };
- var output = cslEngine.previewCitationCluster(citation, [], [], "html");
- }
- else {
- var output = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "html");
- }
-
- // add HTML
- var str = Components.classes["@mozilla.org/supports-string;1"].
- createInstance(Components.interfaces.nsISupportsString);
- str.data = output;
- transferable.addDataFlavor("text/html");
- transferable.setTransferData("text/html", str, output.length * 2);
-
- // If not "Copy as HTML", add plaintext; otherwise use HTML from above and just mark as text
- if(!asHTML) {
- if (asCitations) {
- output = cslEngine.previewCitationCluster(citation, [], [], "text");
- }
- else {
- output = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, 'text');
- }
- }
- cslEngine.free();
-
- var str = Components.classes["@mozilla.org/supports-string;1"].
- createInstance(Components.interfaces.nsISupportsString);
- str.data = output;
- transferable.addDataFlavor("text/plain");
- transferable.setTransferData("text/plain", str, output.length * 2);
-
- clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard);
-
- Zotero.debug(`Copied bibliography to clipboard in ${new Date() - d} ms`);
- }
-
-
/*
* Shows bibliography options and creates a bibliography
*/
@@ -852,7 +744,12 @@ var Zotero_File_Interface = new function () {
// generate bibliography
try {
if(io.method == 'copy-to-clipboard') {
- Zotero_File_Interface.copyItemsToClipboard(items, io.style, locale, false, io.mode === "citations");
+ let content = Zotero.QuickCopy.getContentFromItems(
+ items,
+ { mode: 'bibliography', id: io.style, contentType: '', locale },
+ { asCitations: io.mode === "citations" }
+ );
+ Zotero_File_Interface.writeToClipboard(content);
}
else {
var style = Zotero.Styles.get(io.style);
diff --git a/chrome/content/zotero/standalone/standalone.js b/chrome/content/zotero/standalone/standalone.js
index 6f245e423d..dfff443f2c 100644
--- a/chrome/content/zotero/standalone/standalone.js
+++ b/chrome/content/zotero/standalone/standalone.js
@@ -367,7 +367,7 @@ const ZoteroStandalone = new function () {
document.l10n.setAttributes(copyAnnotation, "menu-edit-copy-annotation", { count: selected.length });
// Label the export menu with the selected translator name
- var exportFormat = Zotero.QuickCopy.getFormat('export');
+ var exportFormat = Zotero.QuickCopy.getFormat({ mode: 'export' });
if (exportFormat.id) {
try {
let translator = Zotero.Translators.get(exportFormat.id);
diff --git a/chrome/content/zotero/xpcom/quickCopy.js b/chrome/content/zotero/xpcom/quickCopy.js
index 816844feb9..91965c13b9 100644
--- a/chrome/content/zotero/xpcom/quickCopy.js
+++ b/chrome/content/zotero/xpcom/quickCopy.js
@@ -235,11 +235,6 @@ Zotero.QuickCopy = new function () {
return '';
};
- this.getNoteFormat = function () {
- return this.unserializeSetting(Zotero.Prefs.get('export.noteQuickCopy.setting'));
- };
-
-
this.getLocale = function () {
return this.unserializeSetting(
Zotero.Prefs.get('export.quickCopy.bibliographySetting')
@@ -303,12 +298,21 @@ Zotero.QuickCopy = new function () {
/**
- * Get the Quick Copy format for the currently active URL if any.
+ * Get the Quick Copy format for the currently active URL.
+ * When `items` is a selection composed entirely of notes/annotations,
+ * return the user's note-format pref.
*
- * @param {String} [mode] - Optional 'bibliography' or 'export' - omit for drag-drop
- * @return {Object} - `{mode, id, contentType, locale}` for bibliography; `{mode, id}` for export
+ * @param {Object} [options]
+ * @param {String} [options.mode] - 'bibliography' or 'export'; omit for drag-drop
+ * @param {Zotero.Item[]} [options.items] - Items the format will be applied to
+ * @return {Object} - `{mode, id, contentType, locale}` for bibliography; `{mode, id, ...}` for export
*/
- this.getFormat = function (mode) {
+ this.getFormat = function ({ mode, items } = {}) {
+ // Note-only selections always use the note format
+ if (items && items.length && items.every(item => item.isNote() || item.isAnnotation())) {
+ return this.unserializeSetting(Zotero.Prefs.get('export.noteQuickCopy.setting'));
+ }
+
var siteRaw = _findSiteFormatForURL(this.lastActiveURL);
var site = siteRaw ? this.parseSiteFormat(siteRaw) : null;
@@ -334,80 +338,141 @@ Zotero.QuickCopy = new function () {
};
- /*
- * Get text and (when applicable) HTML content from items
+ /**
+ * Produce QuickCopy content from items. Single entry point for clipboard
+ * copy, drag-and-drop, and the Bibliography dialog. Item-type prep is
+ * handled here: annotations are wrapped via annotationsToNote, notes are
+ * passed through reformatNoteCitations, and bibliography mode is filtered
+ * to regular items. Always returns { text, html },
+ * unless export translator provides no html format.
*
- * |items| is an array of Zotero.Item objects
- *
- * |format| may be a Quick Copy format string
- * (e.g. "bibliography=http://www.zotero.org/styles/apa")
- * or an Quick Copy format object
- *
- * |callback| is only necessary if using an export format and should be
- * a function suitable for Zotero.Translate.setHandler, taking parameters
- * |obj| and |worked|. The generated content should be placed in obj.string
- * and |worked| should be true if the operation is successful.
- *
- * If bibliography format, the process is synchronous and an object
- * contain properties 'text' and 'html' is returned.
+ * @param {Zotero.Item[]} items
+ * @param {String|Object} format
+ * @param {Object} [options]
+ * @param {Boolean} [options.asCitations=false] - Bibliography only: in-text citation cluster
+ * @return {{text: String, html?: String} | null}
*/
- this.getContentFromItems = function (items, format, callback, modified) {
- if (items.length > Zotero.Prefs.get('export.quickCopy.dragLimit')) {
- Zotero.debug("Skipping quick copy for " + items.length + " items");
- return false;
- }
+ this.getContentFromItems = function (items, format, options = {}) {
+ if (!items.length) return null;
format = this.unserializeSetting(format);
-
- if (format.mode == 'export') {
- var translation = new Zotero.Translate.Export;
- translation.noWait = true; // needed not to break drags
- // Allow to reuse items array
- translation.setItems(items.slice());
- translation.setTranslator(format.id);
- if (format.options) {
- translation.setDisplayOptions(format.options);
- }
- translation.setHandler("done", callback);
- translation.translate();
- return true;
- }
- else if (format.mode == 'bibliography') {
- items = items.filter(item => !item.isNote());
- var locale = format.locale || this.getLocale();
-
- // Copy citations if shift key pressed
- if (modified) {
- var csl = Zotero.Styles.get(format.id).getCiteProc(locale, "text", { cache: true });
+ // Format-appropriate item transformations:
+ // annotations → wrap into a temp note,
+ // notes → reformat embedded citations in the current bib style,
+ // bibliography mode → keep only regular items.
+ if (items.every(item => item.isAnnotation())) {
+ items = [this.annotationsToNote(items)];
+ }
+ if (items.every(item => item.isNote())) {
+ items = this.reformatNoteCitations(items);
+ }
+ if (format.mode === 'bibliography') {
+ items = items.filter(item => item.isRegularItem());
+ }
+ if (!items.length) return null;
+
+ if (format.mode === 'export') {
+ // Markdown+RichText virtual translator: produces both flavors,
+ // using its `markdownOptions` and `htmlOptions` independently.
+ if (format.id === Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
+ let text = _runExportTranslator(items, {
+ mode: 'export',
+ id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN,
+ options: format.markdownOptions
+ });
+ let html = _runExportTranslator(items, {
+ mode: 'export',
+ id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML,
+ options: format.htmlOptions
+ });
+ if (text === null || html === null) return null;
+ return {
+ text: text.replace(/\r\n/g, '\n'),
+ html: html.replace(/\r\n/g, '\n')
+ };
+ }
+
+ // Note HTML: HTML output, exposed on both flavors so rich-text
+ // targets get the formatting and plain editors get the source.
+ if (format.id === Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
+ let output = _runExportTranslator(items, format);
+ if (output === null) return null;
+ output = output.replace(/\r\n/g, '\n');
+ let parser = new DOMParser();
+ let doc = parser.parseFromString(output, 'text/html');
+ output = doc.body.innerHTML;
+ return { text: output, html: output };
+ }
+
+ // Other export translators (e.g. BibTeX): single-flavor output.
+ let output = _runExportTranslator(items, format);
+ if (output === null) return null;
+ return { text: output.replace(/\r\n/g, '\n') };
+ }
+
+ if (format.mode === 'bibliography') {
+ let locale = format.locale || this.getLocale();
+ let style = Zotero.Styles.get(format.id);
+ if (!style) return null;
+
+ // Bibliography mode always produces both flavors: plain text for
+ // `text/plain`, HTML for `text/html`. "Copy as HTML" (contentType
+ // === 'html') sends the HTML version to `text/plain` as well, so
+ // plain editors paste HTML source.
+ let html, text;
+ if (options.asCitations) {
+ let csl = style.getCiteProc(locale, 'html', { cache: true });
csl.updateItems(items.map(item => item.id));
- var citation = {
+ let citation = {
citationItems: items.map(item => ({ id: item.id })),
properties: {}
};
- var html = csl.previewCitationCluster(citation, [], [], "html");
- var text = csl.previewCitationCluster(citation, [], [], "text");
+ html = csl.previewCitationCluster(citation, [], [], 'html');
+ text = csl.previewCitationCluster(citation, [], [], 'text');
csl.free();
}
else {
- var style = Zotero.Styles.get(format.id);
- var cslEngine = style.getCiteProc(locale, 'html', { cache: true });
- var html = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "html");
- cslEngine.free();
+ let cslEngine = style.getCiteProc(locale, 'html', { cache: true });
+ html = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, 'html');
+ cslEngine.free();
cslEngine = style.getCiteProc(locale, 'text', { cache: true });
- var text = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "text");
+ text = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, 'text');
cslEngine.free();
}
-
+
return {
- text: format.contentType == "html" ? html : text,
- html,
+ text: format.contentType === 'html' ? html : text,
+ html
};
}
-
- throw ("Invalid mode '" + format.mode + "' in Zotero.QuickCopy.getContentFromItems()");
+
+ throw new Error(`Invalid Quick Copy mode '${format.mode}'`);
};
+
+ /**
+ * Run an export translator synchronously (relies on noWait + preloaded
+ * translator code) and return its string output, or null on failure.
+ */
+ function _runExportTranslator(items, format) {
+ let result = null;
+ let translation = new Zotero.Translate.Export();
+ translation.noWait = true;
+ translation.setItems(items.slice());
+ translation.setTranslator(format.id);
+ if (format.options) {
+ translation.setDisplayOptions(format.options);
+ }
+ translation.setHandler("done", (obj, worked) => {
+ if (worked) {
+ result = obj.string;
+ }
+ });
+ translation.translate();
+ return result;
+ }
+
/**
* Generate a note item to pass to getContentFromItems() from an array of annotations
*
@@ -462,7 +527,7 @@ Zotero.QuickCopy = new function () {
// Use getFormat() so site-specific bibliography overrides apply when
// the user is on a URL with a configured site setting
- let format = this.getFormat('bibliography');
+ let format = this.getFormat({ mode: 'bibliography' });
if (format.mode !== 'bibliography' || !format.id) {
return items;
}
diff --git a/chrome/content/zotero/xpcom/reader.js b/chrome/content/zotero/xpcom/reader.js
index 9d592dbf99..7c48ae65bb 100644
--- a/chrome/content/zotero/xpcom/reader.js
+++ b/chrome/content/zotero/xpcom/reader.js
@@ -410,48 +410,20 @@ class ReaderInstance {
if (fromText) {
return;
}
- // annotations are wrapped in a temp note for translation
+ // Convert the iframe's JSON annotations into a single note
+ // item; getContentFromItems handles the rest (reformatting
+ // embedded citations, picking the right output flavors).
let items = [Zotero.QuickCopy.annotationsToNote(annotations)];
- // Reformat any citations in the annotation note using the
- // current bibliography style (matches main pane drag behavior)
- items = Zotero.QuickCopy.reformatNoteCitations(items);
- let format = Zotero.QuickCopy.getNoteFormat();
+ let format = Zotero.QuickCopy.getFormat({ items });
Zotero.debug(`Copying/dragging (${annotations.length}) annotation(s) with ${JSON.stringify(format)}`);
- // Basically the same code is used in itemTree.jsx onDragStart
- if (format.mode === 'export') {
- // If exporting with virtual "Markdown + Rich Text" translator, call Note Markdown
- // and Note HTML translators instead
- if (format.id === Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
- let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN, options: format.markdownOptions };
- let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML, options: format.htmlOptions };
- Zotero.QuickCopy.getContentFromItems(items, markdownFormat, (obj, worked) => {
- if (!worked) {
- return;
- }
- Zotero.QuickCopy.getContentFromItems(items, htmlFormat, (obj2, worked) => {
- if (!worked) {
- return;
- }
- dataTransfer.setData('text/plain', obj.string.replace(/\r\n/g, '\n'));
- dataTransfer.setData('text/html', obj2.string.replace(/\r\n/g, '\n'));
- });
- });
+
+ let content = Zotero.QuickCopy.getContentFromItems(items, format);
+ if (content) {
+ if (content.html) {
+ dataTransfer.setData('text/html', content.html);
}
- else {
- Zotero.QuickCopy.getContentFromItems(items, format, (obj, worked) => {
- if (!worked) {
- return;
- }
- var text = obj.string.replace(/\r\n/g, '\n');
- // For Note HTML translator use body content only
- if (format.id === Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
- // Use body content only
- let parser = new DOMParser();
- let doc = parser.parseFromString(text, 'text/html');
- text = doc.body.innerHTML;
- }
- dataTransfer.setData('text/plain', text);
- });
+ if (content.text) {
+ dataTransfer.setData('text/plain', content.text);
}
}
}
diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js
index 174eb2f5fe..6c04b30902 100644
--- a/chrome/content/zotero/xpcom/utilities_internal.js
+++ b/chrome/content/zotero/xpcom/utilities_internal.js
@@ -3401,73 +3401,46 @@ Zotero.Utilities.Internal.onDragItems = function (event, itemIDs, dragImage = ev
}
}
- // Get Quick Copy format for current URL (set via /ping from connector)
- let format = Zotero.QuickCopy.getFormat();
-
- // If all items are annotations, wrap them in a note object for translation
+ // Skip Quick Copy data for very large drags so we don't hang the drag
+ // preview while generating bibliography for the full set. The limit
+ // applies only to drag; keyboard-shortcut copy has no cap.
+ if (items.length > Zotero.Prefs.get('export.quickCopy.dragLimit')) {
+ Zotero.debug("Skipping Quick Copy drag data for " + items.length + " items");
+ return;
+ }
+ // When dragging annotation items, also set the zotero/annotation flavor
+ // so the note editor knows how to handle them.
if (items.every(item => item.isAnnotation())) {
- items = [Zotero.QuickCopy.annotationsToNote(items)];
+ let jsonAnnotations = items.map((annotation) => {
+ let json = Zotero.Annotations.toJSONSync(annotation);
+ json.id = annotation.key;
+ delete json.key;
+ json.attachmentItemID = annotation.parentItemID;
+ json.tags = json.tags || [];
+ return json;
+ });
+ event.dataTransfer.setData('zotero/annotation', JSON.stringify(jsonAnnotations));
}
- // If all items are notes, use one of the note export translators
- if (items.every(item => item.isNote())) {
- format = Zotero.QuickCopy.getNoteFormat();
- items = Zotero.QuickCopy.reformatNoteCitations(items);
- }
+ // Get Quick Copy format for current URL (set via /ping from connector)
+ // and the items being dragged. getContentFromItems handles the rest
+ // (annotation wrapping, note-citation reformatting, regular-item filter).
+ let format = Zotero.QuickCopy.getFormat({ items });
Zotero.debug("Dragging with format " + JSON.stringify(format));
try {
- if (format.mode == 'export') {
- // If exporting with virtual "Markdown + Rich Text" translator, call Note Markdown
- // and Note HTML translators instead
- if (format.id === Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
- let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN, options: format.markdownOptions };
- let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML, options: format.htmlOptions };
- Zotero.QuickCopy.getContentFromItems(items, markdownFormat, (obj, worked) => {
- if (!worked) {
- Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
- return;
- }
- Zotero.QuickCopy.getContentFromItems(items, htmlFormat, (obj2, worked) => {
- if (!worked) {
- Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
- return;
- }
- event.dataTransfer.setData('text/plain', obj.string.replace(/\r\n/g, '\n'));
- event.dataTransfer.setData('text/html', obj2.string.replace(/\r\n/g, '\n'));
- });
- });
+ let content = Zotero.QuickCopy.getContentFromItems(items, format, {
+ // Shift on drag → in-text citation (bibliography mode only)
+ asCitations: format.mode === 'bibliography' && event.shiftKey
+ });
+ if (content) {
+ if (content.html) {
+ event.dataTransfer.setData('text/html', content.html);
}
- else {
- Zotero.QuickCopy.getContentFromItems(items, format, (obj, worked) => {
- if (!worked) {
- Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
- return;
- }
- let text = obj.string.replace(/\r\n/g, '\n');
- // For Note HTML translator use body content only
- if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
- // Use body content only
- let parser = new DOMParser();
- let doc = parser.parseFromString(text, 'text/html');
- text = doc.body.innerHTML;
- }
- event.dataTransfer.setData('text/plain', text);
- });
+ if (content.text) {
+ event.dataTransfer.setData('text/plain', content.text);
}
}
- else if (format.mode == 'bibliography') {
- let content = Zotero.QuickCopy.getContentFromItems(items, format, null, event.shiftKey);
- if (content) {
- if (content.html) {
- event.dataTransfer.setData("text/html", content.html);
- }
- event.dataTransfer.setData("text/plain", content.text);
- }
- }
- else {
- Zotero.logError("Invalid Quick Copy mode");
- }
}
catch (e) {
Zotero.debug(e);
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index fe1ac8da25..1dba92c990 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -2698,62 +2698,23 @@ var ZoteroPane = new function () {
this.copySelectedItemsToClipboard = function (asCitations, mode = 'bibliography') {
- var items = [];
let itemIDs = this.getSelectedItems(true);
// Get selected item IDs in the item tree order
itemIDs = this.getSortedItems(true).filter(id => itemIDs.includes(id));
- items = Zotero.Items.get(itemIDs);
+ let items = Zotero.Items.get(itemIDs);
+ if (!items.length) return;
- if (!items.length) {
- return;
- }
+ let format = Zotero.QuickCopy.getFormat({ mode, items });
- var format = Zotero.QuickCopy.getFormat(mode);
- if (items.every(item => item.isNote() || item.isAttachment() || item.isAnnotation())) {
- format = Zotero.QuickCopy.getNoteFormat();
- }
- // To copy annotations, wrap them in a temp note
- if (items.every(item => item.isAnnotation())) {
- items = [Zotero.QuickCopy.annotationsToNote(items)];
- }
- if (items.every(item => item.isNote())) {
- items = Zotero.QuickCopy.reformatNoteCitations(items);
- }
+ // asCitations only applies to bibliography mode
+ if (asCitations && format.mode !== 'bibliography') return;
- // In bibliography mode, remove notes and attachments
- if (format.mode == 'bibliography') {
- items = items.filter(item => item.isRegularItem());
- }
-
- // DEBUG: We could copy notes via keyboard shortcut if we altered
- // Z_F_I.copyItemsToClipboard() to use Z.QuickCopy.getContentFromItems(),
- // but 1) we'd need to override that function's drag limit and 2) when I
- // tried it the OS X clipboard seemed to be getting text vs. HTML wrong,
- // automatically converting text/html to plaintext rather than using
- // text/unicode. (That may be fixable, however.)
- //
- // This isn't currently shown, because the commands are disabled when not relevant, so this
- // function isn't called
- if (!items.length) {
+ let content = Zotero.QuickCopy.getContentFromItems(items, format, { asCitations });
+ if (!content) {
Services.prompt.alert(null, "", Zotero.getString("fileInterface.noReferencesError"));
return;
}
-
- if (format.mode == 'bibliography') {
- var locale = format.locale;
- Zotero_File_Interface.copyItemsToClipboard(
- items, format.id, locale, format.contentType == 'html', asCitations
- );
- }
- else if (format.mode == 'export') {
- // Copy citations doesn't work in export mode
- if (asCitations) {
- return;
- }
- else {
- Zotero_File_Interface.exportItemsToClipboard(items, format);
- }
- }
+ Zotero_File_Interface.writeToClipboard(content);
}
diff --git a/test/tests/fileInterfaceTest.js b/test/tests/fileInterfaceTest.js
index 905d89875c..8d5ee62735 100644
--- a/test/tests/fileInterfaceTest.js
+++ b/test/tests/fileInterfaceTest.js
@@ -132,7 +132,7 @@ describe("Zotero_File_Interface", function () {
});
- describe("#copyItemsToClipboard()", function () {
+ describe("#writeToClipboard()", function () {
var clipboardService, item1, item2;
before(function* () {
@@ -164,73 +164,70 @@ describe("Zotero_File_Interface", function () {
// Non-"Copy as HTML" mode
//
it("should copy HTML and text citations to the clipboard", async function () {
- win.Zotero_File_Interface.copyItemsToClipboard(
+ let content = Zotero.QuickCopy.getContentFromItems(
[item1, item2],
- 'http://www.zotero.org/styles/apa',
- 'en-US',
- false,
- true
+ { mode: 'bibliography', id: 'http://www.zotero.org/styles/apa', contentType: '', locale: 'en-US' },
+ { asCitations: true }
);
-
+ win.Zotero_File_Interface.writeToClipboard(content);
+
// HTML
var str = getDataForFlavor('text/html');
assert.equal(str, '(A, 2016; B, 2016)');
-
+
// Plain text
str = getDataForFlavor('text/plain');
assert.equal(str, '(A, 2016; B, 2016)');
});
-
+
it("should copy HTML and text bibliography to the clipboard", async function () {
- win.Zotero_File_Interface.copyItemsToClipboard(
+ let content = Zotero.QuickCopy.getContentFromItems(
[item1, item2],
- 'http://www.zotero.org/styles/apa',
- 'en-US'
+ { mode: 'bibliography', id: 'http://www.zotero.org/styles/apa', contentType: '', locale: 'en-US' }
);
-
+ win.Zotero_File_Interface.writeToClipboard(content);
+
var str = getDataForFlavor('text/html');
assert.include(str, 'line-height');
assert.include(str, 'A');
assert.include(str, 'B');
-
+
// Plain text
str = getDataForFlavor('text/plain');
assert.equal(str, 'A. (2016).\nB. (2016).\n');
});
-
+
//
// "Copy as HTML" mode
//
it("should copy HTML and HTML source citations to the clipboard", async function () {
- win.Zotero_File_Interface.copyItemsToClipboard(
+ let content = Zotero.QuickCopy.getContentFromItems(
[item1, item2],
- 'http://www.zotero.org/styles/apa',
- 'en-US',
- true,
- true
+ { mode: 'bibliography', id: 'http://www.zotero.org/styles/apa', contentType: 'html', locale: 'en-US' },
+ { asCitations: true }
);
-
+ win.Zotero_File_Interface.writeToClipboard(content);
+
var str = getDataForFlavor('text/html');
assert.equal(str, '(A, 2016; B, 2016)');
-
+
// Plain text
str = getDataForFlavor('text/plain');
assert.equal(str, '(A, 2016; B, 2016)');
});
-
+
it("should copy HTML and HTML source bibliography to the clipboard", async function () {
- win.Zotero_File_Interface.copyItemsToClipboard(
+ let content = Zotero.QuickCopy.getContentFromItems(
[item1, item2],
- 'http://www.zotero.org/styles/apa',
- 'en-US',
- true
+ { mode: 'bibliography', id: 'http://www.zotero.org/styles/apa', contentType: 'html', locale: 'en-US' }
);
-
+ win.Zotero_File_Interface.writeToClipboard(content);
+
var str = getDataForFlavor('text/html');
assert.include(str, 'line-height');
assert.include(str, 'A');
assert.include(str, 'B');
-
+
// Plain text
str = getDataForFlavor('text/plain');
assert.include(str, 'line-height');
@@ -475,7 +472,8 @@ describe("Zotero_File_Interface", function () {
markdownOptions: { includeAppLinks: false },
htmlOptions: { includeAppLinks: false }
};
- win.Zotero_File_Interface.exportItemsToClipboard(items, format);
+ let content = Zotero.QuickCopy.getContentFromItems(items, format);
+ win.Zotero_File_Interface.writeToClipboard(content);
await Zotero.Promise.delay();
var text = getClipboardText();
diff --git a/test/tests/quickCopyTest.js b/test/tests/quickCopyTest.js
index ce7b8fba1e..966566a883 100644
--- a/test/tests/quickCopyTest.js
+++ b/test/tests/quickCopyTest.js
@@ -80,7 +80,7 @@ describe("Zotero.QuickCopy", function () {
await Zotero.QuickCopy.loadSiteSettings();
Zotero.QuickCopy.lastActiveURL = 'https://wiki.test.org/page';
- let bib = Zotero.QuickCopy.getFormat('bibliography');
+ let bib = Zotero.QuickCopy.getFormat({ mode: 'bibliography' });
assert.equal(bib.id, APA_STYLE_ID);
});
@@ -107,14 +107,14 @@ describe("Zotero.QuickCopy", function () {
await Zotero.QuickCopy.loadSiteSettings();
Zotero.QuickCopy.lastActiveURL = 'https://test.org/styles/apa';
- let bib = Zotero.QuickCopy.getFormat('bibliography');
+ let bib = Zotero.QuickCopy.getFormat({ mode: 'bibliography' });
assert.equal(bib.id, APA_STYLE_ID);
});
describe("default settings (no site match)", function () {
it("should return the default bibliographySetting for mode='bibliography'", function () {
Zotero.QuickCopy.lastActiveURL = 'https://no-match.test/';
- let result = Zotero.QuickCopy.getFormat('bibliography');
+ let result = Zotero.QuickCopy.getFormat({ mode: 'bibliography' });
let expected = Zotero.QuickCopy.unserializeSetting(
Zotero.Prefs.get('export.quickCopy.bibliographySetting')
);
@@ -124,7 +124,7 @@ describe("Zotero.QuickCopy", function () {
it("should return the default exportSetting for mode='export'", function () {
Zotero.QuickCopy.lastActiveURL = 'https://no-match.test/';
- let result = Zotero.QuickCopy.getFormat('export');
+ let result = Zotero.QuickCopy.getFormat({ mode: 'export' });
let expected = Zotero.QuickCopy.unserializeSetting(
Zotero.Prefs.get('export.quickCopy.exportSetting')
);
@@ -156,14 +156,14 @@ describe("Zotero.QuickCopy", function () {
await setSiteSetting(legacy);
// bibliography mode → site override
- let bib = Zotero.QuickCopy.getFormat('bibliography');
+ let bib = Zotero.QuickCopy.getFormat({ mode: 'bibliography' });
assert.equal(bib.mode, 'bibliography');
assert.equal(bib.id, APA_STYLE_ID);
assert.equal(bib.contentType, 'html');
assert.equal(bib.locale, 'en-US');
// export mode → no site override → global default
- let exp = Zotero.QuickCopy.getFormat('export');
+ let exp = Zotero.QuickCopy.getFormat({ mode: 'export' });
let globalExport = Zotero.QuickCopy.unserializeSetting(
Zotero.Prefs.get('export.quickCopy.exportSetting')
);
@@ -185,12 +185,12 @@ describe("Zotero.QuickCopy", function () {
await setSiteSetting(legacy);
// export mode → site override
- let exp = Zotero.QuickCopy.getFormat('export');
+ let exp = Zotero.QuickCopy.getFormat({ mode: 'export' });
assert.equal(exp.mode, 'export');
assert.equal(exp.id, BIBTEX_TRANSLATOR_ID);
// bibliography mode → no site override → global default
- let bib = Zotero.QuickCopy.getFormat('bibliography');
+ let bib = Zotero.QuickCopy.getFormat({ mode: 'bibliography' });
let globalBib = Zotero.QuickCopy.unserializeSetting(
Zotero.Prefs.get('export.quickCopy.bibliographySetting')
);
@@ -207,7 +207,7 @@ describe("Zotero.QuickCopy", function () {
Zotero.QuickCopy.lastActiveURL = `https://${domain}/`;
await setSiteSetting(`bibliography/html=${APA_STYLE_ID}`);
- let bib = Zotero.QuickCopy.getFormat('bibliography');
+ let bib = Zotero.QuickCopy.getFormat({ mode: 'bibliography' });
assert.equal(bib.mode, 'bibliography');
assert.equal(bib.id, APA_STYLE_ID);
assert.equal(bib.contentType, 'html');
@@ -227,13 +227,13 @@ describe("Zotero.QuickCopy", function () {
});
await setSiteSetting(siteFormat);
- let bib = Zotero.QuickCopy.getFormat('bibliography');
+ let bib = Zotero.QuickCopy.getFormat({ mode: 'bibliography' });
assert.equal(bib.mode, 'bibliography');
assert.equal(bib.id, APA_STYLE_ID);
assert.equal(bib.locale, 'fr-FR');
// export → no site override → global default
- let exp = Zotero.QuickCopy.getFormat('export');
+ let exp = Zotero.QuickCopy.getFormat({ mode: 'export' });
let globalExport = Zotero.QuickCopy.unserializeSetting(
Zotero.Prefs.get('export.quickCopy.exportSetting')
);
@@ -254,11 +254,11 @@ describe("Zotero.QuickCopy", function () {
});
await setSiteSetting(siteFormat);
- let bib = Zotero.QuickCopy.getFormat('bibliography');
+ let bib = Zotero.QuickCopy.getFormat({ mode: 'bibliography' });
assert.equal(bib.mode, 'bibliography');
assert.equal(bib.id, APA_STYLE_ID);
- let exp = Zotero.QuickCopy.getFormat('export');
+ let exp = Zotero.QuickCopy.getFormat({ mode: 'export' });
assert.equal(exp.mode, 'export');
assert.equal(exp.id, BIBTEX_TRANSLATOR_ID);
@@ -288,11 +288,9 @@ describe("Zotero.QuickCopy", function () {
describe("#getContentFromItems()", function () {
it("should generate BibTeX", async function () {
var item = await createDataObject('item');
- var content = "";
- var worked = false;
await Zotero.Translators.init();
-
+
var translatorID = '9cb70025-a888-4a29-a210-93ec52da40d4'; // BibTeX
var format = 'export=' + translatorID;
Zotero.Prefs.set("export.quickCopy.exportSetting", format);
@@ -301,17 +299,10 @@ describe("Zotero.QuickCopy", function () {
while (!translator.code) {
await Zotero.Promise.delay(50);
}
-
- Zotero.QuickCopy.getContentFromItems(
- [item],
- format,
- (obj, w) => {
- content = obj.string;
- worked = w;
- }
- );
- assert.isTrue(worked);
- assert.isTrue(content.trim().startsWith('@'));
+
+ let content = Zotero.QuickCopy.getContentFromItems([item], format);
+ assert.isString(content.text);
+ assert.isTrue(content.text.trim().startsWith('@'));
});
});
@@ -319,13 +310,11 @@ describe("Zotero.QuickCopy", function () {
var item = createUnsavedDataObject('item', { itemType: 'webpage', title: 'Foo' });
item.setField('date', '2020-03-11');
await item.saveTx();
- var content = "";
- var worked = false;
-
+
// This shouldn't be used
Zotero.Prefs.set('export.lastLocale', 'fr-FR');
await Zotero.Styles.init();
-
+
var format = 'bibliography=http://www.zotero.org/styles/apa';
Zotero.Prefs.set("export.quickCopy.bibliographySetting", format);
@@ -357,7 +346,7 @@ describe("Zotero.QuickCopy", function () {
await item.saveTx();
// Copy citation, not bibliography
- let { text } = Zotero.QuickCopy.getContentFromItems([item], format, null, true);
- assert.equal(text, '《新型数据财产的行为主义保护:基于财产权理论的分析》。');
+ let content = Zotero.QuickCopy.getContentFromItems([item], format, { asCitations: true });
+ assert.equal(content.text, '《新型数据财产的行为主义保护:基于财产权理论的分析》。');
});
})