fx-compat: Partial fixes for note editor

This commit is contained in:
Martynas Bagdonas 2022-05-18 17:27:26 +03:00
parent 29bc36c02a
commit e99a0d4515
8 changed files with 404 additions and 35 deletions

View file

@ -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>

View file

@ -340,7 +340,7 @@
case 'note':
var type = Zotero.Libraries.get(this.libraryID).libraryType;
elementName = 'zoteronoteeditor';
elementName = 'note-editor';
break;
case 'annotation':

View file

@ -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);

View 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);
}

View file

@ -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>

View file

@ -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() {

View file

@ -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);
}

View file

@ -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>