fx-compat: Note editor and links box fixes:
- Add links-box component (inside noteEditor.js). - Add related-box component and fix related pane. - Use tagsBox.jsx instead of tagsbox.xml in note editor links box popup. - Remove CSS styles and bindings for noteeditor, relatedbox and tagsbox.
This commit is contained in:
parent
4a1812e5ba
commit
db0ac723fa
24 changed files with 634 additions and 2306 deletions
|
@ -1,8 +0,0 @@
|
||||||
row > label:first-child
|
|
||||||
{
|
|
||||||
color: #7f7f7f;
|
|
||||||
}
|
|
||||||
|
|
||||||
textbox[type="styled"] {
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
|
@ -1,603 +0,0 @@
|
||||||
<?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 () => {
|
|
||||||
let n = 0;
|
|
||||||
while (!this._editorInstance && n++ < 100) {
|
|
||||||
await Zotero.Promise.delay(10);
|
|
||||||
}
|
|
||||||
await this._editorInstance._initPromise;
|
|
||||||
this._iframe.focus();
|
|
||||||
try {
|
|
||||||
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>
|
|
|
@ -1,351 +0,0 @@
|
||||||
<?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 *****
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!DOCTYPE bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
|
|
||||||
|
|
||||||
<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="related-box">
|
|
||||||
<implementation>
|
|
||||||
<!-- Modes are predefined settings groups for particular tasks -->
|
|
||||||
<field name="_mode">"view"</field>
|
|
||||||
<property name="mode" onget="return this._mode;">
|
|
||||||
<setter>
|
|
||||||
<![CDATA[
|
|
||||||
this.clickable = false;
|
|
||||||
this.editable = false;
|
|
||||||
|
|
||||||
switch (val) {
|
|
||||||
case 'view':
|
|
||||||
case 'merge':
|
|
||||||
case 'mergeedit':
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'edit':
|
|
||||||
this.clickable = true;
|
|
||||||
this.editable = true;
|
|
||||||
//this.clickHandler = this.showEditor;
|
|
||||||
//this.blurHandler = this.hideEditor;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw ("Invalid mode '" + val + "' in relatedbox.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
this._mode = val;
|
|
||||||
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
|
|
||||||
]]>
|
|
||||||
</setter>
|
|
||||||
</property>
|
|
||||||
|
|
||||||
<field name="itemRef"/>
|
|
||||||
<property name="item" onget="return this.itemRef;">
|
|
||||||
<setter>
|
|
||||||
<![CDATA[
|
|
||||||
this.itemRef = val;
|
|
||||||
this.refresh();
|
|
||||||
]]>
|
|
||||||
</setter>
|
|
||||||
</property>
|
|
||||||
<property name="summary">
|
|
||||||
<getter>
|
|
||||||
<![CDATA[
|
|
||||||
var r = "";
|
|
||||||
|
|
||||||
if (this.item) {
|
|
||||||
var keys = this.item.relatedItems;
|
|
||||||
if (keys.length) {
|
|
||||||
for (let key of keys) {
|
|
||||||
let item = Zotero.Items.getByLibraryAndKey(this.item.libraryID, key);
|
|
||||||
if (!item) {
|
|
||||||
Zotero.debug(`Related item ${this.item.libraryID}/${key} not found `
|
|
||||||
+ `for item ${this.item.libraryKey}`, 2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
r = r + item.getDisplayTitle() + ", ";
|
|
||||||
}
|
|
||||||
r = r.substr(0,r.length-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
]]>
|
|
||||||
</getter>
|
|
||||||
</property>
|
|
||||||
|
|
||||||
<constructor>
|
|
||||||
<![CDATA[
|
|
||||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'relatedbox');
|
|
||||||
]]>
|
|
||||||
</constructor>
|
|
||||||
|
|
||||||
<destructor>
|
|
||||||
<![CDATA[
|
|
||||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
|
||||||
]]>
|
|
||||||
</destructor>
|
|
||||||
|
|
||||||
<!-- TODO: Asyncify -->
|
|
||||||
<method name="notify">
|
|
||||||
<parameter name="event"/>
|
|
||||||
<parameter name="type"/>
|
|
||||||
<parameter name="ids"/>
|
|
||||||
<parameter name="extraData"/>
|
|
||||||
<body><![CDATA[
|
|
||||||
if (!this.item || !this.item.id) return;
|
|
||||||
|
|
||||||
// Refresh if this item has been modified
|
|
||||||
if (event == 'modify' && ids.includes(this.item.id)) {
|
|
||||||
this.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or if any listed items have been modified or deleted
|
|
||||||
if (event == 'modify' || event == 'delete') {
|
|
||||||
let libraryID = this.item.libraryID;
|
|
||||||
let relatedItemIDs = new Set(this.item.relatedItems
|
|
||||||
.map(key => Zotero.Items.getIDFromLibraryAndKey(libraryID, key)));
|
|
||||||
for (let id of ids) {
|
|
||||||
if (relatedItemIDs.has(id)) {
|
|
||||||
this.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]]></body>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<method name="refresh">
|
|
||||||
<body><![CDATA[
|
|
||||||
var addButton = this.id('addButton');
|
|
||||||
addButton.hidden = !this.editable;
|
|
||||||
|
|
||||||
var rows = this.id('relatedRows');
|
|
||||||
while(rows.hasChildNodes())
|
|
||||||
rows.removeChild(rows.firstChild);
|
|
||||||
|
|
||||||
if (this.item) {
|
|
||||||
var relatedKeys = this.item.relatedItems;
|
|
||||||
for (var i = 0; i < relatedKeys.length; i++) {
|
|
||||||
let key = relatedKeys[i];
|
|
||||||
let relatedItem = Zotero.Items.getByLibraryAndKey(
|
|
||||||
this.item.libraryID, key
|
|
||||||
);
|
|
||||||
if (!relatedItem) {
|
|
||||||
Zotero.debug(`Related item ${this.item.libraryID}/${key} not found `
|
|
||||||
+ `for item ${this.item.libraryKey}`, 2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let id = relatedItem.id;
|
|
||||||
let icon = document.createElement("image");
|
|
||||||
icon.className = "zotero-box-icon";
|
|
||||||
icon.setAttribute('src', relatedItem.getImageSrc());
|
|
||||||
|
|
||||||
var label = document.createElement("label");
|
|
||||||
label.className = "zotero-box-label";
|
|
||||||
label.setAttribute('value', relatedItem.getDisplayTitle());
|
|
||||||
label.setAttribute('crop','end');
|
|
||||||
label.setAttribute('flex','1');
|
|
||||||
|
|
||||||
var box = document.createElement('box');
|
|
||||||
box.setAttribute('onclick',
|
|
||||||
"document.getBindingParent(this).showItem(" + id + ")");
|
|
||||||
box.setAttribute('class','zotero-clicky');
|
|
||||||
box.setAttribute('flex','1');
|
|
||||||
box.appendChild(icon);
|
|
||||||
box.appendChild(label);
|
|
||||||
|
|
||||||
if (this.editable) {
|
|
||||||
var remove = document.createElement("label");
|
|
||||||
remove.setAttribute('value','-');
|
|
||||||
remove.setAttribute('onclick',
|
|
||||||
"document.getBindingParent(this).remove(" + id + ");");
|
|
||||||
remove.setAttribute('class','zotero-clicky zotero-clicky-minus');
|
|
||||||
}
|
|
||||||
|
|
||||||
var row = document.createElement("row");
|
|
||||||
row.appendChild(box);
|
|
||||||
if (this.editable) {
|
|
||||||
row.appendChild(remove);
|
|
||||||
}
|
|
||||||
rows.appendChild(row);
|
|
||||||
}
|
|
||||||
this.updateCount(rows.childNodes.length);
|
|
||||||
}
|
|
||||||
]]></body>
|
|
||||||
</method>
|
|
||||||
<method name="add">
|
|
||||||
<body><![CDATA[
|
|
||||||
return Zotero.spawn(function* () {
|
|
||||||
var io = {dataIn: null, dataOut: null, deferred: Zotero.Promise.defer()};
|
|
||||||
|
|
||||||
window.openDialog('chrome://zotero/content/selectItemsDialog.xul', '',
|
|
||||||
'chrome,dialog=no,centerscreen,resizable=yes', io);
|
|
||||||
|
|
||||||
yield io.deferred.promise;
|
|
||||||
|
|
||||||
if (!io.dataOut || !io.dataOut.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var relItems = yield Zotero.Items.getAsync(io.dataOut);
|
|
||||||
if (!relItems.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relItems[0].libraryID != this.item.libraryID) {
|
|
||||||
// FIXME
|
|
||||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
|
||||||
.getService(Components.interfaces.nsIPromptService);
|
|
||||||
ps.alert(null, "", "You cannot relate items in different libraries.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
yield Zotero.DB.executeTransaction(async function () {
|
|
||||||
for (let relItem of relItems) {
|
|
||||||
if (this.item.addRelatedItem(relItem)) {
|
|
||||||
await this.item.save({
|
|
||||||
skipDateModifiedUpdate: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (relItem.addRelatedItem(this.item)) {
|
|
||||||
await relItem.save({
|
|
||||||
skipDateModifiedUpdate: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
}, this);
|
|
||||||
]]></body>
|
|
||||||
</method>
|
|
||||||
<method name="remove">
|
|
||||||
<parameter name="id"/>
|
|
||||||
<body><![CDATA[
|
|
||||||
return Zotero.spawn(function* () {
|
|
||||||
var item = yield Zotero.Items.getAsync(id);
|
|
||||||
if (item) {
|
|
||||||
yield Zotero.DB.executeTransaction(async function () {
|
|
||||||
if (this.item.removeRelatedItem(item)) {
|
|
||||||
await this.item.save({
|
|
||||||
skipDateModifiedUpdate: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (item.removeRelatedItem(this.item)) {
|
|
||||||
await item.save({
|
|
||||||
skipDateModifiedUpdate: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
]]></body>
|
|
||||||
</method>
|
|
||||||
<method name="showItem">
|
|
||||||
<parameter name="id"/>
|
|
||||||
<body>
|
|
||||||
<![CDATA[
|
|
||||||
if(id)
|
|
||||||
{
|
|
||||||
var p;
|
|
||||||
if(window.ZoteroPane_Local)
|
|
||||||
{
|
|
||||||
p = window.ZoteroPane_Local;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var win;
|
|
||||||
|
|
||||||
if(window.opener && window.opener.ZoteroPane)
|
|
||||||
{
|
|
||||||
win = window.opener;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
|
||||||
.getService(Components.interfaces.nsIWindowMediator);
|
|
||||||
win = wm.getMostRecentWindow('navigator:browser');
|
|
||||||
if(!win)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = win.ZoteroPane;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.selectItem(id);
|
|
||||||
}
|
|
||||||
]]>
|
|
||||||
</body>
|
|
||||||
</method>
|
|
||||||
<method name="updateCount">
|
|
||||||
<parameter name="count"/>
|
|
||||||
<body>
|
|
||||||
<![CDATA[
|
|
||||||
if (count == null) {
|
|
||||||
var count = this.item.relatedItems.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
var str = 'pane.item.related.count.';
|
|
||||||
switch (count){
|
|
||||||
case 0:
|
|
||||||
str += 'zero';
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
str += 'singular';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
str += 'plural';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.id('relatedNum').value = Zotero.getString(str, [count]);
|
|
||||||
]]>
|
|
||||||
</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" class="zotero-box">
|
|
||||||
<xul:hbox align="center">
|
|
||||||
<xul:label id="relatedNum"/>
|
|
||||||
<xul:button id="addButton" label="&zotero.item.add;"
|
|
||||||
oncommand="this.parentNode.parentNode.parentNode.add();"/>
|
|
||||||
</xul:hbox>
|
|
||||||
<xul:grid flex="1">
|
|
||||||
<xul:columns>
|
|
||||||
<xul:column flex="1"/>
|
|
||||||
<xul:column/>
|
|
||||||
</xul:columns>
|
|
||||||
<xul:rows id="relatedRows"/>
|
|
||||||
</xul:grid>
|
|
||||||
</xul:vbox>
|
|
||||||
</content>
|
|
||||||
</binding>
|
|
||||||
</bindings>
|
|
File diff suppressed because it is too large
Load diff
|
@ -269,7 +269,7 @@ var ZoteroContextPane = new function () {
|
||||||
else {
|
else {
|
||||||
var node = _notesPaneDeck.selectedPanel;
|
var node = _notesPaneDeck.selectedPanel;
|
||||||
if (node.selectedIndex == 0) {
|
if (node.selectedIndex == 0) {
|
||||||
node.querySelector('textbox').focus();
|
node.querySelector('search-textbox').focus();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -873,7 +873,7 @@ var ZoteroContextPane = new function () {
|
||||||
);
|
);
|
||||||
// Related panel
|
// Related panel
|
||||||
var panelRelated = document.createXULElement('tabpanel');
|
var panelRelated = document.createXULElement('tabpanel');
|
||||||
var relatedBox = document.createXULElement('relatedbox');
|
var relatedBox = new (customElements.get('related-box'));
|
||||||
relatedBox.setAttribute('flex', '1');
|
relatedBox.setAttribute('flex', '1');
|
||||||
relatedBox.className = 'zotero-editpane-related';
|
relatedBox.className = 'zotero-editpane-related';
|
||||||
panelRelated.addEventListener('click', (event) => {
|
panelRelated.addEventListener('click', (event) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
***** BEGIN LICENSE BLOCK *****
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
Copyright © 2020 Corporation for Digital Scholarship
|
Copyright © 2021 Corporation for Digital Scholarship
|
||||||
Vienna, Virginia, USA
|
Vienna, Virginia, USA
|
||||||
https://www.zotero.org
|
https://www.zotero.org
|
||||||
|
|
||||||
|
@ -30,49 +30,21 @@
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.editable = false;
|
this._notitle = false;
|
||||||
this.displayTags = false;
|
|
||||||
this.displayRelated = false;
|
|
||||||
this.displayButton = false;
|
|
||||||
this.hideLinksContainer = false;
|
|
||||||
|
|
||||||
this.buttonCaption = null;
|
|
||||||
this.parentClickHandler = null;
|
|
||||||
this.keyDownHandler = null;
|
|
||||||
this.commandHandler = null;
|
|
||||||
this.clickHandler = null;
|
|
||||||
this.navigateHandler = null;
|
|
||||||
this.returnHandler = null;
|
|
||||||
|
|
||||||
this._mode = 'view';
|
this._mode = 'view';
|
||||||
this._destroyed = false;
|
this._item = null;
|
||||||
this._noteEditorID = Zotero.Utilities.randomString();
|
this._parentItem = null;
|
||||||
this._iframe = null;
|
this._iframe = null;
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
this._editorInstance = null;
|
this._editorInstance = null;
|
||||||
this._item = null;
|
this._destroyed = false;
|
||||||
this._parentItem = null;
|
|
||||||
|
|
||||||
this.clickable = false;
|
|
||||||
this.editable = false;
|
|
||||||
this.saveOnEdit = false;
|
|
||||||
this.showTypeMenu = false;
|
|
||||||
this.hideEmptyFields = false;
|
|
||||||
this.clickByRow = false;
|
|
||||||
this.clickByItem = false;
|
|
||||||
|
|
||||||
this.clickHandler = null;
|
|
||||||
this.blurHandler = null;
|
|
||||||
this.eventHandlers = [];
|
|
||||||
|
|
||||||
this._mode = 'view';
|
|
||||||
|
|
||||||
this.content = MozXULElement.parseXULToFragment(`
|
this.content = MozXULElement.parseXULToFragment(`
|
||||||
<box flex="1" tooltip="html-tooltip" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
<box flex="1" tooltip="html-tooltip" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
<div id="note-editor" style="display: flex;flex-direction: column;flex-grow: 1;" xmlns="http://www.w3.org/1999/xhtml">
|
<div id="note-editor" style="display: flex;flex-direction: column;flex-grow: 1;" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<iframe id="editor-view" style="border: 0;width: 100%;flex-grow: 1;" src="resource://zotero/note-editor/editor.html" type="content"/>
|
<iframe id="editor-view" style="border: 0;width: 100%;flex-grow: 1;" src="resource://zotero/note-editor/editor.html" type="content"/>
|
||||||
<div id="links-container">
|
<div id="links-container">
|
||||||
<div id="links-box"/>
|
<links-box id="links-box" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</box>
|
</box>
|
||||||
|
@ -81,10 +53,6 @@
|
||||||
<menupopup id="editor-menu"/>
|
<menupopup id="editor-menu"/>
|
||||||
</popupset>
|
</popupset>
|
||||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||||
|
|
||||||
this._destroyed = false;
|
|
||||||
this._noteEditorID = Zotero.Utilities.randomString();
|
|
||||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'file'], 'noteeditor');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
@ -116,9 +84,10 @@
|
||||||
}, true);
|
}, true);
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
});
|
});
|
||||||
shadow.appendChild(content);
|
shadow.append(content);
|
||||||
|
|
||||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'itembox');
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'itembox');
|
||||||
|
this.notitle = !!this.getAttribute('notitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -132,10 +101,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
// Empty the DOM. We will rebuild if reconnected.
|
this.replaceChildren();
|
||||||
while (this.lastChild) {
|
|
||||||
this.removeChild(this.lastChild);
|
|
||||||
}
|
|
||||||
this.destroy();
|
this.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,11 +113,11 @@
|
||||||
if (this._editorInstance) {
|
if (this._editorInstance) {
|
||||||
this._editorInstance.saveSync();
|
this._editorInstance.saveSync();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
getCurrentInstance = () => {
|
getCurrentInstance = () => {
|
||||||
return this._editorInstance;
|
return this._editorInstance;
|
||||||
}
|
};
|
||||||
|
|
||||||
initEditor = async (state, reloaded) => {
|
initEditor = async (state, reloaded) => {
|
||||||
if (this._editorInstance) {
|
if (this._editorInstance) {
|
||||||
|
@ -160,7 +126,7 @@
|
||||||
|
|
||||||
// Automatically upgrade editable v1 note before it's loaded
|
// Automatically upgrade editable v1 note before it's loaded
|
||||||
// TODO: Remove this at some point
|
// TODO: Remove this at some point
|
||||||
if (this.editable) {
|
if (this._mode == 'edit') {
|
||||||
await Zotero.Notes.upgradeSchemaV1(this._item);
|
await Zotero.Notes.upgradeSchemaV1(this._item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,35 +139,35 @@
|
||||||
popup: this._id('editor-menu'),
|
popup: this._id('editor-menu'),
|
||||||
onNavigate: this._navigateHandler,
|
onNavigate: this._navigateHandler,
|
||||||
viewMode: this.viewMode,
|
viewMode: this.viewMode,
|
||||||
readOnly: !this.editable,
|
readOnly: this._mode != 'edit',
|
||||||
disableUI: this._mode === 'merge',
|
disableUI: this._mode == 'merge',
|
||||||
onReturn: this._returnHandler,
|
onReturn: this._returnHandler,
|
||||||
placeholder: this.placeholder
|
placeholder: this.placeholder
|
||||||
});
|
});
|
||||||
if (this._onInitCallback) {
|
if (this._onInitCallback) {
|
||||||
this._onInitCallback();
|
this._onInitCallback();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onInit = (callback) => {
|
onInit = (callback) => {
|
||||||
if (this._editorInstance) {
|
if (this._editorInstance) {
|
||||||
return callback();
|
return callback();
|
||||||
}
|
}
|
||||||
this._onInitCallback = callback;
|
this._onInitCallback = callback;
|
||||||
}
|
};
|
||||||
|
|
||||||
notify = async (event, type, ids, extraData) => {
|
notify = async (event, type, ids, extraData) => {
|
||||||
if (this._editorInstance) {
|
if (this._editorInstance) {
|
||||||
await this._editorInstance.notify(event, type, ids, extraData);
|
await this._editorInstance.notify(event, type, ids, extraData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.item) {
|
if (!this._item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Try to use the state from the item save event
|
// Try to use the state from the item save event
|
||||||
let id = this.item.id;
|
let id = this._item.id;
|
||||||
if (ids.includes(id)) {
|
if (ids.includes(id)) {
|
||||||
if (event === 'delete') {
|
if (event == 'delete') {
|
||||||
if (this._returnHandler) {
|
if (this._returnHandler) {
|
||||||
this._returnHandler();
|
this._returnHandler();
|
||||||
}
|
}
|
||||||
|
@ -214,17 +180,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let curValue = this.item.note;
|
let curValue = this._item.note;
|
||||||
if (curValue !== this._lastHtmlValue) {
|
if (curValue !== this._lastHtmlValue) {
|
||||||
this.initEditor(null, true);
|
this.initEditor(null, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._lastHtmlValue = this.item.note;
|
this._lastHtmlValue = this._item.note;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this._id('links-container').hidden = !(this.displayTags && this.displayRelated) || this._hideLinksContainer;
|
if (ids.includes(id) || this._parentItem && ids.includes(this._parentItem.id)) {
|
||||||
// this._id('links-box').refresh();
|
this._id('links-box').refresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
set notitle(val) {
|
||||||
|
this._notitle = !!val;
|
||||||
|
this._id('links-box').notitle = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
set navigateHandler(val) {
|
set navigateHandler(val) {
|
||||||
|
@ -238,46 +210,28 @@
|
||||||
this._returnHandler = val;
|
this._returnHandler = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Public properties
|
|
||||||
//
|
|
||||||
|
|
||||||
// Modes are predefined settings groups for particular tasks
|
|
||||||
get mode() {
|
get mode() {
|
||||||
return this._mode;
|
return this._mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
set mode(val) {
|
set mode(val) {
|
||||||
// Duplicate default property settings here
|
var displayLinks = true;
|
||||||
this.editable = false;
|
|
||||||
this.displayTags = false;
|
|
||||||
this.displayRelated = false;
|
|
||||||
this.displayButton = false;
|
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case 'view':
|
|
||||||
case 'merge':
|
case 'merge':
|
||||||
this.editable = false;
|
displayLinks = false;
|
||||||
|
case 'view':
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'edit':
|
case 'edit':
|
||||||
this.editable = true;
|
|
||||||
this.parentClickHandler = this.selectParent;
|
|
||||||
this.keyDownHandler = this.handleKeyDown;
|
|
||||||
this.commandHandler = this.save;
|
|
||||||
this.displayTags = true;
|
|
||||||
this.displayRelated = true;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw ("Invalid mode '" + val + "' in noteEditor.js");
|
throw new Error(`Invalid mode '${val}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._mode = val;
|
this._mode = val;
|
||||||
// document.getAnonymousNodes(this)[0].setAttribute('mode', val);
|
this._id('links-container').hidden = !displayLinks;
|
||||||
// this._id('links-box').mode = val;
|
this._id('links-box').mode = val;
|
||||||
// this._id('links-container').hidden = !(this.displayTags && this.displayRelated) || this._hideLinksContainer;
|
|
||||||
// this._id('links-box').refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get item() {
|
get item() {
|
||||||
|
@ -291,7 +245,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._item && this._item.id && this._item.id === val.id) {
|
if (this._item && this._item.id && this._item.id == val.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,12 +257,12 @@
|
||||||
this._lastHtmlValue = val.note;
|
this._lastHtmlValue = val.note;
|
||||||
this._item = val;
|
this._item = val;
|
||||||
|
|
||||||
// var parentKey = this._item.parentKey;
|
var parentKey = this._item.parentKey;
|
||||||
// if (parentKey) {
|
if (parentKey) {
|
||||||
// this.parentItem = Zotero.Items.getByLibraryAndKey(this._item.libraryID, parentKey);
|
this.parentItem = Zotero.Items.getByLibraryAndKey(this._item.libraryID, parentKey);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// this._id('links-box').item = this._item;
|
this._id('links-box').item = this._item;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// `item` field can be set before the constructor is called
|
// `item` field can be set before the constructor is called
|
||||||
|
@ -328,7 +282,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initEditor();
|
this.initEditor();
|
||||||
// this._id('links-box').item = this._item;
|
this._id('links-box').item = this._item;
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,7 +292,7 @@
|
||||||
|
|
||||||
set parentItem(val) {
|
set parentItem(val) {
|
||||||
this._parentItem = val;
|
this._parentItem = val;
|
||||||
// this._id('links-box').parentItem = val;
|
this._id('links-box').parentItem = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
async focus() {
|
async focus() {
|
||||||
|
@ -371,3 +325,239 @@
|
||||||
}
|
}
|
||||||
customElements.define("note-editor", NoteEditor);
|
customElements.define("note-editor", NoteEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
let TagsBoxContainer = require('containers/tagsBoxContainer').default;
|
||||||
|
|
||||||
|
class LinksBox extends XULElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._mode = 'view';
|
||||||
|
this._item = null;
|
||||||
|
this._parentItem = null;
|
||||||
|
this._destroyed = false;
|
||||||
|
|
||||||
|
this.content = MozXULElement.parseXULToFragment(`
|
||||||
|
<div id="links-box" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<div class="grid">
|
||||||
|
<div id="parent-label" class="label" hidden="true"/>
|
||||||
|
<div id="parent-value" class="value zotero-clicky" hidden="true"/>
|
||||||
|
|
||||||
|
<div id="related-label" class="label"/>
|
||||||
|
<div id="related-value" class="value zotero-clicky"/>
|
||||||
|
|
||||||
|
<div id="tags-label" class="label"/>
|
||||||
|
<div id="tags-value" class="value zotero-clicky"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<popupset xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
<menupopup id="related-popup" width="300">
|
||||||
|
<related-box id="related"/>
|
||||||
|
</menupopup>
|
||||||
|
<menupopup id="tags-popup" width="300" ignorekeys="true">
|
||||||
|
<div style="display: flex" id="tags-box-container" xmlns="http://www.w3.org/1999/xhtml"/>
|
||||||
|
</menupopup>
|
||||||
|
</popupset>
|
||||||
|
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this._destroyed = false;
|
||||||
|
window.addEventListener("unload", this.destroy);
|
||||||
|
|
||||||
|
var shadow = this.attachShadow({ mode: "open" });
|
||||||
|
var s1 = document.createElement("link");
|
||||||
|
s1.rel = "stylesheet";
|
||||||
|
s1.href = "chrome://zotero-platform/content/noteEditor.css";
|
||||||
|
shadow.append(s1);
|
||||||
|
|
||||||
|
var s2 = document.createElement("link");
|
||||||
|
s2.rel = "stylesheet";
|
||||||
|
s2.href = "chrome://global/skin/global.css";
|
||||||
|
shadow.append(s2);
|
||||||
|
|
||||||
|
shadow.append(document.importNode(this.content, true));
|
||||||
|
|
||||||
|
this._id('parent-value').addEventListener('click', this._parentClickHandler);
|
||||||
|
this._id('related-value').addEventListener('click', this._relatedClickHandler);
|
||||||
|
this._id('tags-value').addEventListener('click', this._tagsClickHandler);
|
||||||
|
|
||||||
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'itembox');
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this._destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.removeEventListener("unload", this.destroy);
|
||||||
|
this._destroyed = true;
|
||||||
|
|
||||||
|
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.replaceChildren();
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
set item(val) {
|
||||||
|
this._item = val;
|
||||||
|
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('related-popup').addEventListener('click', (event) => {
|
||||||
|
let target = event.originalTarget;
|
||||||
|
if (target.classList.contains('zotero-box-label')) {
|
||||||
|
this._id('related-popup').hidePopup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set mode(val) {
|
||||||
|
this._mode = val;
|
||||||
|
this._id('related').mode = val;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
set notitle(val) {
|
||||||
|
this._notitle = val;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
set parentItem(val) {
|
||||||
|
this._parentItem = val;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this._updateParentRow();
|
||||||
|
this._updateTagsSummary();
|
||||||
|
this._updateRelatedSummary();
|
||||||
|
|
||||||
|
// TODO: Update tagsBox container state via tagsBoxRef and imperative handle, instead of recreating it
|
||||||
|
let container = this._id('tags-box-container');
|
||||||
|
ReactDOM.unmountComponentAtNode(container);
|
||||||
|
if (this._item) {
|
||||||
|
var tagsBoxRef = React.createRef();
|
||||||
|
ReactDOM.render(
|
||||||
|
<TagsBoxContainer
|
||||||
|
key={'tagsBox-' + this._item.id}
|
||||||
|
item={this._item}
|
||||||
|
editable={this._mode == 'edit'}
|
||||||
|
ref={tagsBoxRef}
|
||||||
|
/>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateParentRow() {
|
||||||
|
let hidden = !this._parentItem || !!this._notitle;
|
||||||
|
this._id('parent-value').hidden = hidden;
|
||||||
|
this._id('parent-label').hidden = hidden;
|
||||||
|
if (!hidden) {
|
||||||
|
this._id('parent-label').replaceChildren(Zotero.getString('pane.item.parentItem'));
|
||||||
|
this._id('parent-value').replaceChildren(document.createTextNode(this._parentItem.getDisplayTitle(true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateRelatedSummary() {
|
||||||
|
var r = '';
|
||||||
|
if (this._item) {
|
||||||
|
var keys = this._item.relatedItems;
|
||||||
|
if (keys.length) {
|
||||||
|
for (let key of keys) {
|
||||||
|
let item = Zotero.Items.getByLibraryAndKey(this._item.libraryID, key);
|
||||||
|
if (!item) {
|
||||||
|
Zotero.debug(`Related item ${this._item.libraryID}/${key} not found `
|
||||||
|
+ `for item ${this._item.libraryKey}`, 2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
r = r + item.getDisplayTitle() + ", ";
|
||||||
|
}
|
||||||
|
r = r.slice(0, -2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let v = r;
|
||||||
|
|
||||||
|
if (!v || v == '') {
|
||||||
|
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
this._id('related-label').innerText = Zotero.getString('itemFields.related')
|
||||||
|
+ Zotero.getString('punctuation.colon');
|
||||||
|
this._id('related-value').innerText = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateTagsSummary() {
|
||||||
|
var r = '';
|
||||||
|
|
||||||
|
if (this._item) {
|
||||||
|
var tags = this._item.getTags();
|
||||||
|
|
||||||
|
// Sort tags alphabetically
|
||||||
|
var collation = Zotero.getLocaleCollation();
|
||||||
|
tags.sort((a, b) => collation.compareString(1, a.tag, b.tag));
|
||||||
|
|
||||||
|
for (let i = 0; i < tags.length; i++) {
|
||||||
|
r = r + tags[i].tag + ", ";
|
||||||
|
}
|
||||||
|
r = r.slice(0, -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
let v = r;
|
||||||
|
|
||||||
|
if (!v || v == '') {
|
||||||
|
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
this._id('tags-label').innerText = Zotero.getString('itemFields.tags')
|
||||||
|
+ Zotero.getString('punctuation.colon');
|
||||||
|
this._id('tags-value').innerText = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
_parentClickHandler = () => {
|
||||||
|
if (!this._item || !this._item.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var parentID = this._item.parentID;
|
||||||
|
var win = Zotero.getMainWindow();
|
||||||
|
if (win) {
|
||||||
|
win.ZoteroPane.selectItem(parentID);
|
||||||
|
win.Zotero_Tabs.select('zotero-pane');
|
||||||
|
win.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_relatedClickHandler = (event) => {
|
||||||
|
var relatedList = this._item.relatedItems;
|
||||||
|
if (relatedList.length > 0) {
|
||||||
|
this._id('related-popup').openPopup(this, 'topleft topleft', 0, 0, false);
|
||||||
|
}
|
||||||
|
else if (this._mode == 'edit') {
|
||||||
|
this._id('related').add();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_tagsClickHandler = (event) => {
|
||||||
|
this._id('tags-popup').openPopup(this, 'topleft topleft', 0, 0, false);
|
||||||
|
// If editable and no existing tags, open new empty row
|
||||||
|
if (this._mode == 'edit' && !this._item.getTags().length) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this._id('tags-popup').querySelector('.tags-box-header button').click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_id(id) {
|
||||||
|
return this.shadowRoot.querySelector(`[id=${id}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define("links-box", LinksBox);
|
||||||
|
}
|
||||||
|
|
265
chrome/content/zotero/elements/relatedBox.js
Normal file
265
chrome/content/zotero/elements/relatedBox.js
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2021 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://www.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 *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
{
|
||||||
|
class RelatedBox extends XULElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._mode = 'view';
|
||||||
|
this._item = null;
|
||||||
|
this._destroyed = false;
|
||||||
|
|
||||||
|
this.content = MozXULElement.parseXULToFragment(`
|
||||||
|
<box flex="1" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
<div style="flex-grow: 1" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<div class="header">
|
||||||
|
<label id="related-num"/>
|
||||||
|
<button id="related-add">&zotero.item.add;</button>
|
||||||
|
</div>
|
||||||
|
<div id="related-grid" class="grid"/>
|
||||||
|
</div>
|
||||||
|
</box>
|
||||||
|
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||||
|
|
||||||
|
this._destroyed = false;
|
||||||
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'relatedbox');
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this._destroyed = false;
|
||||||
|
window.addEventListener("unload", this.destroy);
|
||||||
|
|
||||||
|
let shadow = this.attachShadow({ mode: "open" });
|
||||||
|
|
||||||
|
let s1 = document.createElement("link");
|
||||||
|
s1.rel = "stylesheet";
|
||||||
|
s1.href = "chrome://zotero-platform/content/relatedBox.css";
|
||||||
|
shadow.append(s1);
|
||||||
|
|
||||||
|
let content = document.importNode(this.content, true);
|
||||||
|
shadow.append(content);
|
||||||
|
|
||||||
|
this._id('related-add').addEventListener('click', this.add);
|
||||||
|
|
||||||
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'relatedbox');
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this._destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.removeEventListener("unload", this.destroy);
|
||||||
|
this._destroyed = true;
|
||||||
|
|
||||||
|
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.replaceChildren();
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
get mode() {
|
||||||
|
return this._mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
set mode(val) {
|
||||||
|
switch (val) {
|
||||||
|
case 'view':
|
||||||
|
case 'merge':
|
||||||
|
case 'mergeedit':
|
||||||
|
case 'edit':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Invalid mode '${val}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._mode = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
get item() {
|
||||||
|
return this._item;
|
||||||
|
}
|
||||||
|
|
||||||
|
set item(val) {
|
||||||
|
this._item = val;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(event, type, ids, extraData) {
|
||||||
|
if (!this._item || !this._item.id) return;
|
||||||
|
|
||||||
|
// Refresh if this item has been modified
|
||||||
|
if (event == 'modify' && ids.includes(this._item.id)) {
|
||||||
|
this.refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or if any listed items have been modified or deleted
|
||||||
|
if (event == 'modify' || event == 'delete') {
|
||||||
|
let libraryID = this._item.libraryID;
|
||||||
|
let relatedItemIDs = new Set(this._item.relatedItems.map(key => Zotero.Items.getIDFromLibraryAndKey(libraryID, key)));
|
||||||
|
for (let id of ids) {
|
||||||
|
if (relatedItemIDs.has(id)) {
|
||||||
|
this.refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this._id('related-add').hidden = this._mode == 'edit';
|
||||||
|
|
||||||
|
let grid = this._id('related-grid');
|
||||||
|
grid.replaceChildren();
|
||||||
|
|
||||||
|
if (this._item) {
|
||||||
|
let relatedKeys = this._item.relatedItems;
|
||||||
|
for (let i = 0; i < relatedKeys.length; i++) {
|
||||||
|
let key = relatedKeys[i];
|
||||||
|
let relatedItem = Zotero.Items.getByLibraryAndKey(
|
||||||
|
this._item.libraryID, key
|
||||||
|
);
|
||||||
|
if (!relatedItem) {
|
||||||
|
Zotero.debug(`Related item ${this._item.libraryID}/${key} not found `
|
||||||
|
+ `for item ${this._item.libraryKey}`, 2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let id = relatedItem.id;
|
||||||
|
let icon = document.createElement("img");
|
||||||
|
icon.src = relatedItem.getImageSrc();
|
||||||
|
|
||||||
|
let label = document.createElement("label");
|
||||||
|
label.append(relatedItem.getDisplayTitle());
|
||||||
|
|
||||||
|
let box = document.createElement('div');
|
||||||
|
box.addEventListener('click', () => this._handleShowItem(id));
|
||||||
|
box.className = 'box zotero-clicky';
|
||||||
|
box.appendChild(icon);
|
||||||
|
box.appendChild(label);
|
||||||
|
|
||||||
|
grid.append(box);
|
||||||
|
|
||||||
|
if (this._mode == 'edit') {
|
||||||
|
let remove = document.createElement("label");
|
||||||
|
remove.addEventListener('click', () => this._handleRemove(id));
|
||||||
|
remove.className = 'zotero-clicky zotero-clicky-minus';
|
||||||
|
remove.append('-');
|
||||||
|
grid.append(remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._updateCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add = async () => {
|
||||||
|
let io = { dataIn: null, dataOut: null, deferred: Zotero.Promise.defer() };
|
||||||
|
window.openDialog('chrome://zotero/content/selectItemsDialog.xhtml', '',
|
||||||
|
'chrome,dialog=no,centerscreen,resizable=yes', io);
|
||||||
|
|
||||||
|
await io.deferred.promise;
|
||||||
|
if (!io.dataOut || !io.dataOut.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let relItems = await Zotero.Items.getAsync(io.dataOut);
|
||||||
|
if (!relItems.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (relItems[0].libraryID != this._item.libraryID) {
|
||||||
|
Zotero.alert.alert(null, "", "You cannot relate items in different libraries.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Zotero.DB.executeTransaction(async () => {
|
||||||
|
for (let relItem of relItems) {
|
||||||
|
if (this._item.addRelatedItem(relItem)) {
|
||||||
|
await this._item.save({
|
||||||
|
skipDateModifiedUpdate: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (relItem.addRelatedItem(this._item)) {
|
||||||
|
await relItem.save({
|
||||||
|
skipDateModifiedUpdate: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async _handleRemove(id) {
|
||||||
|
let item = await Zotero.Items.getAsync(id);
|
||||||
|
if (item) {
|
||||||
|
await Zotero.DB.executeTransaction(async () => {
|
||||||
|
if (this._item.removeRelatedItem(item)) {
|
||||||
|
await this._item.save({
|
||||||
|
skipDateModifiedUpdate: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (item.removeRelatedItem(this._item)) {
|
||||||
|
await item.save({
|
||||||
|
skipDateModifiedUpdate: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleShowItem(id) {
|
||||||
|
let win = Zotero.getMainWindow();
|
||||||
|
if (win) {
|
||||||
|
win.ZoteroPane.selectItem(id);
|
||||||
|
win.Zotero_Tabs.select('zotero-pane');
|
||||||
|
win.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateCount() {
|
||||||
|
let count = this._item.relatedItems.length;
|
||||||
|
let str = 'pane.item.related.count.';
|
||||||
|
switch (count) {
|
||||||
|
case 0:
|
||||||
|
str += 'zero';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
str += 'singular';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
str += 'plural';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this._id('related-num').replaceChildren(Zotero.getString(str, [count]));
|
||||||
|
}
|
||||||
|
|
||||||
|
_id(id) {
|
||||||
|
return this.shadowRoot.querySelector(`[id=${id}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define("related-box", RelatedBox);
|
||||||
|
}
|
|
@ -18,8 +18,14 @@
|
||||||
<script>
|
<script>
|
||||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
Services.scriptloader.loadSubScript("chrome://zotero/content/include.js", this);
|
Services.scriptloader.loadSubScript("chrome://zotero/content/include.js", this);
|
||||||
|
Services.scriptloader.loadSubScript("resource://zotero/require.js", this);
|
||||||
|
|
||||||
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/noteEditor.js", this);
|
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/noteEditor.js", this);
|
||||||
|
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/relatedBox.js", this);
|
||||||
Services.scriptloader.loadSubScript("chrome://zotero/content/note.js", this);
|
Services.scriptloader.loadSubScript("chrome://zotero/content/note.js", this);
|
||||||
|
|
||||||
|
var React = require('react');
|
||||||
|
var ReactDOM = require('react-dom');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<keyset>
|
<keyset>
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/menulistItemTypes.js", this);
|
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/menulistItemTypes.js", this);
|
||||||
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/itemBox.js", this);
|
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/itemBox.js", this);
|
||||||
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/noteEditor.js", this);
|
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/noteEditor.js", this);
|
||||||
|
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/relatedBox.js", this);
|
||||||
|
|
||||||
Services.scriptloader.loadSubScript("chrome://zotero/content/tabs.js", this);
|
Services.scriptloader.loadSubScript("chrome://zotero/content/tabs.js", this);
|
||||||
Services.scriptloader.loadSubScript("chrome://zotero/content/zoteroPane.js", this);
|
Services.scriptloader.loadSubScript("chrome://zotero/content/zoteroPane.js", this);
|
||||||
|
@ -1137,7 +1138,7 @@
|
||||||
</tabpanel>
|
</tabpanel>
|
||||||
|
|
||||||
<tabpanel>
|
<tabpanel>
|
||||||
<relatedbox id="zotero-editpane-related" class="zotero-editpane-related" flex="1"/>
|
<related-box id="zotero-editpane-related" class="zotero-editpane-related" flex="1"/>
|
||||||
</tabpanel>
|
</tabpanel>
|
||||||
</tabpanels>
|
</tabpanels>
|
||||||
</tabbox>
|
</tabbox>
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
/* Don't collapse blank note parent labels, since it prevents access to parent */
|
|
||||||
#citeLabel[onclick]:hover
|
|
||||||
{
|
|
||||||
cursor: pointer !important;
|
|
||||||
min-height: 1.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tagsPopup {
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
row label
|
|
||||||
{
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
row > label, row > hbox
|
|
||||||
{
|
|
||||||
margin-top: 1px !important;
|
|
||||||
margin-bottom: 1px !important;
|
|
||||||
-moz-box-pack: start;
|
|
||||||
-moz-margin-start: 1px !important;
|
|
||||||
-moz-margin-end: 5px !important;
|
|
||||||
padding: 0 2px 0 2px !important;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
row > hbox
|
|
||||||
{
|
|
||||||
-moz-box-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
row > label:first-child
|
|
||||||
{
|
|
||||||
text-align: right;
|
|
||||||
font-weight: bold;
|
|
||||||
-moz-margin-start: 5px !important;
|
|
||||||
-moz-margin-end: 0 !important;
|
|
||||||
width: 62px;
|
|
||||||
text-align: right;
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
vbox > hbox:first-child > linksbox {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
vbox > hbox:not(:first-child) > linksbox {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
textbox[type="styled"] {
|
|
||||||
border-width: 0 1px 0 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: rgb(204, 204, 204);
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
textbox {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
row > label {
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
4
scss/_noteEditor.scss
Normal file
4
scss/_noteEditor.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
@import "components/noteEditor";
|
||||||
|
@import "components/clicky";
|
||||||
|
@import "components/tagsBox";
|
||||||
|
@import "components/autosuggest";
|
2
scss/_relatedBox.scss
Normal file
2
scss/_relatedBox.scss
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
@import "components/relatedBox";
|
||||||
|
@import "components/clicky";
|
26
scss/components/_noteEditor.scss
Normal file
26
scss/components/_noteEditor.scss
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#links-box {
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-top: 1px !important;
|
||||||
|
margin-bottom: 1px !important;
|
||||||
|
padding: 0 2px 0 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #7f7f7f;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 62px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
scss/components/_relatedBox.scss
Normal file
42
scss/components/_relatedBox.scss
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
padding-left: 10px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
min-width: 79px;
|
||||||
|
margin: 5px 6px 3px;
|
||||||
|
padding-top: 1px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
color: ButtonText;
|
||||||
|
text-shadow: none;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
|
||||||
|
.box {
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
margin-left: 5px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-left: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,6 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
//width: 330px;
|
//width: 330px;
|
||||||
|
|
||||||
// This is necessary for XUL layout to prevent children
|
|
||||||
// container to force its height for the parent
|
|
||||||
height: 0;
|
|
||||||
|
|
||||||
.tags-box-header {
|
.tags-box-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
|
1
scss/noteEditor-mac.scss
Normal file
1
scss/noteEditor-mac.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "noteEditor";
|
1
scss/noteEditor-unix.scss
Normal file
1
scss/noteEditor-unix.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "noteEditor";
|
1
scss/noteEditor-win.scss
Normal file
1
scss/noteEditor-win.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "noteEditor";
|
1
scss/relatedBox-mac.scss
Normal file
1
scss/relatedBox-mac.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "relatedBox";
|
1
scss/relatedBox-unix.scss
Normal file
1
scss/relatedBox-unix.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "relatedBox";
|
1
scss/relatedBox-win.scss
Normal file
1
scss/relatedBox-win.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "relatedBox";
|
Loading…
Add table
Add a link
Reference in a new issue