fx-compat: Partial fixes for note editor
This commit is contained in:
parent
29bc36c02a
commit
e99a0d4515
8 changed files with 404 additions and 35 deletions
|
@ -631,7 +631,7 @@
|
|||
</grid>
|
||||
</vbox>
|
||||
|
||||
<zoteronoteeditor id="attachment-note-editor" notitle="1" flex="1"/>
|
||||
<note-editor id="attachment-note-editor" notitle="1" flex="1"/>
|
||||
|
||||
<button id="select-button" hidden="true"/>
|
||||
</vbox>
|
||||
|
|
|
@ -340,7 +340,7 @@
|
|||
|
||||
case 'note':
|
||||
var type = Zotero.Libraries.get(this.libraryID).libraryType;
|
||||
elementName = 'zoteronoteeditor';
|
||||
elementName = 'note-editor';
|
||||
break;
|
||||
|
||||
case 'annotation':
|
||||
|
|
|
@ -245,7 +245,7 @@ var ZoteroContextPane = new function () {
|
|||
if (_panesDeck.selectedIndex == 1) {
|
||||
var node = _notesPaneDeck.selectedPanel;
|
||||
if (node.selectedIndex == 1) {
|
||||
return node.querySelector('zoteronoteeditor');
|
||||
return node.querySelector('note-editor');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -273,7 +273,7 @@ var ZoteroContextPane = new function () {
|
|||
return true;
|
||||
}
|
||||
else {
|
||||
node.querySelector('zoteronoteeditor').focusFirst();
|
||||
node.querySelector('note-editor').focusFirst();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -415,7 +415,7 @@ var ZoteroContextPane = new function () {
|
|||
var noteContainer = document.createXULElement('vbox');
|
||||
var title = document.createXULElement('vbox');
|
||||
title.className = 'zotero-context-pane-editor-parent-line';
|
||||
var editor = document.createXULElement('zoteronoteeditor');
|
||||
var editor = new (customElements.get('note-editor'));
|
||||
editor.className = 'zotero-context-pane-pinned-note';
|
||||
editor.setAttribute('flex', 1);
|
||||
noteContainer.append(title, editor);
|
||||
|
|
373
chrome/content/zotero/elements/noteEditor.js
Normal file
373
chrome/content/zotero/elements/noteEditor.js
Normal file
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2020 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 NoteEditor extends XULElement {
|
||||
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._mode = 'view';
|
||||
this._destroyed = false;
|
||||
this._noteEditorID = Zotero.Utilities.randomString();
|
||||
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.content = MozXULElement.parseXULToFragment(`
|
||||
<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">
|
||||
<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-box"/>
|
||||
</div>
|
||||
</div>
|
||||
</box>
|
||||
<popupset xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<tooltip id="html-tooltip" page="true"/>
|
||||
<menupopup id="editor-menu"/>
|
||||
</popupset>
|
||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||
|
||||
this._destroyed = false;
|
||||
this._noteEditorID = Zotero.Utilities.randomString();
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'file'], 'noteeditor');
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let content = document.importNode(this.content, true);
|
||||
this._iframe = content.querySelector('#editor-view');
|
||||
this._iframe.addEventListener('DOMContentLoaded', (event) => {
|
||||
// For iframes without chrome priviledges, for unknown reasons,
|
||||
// dataTransfer.getData() returns empty value for `drop` event
|
||||
// when dragging something from the outside of Zotero.
|
||||
// Update: Since fx102 non-standard data types don't work when dragging into an§ iframe,
|
||||
// while the original problem probably no longer exists
|
||||
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;
|
||||
});
|
||||
shadow.appendChild(content);
|
||||
|
||||
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() {
|
||||
// Empty the DOM. We will rebuild if reconnected.
|
||||
while (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
async save() {
|
||||
|
||||
}
|
||||
|
||||
saveSync = () => {
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.saveSync();
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentInstance = () => {
|
||||
return this._editorInstance;
|
||||
}
|
||||
|
||||
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: this._id('editor-view').contentWindow,
|
||||
popup: this._id('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();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
|
||||
set navigateHandler(val) {
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.onNavigate = val;
|
||||
}
|
||||
this._navigateHandler = val;
|
||||
}
|
||||
|
||||
set returnHandler(val) {
|
||||
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;
|
||||
|
||||
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.js");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
get item() {
|
||||
return this._item;
|
||||
}
|
||||
|
||||
set item(val) {
|
||||
// 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;
|
||||
})();
|
||||
}
|
||||
|
||||
get parentItem() {
|
||||
return this._parentItem;
|
||||
}
|
||||
|
||||
set parentItem(val) {
|
||||
this._parentItem = val;
|
||||
// this._id('links-box').parentItem = val;
|
||||
}
|
||||
|
||||
async focus() {
|
||||
let n = 0;
|
||||
while (!this._editorInstance && n++ < 100) {
|
||||
await Zotero.Promise.delay(10);
|
||||
}
|
||||
await this._editorInstance._initPromise;
|
||||
this._iframe.focus();
|
||||
this._editorInstance.focus();
|
||||
}
|
||||
|
||||
async focusFirst() {
|
||||
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) {
|
||||
}
|
||||
}
|
||||
|
||||
_id(id) {
|
||||
return this.shadowRoot.querySelector(`[id=${id}]`);
|
||||
}
|
||||
}
|
||||
customElements.define("note-editor", NoteEditor);
|
||||
}
|
|
@ -15,13 +15,17 @@
|
|||
windowtype="zotero:note"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script src="include.js"/>
|
||||
<script src="note.js"/>
|
||||
<script>
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/include.js", this);
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/noteEditor.js", this);
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/note.js", this);
|
||||
</script>
|
||||
|
||||
<keyset>
|
||||
<key id="key_close" key="W" modifiers="accel" command="cmd_close"/>
|
||||
</keyset>
|
||||
<command id="cmd_close" oncommand="window.close();"/>
|
||||
|
||||
<zoteronoteeditor id="zotero-note-editor" flex="1" onerror="return;onError()"/>
|
||||
<note-editor id="zotero-note-editor" flex="1" onerror="return;onError()"/>
|
||||
</window>
|
|
@ -679,15 +679,15 @@ class EditorInstance {
|
|||
for (let itemGroup of itemGroups) {
|
||||
for (let item of itemGroup) {
|
||||
if (item.groups) {
|
||||
let menu = parentNode.ownerDocument.createElement('menu');
|
||||
let menu = parentNode.ownerDocument.createXULElement('menu');
|
||||
menu.setAttribute('label', item.label);
|
||||
let menupopup = parentNode.ownerDocument.createElement('menupopup');
|
||||
let menupopup = parentNode.ownerDocument.createXULElement('menupopup');
|
||||
menu.append(menupopup);
|
||||
appendItems(menupopup, item.groups);
|
||||
parentNode.appendChild(menu);
|
||||
}
|
||||
else {
|
||||
let menuitem = parentNode.ownerDocument.createElement('menuitem');
|
||||
let menuitem = parentNode.ownerDocument.createXULElement('menuitem');
|
||||
menuitem.setAttribute('value', item.name);
|
||||
menuitem.setAttribute('label', item.label);
|
||||
menuitem.setAttribute('disabled', !item.enabled);
|
||||
|
@ -707,7 +707,7 @@ class EditorInstance {
|
|||
}
|
||||
|
||||
if (itemGroups.indexOf(itemGroup) !== itemGroups.length - 1) {
|
||||
let separator = parentNode.ownerDocument.createElement('menuseparator');
|
||||
let separator = parentNode.ownerDocument.createXULElement('menuseparator');
|
||||
parentNode.appendChild(separator);
|
||||
}
|
||||
}
|
||||
|
@ -742,10 +742,10 @@ class EditorInstance {
|
|||
}
|
||||
|
||||
// Separator
|
||||
var separator = this._popup.ownerDocument.createElement('menuseparator');
|
||||
var separator = this._popup.ownerDocument.createXULElement('menuseparator');
|
||||
this._popup.appendChild(separator);
|
||||
// Check Spelling
|
||||
var menuitem = this._popup.ownerDocument.createElement('menuitem');
|
||||
var menuitem = this._popup.ownerDocument.createXULElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('spellCheck.checkSpelling'));
|
||||
menuitem.setAttribute('checked', spellChecker.enabled);
|
||||
menuitem.setAttribute('type', 'checkbox');
|
||||
|
@ -757,11 +757,11 @@ class EditorInstance {
|
|||
|
||||
if (spellChecker.enabled) {
|
||||
// Languages menu
|
||||
var menu = this._popup.ownerDocument.createElement('menu');
|
||||
var menu = this._popup.ownerDocument.createXULElement('menu');
|
||||
menu.setAttribute('label', Zotero.getString('general.languages'));
|
||||
this._popup.append(menu);
|
||||
// Languages menu popup
|
||||
var menupopup = this._popup.ownerDocument.createElement('menupopup');
|
||||
var menupopup = this._popup.ownerDocument.createXULElement('menupopup');
|
||||
menu.append(menupopup);
|
||||
|
||||
spellChecker.addDictionaryListToMenu(menupopup, null);
|
||||
|
@ -778,10 +778,10 @@ class EditorInstance {
|
|||
}
|
||||
|
||||
// Separator
|
||||
var separator = this._popup.ownerDocument.createElement('menuseparator');
|
||||
var separator = this._popup.ownerDocument.createXULElement('menuseparator');
|
||||
menupopup.appendChild(separator);
|
||||
// Add Dictionaries
|
||||
var menuitem = this._popup.ownerDocument.createElement('menuitem');
|
||||
var menuitem = this._popup.ownerDocument.createXULElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('spellCheck.addRemoveDictionaries'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
Services.ww.openWindow(null, "chrome://zotero/content/dictionaryManager.xul",
|
||||
|
@ -801,7 +801,7 @@ class EditorInstance {
|
|||
let firstElementChild = this._popup.firstElementChild;
|
||||
let suggestionCount = spellChecker.addSuggestionsToMenu(this._popup, firstElementChild, 5);
|
||||
if (suggestionCount) {
|
||||
let separator = this._popup.ownerDocument.createElement('menuseparator');
|
||||
let separator = this._popup.ownerDocument.createXULElement('menuseparator');
|
||||
this._popup.insertBefore(separator, firstElementChild);
|
||||
}
|
||||
}
|
||||
|
@ -810,13 +810,10 @@ class EditorInstance {
|
|||
}
|
||||
|
||||
_getSpellChecker() {
|
||||
let spellChecker = new InlineSpellChecker();
|
||||
let editingSession = this._iframeWindow
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
spellChecker.init(editingSession.getEditorForWindow(this._iframeWindow));
|
||||
return spellChecker;
|
||||
let editingSession = this._iframeWindow.docShell.editingSession;
|
||||
return new InlineSpellChecker(
|
||||
editingSession.getEditorForWindow(this._iframeWindow)
|
||||
);
|
||||
}
|
||||
|
||||
async _ensureNoteCreated() {
|
||||
|
|
|
@ -1433,12 +1433,6 @@ var ZoteroPane = new function()
|
|||
|
||||
var tabs = document.getElementById('zotero-view-tabbox');
|
||||
|
||||
// save note when switching from a note
|
||||
if(document.getElementById('zotero-item-pane-content').selectedIndex == 2) {
|
||||
// TODO: only try to save when selected item is different
|
||||
yield document.getElementById('zotero-note-editor').save();
|
||||
}
|
||||
|
||||
var collectionTreeRow = this.getCollectionTreeRow();
|
||||
// I don't think this happens in normal usage, but it can happen during tests
|
||||
if (!collectionTreeRow) {
|
||||
|
@ -3777,7 +3771,7 @@ var ZoteroPane = new function()
|
|||
}
|
||||
|
||||
var io = { itemID: itemID, collectionID: col, parentItemKey: parentKey };
|
||||
window.openDialog('chrome://zotero/content/note.xul', name, 'chrome,resizable,centerscreen,dialog=false', io);
|
||||
window.openDialog('chrome://zotero/content/note.xhtml', name, 'chrome,resizable,centerscreen,dialog=false', io);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -77,7 +77,8 @@
|
|||
Services.scriptloader.loadSubScript("chrome://global/content/customElements.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/noteEditor.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/itemPane.js", this);
|
||||
|
@ -1146,7 +1147,7 @@
|
|||
'onerror' handler crashes the app on a save error to prevent typing in notes
|
||||
while they're not being saved
|
||||
-->
|
||||
<zoteronoteeditor id="zotero-note-editor" flex="1" notitle="1"
|
||||
<note-editor id="zotero-note-editor" flex="1" notitle="1"
|
||||
previousfocus="zotero-items-tree"/>
|
||||
</groupbox>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue