all content generation in getContentFromItems

- getContentFromItems is the single content producer for clipboard copy,
drag-and-drop from library and reader tabs. Handles annotation
wrapping, note-citation reformatting, and bib-mode regular-item
filtering internally.
- writeToClipboard replaces copyItemsToClipboard and exportItemsToClipboard.
- getFormat takes { mode, items }; note-only selections return the note
pref's format regardless of mode. getNoteFormat removed.
This commit is contained in:
Bogdan Abaev 2026-05-18 19:07:09 -07:00
parent b2a884bf5f
commit ac6c26096e
9 changed files with 277 additions and 419 deletions

View file

@ -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);
}
}

View file

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

View file

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

View file

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

View file

@ -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);
}
}
}

View file

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

View file

@ -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);
}

View file

@ -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, '(<i>A</i>, 2016; <i>B</i>, 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, '<i>A</i>');
assert.include(str, '<i>B</i>');
// 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, '(<i>A</i>, 2016; <i>B</i>, 2016)');
// Plain text
str = getDataForFlavor('text/plain');
assert.equal(str, '(<i>A</i>, 2016; <i>B</i>, 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, '<i>A</i>');
assert.include(str, '<i>B</i>');
// 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();

View file

@ -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, '《新型数据财产的行为主义保护:基于财产权理论的分析》。');
});
})