Reorganize and improve notes
This commit is contained in:
parent
078a18f7c0
commit
af57565acf
9 changed files with 708 additions and 678 deletions
|
@ -53,25 +53,38 @@
|
|||
|
||||
<constructor><![CDATA[
|
||||
this._noteEditorID = Zotero.Utilities.randomString();
|
||||
this._iframe = document.getAnonymousElementByAttribute(this, "anonid", "rt-view1");
|
||||
this._iframe = document.getAnonymousElementByAttribute(this, 'anonid', 'editor-view');
|
||||
this._iframe.addEventListener('DOMContentLoaded', (e) => {
|
||||
this._initialized = true;
|
||||
});
|
||||
|
||||
window.fillTooltip = (tooltip) => {
|
||||
let node = window.document.tooltipNode.closest('*[title]');
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.getNoteDataSync = () => {
|
||||
return this._editor.getNoteDataSync();
|
||||
tooltip.setAttribute('label', node.getAttribute('title'));
|
||||
return true;
|
||||
}
|
||||
|
||||
this.saveSync = () => {
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.saveSync();
|
||||
}
|
||||
}
|
||||
|
||||
this.initEditor = async (state) => {
|
||||
if (this._editor) {
|
||||
this._editor.uninit();
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.uninit();
|
||||
}
|
||||
this._editor = new Zotero.NoteEditor();
|
||||
await this._editor.init({
|
||||
this._editorInstance = new Zotero.EditorInstance();
|
||||
await this._editorInstance.init({
|
||||
state,
|
||||
item: this._item,
|
||||
window: document.getAnonymousElementByAttribute(this, "anonid", "rt-view1").contentWindow,
|
||||
onNavigate: this._navigateHandler,
|
||||
iframeWindow: document.getAnonymousElementByAttribute(this, 'anonid', 'editor-view').contentWindow,
|
||||
popup: document.getAnonymousElementByAttribute(this, 'anonid', 'editor-menu'),
|
||||
onNavigate: this._navigateHandler
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -86,8 +99,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (this._editor) {
|
||||
await this._editor.updateCitationsForURIs(uris);
|
||||
if (this._editorInstance) {
|
||||
await this._editorInstance.updateCitationsForURIs(uris);
|
||||
}
|
||||
|
||||
if (!this.item) return;
|
||||
|
@ -96,7 +109,7 @@
|
|||
if (ids.includes(id)) {
|
||||
let state = extraData && extraData[id] && extraData[id].state;
|
||||
if (state) {
|
||||
if (extraData[id].noteEditorID !== this._editor.instanceID) {
|
||||
if (extraData[id].noteEditorID !== this._editorInstance.instanceID) {
|
||||
this.initEditor(state);
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +129,7 @@
|
|||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteeditor');
|
||||
]]></constructor>
|
||||
|
||||
<property name="editorInstance" onget="return this._editorInstance"/>
|
||||
|
||||
<!-- Modes are predefined settings groups for particular tasks -->
|
||||
<field name="_mode">"view"</field>
|
||||
|
@ -194,10 +208,11 @@
|
|||
|
||||
this._lastHtmlValue = val.note;
|
||||
|
||||
this._editor = new Zotero.NoteEditor();
|
||||
this._editor.init({
|
||||
this._editorInstance = new Zotero.EditorInstance();
|
||||
this._editorInstance.init({
|
||||
item: val,
|
||||
window: document.getAnonymousElementByAttribute(this, "anonid", "rt-view1").contentWindow,
|
||||
iframeWindow: document.getAnonymousElementByAttribute(this, "anonid", "editor-view").contentWindow,
|
||||
popup: document.getAnonymousElementByAttribute(this, "anonid", "editor-menu"),
|
||||
readOnly: !this.editable,
|
||||
onNavigate: this._navigateHandler
|
||||
});
|
||||
|
@ -233,8 +248,8 @@
|
|||
<property name="navigateHandler">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (this._editor) {
|
||||
this._editor.onNavigate = val;
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.onNavigate = val;
|
||||
}
|
||||
this._navigateHandler = val;
|
||||
]]>
|
||||
|
@ -246,6 +261,9 @@
|
|||
<destructor>
|
||||
<![CDATA[
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.uninit();
|
||||
}
|
||||
this._destroyed = true;
|
||||
]]>
|
||||
</destructor>
|
||||
|
@ -258,44 +276,13 @@
|
|||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- Used to insert a tab manually -->
|
||||
<method name="handleKeyDown">
|
||||
<parameter name="event"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// var noteField = this._id('noteField');
|
||||
//
|
||||
// switch (event.keyCode) {
|
||||
// case 9:
|
||||
// // On Shift-Tab, if focus was moved out of the note, focus the element
|
||||
// // specified in the 'previousfocus' attribute. We check for focus
|
||||
// // because Shift-Tab doesn't and shouldn't move focus out of the note if
|
||||
// // the cursor is in a list.
|
||||
// if (event.shiftKey) {
|
||||
// let id = this.getAttribute('previousfocus');
|
||||
// if (id) {
|
||||
// setTimeout(() => {
|
||||
// if (!noteField.hasFocus()) {
|
||||
// document.getElementById(id).focus();
|
||||
// }
|
||||
// }, 0);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="focus">
|
||||
<body>
|
||||
<![CDATA[
|
||||
setTimeout(() => {
|
||||
if (this._iframe && this._iframe.contentWindow) {
|
||||
this._iframe.focus();
|
||||
this._editor.focus();
|
||||
this._editorInstance.focus();
|
||||
}
|
||||
|
||||
}, 500);
|
||||
|
@ -315,13 +302,14 @@
|
|||
|
||||
<content>
|
||||
<xul:vbox xbl:inherits="flex">
|
||||
<xul:iframe anonid="rt-view1" flex="1" overflow="auto" style="width: 100%;margin-right: 5px;border: 0"
|
||||
<xul:iframe tooltip="editor-tooltip" anonid="editor-view" flex="1" overflow="auto" style="width: 100%;margin-right: 5px;border: 0"
|
||||
frameBorder="0" src="resource://zotero/zotero-note-editor/editor.html" type="content"/>
|
||||
<xul:hbox id="links-container" hidden="true">
|
||||
<xul:linksbox id="links-box" flex="1" xbl:inherits="notitle"/>
|
||||
</xul:hbox>
|
||||
|
||||
<xul:popupset>
|
||||
<xul:tooltip id="editor-tooltip" onpopupshowing="return fillTooltip(this);"/>
|
||||
<xul:menupopup anonid="editor-menu" id="editor-menu" flex="1">
|
||||
</xul:menupopup>
|
||||
</xul:popupset>
|
||||
|
|
|
@ -26,11 +26,9 @@
|
|||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
|
||||
<?xul-overlay href="chrome://zotero/content/containers/tagSelector.xul"?>
|
||||
<?xul-overlay href="chrome://zotero/content/containers/tagsBox.xul"?>
|
||||
<?xul-overlay href="chrome://zotero/content/containers/noteEditor.xul"?>
|
||||
|
||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script src="chrome://zotero/content/include.js"></script>
|
||||
|
||||
<script src="tagSelectorContainer.js"></script>
|
||||
<script src="noteEditorContainer.js"></script>
|
||||
</overlay>
|
|
@ -1,594 +0,0 @@
|
|||
class NoteEditor {
|
||||
constructor() {
|
||||
this.instanceID = Zotero.Utilities.randomString();
|
||||
Zotero.Notes.editorInstances.push(this);
|
||||
Zotero.debug('Creating a new editor instance');
|
||||
}
|
||||
|
||||
async init(options) {
|
||||
this.id = options.item.id;
|
||||
|
||||
this.item = options.item;
|
||||
// this._onNavigate = options.onNavigate;
|
||||
this.saveOnEdit = true;
|
||||
this.state = options.state;
|
||||
this.citations = [];
|
||||
this.disableSaving = false;
|
||||
this._readOnly = options.readOnly;
|
||||
this.window = options.window;
|
||||
|
||||
await this.waitForEditor();
|
||||
// Zotero.Notes.updateURIs(h1);
|
||||
|
||||
// Run Cut/Copy/Paste with chrome privileges
|
||||
this.window.wrappedJSObject.zoteroExecCommand = function (doc, command, ui, value) {
|
||||
// Is that safe enough?
|
||||
if (!['cut', 'copy', 'paste'].includes(command)) {
|
||||
return;
|
||||
}
|
||||
return doc.execCommand(command, ui, value);
|
||||
}
|
||||
|
||||
this.window.addEventListener('message', this.listener);
|
||||
this.quickFormatWindow = null;
|
||||
let data = this.state ? { state: this.state } : { html: this.item.note };
|
||||
this.postMessage({
|
||||
op: 'init', ...data,
|
||||
libraryId: this.item.libraryID,
|
||||
key: this.item.key,
|
||||
readOnly: this._readOnly
|
||||
});
|
||||
}
|
||||
|
||||
uninit() {
|
||||
this.window.removeEventListener('message', this.listener);
|
||||
let index = Zotero.Notes.editorInstances.indexOf(this);
|
||||
if (index >= 0) {
|
||||
Zotero.Notes.editorInstances.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
async waitForEditor() {
|
||||
let n = 0;
|
||||
while (!this.window) {
|
||||
if (n >= 1000) {
|
||||
throw new Error('Waiting for editor failed ');
|
||||
}
|
||||
await Zotero.Promise.delay(10);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
postMessage(message) {
|
||||
this.window.postMessage({ instanceId: this.instanceID, message }, '*');
|
||||
}
|
||||
|
||||
listener = async (e) => {
|
||||
if (e.data.instanceId !== this.instanceID) {
|
||||
return;
|
||||
}
|
||||
// Zotero.debug('Message received from editor ' + e.data.instanceId + ' ' + this.instanceID + ' ' + e.data.message.op);
|
||||
|
||||
let message = e.data.message;
|
||||
|
||||
if (message.op === 'getItemData') {
|
||||
let parent = message.parent;
|
||||
let item = await Zotero.Items.getAsync(message.itemId);
|
||||
if (parent && item && item.parentID) {
|
||||
item = await Zotero.Items.getAsync(item.parentID);
|
||||
}
|
||||
if (item) {
|
||||
let data = {
|
||||
uri: Zotero.URI.getItemURI(item),
|
||||
backupText: this.getBackupStr(item)
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (message.op === 'insertObject') {
|
||||
let { type, data, pos } = message;
|
||||
|
||||
if (type === 'zotero/item') {
|
||||
let ids = data.split(',').map(id => parseInt(id));
|
||||
let citations = [];
|
||||
for (let id of ids) {
|
||||
let item = await Zotero.Items.getAsync(id);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
citations.push({
|
||||
citationItems: [{
|
||||
uri: Zotero.URI.getItemURI(item),
|
||||
backupText: this.getBackupStr(item)
|
||||
}],
|
||||
properties: {}
|
||||
});
|
||||
}
|
||||
this.postMessage({ op: 'insertCitations', citations, pos });
|
||||
}
|
||||
else if (type === 'zotero/annotation') {
|
||||
let annotations = JSON.parse(data);
|
||||
let list = [];
|
||||
for (let annotation of annotations) {
|
||||
let attachmentItem = await Zotero.Items.getAsync(annotation.itemId);
|
||||
if (!attachmentItem) {
|
||||
continue;
|
||||
}
|
||||
let citationItem = attachmentItem.parentID && await Zotero.Items.getAsync(attachmentItem.parentID) || attachmentItem;
|
||||
annotation.uri = Zotero.URI.getItemURI(attachmentItem);
|
||||
let citation = {
|
||||
citationItems: [{
|
||||
uri: Zotero.URI.getItemURI(citationItem),
|
||||
backupText: this.getBackupStr(citationItem),
|
||||
locator: annotation.pageLabel
|
||||
}],
|
||||
properties: {}
|
||||
};
|
||||
list.push({ annotation, citation });
|
||||
}
|
||||
this.postMessage({ op: 'insertAnnotationsAndCitations', list, pos });
|
||||
}
|
||||
}
|
||||
else if (message.op === 'navigate') {
|
||||
if (this._onNavigate) {
|
||||
this._onNavigate(message.uri, { position: message.position });
|
||||
}
|
||||
else {
|
||||
await Zotero.Viewer.openURI(message.uri, { position: message.position });
|
||||
}
|
||||
}
|
||||
else if (message.op === 'openURL') {
|
||||
var zp = typeof ZoteroPane !== 'undefined' ? ZoteroPane : window.opener.ZoteroPane;
|
||||
zp.loadURI(message.url);
|
||||
}
|
||||
else if (message.op === 'showInLibrary') {
|
||||
let zp = Zotero.getActiveZoteroPane();
|
||||
if (zp) {
|
||||
let item = await Zotero.URI.getURIItem(message.itemURI);
|
||||
if (item) {
|
||||
zp.selectItems([item.id]);
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (message.op === 'update') {
|
||||
this.save(message.noteData);
|
||||
}
|
||||
else if (message.op === 'getFormattedCitations') {
|
||||
let formattedCitations = await this.getFormattedCitations(message.citations);
|
||||
for (let newCitation of message.citations) {
|
||||
if (!this.citations.find(citation => citation.id === newCitation.id)) {
|
||||
this.citations.push(newCitation);
|
||||
}
|
||||
}
|
||||
this.postMessage({
|
||||
op: 'setFormattedCitations',
|
||||
formattedCitations
|
||||
});
|
||||
}
|
||||
else if (message.op === 'quickFormat') {
|
||||
let id = message.id;
|
||||
let citation = message.citation;
|
||||
citation = JSON.parse(JSON.stringify(citation));
|
||||
let availableCitationItems = [];
|
||||
for (let citationItem of citation.citationItems) {
|
||||
let item = await Zotero.URI.getURIItem(citationItem.uri);
|
||||
if (item) {
|
||||
availableCitationItems.push({ ...citationItem, id: item.id });
|
||||
}
|
||||
}
|
||||
citation.citationItems = availableCitationItems;
|
||||
let libraryID = this.item.libraryID;
|
||||
this.quickFormatDialog(id, citation, [libraryID]);
|
||||
}
|
||||
else if (message.op === 'updateImages') {
|
||||
for (let image of message.added) {
|
||||
let blob = this.dataURLtoBlob(image.dataUrl);
|
||||
let imageAttachment = await Zotero.Attachments.importEmbeddedImage({
|
||||
blob,
|
||||
parentItemID: this.item.id,
|
||||
itemKey: image.attachmentKey,
|
||||
saveOptions: {
|
||||
notifierData: {
|
||||
noteEditorID: this.instanceID
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
let attachmentItems = this.item.getAttachments().map(id => Zotero.Items.get(id));
|
||||
let abandonedItems = attachmentItems.filter(item => !message.all.includes(item.key));
|
||||
for (let item of abandonedItems) {
|
||||
await item.eraseTx();
|
||||
}
|
||||
}
|
||||
else if (message.op === 'requestImage') {
|
||||
let { attachmentKey } = message;
|
||||
var item = Zotero.Items.getByLibraryAndKey(this.item.libraryID, attachmentKey);
|
||||
if (!item) return;
|
||||
let path = await item.getFilePathAsync();
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
let dataURL = 'data:' + item.attachmentContentType + ';base64,' + this.arrayBufferToBase64(buf);
|
||||
this.postMessage({
|
||||
op: 'updateImage',
|
||||
attachmentKey,
|
||||
dataUrl: dataURL
|
||||
});
|
||||
}
|
||||
else if (message.op === 'popup') {
|
||||
this.openPopup(message.x, message.y, message.items);
|
||||
}
|
||||
}
|
||||
|
||||
openPopup(x, y, items) {
|
||||
let popup = document.getElementById('editor-menu');
|
||||
popup.hidePopup();
|
||||
|
||||
while (popup.firstChild) {
|
||||
popup.removeChild(popup.firstChild);
|
||||
}
|
||||
|
||||
for (let item of items) {
|
||||
let menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('value', item[0]);
|
||||
menuitem.setAttribute('label', item[1]);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this.postMessage({
|
||||
op: 'contextMenuAction',
|
||||
ctxAction: item[0],
|
||||
payload: item.payload
|
||||
});
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
}
|
||||
|
||||
popup.openPopupAtScreen(x, y, true);
|
||||
}
|
||||
|
||||
async save(noteData) {
|
||||
if (!noteData) return;
|
||||
let { state, html } = noteData;
|
||||
if (html === undefined) return;
|
||||
try {
|
||||
if (this.disableSaving) {
|
||||
Zotero.debug('Saving is disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._readOnly) {
|
||||
Zotero.debug('Not saving read-only note');
|
||||
return;
|
||||
}
|
||||
if (html === null) {
|
||||
Zotero.debug('Note value not available -- not saving', 2);
|
||||
return;
|
||||
}
|
||||
// Update note
|
||||
if (this.item) {
|
||||
let changed = this.item.setNote(html);
|
||||
if (changed && this.saveOnEdit) {
|
||||
// this.noteField.changed = false;
|
||||
await this.item.saveTx({
|
||||
notifierData: {
|
||||
noteEditorID: this.instanceID,
|
||||
state
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Create a new note
|
||||
var item = new Zotero.Item('note');
|
||||
if (this.parentItem) {
|
||||
item.libraryID = this.parentItem.libraryID;
|
||||
}
|
||||
item.setNote(html);
|
||||
if (this.parentItem) {
|
||||
item.parentKey = this.parentItem.key;
|
||||
}
|
||||
if (this.saveOnEdit) {
|
||||
var id = await item.saveTx();
|
||||
|
||||
if (!this.parentItem && this.collection) {
|
||||
this.collection.addItem(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
if (this.hasAttribute('onerror')) {
|
||||
let fn = new Function('', this.getAttribute('onerror'));
|
||||
fn.call(this)
|
||||
}
|
||||
if (this.onError) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
focus = () => {
|
||||
|
||||
}
|
||||
|
||||
getNoteDataSync = () => {
|
||||
if (!this._readOnly && !this.disableSaving && this.window) {
|
||||
return this.window.wrappedJSObject.getDataSync();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds the string to go inside a bubble
|
||||
*/
|
||||
_buildBubbleString(citationItem, str) {
|
||||
// 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 && Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP) {
|
||||
str = citationItem.prefix
|
||||
+ (Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP.test(citationItem.prefix) ? ' ' : '')
|
||||
+ str;
|
||||
}
|
||||
|
||||
// Suffix
|
||||
if (citationItem.suffix && Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP) {
|
||||
str += (Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP.test(citationItem.suffix) ? ' ' : '')
|
||||
+ citationItem.suffix;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
async updateCitationsForURIs(uris) {
|
||||
let citations = this.citations
|
||||
.filter(citation => citation.citationItems
|
||||
.some(citationItem => uris.includes(citationItem.uri)));
|
||||
|
||||
if (citations.length) {
|
||||
let formattedCitations = await this.getFormattedCitations(citations);
|
||||
this.postMessage({
|
||||
op: 'setFormattedCitations',
|
||||
formattedCitations
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getFormattedCitations = async (citations) => {
|
||||
let formattedCitations = {};
|
||||
for (let citation of citations) {
|
||||
formattedCitations[citation.id] = await this.getFormattedCitation(citation);
|
||||
}
|
||||
return formattedCitations;
|
||||
}
|
||||
|
||||
getFormattedCitation = async (citation) => {
|
||||
let formattedItems = [];
|
||||
for (let citationItem of citation.citationItems) {
|
||||
let item = await Zotero.URI.getURIItem(citationItem.uri);
|
||||
if (item && !item.deleted) {
|
||||
formattedItems.push(this._buildBubbleString(citationItem, this.getBackupStr(item)));
|
||||
}
|
||||
else {
|
||||
let formattedItem = this._buildBubbleString(citationItem, citationItem.backupText);
|
||||
formattedItem = `<span style="color: red;">${formattedItem}</span>`;
|
||||
formattedItems.push(formattedItem);
|
||||
}
|
||||
}
|
||||
return formattedItems.join(';');
|
||||
}
|
||||
|
||||
getBackupStr(item) {
|
||||
var str = item.getField('firstCreator');
|
||||
|
||||
// Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field)
|
||||
if (!str) {
|
||||
str = Zotero.getString('punctuation.openingQMark') + item.getDisplayTitle() + Zotero.getString('punctuation.closingQMark');
|
||||
}
|
||||
|
||||
// Date
|
||||
var date = item.getField('date', true, true);
|
||||
if (date && (date = date.substr(0, 4)) !== '0000') {
|
||||
str += ', ' + date;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
arrayBufferToBase64(buffer) {
|
||||
var binary = '';
|
||||
var bytes = new Uint8Array(buffer);
|
||||
var len = bytes.byteLength;
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return self.btoa(binary);
|
||||
}
|
||||
|
||||
dataURLtoBlob(dataurl) {
|
||||
let parts = dataurl.split(',');
|
||||
let mime = parts[0].match(/:(.*?);/)[1];
|
||||
if (parts[0].indexOf('base64') !== -1) {
|
||||
let bstr = atob(parts[1]);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
|
||||
return new self.Blob([u8arr], { type: mime });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
quickFormatDialog(id, citationData, filterLibraryIDs) {
|
||||
let that = this;
|
||||
let win;
|
||||
/**
|
||||
* Citation editing functions and propertiesaccessible to quickFormat.js and addCitationDialog.js
|
||||
*/
|
||||
let CI = function (citation, sortable, fieldIndexPromise, citationsByItemIDPromise, previewFn) {
|
||||
this.citation = citation;
|
||||
this.sortable = sortable;
|
||||
this.filterLibraryIDs = filterLibraryIDs;
|
||||
this.disableClassicDialog = true;
|
||||
}
|
||||
|
||||
CI.prototype = {
|
||||
/**
|
||||
* Execute a callback with a preview of the given citation
|
||||
* @return {Promise} A promise resolved with the previewed citation string
|
||||
*/
|
||||
preview: function () {
|
||||
Zotero.debug('CI: preview')
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort the citationItems within citation (depends on this.citation.properties.unsorted)
|
||||
* @return {Promise} A promise resolved with the previewed citation string
|
||||
*/
|
||||
sort: function () {
|
||||
Zotero.debug('CI: sort')
|
||||
return async function () {
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Accept changes to the citation
|
||||
* @param {Function} [progressCallback] A callback to be run when progress has changed.
|
||||
* Receives a number from 0 to 100 indicating current status.
|
||||
*/
|
||||
accept: async function (progressCallback) {
|
||||
Zotero.debug('CI: accept');
|
||||
if (progressCallback) progressCallback(100);
|
||||
|
||||
if (win) {
|
||||
win.close();
|
||||
}
|
||||
|
||||
let citation = {
|
||||
citationItems: this.citation.citationItems,
|
||||
properties: this.citation.properties
|
||||
}
|
||||
|
||||
for (let citationItem of citation.citationItems) {
|
||||
let itm = await Zotero.Items.getAsync(citationItem.id);
|
||||
delete citationItem.id;
|
||||
citationItem.uri = Zotero.URI.getItemURI(itm);
|
||||
citationItem.backupText = that.getBackupStr(itm);
|
||||
}
|
||||
|
||||
let formattedCitation = await that.getFormattedCitation(citation);
|
||||
|
||||
if (this.citation.citationItems.length) {
|
||||
that.postMessage({
|
||||
op: 'setCitation',
|
||||
id, citation, formattedCitation
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of items used in the current document
|
||||
* @return {Promise} A promise resolved by the items
|
||||
*/
|
||||
getItems: async function () {
|
||||
Zotero.debug('CI: getItems')
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let Citation = class {
|
||||
constructor(citationField, data, noteIndex) {
|
||||
if (!data) {
|
||||
data = { citationItems: [], properties: {} };
|
||||
}
|
||||
this.citationID = data.citationID;
|
||||
this.citationItems = data.citationItems;
|
||||
this.properties = data.properties;
|
||||
this.properties.noteIndex = noteIndex;
|
||||
|
||||
this._field = citationField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load citation item data
|
||||
* @param {Boolean} [promptToReselect=true] - will throw a MissingItemException if false
|
||||
* @returns {Promise{Number}}
|
||||
* - Zotero.Integration.NO_ACTION
|
||||
* - Zotero.Integration.UPDATE
|
||||
* - Zotero.Integration.REMOVE_CODE
|
||||
* - Zotero.Integration.DELETE
|
||||
*/
|
||||
loadItemData() {
|
||||
Zotero.debug('Citation: loadItemData');
|
||||
}
|
||||
|
||||
async handleMissingItem(idx) {
|
||||
Zotero.debug('Citation: handleMissingItem');
|
||||
}
|
||||
|
||||
async prepareForEditing() {
|
||||
Zotero.debug('Citation: prepareForEditing');
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
Zotero.debug('Citation: toJSON');
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the citation into CSL code representation
|
||||
* @returns {string}
|
||||
*/
|
||||
serialize() {
|
||||
Zotero.debug('Citation: serialize');
|
||||
}
|
||||
};
|
||||
|
||||
if (that.quickFormatWindow) {
|
||||
that.quickFormatWindow.close();
|
||||
that.quickFormatWindow = null;
|
||||
}
|
||||
|
||||
let citation = new Citation();
|
||||
citation.citationItems = citationData.citationItems;
|
||||
citation.properties = citationData.properties;
|
||||
let styleID = Zotero.Prefs.get('export.lastStyle');
|
||||
let locale = Zotero.Prefs.get('export.lastLocale');
|
||||
let csl = Zotero.Styles.get(styleID).getCiteProc(locale);
|
||||
var io = new CI(citation, csl.opt.sort_citations);
|
||||
|
||||
|
||||
var allOptions = 'chrome,centerscreen';
|
||||
// without this, Firefox gets raised with our windows under Compiz
|
||||
if (Zotero.isLinux) allOptions += ',dialog=no';
|
||||
// if(options) allOptions += ','+options;
|
||||
|
||||
var mode = (!Zotero.isMac && Zotero.Prefs.get('integration.keepAddCitationDialogRaised')
|
||||
? 'popup' : 'alwaysRaised') + ',resizable=false,centerscreen';
|
||||
|
||||
win = that.quickFormatWindow = Components.classes['@mozilla.org/embedcomp/window-watcher;1']
|
||||
.getService(Components.interfaces.nsIWindowWatcher)
|
||||
.openWindow(null, 'chrome://zotero/content/integration/quickFormat.xul', '', mode, {
|
||||
wrappedJSObject: io
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.NoteEditor = NoteEditor;
|
|
@ -77,15 +77,7 @@ function onError() {
|
|||
|
||||
function onUnload() {
|
||||
Zotero.Notifier.unregisterObserver(notifierUnregisterID);
|
||||
|
||||
if (noteEditor.item) {
|
||||
// noteData will be null if noteEditor current editor instance
|
||||
// has disabled saving, which might happen at the time of the initial sync
|
||||
let noteData = JSON.parse(JSON.stringify(noteEditor.getNoteDataSync()));
|
||||
if (noteData) {
|
||||
window.opener.ZoteroPane.onNoteWindowClosed(noteEditor.item.id, noteData);
|
||||
}
|
||||
}
|
||||
noteEditor.saveSync();
|
||||
}
|
||||
|
||||
var NotifyCallback = {
|
||||
|
|
|
@ -362,7 +362,7 @@ Zotero.Attachments = new function(){
|
|||
* @param {Object} [params.saveOptions] - Options to pass to Zotero.Item::save()
|
||||
* @return {Promise<Zotero.Item>}
|
||||
*/
|
||||
this.importEmbeddedImage = async function ({ blob, itemKey, parentItemID, saveOptions }) {
|
||||
this.importEmbeddedImage = async function ({ blob, parentItemID, saveOptions }) {
|
||||
Zotero.debug('Importing note or annotation image');
|
||||
|
||||
var contentType = blob.type;
|
||||
|
@ -391,11 +391,6 @@ Zotero.Attachments = new function(){
|
|||
attachmentItem = new Zotero.Item('attachment');
|
||||
let { libraryID: parentLibraryID } = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
|
||||
attachmentItem.libraryID = parentLibraryID;
|
||||
if (itemKey) {
|
||||
// Let it fail if the key already exists
|
||||
attachmentItem.key = itemKey;
|
||||
await attachmentItem.loadPrimaryData();
|
||||
}
|
||||
attachmentItem.parentID = parentItemID;
|
||||
attachmentItem.attachmentLinkMode = this.LINK_MODE_EMBEDDED_IMAGE;
|
||||
attachmentItem.attachmentPath = 'storage:' + filename;
|
||||
|
|
632
chrome/content/zotero/xpcom/editorInstance.js
Normal file
632
chrome/content/zotero/xpcom/editorInstance.js
Normal file
|
@ -0,0 +1,632 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2020 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
http://digitalscholar.org/
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
class EditorInstance {
|
||||
constructor() {
|
||||
this.instanceID = Zotero.Utilities.randomString();
|
||||
Zotero.Notes.registerEditorInstance(this);
|
||||
Zotero.debug('Creating a new editor instance');
|
||||
}
|
||||
|
||||
async init(options) {
|
||||
this.onNavigate = options.onNavigate;
|
||||
this._item = options.item;
|
||||
this._readOnly = options.readOnly;
|
||||
this._iframeWindow = options.iframeWindow;
|
||||
this._popup = options.popup;
|
||||
this._state = options.state;
|
||||
this._saveOnEdit = true;
|
||||
this._disableSaving = false;
|
||||
this._subscriptions = [];
|
||||
this._quickFormatWindow = null;
|
||||
|
||||
await this._waitForEditor();
|
||||
|
||||
// Run Cut/Copy/Paste with chrome privileges
|
||||
this._iframeWindow.wrappedJSObject.zoteroExecCommand = function (doc, command, ui, value) {
|
||||
// Is that safe enough?
|
||||
if (!['cut', 'copy', 'paste'].includes(command)) {
|
||||
return;
|
||||
}
|
||||
return doc.execCommand(command, ui, value);
|
||||
};
|
||||
|
||||
this._iframeWindow.addEventListener('message', this._listener);
|
||||
|
||||
this._postMessage({
|
||||
action: 'init',
|
||||
value: this._state || this._item.note,
|
||||
schemaVersion: this._item.noteSchemaVersion,
|
||||
readOnly: this._readOnly
|
||||
});
|
||||
}
|
||||
|
||||
uninit() {
|
||||
this._iframeWindow.removeEventListener('message', this._listener);
|
||||
Zotero.Notes.unregisterEditorInstance(this);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this._postMessage({ action: 'focus' });
|
||||
}
|
||||
|
||||
async updateCitationsForURIs(uris) {
|
||||
let subscriptions = this._subscriptions
|
||||
.filter(s => s.data.citation && s.data.citation.citationItems
|
||||
.some(citationItem => uris.includes(citationItem.uri)));
|
||||
for (let subscription of subscriptions) {
|
||||
await this._feedSubscription(subscription);
|
||||
}
|
||||
}
|
||||
|
||||
saveSync() {
|
||||
if (!this._readOnly && !this._disableSaving && this._iframeWindow) {
|
||||
let noteData = this._iframeWindow.wrappedJSObject.getDataSync();
|
||||
noteData = JSON.parse(JSON.stringify(noteData));
|
||||
this._save(noteData);
|
||||
}
|
||||
}
|
||||
|
||||
async _waitForEditor() {
|
||||
let n = 0;
|
||||
while (!this._iframeWindow) {
|
||||
if (n >= 1000) {
|
||||
throw new Error('Waiting for editor failed');
|
||||
}
|
||||
await Zotero.Promise.delay(10);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
_postMessage(message) {
|
||||
this._iframeWindow.postMessage({ instanceId: this.instanceID, message }, '*');
|
||||
}
|
||||
|
||||
_listener = async (e) => {
|
||||
if (e.data.instanceId !== this.instanceID) {
|
||||
return;
|
||||
}
|
||||
let message = e.data.message;
|
||||
switch (message.action) {
|
||||
case 'insertObject': {
|
||||
let { type, data, pos } = message;
|
||||
let list = [];
|
||||
if (type === 'zotero/item') {
|
||||
let ids = data.split(',').map(id => parseInt(id));
|
||||
for (let id of ids) {
|
||||
let item = await Zotero.Items.getAsync(id);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
list.push({
|
||||
citation: {
|
||||
citationItems: [{
|
||||
uri: Zotero.URI.getItemURI(item),
|
||||
backupText: this._getBackupStr(item)
|
||||
}],
|
||||
properties: {}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (type === 'zotero/annotation') {
|
||||
let annotations = JSON.parse(data);
|
||||
for (let annotation of annotations) {
|
||||
let attachmentItem = await Zotero.Items.getAsync(annotation.itemId);
|
||||
if (!attachmentItem) {
|
||||
continue;
|
||||
}
|
||||
let citationItem = attachmentItem.parentID && await Zotero.Items.getAsync(attachmentItem.parentID) || attachmentItem;
|
||||
annotation.uri = Zotero.URI.getItemURI(attachmentItem);
|
||||
let citation = {
|
||||
citationItems: [{
|
||||
uri: Zotero.URI.getItemURI(citationItem),
|
||||
backupText: this._getBackupStr(citationItem),
|
||||
locator: annotation.pageLabel
|
||||
}],
|
||||
properties: {}
|
||||
};
|
||||
list.push({ annotation, citation });
|
||||
}
|
||||
}
|
||||
if (list.length) {
|
||||
this._postMessage({ action: 'insertAnnotationsAndCitations', list, pos });
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'openAnnotation': {
|
||||
let { uri, position } = message;
|
||||
if (this.onNavigate) {
|
||||
this.onNavigate(uri, { position });
|
||||
}
|
||||
else {
|
||||
await Zotero.Viewer.openURI(uri, { position });
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'openUrl': {
|
||||
let { url } = message;
|
||||
let zp = Zotero.getActiveZoteroPane();
|
||||
if (zp) {
|
||||
zp.loadURI(url);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'showInLibrary': {
|
||||
let { uri } = message;
|
||||
let zp = Zotero.getActiveZoteroPane();
|
||||
if (zp) {
|
||||
let item = await Zotero.URI.getURIItem(uri);
|
||||
if (item) {
|
||||
zp.selectItems([item.id]);
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'openBackup': {
|
||||
let zp = Zotero.getActiveZoteroPane();
|
||||
if (zp) {
|
||||
zp.openBackupNoteWindow(this._item.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'update': {
|
||||
let { noteData } = message;
|
||||
this._save(noteData);
|
||||
return;
|
||||
}
|
||||
case 'subscribeProvider': {
|
||||
let { id, type, data } = message;
|
||||
let subscription = { id, type, data };
|
||||
this._subscriptions.push(subscription);
|
||||
await this._feedSubscription(subscription);
|
||||
return;
|
||||
}
|
||||
case 'unsubscribeProvider': {
|
||||
let { id } = message;
|
||||
this._subscriptions.splice(this._subscriptions.findIndex(s => s.id === id), 1);
|
||||
return;
|
||||
}
|
||||
case 'openCitationPopup': {
|
||||
let { nodeId, citation } = message;
|
||||
citation = JSON.parse(JSON.stringify(citation));
|
||||
let availableCitationItems = [];
|
||||
for (let citationItem of citation.citationItems) {
|
||||
let item = await Zotero.URI.getURIItem(citationItem.uri);
|
||||
if (item) {
|
||||
availableCitationItems.push({ ...citationItem, id: item.id });
|
||||
}
|
||||
}
|
||||
citation.citationItems = availableCitationItems;
|
||||
let libraryID = this._item.libraryID;
|
||||
this._openQuickFormatDialog(nodeId, citation, [libraryID]);
|
||||
return;
|
||||
}
|
||||
case 'importImages': {
|
||||
let { images } = message;
|
||||
for (let image of images) {
|
||||
let { nodeId, src } = image;
|
||||
let attachmentKey = await this._importImage(src);
|
||||
this._postMessage({ action: 'attachImportedImage', nodeId, attachmentKey });
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'syncAttachmentKeys': {
|
||||
let { attachmentKeys } = message;
|
||||
let attachmentItems = this._item.getAttachments().map(id => Zotero.Items.get(id));
|
||||
let abandonedItems = attachmentItems.filter(item => !attachmentKeys.includes(item.key));
|
||||
for (let item of abandonedItems) {
|
||||
await item.eraseTx();
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'popup': {
|
||||
let { x, y, pos, items } = message;
|
||||
this._openPopup(x, y, pos, items);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _feedSubscription(subscription) {
|
||||
let { id, type, data } = subscription;
|
||||
if (type === 'citation') {
|
||||
let formattedCitation = await this._getFormattedCitation(data.citation);
|
||||
this._postMessage({ action: 'notifyProvider', id, type, data: { formattedCitation } });
|
||||
}
|
||||
else if (type === 'image') {
|
||||
let { attachmentKey } = data;
|
||||
let item = Zotero.Items.getByLibraryAndKey(this._item.libraryID, attachmentKey);
|
||||
if (!item) return;
|
||||
let path = await item.getFilePathAsync();
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
let src = 'data:' + item.attachmentContentType + ';base64,' + this._arrayBufferToBase64(buf);
|
||||
this._postMessage({ action: 'notifyProvider', id, type, data: { src } });
|
||||
}
|
||||
}
|
||||
|
||||
async _importImage(src) {
|
||||
let blob;
|
||||
if (src.startsWith('data:')) {
|
||||
blob = this._dataURLtoBlob(src);
|
||||
}
|
||||
else {
|
||||
let res;
|
||||
|
||||
try {
|
||||
res = await Zotero.HTTP.request('GET', src, { responseType: 'blob' });
|
||||
}
|
||||
catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
blob = res.response;
|
||||
}
|
||||
|
||||
let attachment = await Zotero.Attachments.importEmbeddedImage({
|
||||
blob,
|
||||
parentItemID: this._item.id,
|
||||
saveOptions: {
|
||||
notifierData: {
|
||||
noteEditorID: this.instanceID
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return attachment.key;
|
||||
}
|
||||
|
||||
_openPopup(x, y, pos, items) {
|
||||
this._popup.hidePopup();
|
||||
|
||||
while (this._popup.firstChild) {
|
||||
this._popup.removeChild(this._popup.firstChild);
|
||||
}
|
||||
|
||||
for (let item of items) {
|
||||
let menuitem = this._popup.ownerDocument.createElement('menuitem');
|
||||
menuitem.setAttribute('value', item[0]);
|
||||
menuitem.setAttribute('label', item[1]);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({
|
||||
action: 'contextMenuAction',
|
||||
ctxAction: item[0],
|
||||
pos
|
||||
});
|
||||
});
|
||||
this._popup.appendChild(menuitem);
|
||||
}
|
||||
|
||||
this._popup.openPopupAtScreen(x, y, true);
|
||||
}
|
||||
|
||||
async _save(noteData) {
|
||||
if (!noteData) return;
|
||||
let { schemaVersion, state, html } = noteData;
|
||||
if (html === undefined) return;
|
||||
try {
|
||||
if (this._disableSaving) {
|
||||
Zotero.debug('Saving is disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._readOnly) {
|
||||
Zotero.debug('Not saving read-only note');
|
||||
return;
|
||||
}
|
||||
if (html === null) {
|
||||
Zotero.debug('Note value not available -- not saving', 2);
|
||||
return;
|
||||
}
|
||||
// Update note
|
||||
if (this._item) {
|
||||
let changed = this._item.setNote(html, schemaVersion);
|
||||
if (changed && this._saveOnEdit) {
|
||||
// Make sure saving is not disabled
|
||||
if (this._disableSaving) {
|
||||
return;
|
||||
}
|
||||
await this._item.saveTx({
|
||||
notifierData: {
|
||||
noteEditorID: this.instanceID,
|
||||
state
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Create a new note
|
||||
else {
|
||||
var item = new Zotero.Item('note');
|
||||
if (this.parentItem) {
|
||||
item.libraryID = this.parentItem.libraryID;
|
||||
}
|
||||
item.setNote(html, schemaVersion);
|
||||
if (this.parentItem) {
|
||||
item.parentKey = this.parentItem.key;
|
||||
}
|
||||
if (this._saveOnEdit) {
|
||||
var id = await item.saveTx();
|
||||
|
||||
if (!this.parentItem && this.collection) {
|
||||
this.collection.addItem(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the string to go inside a bubble
|
||||
*/
|
||||
_buildBubbleString(citationItem, str) {
|
||||
// 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 && Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP) {
|
||||
str = citationItem.prefix
|
||||
+ (Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP.test(citationItem.prefix) ? ' ' : '')
|
||||
+ str;
|
||||
}
|
||||
|
||||
// Suffix
|
||||
if (citationItem.suffix && Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP) {
|
||||
str += (Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP.test(citationItem.suffix) ? ' ' : '')
|
||||
+ citationItem.suffix;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
async _getFormattedCitation(citation) {
|
||||
let formattedItems = [];
|
||||
for (let citationItem of citation.citationItems) {
|
||||
let item = await Zotero.URI.getURIItem(citationItem.uri);
|
||||
if (item && !item.deleted) {
|
||||
formattedItems.push(this._buildBubbleString(citationItem, this._getBackupStr(item)));
|
||||
}
|
||||
else {
|
||||
let formattedItem = this._buildBubbleString(citationItem, citationItem.backupText);
|
||||
formattedItem = `<span style="color: red;">${formattedItem}</span>`;
|
||||
formattedItems.push(formattedItem);
|
||||
}
|
||||
}
|
||||
return formattedItems.join(';');
|
||||
}
|
||||
|
||||
_getBackupStr(item) {
|
||||
var str = item.getField('firstCreator');
|
||||
|
||||
// Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field)
|
||||
if (!str) {
|
||||
str = Zotero.getString('punctuation.openingQMark') + item.getDisplayTitle() + Zotero.getString('punctuation.closingQMark');
|
||||
}
|
||||
|
||||
// Date
|
||||
var date = item.getField('date', true, true);
|
||||
if (date && (date = date.substr(0, 4)) !== '0000') {
|
||||
str += ', ' + date;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
_arrayBufferToBase64(buffer) {
|
||||
var binary = '';
|
||||
var bytes = new Uint8Array(buffer);
|
||||
var len = bytes.byteLength;
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return this._iframeWindow.btoa(binary);
|
||||
}
|
||||
|
||||
_dataURLtoBlob(dataurl) {
|
||||
let parts = dataurl.split(',');
|
||||
let mime = parts[0].match(/:(.*?);/)[1];
|
||||
if (parts[0].indexOf('base64') !== -1) {
|
||||
let bstr = atob(parts[1]);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
|
||||
return new this._iframeWindow.Blob([u8arr], { type: mime });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_openQuickFormatDialog(nodeId, citationData, filterLibraryIDs) {
|
||||
let that = this;
|
||||
let win;
|
||||
/**
|
||||
* Citation editing functions and propertiesaccessible to quickFormat.js and addCitationDialog.js
|
||||
*/
|
||||
let CI = function (citation, sortable, fieldIndexPromise, citationsByItemIDPromise, previewFn) {
|
||||
this.citation = citation;
|
||||
this.sortable = sortable;
|
||||
this.filterLibraryIDs = filterLibraryIDs;
|
||||
this.disableClassicDialog = true;
|
||||
}
|
||||
|
||||
CI.prototype = {
|
||||
/**
|
||||
* Execute a callback with a preview of the given citation
|
||||
* @return {Promise} A promise resolved with the previewed citation string
|
||||
*/
|
||||
preview: function () {
|
||||
Zotero.debug('CI: preview');
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort the citationItems within citation (depends on this.citation.properties.unsorted)
|
||||
* @return {Promise} A promise resolved with the previewed citation string
|
||||
*/
|
||||
sort: function () {
|
||||
Zotero.debug('CI: sort');
|
||||
return async function () {
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Accept changes to the citation
|
||||
* @param {Function} [progressCallback] A callback to be run when progress has changed.
|
||||
* Receives a number from 0 to 100 indicating current status.
|
||||
*/
|
||||
accept: async function (progressCallback) {
|
||||
Zotero.debug('CI: accept');
|
||||
if (progressCallback) progressCallback(100);
|
||||
|
||||
if (win) {
|
||||
win.close();
|
||||
}
|
||||
|
||||
let citation = {
|
||||
citationItems: this.citation.citationItems,
|
||||
properties: this.citation.properties
|
||||
}
|
||||
|
||||
for (let citationItem of citation.citationItems) {
|
||||
let itm = await Zotero.Items.getAsync(citationItem.id);
|
||||
delete citationItem.id;
|
||||
citationItem.uri = Zotero.URI.getItemURI(itm);
|
||||
citationItem.backupText = that._getBackupStr(itm);
|
||||
}
|
||||
|
||||
if (this.citation.citationItems.length) {
|
||||
that._postMessage({ action: 'setCitation', nodeId, citation });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of items used in the current document
|
||||
* @return {Promise} A promise resolved by the items
|
||||
*/
|
||||
getItems: async function () {
|
||||
Zotero.debug('CI: getItems');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let Citation = class {
|
||||
constructor(citationField, data, noteIndex) {
|
||||
if (!data) {
|
||||
data = { citationItems: [], properties: {} };
|
||||
}
|
||||
this.citationID = data.citationID;
|
||||
this.citationItems = data.citationItems;
|
||||
this.properties = data.properties;
|
||||
this.properties.noteIndex = noteIndex;
|
||||
|
||||
this._field = citationField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load citation item data
|
||||
* @param {Boolean} [promptToReselect=true] - will throw a MissingItemException if false
|
||||
* @returns {Promise{Number}}
|
||||
* - Zotero.Integration.NO_ACTION
|
||||
* - Zotero.Integration.UPDATE
|
||||
* - Zotero.Integration.REMOVE_CODE
|
||||
* - Zotero.Integration.DELETE
|
||||
*/
|
||||
loadItemData() {
|
||||
Zotero.debug('Citation: loadItemData');
|
||||
}
|
||||
|
||||
async handleMissingItem(idx) {
|
||||
Zotero.debug('Citation: handleMissingItem');
|
||||
}
|
||||
|
||||
async prepareForEditing() {
|
||||
Zotero.debug('Citation: prepareForEditing');
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
Zotero.debug('Citation: toJSON');
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the citation into CSL code representation
|
||||
* @returns {string}
|
||||
*/
|
||||
serialize() {
|
||||
Zotero.debug('Citation: serialize');
|
||||
}
|
||||
};
|
||||
|
||||
if (that.quickFormatWindow) {
|
||||
that.quickFormatWindow.close();
|
||||
that.quickFormatWindow = null;
|
||||
}
|
||||
|
||||
let citation = new Citation();
|
||||
citation.citationItems = citationData.citationItems;
|
||||
citation.properties = citationData.properties;
|
||||
let styleID = Zotero.Prefs.get('export.lastStyle');
|
||||
let locale = Zotero.Prefs.get('export.lastLocale');
|
||||
let csl = Zotero.Styles.get(styleID).getCiteProc(locale);
|
||||
var io = new CI(citation, csl.opt.sort_citations);
|
||||
|
||||
|
||||
var allOptions = 'chrome,centerscreen';
|
||||
// without this, Firefox gets raised with our windows under Compiz
|
||||
if (Zotero.isLinux) allOptions += ',dialog=no';
|
||||
// if(options) allOptions += ','+options;
|
||||
|
||||
var mode = (!Zotero.isMac && Zotero.Prefs.get('integration.keepAddCitationDialogRaised')
|
||||
? 'popup' : 'alwaysRaised') + ',resizable=false,centerscreen';
|
||||
|
||||
win = that.quickFormatWindow = Components.classes['@mozilla.org/embedcomp/window-watcher;1']
|
||||
.getService(Components.interfaces.nsIWindowWatcher)
|
||||
.openWindow(null, 'chrome://zotero/content/integration/quickFormat.xul', '', mode, {
|
||||
wrappedJSObject: io
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.EditorInstance = EditorInstance;
|
|
@ -3544,24 +3544,42 @@ var ZoteroPane = new function()
|
|||
};
|
||||
|
||||
|
||||
this.onNoteWindowClosed = async function (itemID, noteData) {
|
||||
var item = Zotero.Items.get(itemID);
|
||||
|
||||
if (noteData) {
|
||||
let changed = item.setNote(noteData.html);
|
||||
if (changed) {
|
||||
await item.saveTx({
|
||||
notifierData: {
|
||||
state: noteData.state
|
||||
}
|
||||
});
|
||||
}
|
||||
this.openBackupNoteWindow = function (itemID) {
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
// If note is still selected, show the editor again when the note window closes
|
||||
var selectedItems = this.getSelectedItems(true);
|
||||
if (selectedItems.length == 1 && itemID == selectedItems[0]) {
|
||||
ZoteroItemPane.onNoteSelected(item, this.collectionsView.editable);
|
||||
var name = null;
|
||||
|
||||
if (itemID) {
|
||||
let w = this.findBackupNoteWindow(itemID);
|
||||
if (w) {
|
||||
w.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a name for this window so we can focus it later
|
||||
//
|
||||
// Collection is only used on new notes, so we don't need to
|
||||
// include it in the name
|
||||
name = 'zotero-backup-note-' + itemID;
|
||||
}
|
||||
|
||||
var io = { itemID: itemID };
|
||||
window.openDialog('chrome://zotero/content/noteBackup.xul', name, 'chrome,resizable,centerscreen,dialog=false', io);
|
||||
}
|
||||
|
||||
|
||||
this.findBackupNoteWindow = function (itemID) {
|
||||
var name = 'zotero-backup-note-' + itemID;
|
||||
var wm = Services.wm;
|
||||
var e = wm.getEnumerator('zotero:note');
|
||||
while (e.hasMoreElements()) {
|
||||
var w = e.getNext();
|
||||
if (w.name == name) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ const xpcomFilesLocal = [
|
|||
'data/tags',
|
||||
'db',
|
||||
'duplicates',
|
||||
'editorInstance',
|
||||
'feedReader',
|
||||
'fulltext',
|
||||
'id',
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 918d6d2787b2abfd90a8f27d96e6a9cba097433e
|
||||
Subproject commit d60cd22eee9d5cca35dd5ad77d73f3d99c755c92
|
Loading…
Reference in a new issue