Allow to Quick Copy annotations (#2377)
This commit is contained in:
parent
07aeff4f64
commit
3c42103848
3 changed files with 343 additions and 273 deletions
|
@ -178,7 +178,8 @@ class EditorInstance {
|
||||||
|
|
||||||
async insertAnnotations(annotations) {
|
async insertAnnotations(annotations) {
|
||||||
await this._ensureNoteCreated();
|
await this._ensureNoteCreated();
|
||||||
let { html } = await this._serializeAnnotations(annotations);
|
await this.importImages(annotations);
|
||||||
|
let { html } = Zotero.EditorInstanceUtilities.serializeAnnotations(annotations);
|
||||||
if (html) {
|
if (html) {
|
||||||
this._postMessage({ action: 'insertHTML', pos: -1, html });
|
this._postMessage({ action: 'insertHTML', pos: -1, html });
|
||||||
}
|
}
|
||||||
|
@ -235,198 +236,13 @@ class EditorInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async importImages(annotations) {
|
||||||
* Transform plain text, containing some supported HTML tags, into actual HTML.
|
|
||||||
* A similar code is also used in pdf-reader mini editor for annotation text and comments.
|
|
||||||
* It basically creates a text node and then parses and wraps specific parts
|
|
||||||
* of it into supported HTML tags
|
|
||||||
*
|
|
||||||
* @param text Plain text flavored with some HTML tags
|
|
||||||
* @returns {string} HTML
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_transformTextToHTML(text) {
|
|
||||||
const supportedFormats = ['i', 'b', 'sub', 'sup'];
|
|
||||||
|
|
||||||
function getFormatter(str) {
|
|
||||||
let results = supportedFormats.map(format => str.toLowerCase().indexOf('<' + format + '>'));
|
|
||||||
results = results.map((offset, idx) => [supportedFormats[idx], offset]);
|
|
||||||
results.sort((a, b) => a[1] - b[1]);
|
|
||||||
for (let result of results) {
|
|
||||||
let format = result[0];
|
|
||||||
let offset = result[1];
|
|
||||||
if (offset < 0) continue;
|
|
||||||
let lastIndex = str.toLowerCase().indexOf('</' + format + '>', offset);
|
|
||||||
if (lastIndex >= 0) {
|
|
||||||
let parts = [];
|
|
||||||
parts.push(str.slice(0, offset));
|
|
||||||
parts.push(str.slice(offset + format.length + 2, lastIndex));
|
|
||||||
parts.push(str.slice(lastIndex + format.length + 3));
|
|
||||||
return {
|
|
||||||
format,
|
|
||||||
parts
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function walkFormat(parent) {
|
|
||||||
let child = parent.firstChild;
|
|
||||||
while (child) {
|
|
||||||
if (child.nodeType === 3) {
|
|
||||||
let text = child.nodeValue;
|
|
||||||
let formatter = getFormatter(text);
|
|
||||||
if (formatter) {
|
|
||||||
let nodes = [];
|
|
||||||
nodes.push(doc.createTextNode(formatter.parts[0]));
|
|
||||||
let midNode = doc.createElement(formatter.format);
|
|
||||||
midNode.appendChild(doc.createTextNode(formatter.parts[1]));
|
|
||||||
nodes.push(midNode);
|
|
||||||
nodes.push(doc.createTextNode(formatter.parts[2]));
|
|
||||||
child.replaceWith(...nodes);
|
|
||||||
child = midNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
walkFormat(child);
|
|
||||||
child = child.nextSibling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let parser = Components.classes['@mozilla.org/xmlextras/domparser;1']
|
|
||||||
.createInstance(Components.interfaces.nsIDOMParser);
|
|
||||||
let doc = parser.parseFromString('', 'text/html');
|
|
||||||
|
|
||||||
// innerText transforms \n into <br>
|
|
||||||
doc.body.innerText = text;
|
|
||||||
walkFormat(doc.body);
|
|
||||||
return doc.body.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object[]} annotations JSON annotations
|
|
||||||
* @param {Boolean} skipEmbeddingItemData Do not add itemData to citation items
|
|
||||||
* @return {Object} Object with `html` string and `citationItems` array to embed into metadata container
|
|
||||||
*/
|
|
||||||
async _serializeAnnotations(annotations, skipEmbeddingItemData) {
|
|
||||||
let storedCitationItems = [];
|
|
||||||
let html = '';
|
|
||||||
for (let annotation of annotations) {
|
for (let annotation of annotations) {
|
||||||
let attachmentItem = await Zotero.Items.getAsync(annotation.attachmentItemID);
|
|
||||||
if (!attachmentItem) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!annotation.text
|
|
||||||
&& !annotation.comment
|
|
||||||
&& !annotation.image) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let citationHTML = '';
|
|
||||||
let imageHTML = '';
|
|
||||||
let highlightHTML = '';
|
|
||||||
let quotedHighlightHTML = '';
|
|
||||||
let commentHTML = '';
|
|
||||||
|
|
||||||
let storedAnnotation = {
|
|
||||||
attachmentURI: Zotero.URI.getItemURI(attachmentItem),
|
|
||||||
annotationKey: annotation.id,
|
|
||||||
color: annotation.color,
|
|
||||||
pageLabel: annotation.pageLabel,
|
|
||||||
position: annotation.position
|
|
||||||
};
|
|
||||||
|
|
||||||
// Citation
|
|
||||||
let parentItem = attachmentItem.parentID && await Zotero.Items.getAsync(attachmentItem.parentID);
|
|
||||||
if (parentItem) {
|
|
||||||
let uris = [Zotero.URI.getItemURI(parentItem)];
|
|
||||||
let citationItem = {
|
|
||||||
uris,
|
|
||||||
locator: annotation.pageLabel
|
|
||||||
};
|
|
||||||
|
|
||||||
// Note: integration.js` uses `Zotero.Cite.System.prototype.retrieveItem`,
|
|
||||||
// which produces a little bit different CSL JSON
|
|
||||||
let itemData = Zotero.Utilities.Item.itemToCSLJSON(parentItem);
|
|
||||||
if (!skipEmbeddingItemData) {
|
|
||||||
citationItem.itemData = itemData;
|
|
||||||
}
|
|
||||||
|
|
||||||
let item = storedCitationItems.find(item => item.uris.some(uri => uris.includes(uri)));
|
|
||||||
if (!item) {
|
|
||||||
storedCitationItems.push({ uris, itemData });
|
|
||||||
}
|
|
||||||
|
|
||||||
storedAnnotation.citationItem = citationItem;
|
|
||||||
let citation = {
|
|
||||||
citationItems: [citationItem],
|
|
||||||
properties: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
let citationWithData = JSON.parse(JSON.stringify(citation));
|
|
||||||
citationWithData.citationItems[0].itemData = itemData;
|
|
||||||
let formatted = this._formatCitation(citationWithData);
|
|
||||||
citationHTML = `<span class="citation" data-citation="${encodeURIComponent(JSON.stringify(citation))}">${formatted}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image
|
|
||||||
if (annotation.image && !this._filesReadOnly) {
|
if (annotation.image && !this._filesReadOnly) {
|
||||||
// We assume that annotation.image is always PNG
|
annotation.imageAttachmentKey = await this._importImage(annotation.image);
|
||||||
let imageAttachmentKey = await this._importImage(annotation.image);
|
|
||||||
delete annotation.image;
|
|
||||||
|
|
||||||
// Normalize image dimensions to 1.25 of the print size
|
|
||||||
let rect = annotation.position.rects[0];
|
|
||||||
let rectWidth = rect[2] - rect[0];
|
|
||||||
let rectHeight = rect[3] - rect[1];
|
|
||||||
// Constants from pdf.js
|
|
||||||
const CSS_UNITS = 96.0 / 72.0;
|
|
||||||
const PDFJS_DEFAULT_SCALE = 1.25;
|
|
||||||
let width = Math.round(rectWidth * CSS_UNITS * PDFJS_DEFAULT_SCALE);
|
|
||||||
let height = Math.round(rectHeight * width / rectWidth);
|
|
||||||
imageHTML = `<img data-attachment-key="${imageAttachmentKey}" width="${width}" height="${height}" data-annotation="${encodeURIComponent(JSON.stringify(storedAnnotation))}"/>`;
|
|
||||||
}
|
}
|
||||||
|
delete annotation.image;
|
||||||
// Text
|
|
||||||
if (annotation.text) {
|
|
||||||
let text = this._transformTextToHTML(annotation.text.trim());
|
|
||||||
highlightHTML = `<span class="highlight" data-annotation="${encodeURIComponent(JSON.stringify(storedAnnotation))}">${text}</span>`;
|
|
||||||
quotedHighlightHTML = `<span class="highlight" data-annotation="${encodeURIComponent(JSON.stringify(storedAnnotation))}">${Zotero.getString('punctuation.openingQMark')}${text}${Zotero.getString('punctuation.closingQMark')}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note
|
|
||||||
if (annotation.comment) {
|
|
||||||
commentHTML = this._transformTextToHTML(annotation.comment.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
let template;
|
|
||||||
if (annotation.type === 'highlight') {
|
|
||||||
template = Zotero.Prefs.get('annotations.noteTemplates.highlight');
|
|
||||||
}
|
|
||||||
else if (annotation.type === 'note') {
|
|
||||||
template = Zotero.Prefs.get('annotations.noteTemplates.note');
|
|
||||||
}
|
|
||||||
else if (annotation.type === 'image') {
|
|
||||||
template = '<p>{{image}}<br/>{{citation}} {{comment}}</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
let vars = {
|
|
||||||
color: annotation.color,
|
|
||||||
highlight: (attrs) => attrs.quotes === 'true' ? quotedHighlightHTML : highlightHTML,
|
|
||||||
comment: commentHTML,
|
|
||||||
citation: citationHTML,
|
|
||||||
image: imageHTML,
|
|
||||||
tags: (attrs) => annotation.tags && annotation.tags.map(tag => tag.name).join(attrs.join || ' ')
|
|
||||||
};
|
|
||||||
let templateHTML = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
|
||||||
// Remove some spaces at the end of paragraph
|
|
||||||
templateHTML = templateHTML.replace(/([\s]*)(<\/p)/g, '$2');
|
|
||||||
// Remove multiple spaces
|
|
||||||
templateHTML = templateHTML.replace(/\s\s+/g, ' ');
|
|
||||||
html += templateHTML;
|
|
||||||
}
|
}
|
||||||
return { html, citationItems: storedCitationItems };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _digestItems(ids) {
|
async _digestItems(ids) {
|
||||||
|
@ -450,7 +266,7 @@ class EditorInstance {
|
||||||
}],
|
}],
|
||||||
properties: {}
|
properties: {}
|
||||||
};
|
};
|
||||||
let formatted = this._formatCitation(citation);
|
let formatted = Zotero.EditorInstanceUtilities.formatCitation(citation);
|
||||||
html += `<p><span class="citation" data-citation="${encodeURIComponent(JSON.stringify(citation))}">${formatted}</span></p>`;
|
html += `<p><span class="citation" data-citation="${encodeURIComponent(JSON.stringify(citation))}">${formatted}</span></p>`;
|
||||||
}
|
}
|
||||||
else if (item.isNote()) {
|
else if (item.isNote()) {
|
||||||
|
@ -576,7 +392,8 @@ class EditorInstance {
|
||||||
}
|
}
|
||||||
else if (type === 'zotero/annotation') {
|
else if (type === 'zotero/annotation') {
|
||||||
let annotations = JSON.parse(data);
|
let annotations = JSON.parse(data);
|
||||||
let { html: serializedHTML } = await this._serializeAnnotations(annotations);
|
await this.importImages(annotations);
|
||||||
|
let { html: serializedHTML } = Zotero.EditorInstanceUtilities.serializeAnnotations(annotations);
|
||||||
html = serializedHTML;
|
html = serializedHTML;
|
||||||
}
|
}
|
||||||
if (html) {
|
if (html) {
|
||||||
|
@ -1077,86 +894,6 @@ class EditorInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build citation item preview string (based on _buildBubbleString in quickFormat.js)
|
|
||||||
* TODO: Try to avoid duplicating this code here and inside note-editor
|
|
||||||
*/
|
|
||||||
_formatCitationItemPreview(citationItem) {
|
|
||||||
const STARTSWITH_ROMANESQUE_REGEXP = /^[&a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]/;
|
|
||||||
const ENDSWITH_ROMANESQUE_REGEXP = /[.;:&a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]$/;
|
|
||||||
|
|
||||||
let { itemData } = citationItem;
|
|
||||||
let str = '';
|
|
||||||
|
|
||||||
// Authors
|
|
||||||
let authors = itemData.author;
|
|
||||||
if (authors) {
|
|
||||||
if (authors.length === 1) {
|
|
||||||
str = authors[0].family || authors[0].literal;
|
|
||||||
}
|
|
||||||
else if (authors.length === 2) {
|
|
||||||
let a = authors[0].family || authors[0].literal;
|
|
||||||
let b = authors[1].family || authors[1].literal;
|
|
||||||
str = a + ' ' + Zotero.getString('general.and') + ' ' + b;
|
|
||||||
}
|
|
||||||
else if (authors.length >= 3) {
|
|
||||||
str = (authors[0].family || authors[0].literal) + ' ' + Zotero.getString('general.etAl');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title
|
|
||||||
if (!str && itemData.title) {
|
|
||||||
str = `“${itemData.title}”`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date
|
|
||||||
if (itemData.issued
|
|
||||||
&& itemData.issued['date-parts']
|
|
||||||
&& itemData.issued['date-parts'][0]) {
|
|
||||||
let year = itemData.issued['date-parts'][0][0];
|
|
||||||
if (year && year != '0000') {
|
|
||||||
str += ', ' + year;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Locator
|
|
||||||
if (citationItem.locator) {
|
|
||||||
if (citationItem.label) {
|
|
||||||
// TODO: Localize and use short forms
|
|
||||||
var label = citationItem.label;
|
|
||||||
}
|
|
||||||
else if (/[\-–,]/.test(citationItem.locator)) {
|
|
||||||
var label = 'pp.';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var label = 'p.';
|
|
||||||
}
|
|
||||||
|
|
||||||
str += ', ' + label + ' ' + citationItem.locator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefix
|
|
||||||
if (citationItem.prefix && ENDSWITH_ROMANESQUE_REGEXP) {
|
|
||||||
str = citationItem.prefix
|
|
||||||
+ (ENDSWITH_ROMANESQUE_REGEXP.test(citationItem.prefix) ? ' ' : '')
|
|
||||||
+ str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suffix
|
|
||||||
if (citationItem.suffix && STARTSWITH_ROMANESQUE_REGEXP) {
|
|
||||||
str += (STARTSWITH_ROMANESQUE_REGEXP.test(citationItem.suffix) ? ' ' : '')
|
|
||||||
+ citationItem.suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
_formatCitation(citation) {
|
|
||||||
return '(' + citation.citationItems.map((x) => {
|
|
||||||
return `<span class="citation-item">${this._formatCitationItemPreview(x)}</span>`;
|
|
||||||
}).join('; ') + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
_arrayBufferToBase64(buffer) {
|
_arrayBufferToBase64(buffer) {
|
||||||
var binary = '';
|
var binary = '';
|
||||||
var bytes = new Uint8Array(buffer);
|
var bytes = new Uint8Array(buffer);
|
||||||
|
@ -1494,7 +1231,8 @@ class EditorInstance {
|
||||||
// New line is needed for note title parser
|
// New line is needed for note title parser
|
||||||
html += '\n';
|
html += '\n';
|
||||||
|
|
||||||
let { html: serializedHTML, citationItems } = await editorInstance._serializeAnnotations(jsonAnnotations, true);
|
await editorInstance.importImages(jsonAnnotations);
|
||||||
|
let { html: serializedHTML, citationItems } = Zotero.EditorInstanceUtilities.serializeAnnotations(jsonAnnotations, true);
|
||||||
html += serializedHTML;
|
html += serializedHTML;
|
||||||
citationItems = encodeURIComponent(JSON.stringify(citationItems));
|
citationItems = encodeURIComponent(JSON.stringify(citationItems));
|
||||||
html = `<div data-citation-items="${citationItems}" data-schema-version="${SCHEMA_VERSION}">${html}</div>`;
|
html = `<div data-citation-items="${citationItems}" data-schema-version="${SCHEMA_VERSION}">${html}</div>`;
|
||||||
|
@ -1504,5 +1242,283 @@ class EditorInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EditorInstanceUtilities {
|
||||||
|
/**
|
||||||
|
* Serialize annotations into HTML
|
||||||
|
*
|
||||||
|
* @param {Object[]} annotations JSON annotations
|
||||||
|
* @param {Boolean} skipEmbeddingItemData Do not add itemData to citation items
|
||||||
|
* @return {Object} Object with `html` string and `citationItems` array to embed into metadata container
|
||||||
|
*/
|
||||||
|
serializeAnnotations(annotations, skipEmbeddingItemData) {
|
||||||
|
let storedCitationItems = [];
|
||||||
|
let html = '';
|
||||||
|
for (let annotation of annotations) {
|
||||||
|
let attachmentItem = Zotero.Items.get(annotation.attachmentItemID);
|
||||||
|
if (!attachmentItem) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!annotation.text
|
||||||
|
&& !annotation.comment
|
||||||
|
&& !annotation.imageAttachmentKey) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let citationHTML = '';
|
||||||
|
let imageHTML = '';
|
||||||
|
let highlightHTML = '';
|
||||||
|
let quotedHighlightHTML = '';
|
||||||
|
let commentHTML = '';
|
||||||
|
|
||||||
|
let storedAnnotation = {
|
||||||
|
attachmentURI: Zotero.URI.getItemURI(attachmentItem),
|
||||||
|
annotationKey: annotation.id,
|
||||||
|
color: annotation.color,
|
||||||
|
pageLabel: annotation.pageLabel,
|
||||||
|
position: annotation.position
|
||||||
|
};
|
||||||
|
|
||||||
|
// Citation
|
||||||
|
let parentItem = attachmentItem.parentID && Zotero.Items.get(attachmentItem.parentID);
|
||||||
|
if (parentItem) {
|
||||||
|
let uris = [Zotero.URI.getItemURI(parentItem)];
|
||||||
|
let citationItem = {
|
||||||
|
uris,
|
||||||
|
locator: annotation.pageLabel
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: integration.js` uses `Zotero.Cite.System.prototype.retrieveItem`,
|
||||||
|
// which produces a little bit different CSL JSON
|
||||||
|
let itemData = Zotero.Utilities.Item.itemToCSLJSON(parentItem);
|
||||||
|
if (!skipEmbeddingItemData) {
|
||||||
|
citationItem.itemData = itemData;
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = storedCitationItems.find(item => item.uris.some(uri => uris.includes(uri)));
|
||||||
|
if (!item) {
|
||||||
|
storedCitationItems.push({ uris, itemData });
|
||||||
|
}
|
||||||
|
|
||||||
|
storedAnnotation.citationItem = citationItem;
|
||||||
|
let citation = {
|
||||||
|
citationItems: [citationItem],
|
||||||
|
properties: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let citationWithData = JSON.parse(JSON.stringify(citation));
|
||||||
|
citationWithData.citationItems[0].itemData = itemData;
|
||||||
|
let formatted = Zotero.EditorInstanceUtilities.formatCitation(citationWithData);
|
||||||
|
citationHTML = `<span class="citation" data-citation="${encodeURIComponent(JSON.stringify(citation))}">${formatted}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image
|
||||||
|
if (annotation.imageAttachmentKey) {
|
||||||
|
// // let imageAttachmentKey = await this._importImage(annotation.image);
|
||||||
|
// delete annotation.image;
|
||||||
|
|
||||||
|
// Normalize image dimensions to 1.25 of the print size
|
||||||
|
let rect = annotation.position.rects[0];
|
||||||
|
let rectWidth = rect[2] - rect[0];
|
||||||
|
let rectHeight = rect[3] - rect[1];
|
||||||
|
// Constants from pdf.js
|
||||||
|
const CSS_UNITS = 96.0 / 72.0;
|
||||||
|
const PDFJS_DEFAULT_SCALE = 1.25;
|
||||||
|
let width = Math.round(rectWidth * CSS_UNITS * PDFJS_DEFAULT_SCALE);
|
||||||
|
let height = Math.round(rectHeight * width / rectWidth);
|
||||||
|
imageHTML = `<img data-attachment-key="${annotation.imageAttachmentKey}" width="${width}" height="${height}" data-annotation="${encodeURIComponent(JSON.stringify(storedAnnotation))}"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text
|
||||||
|
if (annotation.text) {
|
||||||
|
let text = this._transformTextToHTML(annotation.text.trim());
|
||||||
|
highlightHTML = `<span class="highlight" data-annotation="${encodeURIComponent(JSON.stringify(storedAnnotation))}">${text}</span>`;
|
||||||
|
quotedHighlightHTML = `<span class="highlight" data-annotation="${encodeURIComponent(JSON.stringify(storedAnnotation))}">${Zotero.getString('punctuation.openingQMark')}${text}${Zotero.getString('punctuation.closingQMark')}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note
|
||||||
|
if (annotation.comment) {
|
||||||
|
commentHTML = this._transformTextToHTML(annotation.comment.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
let template;
|
||||||
|
if (annotation.type === 'highlight') {
|
||||||
|
template = Zotero.Prefs.get('annotations.noteTemplates.highlight');
|
||||||
|
}
|
||||||
|
else if (annotation.type === 'note') {
|
||||||
|
template = Zotero.Prefs.get('annotations.noteTemplates.note');
|
||||||
|
}
|
||||||
|
else if (annotation.type === 'image') {
|
||||||
|
template = '<p>{{image}}<br/>{{citation}} {{comment}}</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
let vars = {
|
||||||
|
color: annotation.color,
|
||||||
|
highlight: (attrs) => attrs.quotes === 'true' ? quotedHighlightHTML : highlightHTML,
|
||||||
|
comment: commentHTML,
|
||||||
|
citation: citationHTML,
|
||||||
|
image: imageHTML,
|
||||||
|
tags: (attrs) => annotation.tags && annotation.tags.map(tag => tag.name).join(attrs.join || ' ')
|
||||||
|
};
|
||||||
|
let templateHTML = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
|
// Remove some spaces at the end of paragraph
|
||||||
|
templateHTML = templateHTML.replace(/([\s]*)(<\/p)/g, '$2');
|
||||||
|
// Remove multiple spaces
|
||||||
|
templateHTML = templateHTML.replace(/\s\s+/g, ' ');
|
||||||
|
html += templateHTML;
|
||||||
|
}
|
||||||
|
return { html, citationItems: storedCitationItems };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform plain text, containing some supported HTML tags, into actual HTML.
|
||||||
|
* A similar code is also used in pdf-reader mini editor for annotation text and comments.
|
||||||
|
* It basically creates a text node and then parses and wraps specific parts
|
||||||
|
* of it into supported HTML tags
|
||||||
|
*
|
||||||
|
* @param text Plain text flavored with some HTML tags
|
||||||
|
* @returns {string} HTML
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_transformTextToHTML(text) {
|
||||||
|
const supportedFormats = ['i', 'b', 'sub', 'sup'];
|
||||||
|
|
||||||
|
function getFormatter(str) {
|
||||||
|
let results = supportedFormats.map(format => str.toLowerCase().indexOf('<' + format + '>'));
|
||||||
|
results = results.map((offset, idx) => [supportedFormats[idx], offset]);
|
||||||
|
results.sort((a, b) => a[1] - b[1]);
|
||||||
|
for (let result of results) {
|
||||||
|
let format = result[0];
|
||||||
|
let offset = result[1];
|
||||||
|
if (offset < 0) continue;
|
||||||
|
let lastIndex = str.toLowerCase().indexOf('</' + format + '>', offset);
|
||||||
|
if (lastIndex >= 0) {
|
||||||
|
let parts = [];
|
||||||
|
parts.push(str.slice(0, offset));
|
||||||
|
parts.push(str.slice(offset + format.length + 2, lastIndex));
|
||||||
|
parts.push(str.slice(lastIndex + format.length + 3));
|
||||||
|
return {
|
||||||
|
format,
|
||||||
|
parts
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function walkFormat(parent) {
|
||||||
|
let child = parent.firstChild;
|
||||||
|
while (child) {
|
||||||
|
if (child.nodeType === 3) {
|
||||||
|
let text = child.nodeValue;
|
||||||
|
let formatter = getFormatter(text);
|
||||||
|
if (formatter) {
|
||||||
|
let nodes = [];
|
||||||
|
nodes.push(doc.createTextNode(formatter.parts[0]));
|
||||||
|
let midNode = doc.createElement(formatter.format);
|
||||||
|
midNode.appendChild(doc.createTextNode(formatter.parts[1]));
|
||||||
|
nodes.push(midNode);
|
||||||
|
nodes.push(doc.createTextNode(formatter.parts[2]));
|
||||||
|
child.replaceWith(...nodes);
|
||||||
|
child = midNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walkFormat(child);
|
||||||
|
child = child.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let parser = Components.classes['@mozilla.org/xmlextras/domparser;1']
|
||||||
|
.createInstance(Components.interfaces.nsIDOMParser);
|
||||||
|
let doc = parser.parseFromString('', 'text/html');
|
||||||
|
|
||||||
|
// innerText transforms \n into <br>
|
||||||
|
doc.body.innerText = text;
|
||||||
|
walkFormat(doc.body);
|
||||||
|
return doc.body.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build citation item preview string (based on _buildBubbleString in quickFormat.js)
|
||||||
|
* TODO: Try to avoid duplicating this code here and inside note-editor
|
||||||
|
*/
|
||||||
|
_formatCitationItemPreview(citationItem) {
|
||||||
|
const STARTSWITH_ROMANESQUE_REGEXP = /^[&a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]/;
|
||||||
|
const ENDSWITH_ROMANESQUE_REGEXP = /[.;:&a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]$/;
|
||||||
|
|
||||||
|
let { itemData } = citationItem;
|
||||||
|
let str = '';
|
||||||
|
|
||||||
|
// Authors
|
||||||
|
let authors = itemData.author;
|
||||||
|
if (authors) {
|
||||||
|
if (authors.length === 1) {
|
||||||
|
str = authors[0].family || authors[0].literal;
|
||||||
|
}
|
||||||
|
else if (authors.length === 2) {
|
||||||
|
let a = authors[0].family || authors[0].literal;
|
||||||
|
let b = authors[1].family || authors[1].literal;
|
||||||
|
str = a + ' ' + Zotero.getString('general.and') + ' ' + b;
|
||||||
|
}
|
||||||
|
else if (authors.length >= 3) {
|
||||||
|
str = (authors[0].family || authors[0].literal) + ' ' + Zotero.getString('general.etAl');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
|
if (!str && itemData.title) {
|
||||||
|
str = `“${itemData.title}”`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date
|
||||||
|
if (itemData.issued
|
||||||
|
&& itemData.issued['date-parts']
|
||||||
|
&& itemData.issued['date-parts'][0]) {
|
||||||
|
let year = itemData.issued['date-parts'][0][0];
|
||||||
|
if (year && year != '0000') {
|
||||||
|
str += ', ' + year;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locator
|
||||||
|
if (citationItem.locator) {
|
||||||
|
if (citationItem.label) {
|
||||||
|
// TODO: Localize and use short forms
|
||||||
|
var label = citationItem.label;
|
||||||
|
}
|
||||||
|
else if (/[\-–,]/.test(citationItem.locator)) {
|
||||||
|
var label = 'pp.';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var label = 'p.';
|
||||||
|
}
|
||||||
|
|
||||||
|
str += ', ' + label + ' ' + citationItem.locator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix
|
||||||
|
if (citationItem.prefix && ENDSWITH_ROMANESQUE_REGEXP) {
|
||||||
|
str = citationItem.prefix
|
||||||
|
+ (ENDSWITH_ROMANESQUE_REGEXP.test(citationItem.prefix) ? ' ' : '')
|
||||||
|
+ str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suffix
|
||||||
|
if (citationItem.suffix && STARTSWITH_ROMANESQUE_REGEXP) {
|
||||||
|
str += (STARTSWITH_ROMANESQUE_REGEXP.test(citationItem.suffix) ? ' ' : '')
|
||||||
|
+ citationItem.suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatCitation(citation) {
|
||||||
|
return '(' + citation.citationItems.map((x) => {
|
||||||
|
return `<span class="citation-item">${this._formatCitationItemPreview(x)}</span>`;
|
||||||
|
}).join('; ') + ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Zotero.EditorInstance = EditorInstance;
|
Zotero.EditorInstance = EditorInstance;
|
||||||
Zotero.EditorInstance.SCHEMA_VERSION = SCHEMA_VERSION;
|
Zotero.EditorInstance.SCHEMA_VERSION = SCHEMA_VERSION;
|
||||||
|
Zotero.EditorInstanceUtilities = new EditorInstanceUtilities();
|
||||||
|
|
|
@ -838,6 +838,60 @@ class ReaderTab extends ReaderInstance {
|
||||||
Zotero.logError(event.error);
|
Zotero.logError(event.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._iframeWindow.wrappedJSObject.zoteroSetDataTransferAnnotations = (dataTransfer, annotations) => {
|
||||||
|
let res = Zotero.EditorInstanceUtilities.serializeAnnotations(annotations);
|
||||||
|
let tmpNote = new Zotero.Item('note');
|
||||||
|
tmpNote.libraryID = Zotero.Libraries.userLibraryID;
|
||||||
|
tmpNote.setNote(res.html);
|
||||||
|
let items = [tmpNote];
|
||||||
|
let format = Zotero.QuickCopy.getNoteFormat();
|
||||||
|
Zotero.debug('Copying/dragging annotation(s) with ' + format);
|
||||||
|
format = Zotero.QuickCopy.unserializeSetting(format);
|
||||||
|
// Basically the same code is used in itemTree.jsx onDragStart
|
||||||
|
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 };
|
||||||
|
let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML };
|
||||||
|
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'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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 = Cc['@mozilla.org/xmlextras/domparser;1']
|
||||||
|
.createInstance(Ci.nsIDOMParser);
|
||||||
|
let doc = parser.parseFromString(text, 'text/html');
|
||||||
|
text = doc.body.innerHTML;
|
||||||
|
}
|
||||||
|
dataTransfer.setData('text/plain', text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.debug(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this._iframeWindow.wrappedJSObject.zoteroConfirmDeletion = function (plural) {
|
this._iframeWindow.wrappedJSObject.zoteroConfirmDeletion = function (plural) {
|
||||||
let ps = Services.prompt;
|
let ps = Services.prompt;
|
||||||
let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
|
let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 826cce2fa0c754b9de456aa2d52f9bfc95dfd3ba
|
Subproject commit b97d62e02bdba96ef95a74dcefeab23ce2520eae
|
Loading…
Add table
Reference in a new issue