Merge pull request #2214 from mrtcode/notes-export

Markdown Note Export
This commit is contained in:
Dan Stillman 2021-12-15 06:11:51 -05:00
commit 0a7eebbab4
19 changed files with 392 additions and 250 deletions

View file

@ -49,8 +49,7 @@ var Zotero_File_Interface_Export = new function() {
var addedOptions = new Object();
var translators = window.arguments[0].translators;
translators.sort(function(a, b) { return a.label.localeCompare(b.label) });
var { translators, exportingNotes } = window.arguments[0];
// get format popup
var formatPopup = document.getElementById("format-popup");
@ -58,7 +57,9 @@ var Zotero_File_Interface_Export = new function() {
var optionsBox = document.getElementById("translator-options");
var charsetBox = document.getElementById("charset-box");
var selectedTranslator = Zotero.Prefs.get("export.lastTranslator");
var selectedTranslator = Zotero.Prefs.get(
exportingNotes ? "export.lastNoteTranslator" : "export.lastTranslator"
);
// add styles to format popup
for(var i in translators) {
@ -72,7 +73,12 @@ var Zotero_File_Interface_Export = new function() {
// presented to the user
// get readable name for option
try {
var optionLabel = Zotero.getString("exportOptions."+option);
if (option == 'includeAppLinks') {
var optionLabel = Zotero.getString("exportOptions." + option, Zotero.appName);
}
else {
var optionLabel = Zotero.getString("exportOptions." + option);
}
} catch(e) {
var optionLabel = option;
}
@ -121,7 +127,9 @@ var Zotero_File_Interface_Export = new function() {
_charsets = Zotero_Charset_Menu.populate(document.getElementById(OPTION_PREFIX+"exportCharset"), true);
}
this.updateOptions(Zotero.Prefs.get("export.translatorSettings"));
this.updateOptions(Zotero.Prefs.get(
exportingNotes ? "export.noteTranslatorSettings" : "export.translatorSettings"
));
}
/*
@ -224,7 +232,10 @@ var Zotero_File_Interface_Export = new function() {
window.arguments[0].selectedTranslator = window.arguments[0].translators[index];
// save selected translator
Zotero.Prefs.set("export.lastTranslator", window.arguments[0].translators[index].translatorID);
Zotero.Prefs.set(
window.arguments[0].exportingNotes ? "export.lastNoteTranslator" : "export.lastTranslator",
window.arguments[0].translators[index].translatorID
);
// set options on selected translator and generate optionString
var optionsAvailable = window.arguments[0].selectedTranslator.displayOptions;
@ -253,7 +264,10 @@ var Zotero_File_Interface_Export = new function() {
// save options
var optionString = JSON.stringify(displayOptions);
Zotero.Prefs.set("export.translatorSettings", optionString);
Zotero.Prefs.set(
window.arguments[0].exportingNotes ? "export.noteTranslatorSettings" : "export.translatorSettings",
optionString
);
}
/*

View file

@ -49,9 +49,46 @@ var Zotero_File_Exporter = function() {
Zotero_File_Exporter.prototype.save = async function () {
var translation = new Zotero.Translate.Export();
var translators = await translation.getTranslators();
if (!this.items) {
return;
}
let exportingNotes = this.items.every(item => item.isNote() || item.isAttachment());
// Keep only note export and Zotero RDF translators, if all items are notes or attachments
if (exportingNotes) {
translators = translators.filter((translator) => {
return (
translator.translatorID === '14763d24-8ba0-45df-8f52-b8d1108e7ac9'
|| translator.configOptions && translator.configOptions.noteTranslator
);
});
}
// Otherwise exclude note export translators
else {
translators = translators.filter(t => !t.configOptions || !t.configOptions.noteTranslator);
}
translators.sort((a, b) => a.label.localeCompare(b.label));
// Remove "Note" prefix from Note Markdown and Note HTML translators
let markdownTranslator = translators.find(
t => t.translatorID == Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN
);
if (markdownTranslator) {
markdownTranslator.label = 'Markdown';
// Move Note Markdown translator to the top
translators.unshift(...translators.splice(translators.indexOf(markdownTranslator), 1));
}
let htmlTranslator = translators.find(
t => t.translatorID == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML
);
if (htmlTranslator) {
htmlTranslator.label = 'HTML';
}
// present options dialog
var io = {translators:translators}
var io = { translators, exportingNotes };
window.openDialog("chrome://zotero/content/exportOptions.xul",
"_blank", "chrome,modal,centerscreen,resizable=no", io);
if(!io.selectedTranslator) {
@ -89,33 +126,34 @@ Zotero_File_Exporter.prototype.save = async function () {
translation.setLibraryID(this.libraryID);
}
async function _exportDone(obj, worked) {
// Close the items exported indicator
Zotero_File_Interface.Progress.close();
if (!worked) {
Zotero.alert(
null,
Zotero.getString('general.error'),
Zotero.getString('fileInterface.exportError')
);
Zotero_File_Interface.Progress.close();
return;
}
}
translation.setLocation(Zotero.File.pathToFile(fp.file));
translation.setTranslator(io.selectedTranslator);
translation.setDisplayOptions(io.displayOptions);
translation.setHandler("itemDone", function () {
Zotero.updateZoteroPaneProgressMeter(translation.getProgress());
});
translation.setHandler("done", this._exportDone);
translation.setHandler("done", _exportDone);
Zotero_File_Interface.Progress.show(
Zotero.getString("fileInterface.itemsExported")
);
translation.translate()
};
/*
* Closes the items exported indicator
*/
Zotero_File_Exporter.prototype._exportDone = function(obj, worked) {
Zotero_File_Interface.Progress.close();
if(!worked) {
Zotero.alert(
null,
Zotero.getString('general.error'),
Zotero.getString("fileInterface.exportError")
);
}
}
/****Zotero_File_Interface****
**
@ -175,36 +213,91 @@ var Zotero_File_Interface = new function() {
*/
function exportItems() {
var exporter = new Zotero_File_Exporter();
exporter.items = ZoteroPane_Local.getSelectedItems();
let itemIDs = ZoteroPane_Local.getSelectedItems(true);
// Get selected item IDs in the item tree order
itemIDs = ZoteroPane_Local.getSortedItems(true).filter(id => itemIDs.includes(id));
exporter.items = Zotero.Items.get(itemIDs);
if(!exporter.items || !exporter.items.length) throw("no items currently selected");
exporter.save();
}
/*
* exports items to clipboard
*/
function exportItemsToClipboard(items, translatorID) {
var translation = new Zotero.Translate.Export();
translation.setItems(items);
translation.setTranslator(translatorID);
translation.setHandler("done", _copyToClipboard);
translation.translate();
}
/*
* handler when done exporting items to clipboard
*/
function _copyToClipboard(obj, worked) {
if(!worked) {
Zotero.alert(
null, Zotero.getString('general.error'), Zotero.getString("fileInterface.exportError")
);
} else {
Components.classes["@mozilla.org/widget/clipboardhelper;1"]
.getService(Components.interfaces.nsIClipboardHelper)
.copyString(obj.string.replace(/\r\n/g, "\n"));
function _translate(items, translatorID, callback) {
let translation = new Zotero.Translate.Export();
translation.setItems(items.slice());
translation.setTranslator(translatorID);
translation.setHandler("done", callback);
translation.translate();
}
// If translating with virtual "Markdown + Rich Text" translator, use Note Markdown and
// Note HTML instead
if (translatorID == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
translatorID = Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN;
_translate(items, translatorID, (obj, worked) => {
if (!worked) {
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
return;
}
translatorID = Zotero.Translators.TRANSLATOR_ID_NOTE_HTML;
_translate(items, translatorID, (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/unicode');
transferable.setTransferData('text/unicode', 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
);
});
});
}
else {
_translate(items, translatorID, (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 (translatorID == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
let parser = Components.classes['@mozilla.org/xmlextras/domparser;1']
.createInstance(Components.interfaces.nsIDOMParser);
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'));
});
}
}

View file

@ -1891,6 +1891,8 @@ var ItemTree = class ItemTree extends LibraryTree {
event.dataTransfer.setDragImage(this._dragImageContainer, 0, 0);
var itemIDs = this.getSelectedItems(true);
// Get selected item IDs in the item tree order
itemIDs = this.getSortedItems(true).filter(id => itemIDs.includes(id));
event.dataTransfer.setData("zotero/item", itemIDs);
var items = Zotero.Items.get(itemIDs);
@ -1946,22 +1948,53 @@ var ItemTree = class ItemTree extends LibraryTree {
// Get Quick Copy format for current URL (set via /ping from connector)
var format = Zotero.QuickCopy.getFormatFromURL(Zotero.QuickCopy.lastActiveURL);
Zotero.debug("Dragging with format " + format);
var exportCallback = function(obj, worked) {
if (!worked) {
Zotero.log(Zotero.getString("fileInterface.exportError"), 'warning');
return;
}
var text = obj.string.replace(/\r\n/g, "\n");
event.dataTransfer.setData("text/plain", text);
// If all items are notes, use one of the note export translators
if (items.every(item => item.isNote())) {
format = Zotero.QuickCopy.getNoteFormat();
}
Zotero.debug("Dragging with format " + format);
format = Zotero.QuickCopy.unserializeSetting(format);
try {
if (format.mode == 'export') {
Zotero.QuickCopy.getContentFromItems(items, format, exportCallback);
// 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 };
let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML };
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'));
});
});
}
else {
Zotero.QuickCopy.getContentFromItems(items, format, (obj, worked) => {
if (!worked) {
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
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 = Cc['@mozilla.org/xmlextras/domparser;1']
.createInstance(Ci.nsIDOMParser);
let doc = parser.parseFromString(text, 'text/html');
text = doc.body.innerHTML;
}
event.dataTransfer.setData('text/plain', text);
});
}
}
else if (format.mode == 'bibliography') {
var content = Zotero.QuickCopy.getContentFromItems(items, format, null, event.shiftKey);

View file

@ -35,6 +35,7 @@ Zotero_Preferences.Export = {
init: Zotero.Promise.coroutine(function* () {
this.updateQuickCopyInstructions();
yield this.populateQuickCopyList();
yield this.populateNoteQuickCopyList();
var charsetMenu = document.getElementById("zotero-import-charsetMenu");
var charsetMap = Zotero_Charset_Menu.populate(charsetMenu, false);
@ -51,6 +52,8 @@ Zotero_Preferences.Export = {
var collation = Zotero.getLocaleCollation();
return collation.compareString(1, a.label, b.label);
});
// Exclude note export translators
translators = translators.filter(x => !x.configOptions || !x.configOptions.noteTranslator);
return translators;
},
@ -82,6 +85,86 @@ Zotero_Preferences.Export = {
}),
/*
* Builds the note Quick Copy drop-down from the current global pref
*/
populateNoteQuickCopyList: async function () {
// Initialize default format drop-down
var format = Zotero.Prefs.get("export.noteQuickCopy.setting");
format = Zotero.QuickCopy.unserializeSetting(format);
var menulist = document.getElementById("zotero-noteQuickCopy-menu");
menulist.setAttribute('preference', "pref-noteQuickCopy-setting");
if (!format) {
format = menulist.value;
}
format = Zotero.QuickCopy.unserializeSetting(format);
menulist.selectedItem = null;
menulist.removeAllItems();
var popup = document.createElement('menupopup');
menulist.appendChild(popup);
// add export formats to list
var translation = new Zotero.Translate("export");
var translators = await translation.getTranslators();
translators.sort((a, b) => a.label.localeCompare(b.label));
// Remove "Note" prefix from Note HTML translator
let htmlTranslator = translators.find(
x => x.translatorID == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML
);
if (htmlTranslator) {
htmlTranslator.label = 'HTML';
}
// Make sure virtual "Markdown + Rich Text" translator doesn't actually exist
translators = translators.filter(
x => x.translatorID != Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT
);
let markdownTranslatorIdx = translators.findIndex(
x => x.translatorID == Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN
);
// Make sure we actually have both translators
if (markdownTranslatorIdx != -1 && htmlTranslator) {
// Exclude standalone Note Markdown translator
translators.splice(markdownTranslatorIdx, 1);
// Add virtual "Markdown + Rich Text" translator to the top
translators.unshift({
translatorID: Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT,
label: 'Markdown + ' + Zotero.getString('general.richText'),
configOptions: {
noteTranslator: true
}
});
}
translators.forEach(function (translator) {
// Allow only note export translators
if (!translator.configOptions || !translator.configOptions.noteTranslator) {
return;
}
var val = JSON.stringify({ mode: 'export', id: translator.translatorID });
var itemNode = document.createElement('menuitem');
itemNode.setAttribute('value', val);
itemNode.setAttribute('label', translator.label);
// itemNode.setAttribute('oncommand', 'Zotero_Preferences.Export.updateQuickCopyUI()');
popup.appendChild(itemNode);
if (format.mode == 'export' && format.id == translator.translatorID) {
menulist.selectedItem = itemNode;
}
});
menulist.click();
},
/*
* Builds a Quick Copy drop-down
*/

View file

@ -41,6 +41,7 @@
<preference id="pref-quickCopy-setting" name="extensions.zotero.export.quickCopy.setting" type="string"/>
<preference id="pref-quickCopy-dragLimit" name="extensions.zotero.export.quickCopy.dragLimit" type="int"/>
<preference id="pref-quickCopy-locale" name="extensions.zotero.export.quickCopy.locale" type="string"/>
<preference id="pref-noteQuickCopy-setting" name="extensions.zotero.export.noteQuickCopy.setting" type="string"/>
<preference id="pref-import-charset" name="extensions.zotero.import.charset" type="string"/>
</preferences>
@ -53,7 +54,7 @@
<separator/>
<label value="&zotero.preferences.quickCopy.defaultFormat;" control="quickCopy-menu"/>
<label value="&zotero.preferences.quickCopy.itemFormat;" control="quickCopy-menu"/>
<menulist id="zotero-quickCopy-menu" label="&zotero.general.loading;"/>
<hbox align="center">
@ -68,8 +69,13 @@
<separator/>
<label value="&zotero.preferences.quickCopy.noteFormat;" control="zotero-noteQuickCopy-menu"/>
<menulist id="zotero-noteQuickCopy-menu" label="&zotero.general.loading;"/>
<separator/>
<label value="&zotero.preferences.quickCopy.siteEditor.setings;" control="quickCopy-siteSettings"/>
<hbox class="virtualized-table-container" flex="1" height="300px">
<hbox class="virtualized-table-container" flex="1" height="120px">
<html:div id="quickCopy-siteSettings"/>
</hbox>
<separator class="thin"/>

View file

@ -306,42 +306,45 @@ const ZoteroStandalone = new function() {
this.updateQuickCopyOptions = function () {
var selected = false;
var selected = [];
let win = Zotero.getMainWindow();
if (win) {
if (win.Zotero_Tabs.selectedID == 'zotero-pane') {
try {
selected = win.ZoteroPane
.getSelectedItems()
.filter(item => item.isRegularItem())
.length;
selected = win.ZoteroPane.getSelectedItems();
}
catch (e) {
}
win.ZoteroPane.updateQuickCopyCommands(win.ZoteroPane.getSelectedItems());
win.ZoteroPane.updateQuickCopyCommands(selected);
}
else {
let reader = Zotero.Reader.getByTabID(win.Zotero_Tabs.selectedID);
if (reader) {
let item = Zotero.Items.get(reader.itemID);
selected = !!item.parentItemID;
selected = item.parentItem && [item.parentItem] || [];
item = item.parentItem || item;
win.ZoteroPane.updateQuickCopyCommands([item]);
}
}
}
var format = Zotero.QuickCopy.getFormatFromURL(Zotero.QuickCopy.lastActiveURL);
var exportingNotes = selected.every(item => item.isNote() || item.isAttachment());
if (exportingNotes) {
format = Zotero.QuickCopy.getNoteFormat();
}
format = Zotero.QuickCopy.unserializeSetting(format);
var copyCitation = document.getElementById('menu_copyCitation');
var copyBibliography = document.getElementById('menu_copyBibliography');
var copyExport = document.getElementById('menu_copyExport');
var copyNote = document.getElementById('menu_copyNote');
copyCitation.hidden = !selected || format.mode != 'bibliography';
copyBibliography.hidden = !selected || format.mode != 'bibliography';
copyExport.hidden = !selected || format.mode != 'export';
copyCitation.hidden = !selected.length || format.mode != 'bibliography';
copyBibliography.hidden = !selected.length || format.mode != 'bibliography';
copyExport.hidden = !selected.length || format.mode != 'export' || exportingNotes;
copyNote.hidden = !selected.length || format.mode != 'export' || !exportingNotes;
if (format.mode == 'export') {
try {
let obj = Zotero.Translators.get(format.id);

View file

@ -210,6 +210,11 @@
key="key_copyBibliography"
command="cmd_zotero_copyBibliography"
hidden="true"/>
<menuitem id="menu_copyNote"
label="&copyNoteCmd.label;"
command="cmd_zotero_copyBibliography"
key="key_copyBibliography"
hidden="true"/>
<menuitem id="menu_paste"/>
<menuitem id="menu_delete"/>
<menuseparator class="menu-type-library"/>

View file

@ -43,6 +43,10 @@ Zotero.QuickCopy = new function() {
this._prefObserverID = Zotero.Prefs.registerObserver(
"export.quickCopy.setting", _loadOutputFormat
);
this._prefObserverID = Zotero.Prefs.registerObserver(
"export.noteQuickCopy.setting", _loadNoteOutputFormat
);
_initialized = true;
}
@ -59,6 +63,7 @@ Zotero.QuickCopy = new function() {
_initPromise = Zotero.Promise.each(
[
() => _loadOutputFormat(),
() => _loadNoteOutputFormat(),
() => this.loadSiteSettings()
],
f => f()
@ -157,6 +162,11 @@ Zotero.QuickCopy = new function() {
return '';
});
this.getNoteFormat = function () {
var pref = Zotero.Prefs.get('export.noteQuickCopy.setting');
pref = JSON.stringify(this.unserializeSetting(pref));
return pref;
};
this.getFormatFromURL = function(url) {
var quickCopyPref = Zotero.Prefs.get("export.quickCopy.setting");
@ -256,167 +266,15 @@ Zotero.QuickCopy = new function() {
if (format.mode == 'export') {
var translation = new Zotero.Translate.Export;
translation.noWait = true; // needed not to break drags
translation.setItems(items);
// Allow to reuse items array
translation.setItems(items.slice());
translation.setTranslator(format.id);
translation.setHandler("done", callback);
translation.translate();
return true;
}
else if (format.mode == 'bibliography') {
// Move notes to separate array
var allNotes = true;
var notes = [];
for (var i=0; i<items.length; i++) {
if (items[i].isNote()) {
notes.push(items.splice(i, 1)[0]);
i--;
}
else {
allNotes = false;
}
}
// If all notes, export full content
if (allNotes) {
var content = [];
let parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser);
let docHTML = '<html><body><div class="zotero-notes"/></body></html>';
let doc = parser.parseFromString(docHTML, 'text/html');
let textDoc = parser.parseFromString(docHTML, 'text/html');
let container = doc.body.firstChild;
let textContainer = textDoc.body.firstChild;
for (var i=0; i<notes.length; i++) {
var div = doc.createElement("div");
div.className = "zotero-note";
// AMO reviewer: This documented is never rendered (and the inserted markup
// is sanitized anyway)
div.insertAdjacentHTML('afterbegin', notes[i].note);
container.appendChild(div);
textContainer.appendChild(textDoc.importNode(div, true));
}
// Raw HTML output
var html = container.outerHTML;
// Add placeholders for newlines between notes
if (notes.length > 1) {
var divs = Zotero.Utilities.xpath(container, "div"),
textDivs = Zotero.Utilities.xpath(textContainer, "div");
for (var i=1, len=divs.length; i<len; i++) {
var p = doc.createElement("p");
p.appendChild(doc.createTextNode("--------------------------------------------------"));
container.insertBefore(p, divs[i]);
textContainer.insertBefore(textDoc.importNode(p, true), textDivs[i]);
}
}
const BLOCKQUOTE_PREFS = {
'export.quickCopy.quoteBlockquotes.richText':doc,
'export.quickCopy.quoteBlockquotes.plainText':textDoc
};
for(var pref in BLOCKQUOTE_PREFS) {
if (Zotero.Prefs.get(pref)) {
var currentDoc = BLOCKQUOTE_PREFS[pref];
// Add quotes around blockquote paragraphs
var addOpenQuote = Zotero.Utilities.xpath(currentDoc, "//blockquote/p[1]"),
addCloseQuote = Zotero.Utilities.xpath(currentDoc, "//blockquote/p[last()]");
for(var i=0; i<addOpenQuote.length; i++) {
addOpenQuote[i].insertBefore(currentDoc.createTextNode("\u201c"),
addOpenQuote[i].firstChild);
}
for(var i=0; i<addCloseQuote.length; i++) {
addCloseQuote[i].appendChild(currentDoc.createTextNode("\u201d"));
}
}
}
//
// Text-only adjustments
//
// Replace span styles with characters
var spans = textDoc.getElementsByTagName("span");
for(var i=0; i<spans.length; i++) {
var span = spans[i];
if(span.style.textDecoration == "underline") {
span.insertBefore(textDoc.createTextNode("_"), span.firstChild);
span.appendChild(textDoc.createTextNode("_"));
}
}
//
// And add spaces for indents
//
// Placeholder for 4 spaces in final output
const ZTAB = "%%ZOTEROTAB%%";
var ps = textDoc.getElementsByTagName("p");
for(var i=0; i<ps.length; i++) {
var p = ps[i],
paddingLeft = p.style.paddingLeft;
if(paddingLeft && paddingLeft.substr(paddingLeft.length-2) === "px") {
var paddingPx = parseInt(paddingLeft, 10),
ztabs = "";
for (let j = 30; j <= paddingPx; j += 30) ztabs += ZTAB;
p.insertBefore(textDoc.createTextNode(ztabs), p.firstChild);
}
}
// Use plaintext serializer to output formatted text
var docEncoder = Components.classes["@mozilla.org/layout/documentEncoder;1?type=text/html"]
.createInstance(Components.interfaces.nsIDocumentEncoder);
docEncoder.init(textDoc, "text/plain", docEncoder.OutputFormatted);
var text = docEncoder.encodeToString().trim().replace(ZTAB, " ", "g");
//
// Adjustments for the HTML copied to the clipboard
//
// Everything seems to like margin-left better than padding-left
var ps = Zotero.Utilities.xpath(doc, "p");
for(var i=0; i<ps.length; i++) {
var p = ps[i];
if(p.style.paddingLeft) {
p.style.marginLeft = p.style.paddingLeft;
p.style.paddingLeft = "";
}
}
// Word and TextEdit don't indent blockquotes on their own and need this
//
// OO gets it right, so this results in an extra indent
if (Zotero.Prefs.get('export.quickCopy.compatibility.indentBlockquotes')) {
var ps = Zotero.Utilities.xpath(doc, "//blockquote/p");
for(var i=0; i<ps.length; i++) ps[i].style.marginLeft = "30px";
}
// Add Word Normal style to paragraphs and add double-spacing
//
// OO inserts the conditional style code as a document comment
if (Zotero.Prefs.get('export.quickCopy.compatibility.word')) {
var ps = doc.getElementsByTagName("p");
for (var i=0; i<ps.length; i++) ps[i].className = "msoNormal";
var copyHTML = "<!--[if gte mso 0]>"
+ "<style>"
+ "p { margin-top:.1pt;margin-right:0in;margin-bottom:.1pt;margin-left:0in; line-height: 200%; }"
+ "li { margin-top:.1pt;margin-right:0in;margin-bottom:.1pt;margin-left:0in; line-height: 200%; }"
+ "blockquote p { margin-left: 11px; margin-right: 11px }"
+ "</style>"
+ "<![endif]-->\n"
+ container.outerHTML;
}
else {
var copyHTML = container.outerHTML;
}
var content = {
text: format.contentType == "html" ? html : text,
html: copyHTML
};
return content;
}
items = items.filter(item => !item.isNote());
// determine locale preference
var locale = format.locale ? format.locale : Zotero.Prefs.get('export.quickCopy.locale');
@ -458,6 +316,20 @@ Zotero.QuickCopy = new function() {
});
var _loadNoteOutputFormat = async function () {
var format = Zotero.Prefs.get("export.noteQuickCopy.setting");
format = Zotero.QuickCopy.unserializeSetting(format);
// If virtual "Markdown + Rich Text" translator is selected, preload Note Markdown and
// Note HTML translators
if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
await _preloadFormat({ mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN });
await _preloadFormat({ mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML });
return;
}
return _preloadFormat(format);
};
var _preloadFormat = async function (format) {
format = Zotero.QuickCopy.unserializeSetting(format);
if (format.mode == 'export') {

@ -1 +1 @@
Subproject commit 85b39a5be5bb59c5ee5e7447609273a29ea0b799
Subproject commit b75bc2550ae2df3bb307e7abf0d3abad96f79c82

View file

@ -809,9 +809,20 @@ Zotero.Translate.IO.Write.prototype = {
this._charset = charset;
},
/**
* Set a function to modify data on each write()
*/
setDataProcessor: function (processor) {
this._processor = processor;
},
"write":function(data) {
if(!this._charset) this.setCharacterSet("UTF-8");
if (this._processor) {
data = this._processor(data);
}
if(!this._writtenToStream && this._charset.substr(this._charset.length-4) == "xBOM"
&& BOMs[this._charset.substr(0, this._charset.length-4).toUpperCase()]) {
// If stream has not yet been written to, and a UTF type has been selected, write BOM

View file

@ -1014,7 +1014,10 @@ Zotero.Translate.ItemGetter = function() {
Zotero.Translate.ItemGetter.prototype = {
setItems: function (items) {
this._itemsLeft = items;
this._itemsLeft.sort((a, b) => a.id - b.id);
// Don't sort items if doing notes export
if (!items.every(item => item.isNote() || item.isAttachment())) {
this._itemsLeft.sort((a, b) => a.id - b.id);
}
this.numItems = this._itemsLeft.length;
},

View file

@ -34,6 +34,10 @@ Zotero.Translators = new function() {
var _initialized = false;
var _initializationDeferred = false;
this.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT = 'a45eca67-1ee8-45e5-b4c6-23fb8a852873';
this.TRANSLATOR_ID_NOTE_MARKDOWN = '1412e9e2-51e1-42ec-aa35-e036a895534b';
this.TRANSLATOR_ID_NOTE_HTML = '897a81c2-9f60-4bec-ae6b-85a5030b8be5';
/**
* Initializes translator cache, loading all translator metadata into memory
*

View file

@ -1605,18 +1605,28 @@ var ZoteroPane = new function()
/**
* Update the <command> elements that control the shortcut keys and the enabled state of the
* "Copy Citation"/"Copy Bibliography"/"Copy as" menu options. When disabled, the shortcuts are
* "Copy Citation"/"Copy Bibliography"/"Copy as"/"Copy Note" menu options. When disabled, the shortcuts are
* still caught in handleKeyPress so that we can show an alert about not having references selected.
*/
this.updateQuickCopyCommands = function (selectedItems) {
var format = Zotero.QuickCopy.getFormatFromURL(Zotero.QuickCopy.lastActiveURL);
format = Zotero.QuickCopy.unserializeSetting(format);
if (format.mode == 'bibliography') {
var canCopy = selectedItems.some(item => item.isRegularItem());
let canCopy = false;
// If all items are notes/attachments and at least one note is not empty
if (selectedItems.every(item => item.isNote() || item.isAttachment())) {
if (selectedItems.some(item => item.note)) {
canCopy = true;
}
}
else {
var canCopy = true;
let format = Zotero.QuickCopy.getFormatFromURL(Zotero.QuickCopy.lastActiveURL);
format = Zotero.QuickCopy.unserializeSetting(format);
if (format.mode == 'bibliography') {
canCopy = selectedItems.some(item => item.isRegularItem());
}
else {
canCopy = true;
}
}
document.getElementById('cmd_zotero_copyCitation').setAttribute('disabled', !canCopy);
document.getElementById('cmd_zotero_copyBibliography').setAttribute('disabled', !canCopy);
};
@ -2075,7 +2085,10 @@ var ZoteroPane = new function()
this.copySelectedItemsToClipboard = function (asCitations) {
var items = [];
if (Zotero_Tabs.selectedID == 'zotero-pane') {
items = this.getSelectedItems();
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);
}
else {
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
@ -2092,6 +2105,9 @@ var ZoteroPane = new function()
}
var format = Zotero.QuickCopy.getFormatFromURL(Zotero.QuickCopy.lastActiveURL);
if (items.every(item => item.isNote() || item.isAttachment())) {
format = Zotero.QuickCopy.getNoteFormat();
}
format = Zotero.QuickCopy.unserializeSetting(format);
// In bibliography mode, remove notes and attachments
@ -2966,6 +2982,12 @@ var ZoteroPane = new function()
disable.push(m.showInLibrary, m.duplicateItem, m.removeItems,
m.moveToTrash, m.deleteFromLibrary, m.exportItems, m.createBib, m.loadReport);
}
if (!disable.includes(m.exportItems)
&& items.every(item => item.isNote() || item.isAttachment())
&& !items.some(item => item.note)) {
disable.push(m.exportItems);
}
if ((!collectionTreeRow.editable || collectionTreeRow.isPublications()) && !collectionTreeRow.isFeed()) {
for (let i in m) {

View file

@ -104,7 +104,8 @@
<!ENTITY zotero.preferences.export.citePaperJournalArticleURL.description "When this option is disabled, Zotero includes URLs when citing journal, magazine, and newspaper articles only if the article does not have a page range specified.">
<!ENTITY zotero.preferences.quickCopy.caption "Quick Copy">
<!ENTITY zotero.preferences.quickCopy.defaultFormat "Default Format:">
<!ENTITY zotero.preferences.quickCopy.itemFormat "Item Format:">
<!ENTITY zotero.preferences.quickCopy.noteFormat "Note Format:">
<!ENTITY zotero.preferences.quickCopy.copyAsHTML "Copy as HTML">
<!ENTITY zotero.preferences.quickCopy.siteEditor.setings "Site-Specific Settings:">
<!ENTITY zotero.preferences.quickCopy.siteEditor.domainPath "Domain/Path">

View file

@ -37,6 +37,7 @@
<!--EDIT MENU-->
<!ENTITY copyCitationCmd.label "Copy Citation">
<!ENTITY copyBibliographyCmd.label "Copy Bibliography">
<!ENTITY copyNoteCmd.label "Copy Note">
<!ENTITY bidiSwitchPageDirectionItem.label "Switch Page Direction">
<!ENTITY bidiSwitchPageDirectionItem.accesskey "g">

View file

@ -87,6 +87,7 @@ general.languages = Languages
general.default = Default
general.custom = Custom
general.loading = Loading…
general.richText = Rich Text
general.yellow = Yellow
general.red = Red
@ -826,6 +827,7 @@ fulltext.indexState.queued = Queued
exportOptions.exportNotes = Export Notes
exportOptions.exportFileData = Export Files
exportOptions.includeAnnotations = Include Annotations
exportOptions.includeAppLinks = Include %S Links
exportOptions.useJournalAbbreviation = Use Journal Abbreviation
charset.UTF8withoutBOM = Unicode (UTF-8 without BOM)
charset.autoDetect = (auto detect)

View file

@ -109,6 +109,8 @@ pref("extensions.zotero.report.combineChildItems", true);
// Export and citation settings
pref("extensions.zotero.export.lastTranslator", "14763d24-8ba0-45df-8f52-b8d1108e7ac9");
pref("extensions.zotero.export.translatorSettings", "true,false");
pref("extensions.zotero.export.lastNoteTranslator", "1412e9e2-51e1-42ec-aa35-e036a895534b");
pref("extensions.zotero.export.noteTranslatorSettings", "");
pref("extensions.zotero.export.lastStyle", "http://www.zotero.org/styles/chicago-note-bibliography");
pref("extensions.zotero.export.bibliographySettings", "save-as-rtf");
pref("extensions.zotero.export.displayCharsetOption", true);
@ -121,10 +123,8 @@ pref("extensions.zotero.rtfScan.lastOutputFile", "");
pref("extensions.zotero.export.quickCopy.setting", "bibliography=http://www.zotero.org/styles/chicago-note-bibliography");
pref("extensions.zotero.export.quickCopy.dragLimit", 50);
pref("extensions.zotero.export.quickCopy.quoteBlockquotes.plainText", true);
pref("extensions.zotero.export.quickCopy.quoteBlockquotes.richText", true);
pref("extensions.zotero.export.quickCopy.compatibility.indentBlockquotes", true);
pref("extensions.zotero.export.quickCopy.compatibility.word", false);
pref("extensions.zotero.export.noteQuickCopy.setting", '{"mode": "export", "id": "a45eca67-1ee8-45e5-b4c6-23fb8a852873"}');
// Integration settings
pref("extensions.zotero.integration.port", 50001);

View file

@ -73,17 +73,6 @@ describe("Zotero.QuickCopy", function() {
assert.isTrue(worked);
assert.isTrue(content.trim().startsWith('@'));
});
it("should copy note content", async function () {
var item = await createDataObject('item', { itemType: 'note', note: '<p>Foo</p>' });
var format = 'bibliography=http://www.zotero.org/styles/apa';
Zotero.Prefs.set(prefName, format);
var content = Zotero.QuickCopy.getContentFromItems([item], format);
assert.propertyVal(content, 'text', 'Foo');
assert.propertyVal(content, 'html', '<div class=\"zotero-notes\"><div class=\"zotero-note\"><p>Foo</p></div></div>');
});
});
it("should generate bibliography in default locale if Quick Copy locale not set", async function () {

@ -1 +1 @@
Subproject commit d652a4de827b2ce25b6fac69f2e3e6e37cb74488
Subproject commit be5fc1633d0473bd662c35e887564a02abac7874