diff --git a/chrome/content/zotero/reader.xhtml b/chrome/content/zotero/reader.xhtml
index 63ae66b92c..292b24265f 100644
--- a/chrome/content/zotero/reader.xhtml
+++ b/chrome/content/zotero/reader.xhtml
@@ -348,7 +348,6 @@
primary="true"
transparent="transparent"
src="resource://zotero/reader/reader.html"
- context="textbox-contextmenu"
flex="1"/>
diff --git a/chrome/content/zotero/xpcom/reader.js b/chrome/content/zotero/xpcom/reader.js
index 23727711d1..d989b99c14 100644
--- a/chrome/content/zotero/xpcom/reader.js
+++ b/chrome/content/zotero/xpcom/reader.js
@@ -33,6 +33,8 @@ const ZipReader = Components.Constructor(
"open"
);
+Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
const ARRAYBUFFER_MAX_LENGTH = Services.appinfo.is64Bit
? Math.pow(2, 33)
@@ -1186,6 +1188,148 @@ class ReaderInstance {
setTimeout(() => popup.openPopupAtScreen(rect.x, rect.y, true));
}
+ _handleReaderTextboxContextMenuOpen = (event) => {
+ this._window.goUpdateGlobalEditMenuItems(true);
+
+ let iframeWindow = event.target.ownerGlobal;
+
+ this._window.MozXULElement.insertFTLIfNeeded("toolkit/global/textActions.ftl");
+ this._window.MozXULElement.insertFTLIfNeeded("browser/menubar.ftl");
+
+ let popup = this._window.document.createXULElement('menupopup');
+ this._popupset.appendChild(popup);
+
+ popup.addEventListener('popuphidden', (event) => {
+ if (event.target !== popup) {
+ return;
+ }
+ popup.remove();
+ });
+
+ popup.appendChild(
+ this._window.MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+
+
+
+ `)
+ );
+
+ let menuitemSwitchTextDirection = popup.querySelector("[command='cmd_switchTextDirection']");
+ let showSwitchTextDirection = Services.prefs.getBoolPref("bidi.browser.ui", false);
+ menuitemSwitchTextDirection.hidden = !showSwitchTextDirection;
+ menuitemSwitchTextDirection.previousElementSibling.hidden = !showSwitchTextDirection;
+
+ let selection = event.target.ownerGlobal.getSelection();
+ if (!selection || !selection.anchorNode) {
+ return;
+ }
+ let node = selection.anchorNode.nodeType === Node.ELEMENT_NODE
+ ? selection.anchorNode
+ : selection.anchorNode.parentElement;
+
+ let insideContentEditable = node && node.closest('[contenteditable]') !== null;
+ if (insideContentEditable) {
+ let editingSession = iframeWindow.docShell.editingSession;
+ let spellChecker = new InlineSpellChecker(
+ editingSession.getEditorForWindow(iframeWindow)
+ );
+
+ // Separator
+ var separator = popup.ownerDocument.createXULElement('menuseparator');
+ popup.appendChild(separator);
+ // Check Spelling
+ var menuitem = popup.ownerDocument.createXULElement('menuitem');
+ menuitem.setAttribute('data-l10n-id', 'text-action-spell-check-toggle');
+ menuitem.setAttribute('checked', !!Zotero.Prefs.get('layout.spellcheckDefault', true));
+ menuitem.setAttribute('type', 'checkbox');
+ menuitem.addEventListener('command', () => {
+ spellChecker.toggleEnabled();
+ // Possible values: 0 - off, 1 - only multi-line, 2 - multi and single line input boxes
+ Zotero.Prefs.set('layout.spellcheckDefault', !!Zotero.Prefs.get('layout.spellcheckDefault', true) ? 0 : 1, true);
+ });
+ popup.append(menuitem);
+
+ if (spellChecker.enabled) {
+ // Languages menu
+ var menu = popup.ownerDocument.createXULElement('menu');
+ menu.setAttribute('data-l10n-id', 'text-action-spell-dictionaries');
+ popup.append(menu);
+ // Languages menu popup
+ var menupopup = popup.ownerDocument.createXULElement('menupopup');
+ menu.append(menupopup);
+
+ spellChecker.addDictionaryListToMenu(menupopup, null);
+
+ // The menu is prepopulated with names from InlineSpellChecker::getDictionaryDisplayName(),
+ // which will be in English, so swap in native locale names where we have them
+ for (var menuitem of menupopup.children) {
+ // 'spell-check-dictionary-en-US'
+ let locale = menuitem.id.slice(23);
+ let label = Zotero.Dictionaries.getBestDictionaryName(locale);
+ if (label && label != locale) {
+ menuitem.setAttribute('label', label);
+ }
+ }
+
+ // Separator
+ var separator = popup.ownerDocument.createXULElement('menuseparator');
+ menupopup.appendChild(separator);
+ // Add Dictionaries
+ var menuitem = popup.ownerDocument.createXULElement('menuitem');
+ menuitem.setAttribute('data-l10n-id', 'text-action-spell-add-dictionaries');
+ menuitem.addEventListener('command', () => {
+ Services.ww.openWindow(null, "chrome://zotero/content/dictionaryManager.xhtml",
+ "dictionary-manager", "chrome,centerscreen", {});
+ });
+ menupopup.append(menuitem);
+
+ let selection = iframeWindow.getSelection();
+ if (selection) {
+ spellChecker.initFromEvent(selection.anchorNode, selection.anchorOffset);
+ }
+
+ let firstElementChild = popup.firstElementChild;
+ let showSeparator = false;
+ let suggestionCount = spellChecker.addSuggestionsToMenuOnParent(popup, firstElementChild, 5);
+ if (suggestionCount) {
+ showSeparator = true;
+ }
+
+ if (spellChecker.overMisspelling) {
+ let addToDictionary = popup.ownerDocument.createXULElement('menuitem');
+ addToDictionary.setAttribute('data-l10n-id', 'text-action-spell-add-to-dictionary');
+ addToDictionary.addEventListener('command', () => {
+ spellChecker.addToDictionary();
+ });
+ popup.insertBefore(addToDictionary, firstElementChild);
+ showSeparator = true;
+ }
+ if (spellChecker.canUndo()) {
+ let undo = popup.ownerDocument.createXULElement('menuitem');
+ undo.setAttribute('data-l10n-id', 'text-action-spell-undo-add-to-dictionary');
+ undo.addEventListener('command', () => {
+ spellChecker.undoAddToDictionary();
+ });
+ popup.insertBefore(undo, firstElementChild);
+ showSeparator = true;
+ }
+
+ if (showSeparator) {
+ let separator = popup.ownerDocument.createXULElement('menuseparator');
+ popup.insertBefore(separator, firstElementChild);
+ }
+ }
+ }
+ popup.openPopupAtScreen(event.screenX, event.screenY, true);
+ };
+
_updateSecondViewState() {
if (this.tabID) {
let win = Zotero.getMainWindow();
@@ -1270,14 +1414,13 @@ class ReaderTab extends ReaderInstance {
this.tabID = id;
this._tabContainer = container;
}
-
+
this._iframe = this._window.document.createXULElement('browser');
this._iframe.setAttribute('class', 'reader');
this._iframe.setAttribute('flex', '1');
this._iframe.setAttribute('type', 'content');
this._iframe.setAttribute('transparent', 'true');
this._iframe.setAttribute('src', 'resource://zotero/reader/reader.html');
- this._iframe.setAttribute('context', 'textbox-contextmenu');
this._tabContainer.appendChild(this._iframe);
this._iframe.docShell.windowDraggingAllowed = true;
@@ -1288,8 +1431,6 @@ class ReaderTab extends ReaderInstance {
this._window.addEventListener('pointerdown', this._handlePointerDown);
this._window.addEventListener('pointerup', this._handlePointerUp);
- this._window.goBuildEditContextMenu();
-
this._iframe.setAttribute('tooltip', 'html-tooltip');
this._open({ location: options.location, secondViewState: options.secondViewState });
@@ -1309,19 +1450,7 @@ class ReaderTab extends ReaderInstance {
this._window.removeEventListener('DOMContentLoaded', this._handleLoad);
this._iframeWindow = this._iframe.contentWindow;
this._iframeWindow.addEventListener('error', event => Zotero.logError(event.error));
-
- // Disable text direction switching option because it tries to change direction
- // for the whole window and crashes it
- this._iframeWindow.addEventListener('contextmenu', () => {
- let popup = this._window.goBuildEditContextMenu();
- this._window.goUpdateGlobalEditMenuItems(true);
- popup.addEventListener('popupshowing', (e) => {
- let menuitemSwitchTextDirection = e.target.querySelector("[command='cmd_switchTextDirection']");
- if (menuitemSwitchTextDirection) {
- menuitemSwitchTextDirection.hidden = true;
- }
- }, { once: true });
- });
+ this._iframe.addEventListener('contextmenu', this._handleReaderTextboxContextMenuOpen);
}
};
@@ -1420,18 +1549,7 @@ class ReaderWindow extends ReaderInstance {
if (this._iframe.contentWindow && this._iframe.contentWindow.document === event.target) {
this._iframeWindow = this._window.document.getElementById('reader').contentWindow;
this._iframeWindow.addEventListener('error', event => Zotero.logError(event.error));
- this._window.goBuildEditContextMenu();
- // Disable text direction switching option because it changes direction for the whole window
- this._iframeWindow.addEventListener('contextmenu', () => {
- let popup = this._window.goBuildEditContextMenu();
- this._window.goUpdateGlobalEditMenuItems(true);
- popup.addEventListener('popupshowing', (e) => {
- let menuitemSwitchTextDirection = e.target.querySelector("[command='cmd_switchTextDirection']");
- if (menuitemSwitchTextDirection) {
- menuitemSwitchTextDirection.hidden = true;
- }
- }, { once: true });
- });
+ this._iframe.addEventListener('contextmenu', this._handleReaderTextboxContextMenuOpen);
}
this._switchReaderSubtype(this._type);