mirror of
https://github.com/zotero/zotero.git
synced 2026-10-11 03:38:25 +00:00
604 lines
18 KiB
XML
604 lines
18 KiB
XML
<?xml version="1.0"?>
|
|
<!--
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright © 2009 Center for History and New Media
|
|
George Mason University, Fairfax, Virginia, USA
|
|
http://zotero.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 *****
|
|
-->
|
|
|
|
<bindings xmlns="http://www.mozilla.org/xbl"
|
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
|
|
<binding id="note-editor">
|
|
<resources>
|
|
<stylesheet src="chrome://zotero/skin/bindings/noteeditor.css"/>
|
|
<stylesheet src="chrome://zotero-platform/content/noteeditor.css"/>
|
|
</resources>
|
|
|
|
<implementation>
|
|
<!--
|
|
Public properties
|
|
-->
|
|
<field name="editable">false</field>
|
|
<field name="displayTags">false</field>
|
|
<field name="displayRelated">false</field>
|
|
<field name="displayButton">false</field>
|
|
<field name="hideLinksContainer"/>
|
|
|
|
<field name="buttonCaption"/>
|
|
<field name="parentClickHandler"/>
|
|
<field name="keyDownHandler"/>
|
|
<field name="commandHandler"/>
|
|
<field name="clickHandler"/>
|
|
<field name="navigateHandler"/>
|
|
|
|
<field name="returnHandler"/>
|
|
|
|
<constructor><![CDATA[
|
|
this._destroyed = false;
|
|
this._noteEditorID = Zotero.Utilities.randomString();
|
|
this._iframe = document.getAnonymousElementByAttribute(this, 'anonid', 'editor-view');
|
|
this._iframe.addEventListener('DOMContentLoaded', (e) => {
|
|
// For iframes without chrome priviledges, for unknown reasons,
|
|
// dataTransfer.getData() returns empty value for `drop` event
|
|
// when dragging something from the outside of Zotero
|
|
this._iframe.contentWindow.addEventListener('drop', (event) => {
|
|
this._iframe.contentWindow.wrappedJSObject.droppedData = Components.utils.cloneInto({
|
|
'text/plain': event.dataTransfer.getData('text/plain'),
|
|
'text/html': event.dataTransfer.getData('text/html'),
|
|
'zotero/annotation': event.dataTransfer.getData('zotero/annotation'),
|
|
'zotero/item': event.dataTransfer.getData('zotero/item')
|
|
}, this._iframe.contentWindow);
|
|
}, true);
|
|
this._initialized = true;
|
|
});
|
|
|
|
window.fillTooltip = (tooltip) => {
|
|
let node = window.document.tooltipNode.closest('*[title]');
|
|
if (!node || !node.getAttribute('title')) {
|
|
return false;
|
|
}
|
|
tooltip.setAttribute('label', node.getAttribute('title'));
|
|
return true;
|
|
}
|
|
|
|
this.saveSync = () => {
|
|
if (this._editorInstance) {
|
|
this._editorInstance.saveSync();
|
|
}
|
|
}
|
|
|
|
this.getCurrentInstance = () => {
|
|
return this._editorInstance;
|
|
}
|
|
|
|
this.initEditor = async (state, reloaded) => {
|
|
if (this._editorInstance) {
|
|
this._editorInstance.uninit();
|
|
}
|
|
|
|
// Automatically upgrade editable v1 note before it's loaded
|
|
// TODO: Remove this at some point
|
|
if (this.editable) {
|
|
await Zotero.Notes.upgradeSchemaV1(this._item);
|
|
}
|
|
|
|
this._editorInstance = new Zotero.EditorInstance();
|
|
await this._editorInstance.init({
|
|
state,
|
|
item: this._item,
|
|
reloaded,
|
|
iframeWindow: document.getAnonymousElementByAttribute(this, 'anonid', 'editor-view').contentWindow,
|
|
popup: document.getAnonymousElementByAttribute(this, 'anonid', 'editor-menu'),
|
|
onNavigate: this._navigateHandler,
|
|
viewMode: this.viewMode,
|
|
readOnly: !this.editable,
|
|
disableUI: this.mode == 'merge',
|
|
onReturn: this._returnHandler,
|
|
placeholder: this.placeholder
|
|
});
|
|
if (this._onInitCallback) {
|
|
this._onInitCallback();
|
|
}
|
|
}
|
|
|
|
this.onInit = (callback) => {
|
|
if (this._editorInstance) {
|
|
return callback();
|
|
}
|
|
this._onInitCallback = callback;
|
|
}
|
|
|
|
this.notify = async (event, type, ids, extraData) => {
|
|
if (this._editorInstance) {
|
|
await this._editorInstance.notify(event, type, ids, extraData);
|
|
}
|
|
|
|
if (!this.item) return;
|
|
// Try to use the state from the item save event
|
|
let id = this.item.id;
|
|
if (ids.includes(id)) {
|
|
if (event == 'delete') {
|
|
if (this._returnHandler) {
|
|
this._returnHandler();
|
|
}
|
|
}
|
|
else {
|
|
let state = extraData && extraData[id] && extraData[id].state;
|
|
if (state) {
|
|
if (extraData[id].noteEditorID !== this._editorInstance.instanceID) {
|
|
this.initEditor(state, true);
|
|
}
|
|
}
|
|
else {
|
|
let curValue = this.item.note;
|
|
if (curValue !== this._lastHtmlValue) {
|
|
this.initEditor(null, true);
|
|
}
|
|
}
|
|
this._lastHtmlValue = this.item.note;
|
|
}
|
|
}
|
|
|
|
this._id('links-container').hidden = !(this.displayTags && this.displayRelated) || this._hideLinksContainer;
|
|
this._id('links-box').refresh();
|
|
}
|
|
|
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'file'], 'noteeditor');
|
|
]]></constructor>
|
|
|
|
<property name="editorInstance" onget="return this._editorInstance"/>
|
|
|
|
<!-- Modes are predefined settings groups for particular tasks -->
|
|
<field name="_mode">"view"</field>
|
|
<property name="mode" onget="return this._mode;">
|
|
<setter>
|
|
<![CDATA[
|
|
// Duplicate default property settings here
|
|
this.editable = false;
|
|
this.displayTags = false;
|
|
this.displayRelated = false;
|
|
this.displayButton = false;
|
|
|
|
switch (val) {
|
|
case 'view':
|
|
case 'merge':
|
|
this.editable = false;
|
|
break;
|
|
|
|
case 'edit':
|
|
this.editable = true;
|
|
this.parentClickHandler = this.selectParent;
|
|
this.keyDownHandler = this.handleKeyDown;
|
|
this.commandHandler = this.save;
|
|
this.displayTags = true;
|
|
this.displayRelated = true;
|
|
break;
|
|
|
|
default:
|
|
throw ("Invalid mode '" + val + "' in noteeditor.xml");
|
|
}
|
|
|
|
this._mode = val;
|
|
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
|
|
this._id('links-box').mode = val;
|
|
this._id('links-container').hidden = !(this.displayTags && this.displayRelated) || this._hideLinksContainer;
|
|
this._id('links-box').refresh();
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<field name="returnHandler"/>
|
|
<property name="returnHandler" onget="return this._returnHandler;">
|
|
<setter>
|
|
<![CDATA[
|
|
this._returnHandler = val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<field name="_parentItem"/>
|
|
<property name="parentItem" onget="return this._parentItem;">
|
|
<setter>
|
|
<![CDATA[
|
|
this._parentItem = this._id('links-box').parentItem = val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<field name="_item"/>
|
|
<property name="item" onget="return this._item;">
|
|
<setter><![CDATA[
|
|
// The binding can be immediately destroyed
|
|
// (which i.e. happens in merge dialog)
|
|
if (this._destroyed) {
|
|
return;
|
|
}
|
|
|
|
if (this._item && this._item.id && this._item.id === val.id) {
|
|
return;
|
|
}
|
|
|
|
if (this._editorInstance) {
|
|
this._editorInstance.uninit();
|
|
this._editorInstance = null;
|
|
}
|
|
|
|
this._lastHtmlValue = val.note;
|
|
this._item = val;
|
|
|
|
// var parentKey = this._item.parentKey;
|
|
// if (parentKey) {
|
|
// this.parentItem = Zotero.Items.getByLibraryAndKey(this._item.libraryID, parentKey);
|
|
// }
|
|
|
|
this._id('links-box').item = this._item;
|
|
|
|
(async () => {
|
|
// `item` field can be set before the constructor is called
|
|
// or noteeditor is attached to dom (which happens in the
|
|
// merge dialog i.e.), therefore we wait for the initialization
|
|
let n = 0;
|
|
while (!this._initialized && !this._destroyed) {
|
|
if (n >= 1000) {
|
|
throw new Error('Waiting for noteeditor initialization failed');
|
|
}
|
|
await Zotero.Promise.delay(10);
|
|
n++;
|
|
}
|
|
|
|
if (this._destroyed) {
|
|
return;
|
|
}
|
|
|
|
this.initEditor();
|
|
this._id('links-box').item = this._item;
|
|
})();
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="linksOnTop">
|
|
<setter>
|
|
<![CDATA[
|
|
// if (val) {
|
|
// var container = this._id('links-container');
|
|
// var parent = container.parentNode;
|
|
// var sib = container.nextSibling;
|
|
// while (parent.firstChild !== container) {
|
|
// parent.insertBefore(parent.removeChild(parent.firstChild), sib);
|
|
// }
|
|
// }
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="navigateHandler">
|
|
<setter>
|
|
<![CDATA[
|
|
if (this._editorInstance) {
|
|
this._editorInstance.onNavigate = val;
|
|
}
|
|
this._navigateHandler = val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="hideLinksContainer">
|
|
<setter>
|
|
<![CDATA[
|
|
this._hideLinksContainer = val;
|
|
this._id('links-container').hidden = val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<field name="collection"/>
|
|
|
|
<destructor>
|
|
<![CDATA[
|
|
Zotero.Notifier.unregisterObserver(this._notifierID);
|
|
if (this._editorInstance) {
|
|
this._editorInstance.uninit();
|
|
}
|
|
this._destroyed = true;
|
|
this._initialized = false;
|
|
this._editorInstance = null;
|
|
]]>
|
|
</destructor>
|
|
|
|
<method name="save">
|
|
<body><![CDATA[
|
|
return (async () => {
|
|
|
|
})();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="focus">
|
|
<body>
|
|
<![CDATA[
|
|
(async () => {
|
|
let n = 0;
|
|
while (!this._editorInstance && n++ < 100) {
|
|
await Zotero.Promise.delay(10);
|
|
}
|
|
await this._editorInstance._initPromise;
|
|
this._iframe.focus();
|
|
this._editorInstance.focus();
|
|
})();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="focusFirst">
|
|
<body>
|
|
<![CDATA[
|
|
(async () => {
|
|
try {
|
|
let n = 0;
|
|
while (!this._editorInstance && n++ < 100) {
|
|
await Zotero.Promise.delay(10);
|
|
}
|
|
await this._editorInstance._initPromise;
|
|
this._iframe.focus();
|
|
this._editorInstance._iframeWindow.document.querySelector('.toolbar-button-return').focus();
|
|
}
|
|
catch (e) {
|
|
}
|
|
})();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_id">
|
|
<parameter name="id"/>
|
|
<body>
|
|
<![CDATA[
|
|
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<content>
|
|
<xul:vbox xbl:inherits="flex" style="display: flex;flex-direction: column;flex-grow: 1;">
|
|
<!-- Notice: Update query selector for `iframe[anonid="editor-view"]` in contextPane.js if updating this -->
|
|
<xul:iframe tooltip="editor-tooltip" anonid="editor-view" flex="1" overflow="auto" style="border: 0;width: 100%;flex-grow: 1;"
|
|
frameBorder="0" src="resource://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>
|
|
</xul:vbox>
|
|
|
|
</content>
|
|
</binding>
|
|
|
|
|
|
<binding id="links-box">
|
|
<implementation>
|
|
<field name="itemRef"/>
|
|
<property name="item" onget="return this.itemRef;">
|
|
<setter>
|
|
<![CDATA[
|
|
this.itemRef = val;
|
|
|
|
this.id('tags').item = this.item;
|
|
this.id('related').item = this.item;
|
|
this.refresh();
|
|
|
|
// Hide popup to prevent it being visible out of the context or
|
|
// in some cases even invisible but still blocking the next click
|
|
this.id('relatedPopup').addEventListener('click', (event) => {
|
|
let target = event.originalTarget;
|
|
if (target.classList.contains('zotero-box-label')) {
|
|
this.id('relatedPopup').hidePopup();
|
|
}
|
|
});
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
<property name="mode">
|
|
<setter>
|
|
<![CDATA[
|
|
this.id('related').mode = val;
|
|
this.id('tags').mode = val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
<field name="_parentItem"/>
|
|
<property name="parentItem" onget="return this._parentItem;">
|
|
<setter>
|
|
<![CDATA[
|
|
this._parentItem = val;
|
|
|
|
var parentText = this.id('parentText');
|
|
if (parentText.firstChild) {
|
|
parentText.removeChild(parentText.firstChild);
|
|
}
|
|
|
|
if (this._parentItem && this.getAttribute('notitle') != '1') {
|
|
this.id('parent-row').hidden = undefined;
|
|
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
|
|
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
|
|
}
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
<method name="tagsClick">
|
|
<body><![CDATA[
|
|
this.id('tags').reload();
|
|
var x = this.boxObject.screenX;
|
|
var y = this.boxObject.screenY;
|
|
this.id('tagsPopup').openPopupAtScreen(x, y, false);
|
|
|
|
// If editable and no existing tags, open new empty row
|
|
var tagsBox = this.id('tags');
|
|
if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
|
|
this.id('tags').newTag();
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="refresh">
|
|
<body><![CDATA[
|
|
this.updateTagsSummary();
|
|
this.updateRelatedSummary();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="updateTagsSummary">
|
|
<body><![CDATA[
|
|
var v = this.id('tags').summary;
|
|
|
|
if (!v || v == "") {
|
|
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
|
}
|
|
|
|
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
|
|
+ Zotero.getString('punctuation.colon');
|
|
this.id('tagsClick').value = v;
|
|
]]></body>
|
|
</method>
|
|
<method name="relatedClick">
|
|
<body><![CDATA[
|
|
var relatedList = this.item.relatedItems;
|
|
if (relatedList.length > 0) {
|
|
var x = this.boxObject.screenX;
|
|
var y = this.boxObject.screenY;
|
|
this.id('relatedPopup').openPopupAtScreen(x, y, false);
|
|
}
|
|
else {
|
|
this.id('related').add();
|
|
}
|
|
]]></body>
|
|
</method>
|
|
<method name="updateRelatedSummary">
|
|
<body><![CDATA[
|
|
var v = this.id('related').summary;
|
|
|
|
if (!v || v == "") {
|
|
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
|
}
|
|
|
|
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
|
|
+ Zotero.getString('punctuation.colon');
|
|
this.id('relatedClick').value = v;
|
|
]]></body>
|
|
</method>
|
|
<method name="parentClick">
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.item || !this.item.id) {
|
|
return;
|
|
}
|
|
|
|
if (document.getElementById('zotero-pane')) {
|
|
var zp = ZoteroPane;
|
|
}
|
|
else {
|
|
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
|
.getService(Components.interfaces.nsIWindowMediator);
|
|
|
|
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
|
|
|
if (!lastWin) {
|
|
var lastWin = window.open();
|
|
}
|
|
|
|
if (lastWin.ZoteroOverlay && !lastWin.ZoteroPane.isShowing()) {
|
|
lastWin.ZoteroOverlay.toggleDisplay(true);
|
|
}
|
|
|
|
var zp = lastWin.ZoteroPane;
|
|
}
|
|
|
|
Zotero.spawn(function* () {
|
|
var parentID = this.item.parentID;
|
|
yield zp.clearQuicksearch();
|
|
zp.selectItem(parentID);
|
|
}, this);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
<method name="id">
|
|
<parameter name="id"/>
|
|
<body>
|
|
<![CDATA[
|
|
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
<content>
|
|
<xul:vbox xbl:inherits="flex">
|
|
<xul:grid>
|
|
<xul:columns>
|
|
<xul:column/>
|
|
<xul:column flex="1"/>
|
|
</xul:columns>
|
|
<xul:rows>
|
|
<xul:row id="parent-row" hidden="true">
|
|
<xul:label id="parentLabel"/>
|
|
<xul:label id="parentText" class="zotero-clicky" crop="end"
|
|
onclick="document.getBindingParent(this).parentClick();"/>
|
|
</xul:row>
|
|
<xul:row>
|
|
<xul:label id="relatedLabel"/>
|
|
<xul:label id="relatedClick" class="zotero-clicky" crop="end"
|
|
onclick="document.getBindingParent(this).relatedClick();"/>
|
|
</xul:row>
|
|
<xul:row>
|
|
<xul:label id="tagsLabel"/>
|
|
<xul:label id="tagsClick" class="zotero-clicky" crop="end"
|
|
onclick="document.getBindingParent(this).tagsClick();"/>
|
|
</xul:row>
|
|
</xul:rows>
|
|
</xul:grid>
|
|
<xul:popupset>
|
|
<xul:menupopup id="relatedPopup" width="300" onpopupshowing="this.firstChild.refresh();">
|
|
<xul:relatedbox id="related" flex="1"/>
|
|
</xul:menupopup>
|
|
<!-- The onpopup* stuff is an ugly hack to keep track of when the
|
|
popup is open (and not the descendent autocomplete popup, which also
|
|
seems to get triggered by these events for reasons that are less than
|
|
clear) so that we can manually refresh the popup if it's open after
|
|
autocomplete is used to prevent it from becoming unresponsive
|
|
|
|
Note: Code in tagsbox.xml is dependent on the DOM path between the
|
|
tagsbox and tagsLabel above, so be sure to update fixPopup() if it changes
|
|
-->
|
|
<xul:menupopup id="tagsPopup" ignorekeys="true"
|
|
onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ /* DEBUG: it would be nice to make this work -- if (this.firstChild.count==0){ this.firstChild.newTag(); } */ this.setAttribute('showing', 'true'); }"
|
|
onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
|
|
<xul:tagsbox id="tags" flex="1" mode="edit"/>
|
|
</xul:menupopup>
|
|
</xul:popupset>
|
|
</xul:vbox>
|
|
</content>
|
|
</binding>
|
|
</bindings>
|