diff --git a/chrome/content/zotero-platform/mac/noteeditor.css b/chrome/content/zotero-platform/mac/noteeditor.css
deleted file mode 100644
index 9465847767..0000000000
--- a/chrome/content/zotero-platform/mac/noteeditor.css
+++ /dev/null
@@ -1,8 +0,0 @@
-row > label:first-child
-{
- color: #7f7f7f;
-}
-
-textbox[type="styled"] {
- border-style: none;
-}
\ No newline at end of file
diff --git a/chrome/content/zotero-platform/unix/noteeditor.css b/chrome/content/zotero-platform/unix/noteeditor.css
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/chrome/content/zotero-platform/win/noteeditor.css b/chrome/content/zotero-platform/win/noteeditor.css
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/chrome/content/zotero/bindings/noteeditor.xml b/chrome/content/zotero/bindings/noteeditor.xml
deleted file mode 100644
index 8dc4d400cc..0000000000
--- a/chrome/content/zotero/bindings/noteeditor.xml
+++ /dev/null
@@ -1,603 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- false
- false
- false
- false
-
-
-
-
-
-
-
-
-
-
-
- {
- // 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');
- ]]>
-
-
-
-
- "view"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- // `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;
- })();
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
-
- })();
- ]]>
-
-
-
-
- {
- let n = 0;
- while (!this._editorInstance && n++ < 100) {
- await Zotero.Promise.delay(10);
- }
- await this._editorInstance._initPromise;
- this._iframe.focus();
- this._editorInstance.focus();
- })();
- ]]>
-
-
-
-
-
- {
- 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) {
- }
- })();
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- let target = event.originalTarget;
- if (target.classList.contains('zotero-box-label')) {
- this.id('relatedPopup').hidePopup();
- }
- });
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0) {
- var x = this.boxObject.screenX;
- var y = this.boxObject.screenY;
- this.id('relatedPopup').openPopupAtScreen(x, y, false);
- }
- else {
- this.id('related').add();
- }
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/chrome/content/zotero/bindings/relatedbox.xml b/chrome/content/zotero/bindings/relatedbox.xml
deleted file mode 100644
index aa080d51fc..0000000000
--- a/chrome/content/zotero/bindings/relatedbox.xml
+++ /dev/null
@@ -1,351 +0,0 @@
-
-
-
-
-
-
-
-
-
- "view"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Zotero.Items.getIDFromLibraryAndKey(libraryID, key)));
- for (let id of ids) {
- if (relatedItemIDs.has(id)) {
- this.refresh();
- return;
- }
- }
- }
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/chrome/content/zotero/bindings/tagsbox.xml b/chrome/content/zotero/bindings/tagsbox.xml
deleted file mode 100644
index 67f74ba1f5..0000000000
--- a/chrome/content/zotero/bindings/tagsbox.xml
+++ /dev/null
@@ -1,1183 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- false
-
-
-
-
-
- "view"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- collation.compareString(1, a.tag, b.tag));
-
- for (let i = 0; i < tags.length; i++) {
- r = r + tags[i].tag + ", ";
- }
- r = r.substr(0,r.length-2);
- }
-
- return r;
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- val.split("/")[1] == 'tagColors') && this.item) {
- this.reload();
- return;
- }
- }
- else if (type == 'item-tag') {
- let itemID, tagID;
-
- for (let i=0; i parseInt(x));
- if (!this.item || itemID != this.item.id) {
- continue;
- }
- let data = extraData[ids[i]];
- let tagName = data.tag;
- let tagType = data.type;
-
- if (event == 'add') {
- var newTabIndex = this.add(tagName, tagType);
- if (newTabIndex == -1) {
- return;
- }
- if (this._tabDirection == -1) {
- if (this._lastTabIndex > newTabIndex) {
- this._lastTabIndex++;
- }
- }
- else if (this._tabDirection == 1) {
- if (this._lastTabIndex > newTabIndex) {
- this._lastTabIndex++;
- }
- }
- }
- else if (event == 'modify') {
- let oldTagName = data.old.tag;
- this.remove(oldTagName);
- this.add(tagName, tagType);
- }
- else if (event == 'remove') {
- var oldTabIndex = this.remove(tagName);
- if (oldTabIndex == -1) {
- return;
- }
- if (this._tabDirection == -1) {
- if (this._lastTabIndex > oldTabIndex) {
- this._lastTabIndex--;
- }
- }
- else if (this._tabDirection == 1) {
- if (this._lastTabIndex >= oldTabIndex) {
- this._lastTabIndex--;
- }
- }
- }
- }
-
- this.updateCount();
- }
- else if (type == 'tag') {
- if (event == 'modify') {
- this.reload();
- return;
- }
- }
- }.bind(this));
- ]]>
-
-
-
-
- collation.compareString(1, a.tag, b.tag));
-
- for (let i=0; i
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 29 ) || firstSpace > 29) {
- valueElement.setAttribute('crop', 'end');
- valueElement.setAttribute('value',valueText);
- }
- else {
- // Wrap to multiple lines
- valueElement.appendChild(document.createTextNode(valueText));
- }
-
- // Tag color
- var colorData = this._tagColors.get(valueText);
- if (colorData) {
- valueElement.style.color = colorData.color;
- valueElement.style.fontWeight = 'bold';
- }
-
- return valueElement;
- ]]>
-
-
-
-
-
-
-
-
- 1) {
- t.setAttribute('multiline', true);
- t.setAttribute('rows', rows);
- }
- // Add auto-complete
- else {
- t.setAttribute('type', 'autocomplete');
- t.setAttribute('autocompletesearch', 'zotero');
- let params = {
- fieldName: fieldName,
- libraryID: this.item.libraryID
- };
- params.itemID = itemID ? itemID : '';
- t.setAttribute(
- 'autocompletesearchparam', JSON.stringify(params)
- );
- t.setAttribute('completeselectedindex', true);
- }
-
- var box = elem.parentNode;
- box.replaceChild(t, elem);
-
- t.setAttribute('onblur', "return document.getBindingParent(this).blurHandler(event)");
- t.setAttribute('onkeypress', "return document.getBindingParent(this).handleKeyPress(event)");
- t.setAttribute('onpaste', "return document.getBindingParent(this).handlePaste(event)");
-
- this._tabDirection = false;
- this._lastTabIndex = tabindex;
-
- // Prevent error when clicking between a changed field
- // and another -- there's probably a better way
- if (!t.select) {
- return;
- }
- t.select();
-
- return t;
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- val.trim());
-
- // Modifying existing tag with a single new one
- if (!isNew && tags.length < 2) {
- if (value !== "") {
- if (oldValue !== value) {
- // The existing textbox will be removed in notify()
- this.removeRow(row);
- this.add(value);
- if (event.type != 'blur') {
- this._focusField();
- }
- try {
- this.item.replaceTag(oldValue, value);
- yield this.item.saveTx();
- }
- catch (e) {
- this.reload();
- throw e;
- }
- }
- }
- // Existing tag cleared
- else {
- try {
- this.removeRow(row);
- if (event.type != 'blur') {
- this._focusField();
- }
- this.item.removeTag(oldValue);
- yield this.item.saveTx();
- }
- catch (e) {
- this.reload();
- throw e;
- }
- }
- }
- // Multiple tags
- else if (tags.length > 1) {
- var lastTag = row == row.parentNode.lastChild;
-
- if (!isNew) {
- // If old tag isn't in array, remove it
- if (tags.indexOf(oldValue) == -1) {
- this.item.removeTag(oldValue);
- }
- // If old tag is staying, restore the textbox
- // immediately. This isn't strictly necessary, but it
- // makes the transition nicer.
- else {
- textbox.value = textbox.getAttribute('value');
- this.textboxToLabel(textbox);
- }
- }
-
- tags.forEach(tag => this.item.addTag(tag));
- yield this.item.saveTx();
-
- if (lastTag) {
- this._lastTabIndex = this.item.getTags().length;
- }
-
- this.reload();
- }
- // Single tag at end
- else {
- if (event.type == 'blur') {
- this.removeRow(row);
- }
- else {
- textbox.value = '';
- }
- this.add(value);
- this.item.addTag(value);
- try {
- yield this.item.saveTx();
- }
- catch (e) {
- this.reload();
- throw e;
- }
- }
- }.bind(this));
- ]]>
-
-
-
-
-
- {
- row.firstChild.nextSibling.click();
- }, 50);
-
- return row;
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
- // Ignore textbox at end
- && labels[i].tagName != 'textbox') {
- labels[i].setAttribute('ztabindex', index);
- continue;
- }
-
- elem.setAttribute('ztabindex', index);
- rowsElement.insertBefore(row, labels[i].parentNode);
- newTabIndex = index;
- inserted = true;
- }
- if (!inserted) {
- newTabIndex = i + 1;
- elem.setAttribute('ztabindex', newTabIndex);
- rowsElement.appendChild(row);
- }
-
- this.updateCount(this.count + 1);
-
- return newTabIndex;
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/chrome/content/zotero/contextPane.js b/chrome/content/zotero/contextPane.js
index 00883a0c25..38fba2b853 100644
--- a/chrome/content/zotero/contextPane.js
+++ b/chrome/content/zotero/contextPane.js
@@ -269,7 +269,7 @@ var ZoteroContextPane = new function () {
else {
var node = _notesPaneDeck.selectedPanel;
if (node.selectedIndex == 0) {
- node.querySelector('textbox').focus();
+ node.querySelector('search-textbox').focus();
return true;
}
else {
@@ -873,7 +873,7 @@ var ZoteroContextPane = new function () {
);
// Related panel
var panelRelated = document.createXULElement('tabpanel');
- var relatedBox = document.createXULElement('relatedbox');
+ var relatedBox = new (customElements.get('related-box'));
relatedBox.setAttribute('flex', '1');
relatedBox.className = 'zotero-editpane-related';
panelRelated.addEventListener('click', (event) => {
diff --git a/chrome/content/zotero/elements/noteEditor.js b/chrome/content/zotero/elements/noteEditor.js
index 03ebe25108..ec1d865ff9 100644
--- a/chrome/content/zotero/elements/noteEditor.js
+++ b/chrome/content/zotero/elements/noteEditor.js
@@ -1,7 +1,7 @@
/*
***** BEGIN LICENSE BLOCK *****
- Copyright © 2020 Corporation for Digital Scholarship
+ Copyright © 2021 Corporation for Digital Scholarship
Vienna, Virginia, USA
https://www.zotero.org
@@ -30,49 +30,21 @@
constructor() {
super();
- this.editable = 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._notitle = false;
this._mode = 'view';
- this._destroyed = false;
- this._noteEditorID = Zotero.Utilities.randomString();
+ this._item = null;
+ this._parentItem = null;
this._iframe = null;
this._initialized = true;
this._editorInstance = null;
- this._item = null;
- 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._destroyed = false;
this.content = MozXULElement.parseXULToFragment(`
@@ -81,10 +53,6 @@
`, ['chrome://zotero/locale/zotero.dtd']);
-
- this._destroyed = false;
- this._noteEditorID = Zotero.Utilities.randomString();
- this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'file'], 'noteeditor');
}
connectedCallback() {
@@ -116,9 +84,10 @@
}, true);
this._initialized = true;
});
- shadow.appendChild(content);
+ shadow.append(content);
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'itembox');
+ this.notitle = !!this.getAttribute('notitle');
}
destroy() {
@@ -132,10 +101,7 @@
}
disconnectedCallback() {
- // Empty the DOM. We will rebuild if reconnected.
- while (this.lastChild) {
- this.removeChild(this.lastChild);
- }
+ this.replaceChildren();
this.destroy();
}
@@ -147,11 +113,11 @@
if (this._editorInstance) {
this._editorInstance.saveSync();
}
- }
+ };
getCurrentInstance = () => {
return this._editorInstance;
- }
+ };
initEditor = async (state, reloaded) => {
if (this._editorInstance) {
@@ -160,7 +126,7 @@
// Automatically upgrade editable v1 note before it's loaded
// TODO: Remove this at some point
- if (this.editable) {
+ if (this._mode == 'edit') {
await Zotero.Notes.upgradeSchemaV1(this._item);
}
@@ -173,35 +139,35 @@
popup: this._id('editor-menu'),
onNavigate: this._navigateHandler,
viewMode: this.viewMode,
- readOnly: !this.editable,
- disableUI: this._mode === 'merge',
+ readOnly: this._mode != 'edit',
+ disableUI: this._mode == 'merge',
onReturn: this._returnHandler,
placeholder: this.placeholder
});
if (this._onInitCallback) {
this._onInitCallback();
}
- }
+ };
onInit = (callback) => {
if (this._editorInstance) {
return callback();
}
this._onInitCallback = callback;
- }
+ };
notify = async (event, type, ids, extraData) => {
if (this._editorInstance) {
await this._editorInstance.notify(event, type, ids, extraData);
}
- if (!this.item) {
+ if (!this._item) {
return;
}
// 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 (event === 'delete') {
+ if (event == 'delete') {
if (this._returnHandler) {
this._returnHandler();
}
@@ -214,17 +180,23 @@
}
}
else {
- let curValue = this.item.note;
+ let curValue = this._item.note;
if (curValue !== this._lastHtmlValue) {
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;
- // this._id('links-box').refresh();
+ if (ids.includes(id) || this._parentItem && ids.includes(this._parentItem.id)) {
+ this._id('links-box').refresh();
+ }
+ };
+
+ set notitle(val) {
+ this._notitle = !!val;
+ this._id('links-box').notitle = val;
}
set navigateHandler(val) {
@@ -238,46 +210,28 @@
this._returnHandler = val;
}
- //
- // Public properties
- //
-
- // Modes are predefined settings groups for particular tasks
get mode() {
return this._mode;
}
set mode(val) {
- // Duplicate default property settings here
- this.editable = false;
- this.displayTags = false;
- this.displayRelated = false;
- this.displayButton = false;
-
+ var displayLinks = true;
switch (val) {
- case 'view':
case 'merge':
- this.editable = false;
+ displayLinks = false;
+ case 'view':
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.js");
+ throw new Error(`Invalid mode '${val}'`);
}
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();
+ this._id('links-container').hidden = !displayLinks;
+ this._id('links-box').mode = val;
}
get item() {
@@ -291,7 +245,7 @@
return;
}
- if (this._item && this._item.id && this._item.id === val.id) {
+ if (this._item && this._item.id && this._item.id == val.id) {
return;
}
@@ -303,12 +257,12 @@
this._lastHtmlValue = val.note;
this._item = val;
- // var parentKey = this._item.parentKey;
- // if (parentKey) {
- // this.parentItem = Zotero.Items.getByLibraryAndKey(this._item.libraryID, parentKey);
- // }
+ var parentKey = this._item.parentKey;
+ if (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 () => {
// `item` field can be set before the constructor is called
@@ -328,7 +282,7 @@
}
this.initEditor();
- // this._id('links-box').item = this._item;
+ this._id('links-box').item = this._item;
})();
}
@@ -338,7 +292,7 @@
set parentItem(val) {
this._parentItem = val;
- // this._id('links-box').parentItem = val;
+ this._id('links-box').parentItem = val;
}
async focus() {
@@ -371,3 +325,239 @@
}
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(`
+
+
+
+
+
+ `, ['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(
+ ,
+ 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);
+}
diff --git a/chrome/content/zotero/elements/relatedBox.js b/chrome/content/zotero/elements/relatedBox.js
new file mode 100644
index 0000000000..7b66df325a
--- /dev/null
+++ b/chrome/content/zotero/elements/relatedBox.js
@@ -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 .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+"use strict";
+
+{
+ class RelatedBox extends XULElement {
+ constructor() {
+ super();
+
+ this._mode = 'view';
+ this._item = null;
+ this._destroyed = false;
+
+ this.content = MozXULElement.parseXULToFragment(`
+
+
+
+ `, ['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);
+}
diff --git a/chrome/content/zotero/note.xhtml b/chrome/content/zotero/note.xhtml
index 4e27511d4f..2d8538a006 100644
--- a/chrome/content/zotero/note.xhtml
+++ b/chrome/content/zotero/note.xhtml
@@ -18,8 +18,14 @@
diff --git a/chrome/content/zotero/zoteroPane.xhtml b/chrome/content/zotero/zoteroPane.xhtml
index 13d0403d1a..0b15243c81 100644
--- a/chrome/content/zotero/zoteroPane.xhtml
+++ b/chrome/content/zotero/zoteroPane.xhtml
@@ -79,6 +79,7 @@
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/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/zoteroPane.js", this);
@@ -1137,7 +1138,7 @@
-
+
diff --git a/chrome/skin/default/zotero/bindings/noteeditor.css b/chrome/skin/default/zotero/bindings/noteeditor.css
deleted file mode 100644
index 54caa07ba8..0000000000
--- a/chrome/skin/default/zotero/bindings/noteeditor.css
+++ /dev/null
@@ -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);
-}
\ No newline at end of file
diff --git a/chrome/skin/default/zotero/bindings/tagsbox.css b/chrome/skin/default/zotero/bindings/tagsbox.css
deleted file mode 100644
index 3ed688e30c..0000000000
--- a/chrome/skin/default/zotero/bindings/tagsbox.css
+++ /dev/null
@@ -1,7 +0,0 @@
-textbox {
- margin: 0;
-}
-
-row > label {
- white-space: pre;
-}
diff --git a/scss/_noteEditor.scss b/scss/_noteEditor.scss
new file mode 100644
index 0000000000..320440a13c
--- /dev/null
+++ b/scss/_noteEditor.scss
@@ -0,0 +1,4 @@
+@import "components/noteEditor";
+@import "components/clicky";
+@import "components/tagsBox";
+@import "components/autosuggest";
\ No newline at end of file
diff --git a/scss/_relatedBox.scss b/scss/_relatedBox.scss
new file mode 100644
index 0000000000..17d79e1e27
--- /dev/null
+++ b/scss/_relatedBox.scss
@@ -0,0 +1,2 @@
+@import "components/relatedBox";
+@import "components/clicky";
diff --git a/scss/components/_noteEditor.scss b/scss/components/_noteEditor.scss
new file mode 100644
index 0000000000..b32cb22361
--- /dev/null
+++ b/scss/components/_noteEditor.scss
@@ -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
+ }
+ }
+}
diff --git a/scss/components/_relatedBox.scss b/scss/components/_relatedBox.scss
new file mode 100644
index 0000000000..d6982aa3fa
--- /dev/null
+++ b/scss/components/_relatedBox.scss
@@ -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;
+ }
+ }
+}
diff --git a/scss/components/_tagsBox.scss b/scss/components/_tagsBox.scss
index b377f46ab3..42d4d4da5d 100644
--- a/scss/components/_tagsBox.scss
+++ b/scss/components/_tagsBox.scss
@@ -16,10 +16,6 @@
flex-grow: 1;
//width: 330px;
- // This is necessary for XUL layout to prevent children
- // container to force its height for the parent
- height: 0;
-
.tags-box-header {
display: flex;
padding-left: 10px;
diff --git a/scss/noteEditor-mac.scss b/scss/noteEditor-mac.scss
new file mode 100644
index 0000000000..e58630d37a
--- /dev/null
+++ b/scss/noteEditor-mac.scss
@@ -0,0 +1 @@
+@import "noteEditor";
diff --git a/scss/noteEditor-unix.scss b/scss/noteEditor-unix.scss
new file mode 100644
index 0000000000..e58630d37a
--- /dev/null
+++ b/scss/noteEditor-unix.scss
@@ -0,0 +1 @@
+@import "noteEditor";
diff --git a/scss/noteEditor-win.scss b/scss/noteEditor-win.scss
new file mode 100644
index 0000000000..e58630d37a
--- /dev/null
+++ b/scss/noteEditor-win.scss
@@ -0,0 +1 @@
+@import "noteEditor";
diff --git a/scss/relatedBox-mac.scss b/scss/relatedBox-mac.scss
new file mode 100644
index 0000000000..3b53c1e436
--- /dev/null
+++ b/scss/relatedBox-mac.scss
@@ -0,0 +1 @@
+@import "relatedBox";
diff --git a/scss/relatedBox-unix.scss b/scss/relatedBox-unix.scss
new file mode 100644
index 0000000000..3b53c1e436
--- /dev/null
+++ b/scss/relatedBox-unix.scss
@@ -0,0 +1 @@
+@import "relatedBox";
diff --git a/scss/relatedBox-win.scss b/scss/relatedBox-win.scss
new file mode 100644
index 0000000000..3b53c1e436
--- /dev/null
+++ b/scss/relatedBox-win.scss
@@ -0,0 +1 @@
+@import "relatedBox";