mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-08 13:06:15 +02:00
Desktop, Mobile: Improve focus handling
This commit is contained in:
parent
554fb7026a
commit
00084c5798
@ -322,9 +322,7 @@ packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/usePluginServiceRegistration.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useSearchMarkers.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.js
|
||||
packages/app-desktop/gui/NoteList/NoteList.js
|
||||
packages/app-desktop/gui/NoteList/NoteList2.js
|
||||
packages/app-desktop/gui/NoteList/NoteListSource.js
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
||||
packages/app-desktop/gui/NoteList/commands/index.js
|
||||
packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.js
|
||||
@ -350,7 +348,6 @@ packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.js
|
||||
packages/app-desktop/gui/NoteListHeader/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.test.js
|
||||
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.js
|
||||
packages/app-desktop/gui/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListItem/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListItem/utils/getNoteTitleHtml.js
|
||||
packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.test.js
|
||||
@ -1148,6 +1145,7 @@ packages/lib/types.js
|
||||
packages/lib/utils/ActionLogger.test.js
|
||||
packages/lib/utils/ActionLogger.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/focusHandler.js
|
||||
packages/lib/utils/ipc/RemoteMessenger.test.js
|
||||
packages/lib/utils/ipc/RemoteMessenger.js
|
||||
packages/lib/utils/ipc/TestMessenger.js
|
||||
|
11
.eslintrc.js
11
.eslintrc.js
@ -101,6 +101,17 @@ module.exports = {
|
||||
'no-unneeded-ternary': 'error',
|
||||
'github/array-foreach': ['error'],
|
||||
|
||||
'no-restricted-properties': ['error',
|
||||
{
|
||||
'property': 'focus',
|
||||
'message': 'Please use focusHandler::focus() instead',
|
||||
},
|
||||
{
|
||||
'property': 'blur',
|
||||
'message': 'Please use focusHandler::blur() instead',
|
||||
},
|
||||
],
|
||||
|
||||
// -------------------------------
|
||||
// Formatting
|
||||
// -------------------------------
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -302,9 +302,7 @@ packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/usePluginServiceRegistration.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useSearchMarkers.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.js
|
||||
packages/app-desktop/gui/NoteList/NoteList.js
|
||||
packages/app-desktop/gui/NoteList/NoteList2.js
|
||||
packages/app-desktop/gui/NoteList/NoteListSource.js
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
||||
packages/app-desktop/gui/NoteList/commands/index.js
|
||||
packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.js
|
||||
@ -330,7 +328,6 @@ packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.js
|
||||
packages/app-desktop/gui/NoteListHeader/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.test.js
|
||||
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.js
|
||||
packages/app-desktop/gui/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListItem/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListItem/utils/getNoteTitleHtml.js
|
||||
packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.test.js
|
||||
@ -1128,6 +1125,7 @@ packages/lib/types.js
|
||||
packages/lib/utils/ActionLogger.test.js
|
||||
packages/lib/utils/ActionLogger.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/focusHandler.js
|
||||
packages/lib/utils/ipc/RemoteMessenger.test.js
|
||||
packages/lib/utils/ipc/RemoteMessenger.js
|
||||
packages/lib/utils/ipc/TestMessenger.js
|
||||
|
@ -441,6 +441,7 @@ class AppGui {
|
||||
if (cmd === 'activate') {
|
||||
const w = this.widget('mainWindow').focusedWidget;
|
||||
if (w.name === 'folderList') {
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
this.widget('noteList').focus();
|
||||
} else if (w.name === 'noteList' || w.name === 'noteText') {
|
||||
this.processPromptCommand('edit $n');
|
||||
|
@ -243,6 +243,7 @@ class AppComponent extends Component {
|
||||
if (!ref) break;
|
||||
lastRef = ref;
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
if (lastRef) lastRef.focus();
|
||||
}
|
||||
}
|
||||
|
@ -428,6 +428,7 @@ export default class ElectronAppWrapper {
|
||||
if (!win) return;
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.show();
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
win.focus();
|
||||
if (process.platform !== 'darwin') {
|
||||
const url = argv.find((arg) => isCallbackUrl(arg));
|
||||
|
@ -13,6 +13,7 @@ import Button from '../Button/Button';
|
||||
import bridge from '../../services/bridge';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import FolderIconBox from '../FolderIconBox';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
@ -46,7 +47,7 @@ export default function(props: Props) {
|
||||
}, [props.dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
titleInputRef.current.focus();
|
||||
focus('Dialog::titleInputRef', titleInputRef.current);
|
||||
|
||||
setTimeout(() => {
|
||||
titleInputRef.current.select();
|
||||
|
@ -32,6 +32,7 @@ import useStyles from '../utils/useStyles';
|
||||
import useContextMenu from '../utils/useContextMenu';
|
||||
import useWebviewIpcMessage from '../utils/useWebviewIpcMessage';
|
||||
import useEditorSearchHandler from '../utils/useEditorSearchHandler';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
function markupRenderOptions(override: MarkupToHtmlOptions = null): MarkupToHtmlOptions {
|
||||
return { ...override };
|
||||
@ -142,7 +143,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
}
|
||||
} else if (cmd.name === 'editor.focus') {
|
||||
if (props.visiblePanes.indexOf('editor') >= 0) {
|
||||
editorRef.current.focus();
|
||||
focus('v5/CodeMirror::editor.focus', editorRef.current);
|
||||
} else {
|
||||
// If we just call focus() then the iframe is focused,
|
||||
// but not its content, such that scrolling up / down
|
||||
@ -188,7 +189,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
textItalic: () => wrapSelectionWithStrings('*', '*', _('emphasised text')),
|
||||
textLink: async () => {
|
||||
const url = await dialogs.prompt(_('Insert Hyperlink'));
|
||||
editorRef.current.focus();
|
||||
focus('v5/CodeMirror::textLink', editorRef.current);
|
||||
if (url) wrapSelectionWithStrings('[', `](${url})`);
|
||||
},
|
||||
textCode: () => {
|
||||
@ -699,30 +700,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
return output;
|
||||
}, [styles.cellViewer, props.visiblePanes]);
|
||||
|
||||
// Disable this effect to fix this:
|
||||
//
|
||||
// https://github.com/laurent22/joplin/issues/6514 It doesn't seem essential
|
||||
// to automatically focus the editor when the layout changes. The workaround
|
||||
// is to toggle the layout Cmd+L, then manually focus the editor Cmd+Shift+B.
|
||||
//
|
||||
// On the other hand, if we automatically focus the editor, and the user
|
||||
// does not want this, there's no workaround, so it's better to have this
|
||||
// disabled.
|
||||
|
||||
// const editorPaneVisible = props.visiblePanes.indexOf('editor') >= 0;
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!editorRef.current) return;
|
||||
|
||||
// // Anytime the user toggles the visible panes AND the editor is visible as a result
|
||||
// // we should focus the editor
|
||||
// // The intuition is that a panel toggle (with editor in view) is the equivalent of
|
||||
// // an editor interaction so users should expect the editor to be focused
|
||||
// if (editorPaneVisible) {
|
||||
// editorRef.current.focus();
|
||||
// }
|
||||
// }, [editorPaneVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editorRef.current) return;
|
||||
|
||||
|
@ -33,6 +33,7 @@ import Setting from '@joplin/lib/models/Setting';
|
||||
// import eventManager from '@joplin/lib/eventManager';
|
||||
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
// Based on http://pypl.github.io/PYPL.html
|
||||
const topLanguages = [
|
||||
@ -137,7 +138,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const editor_drop = useCallback((cm: any, _event: any) => {
|
||||
cm.focus();
|
||||
focus('v5/Editor::editor_drop', cm);
|
||||
}, []);
|
||||
|
||||
const editor_drag = useCallback((cm: any, event: any) => {
|
||||
|
@ -311,30 +311,6 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
return output;
|
||||
}, [styles.cellViewer, props.visiblePanes]);
|
||||
|
||||
// Disable this effect to fix this:
|
||||
//
|
||||
// https://github.com/laurent22/joplin/issues/6514 It doesn't seem essential
|
||||
// to automatically focus the editor when the layout changes. The workaround
|
||||
// is to toggle the layout Cmd+L, then manually focus the editor Cmd+Shift+B.
|
||||
//
|
||||
// On the other hand, if we automatically focus the editor, and the user
|
||||
// does not want this, there's no workaround, so it's better to have this
|
||||
// disabled.
|
||||
|
||||
// const editorPaneVisible = props.visiblePanes.indexOf('editor') >= 0;
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!editorRef.current) return;
|
||||
|
||||
// // Anytime the user toggles the visible panes AND the editor is visible as a result
|
||||
// // we should focus the editor
|
||||
// // The intuition is that a panel toggle (with editor in view) is the equivalent of
|
||||
// // an editor interaction so users should expect the editor to be focused
|
||||
// if (editorPaneVisible) {
|
||||
// editorRef.current.focus();
|
||||
// }
|
||||
// }, [editorPaneVisible]);
|
||||
|
||||
useEditorSearchHandler({
|
||||
setLocalSearchResultCount: props.setLocalSearchResultCount,
|
||||
searchMarkers: props.searchMarkers,
|
||||
|
@ -8,6 +8,7 @@ import { EditorCommandType } from '@joplin/editor/types';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
|
||||
import { MarkupLanguage } from '@joplin/renderer';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
const logger = Logger.create('CodeMirror 6 commands');
|
||||
|
||||
@ -88,7 +89,7 @@ const useEditorCommands = (props: Props) => {
|
||||
},
|
||||
textLink: async () => {
|
||||
const url = await dialogs.prompt(_('Insert Hyperlink'));
|
||||
editorRef.current.focus();
|
||||
focus('useEditorCommands::textLink', editorRef.current);
|
||||
if (url) wrapSelectionWithStrings(editorRef.current, '[', `](${url})`);
|
||||
},
|
||||
insertText: (value: any) => editorRef.current.insertText(value),
|
||||
@ -116,7 +117,7 @@ const useEditorCommands = (props: Props) => {
|
||||
},
|
||||
'editor.focus': () => {
|
||||
if (props.visiblePanes.indexOf('editor') >= 0) {
|
||||
editorRef.current.editor.focus();
|
||||
focus('useEditorCommands::editor.focus', editorRef.current.editor);
|
||||
} else {
|
||||
// If we just call focus() then the iframe is focused,
|
||||
// but not its content, such that scrolling up / down
|
||||
|
@ -32,6 +32,7 @@ import markupRenderOptions from '../../utils/markupRenderOptions';
|
||||
import { DropHandler } from '../../utils/useDropHandler';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import useWebViewApi from './utils/useWebViewApi';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
const md5 = require('md5');
|
||||
const { clipboard } = require('electron');
|
||||
const supportedLocales = require('./supportedLocales');
|
||||
@ -204,7 +205,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, cmd.value, markupRenderOptions({ bodyOnly: true }));
|
||||
editor.insertContent(result.html);
|
||||
} else if (cmd.name === 'editor.focus') {
|
||||
editor.focus();
|
||||
focus('TinyMCE::editor.focus', editor);
|
||||
} else if (cmd.name === 'editor.execCommand') {
|
||||
if (!('ui' in cmd.value)) cmd.value.ui = false;
|
||||
if (!('value' in cmd.value)) cmd.value.value = null;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { MarkupToHtml } from '@joplin/renderer';
|
||||
import { TinyMceEditorEvents } from './types';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
const taboverride = require('taboverride');
|
||||
|
||||
interface SourceInfo {
|
||||
@ -13,7 +14,7 @@ interface SourceInfo {
|
||||
|
||||
function dialogTextArea_keyDown(event: any) {
|
||||
if (event.key === 'Tab') {
|
||||
window.requestAnimationFrame(() => event.target.focus());
|
||||
window.requestAnimationFrame(() => focus('openEditDialog::dialogTextArea_keyDown', event.target));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'focusElementNoteTitle',
|
||||
@ -11,7 +12,7 @@ export const runtime = (comp: any): CommandRuntime => {
|
||||
return {
|
||||
execute: async () => {
|
||||
if (!comp.titleInputRef.current) return;
|
||||
comp.titleInputRef.current.focus();
|
||||
focus('focusElementNoteTitle', comp.titleInputRef.current);
|
||||
},
|
||||
enabledCondition: 'oneNoteSelected',
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'showLocalSearch',
|
||||
@ -13,7 +14,7 @@ export const runtime = (comp: any): CommandRuntime => {
|
||||
comp.editorRef.current.execCommand({ name: 'search' });
|
||||
} else {
|
||||
if (comp.noteSearchBarRef.current) {
|
||||
comp.noteSearchBarRef.current.focus();
|
||||
focus('showLocalSearch', comp.noteSearchBarRef.current);
|
||||
} else {
|
||||
comp.setShowLocalSearch(true);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import { reg } from '@joplin/lib/registry';
|
||||
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
||||
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
|
||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
export interface OnLoadEvent {
|
||||
formNote: FormNote;
|
||||
@ -191,7 +192,7 @@ export default function useFormNote(dependencies: HookDependencies) {
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (Setting.value(focusSettingName) === 'title') {
|
||||
if (titleInputRef.current) titleInputRef.current.focus();
|
||||
if (titleInputRef.current) focus('useFormNote::handleAutoFocus', titleInputRef.current);
|
||||
} else {
|
||||
if (editorRef.current) editorRef.current.execCommand({ name: 'editor.focus' });
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useState, useCallback, MutableRefObject, useEffect } from 'react';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import { SearchMarkers } from './useSearchMarkers';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
const CommandService = require('@joplin/lib/services/CommandService').default;
|
||||
|
||||
const logger = Logger.create('useNoteSearchBar');
|
||||
@ -36,7 +37,7 @@ export default function useNoteSearchBar({ noteSearchBarRef }: UseNoteSearchBarP
|
||||
|
||||
useEffect(() => {
|
||||
if (showLocalSearch && noteSearchBarRef.current) {
|
||||
noteSearchBarRef.current.focus();
|
||||
focus('useNoteSearchBar', noteSearchBarRef.current);
|
||||
}
|
||||
}, [showLocalSearch, noteSearchBarRef]);
|
||||
|
||||
|
@ -24,6 +24,7 @@ import useDragAndDrop from './utils/useDragAndDrop';
|
||||
import usePrevious from '../hooks/usePrevious';
|
||||
import { itemIsInTrash } from '@joplin/lib/services/trash';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
const { connect } = require('react-redux');
|
||||
|
||||
const commands = {
|
||||
@ -159,7 +160,9 @@ const NoteList = (props: Props) => {
|
||||
makeItemIndexVisible(i);
|
||||
if (doRefocus) {
|
||||
const ref = itemRefs.current[id];
|
||||
if (ref) ref.focus();
|
||||
if (ref) {
|
||||
focus('NoteList::doRefocus', ref);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import shim from '@joplin/lib/shim';
|
||||
import { useRef, useCallback, MutableRefObject } from 'react';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
export type FocusNote = (noteId: string)=> void;
|
||||
|
||||
@ -16,14 +17,14 @@ const useFocusNote = (itemRefs: MutableRefObject<Record<string, HTMLDivElement>>
|
||||
if (focusItemIID.current) shim.clearInterval(focusItemIID.current);
|
||||
focusItemIID.current = shim.setInterval(() => {
|
||||
if (itemRefs.current[noteId]) {
|
||||
itemRefs.current[noteId].focus();
|
||||
focus('useFocusNote1', itemRefs.current[noteId]);
|
||||
shim.clearInterval(focusItemIID.current);
|
||||
focusItemIID.current = null;
|
||||
}
|
||||
}, 10);
|
||||
} else {
|
||||
if (focusItemIID.current) shim.clearInterval(focusItemIID.current);
|
||||
itemRefs.current[noteId].focus();
|
||||
focus('useFocusNote2', itemRefs.current[noteId]);
|
||||
}
|
||||
}, [itemRefs]);
|
||||
|
||||
|
@ -7,6 +7,7 @@ import Note from '@joplin/lib/models/Note';
|
||||
import bridge from '../services/bridge';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
const Datetime = require('react-datetime').default;
|
||||
const { clipboard } = require('electron');
|
||||
const formatcoords = require('formatcoords');
|
||||
@ -77,7 +78,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (this.state.editedKey === null) {
|
||||
if (this.okButton.current) this.okButton.current.focus();
|
||||
if (this.okButton.current) focus('NotePropertiesDialog::componentDidUpdate', this.okButton.current);
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +221,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
if ((this.refs.editField as any).openCalendar) {
|
||||
(this.refs.editField as any).openCalendar();
|
||||
} else {
|
||||
(this.refs.editField as any).focus();
|
||||
focus('NotePropertiesDialog::editPropertyButtonClick', (this.refs.editField as any));
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
@ -255,7 +256,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
public async cancelProperty() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
return new Promise((resolve: Function) => {
|
||||
if (this.okButton.current) this.okButton.current.focus();
|
||||
if (this.okButton.current) focus('NotePropertiesDialog::focus', this.okButton.current);
|
||||
this.setState({
|
||||
editedKey: null,
|
||||
editedValue: null,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
@ -32,6 +33,8 @@ class NoteSearchBar extends React.Component<Props> {
|
||||
this.previousButton_click = this.previousButton_click.bind(this);
|
||||
this.nextButton_click = this.nextButton_click.bind(this);
|
||||
this.closeButton_click = this.closeButton_click.bind(this);
|
||||
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
this.focus = this.focus.bind(this);
|
||||
|
||||
this.backgroundColor = undefined;
|
||||
@ -125,7 +128,7 @@ class NoteSearchBar extends React.Component<Props> {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
(this.refs.searchInput as any).focus();
|
||||
focus('NoteSearchBar::focus', this.refs.searchInput as any);
|
||||
(this.refs.searchInput as any).select();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import PostMessageService, { MessageResponse, ResponderComponentType } from '@joplin/lib/services/PostMessageService';
|
||||
import * as React from 'react';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
interface Props {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
@ -119,7 +120,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
||||
|
||||
public focus() {
|
||||
if (this.webviewRef_.current) {
|
||||
this.webviewRef_.current.focus();
|
||||
focus('NoteTextViewer::focus', this.webviewRef_.current);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ const Datetime = require('react-datetime').default;
|
||||
import CreatableSelect from 'react-select/creatable';
|
||||
import Select from 'react-select';
|
||||
import makeAnimated from 'react-select/animated';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
interface Props {
|
||||
themeId: number;
|
||||
defaultValue: any;
|
||||
@ -67,7 +68,7 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (this.focusInput_ && this.answerInput_.current) this.answerInput_.current.focus();
|
||||
if (this.focusInput_ && this.answerInput_.current) focus('PromptDialog::componentDidUpdate', this.answerInput_.current);
|
||||
this.focusInput_ = false;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import uuid from '@joplin/lib/uuid';
|
||||
const { connect } = require('react-redux');
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import { blur, focus } from '@joplin/lib/utils/focusHandler';
|
||||
const debounce = require('debounce');
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
@ -117,7 +118,7 @@ function SearchBar(props: Props) {
|
||||
|
||||
const onKeyDown = useCallback((event: any) => {
|
||||
if (event.key === 'Escape') {
|
||||
if (document.activeElement) (document.activeElement as any).blur();
|
||||
if (document.activeElement) blur('SearchBar::onKeyDown', document.activeElement as any);
|
||||
void onExitSearch();
|
||||
}
|
||||
}, [onExitSearch]);
|
||||
@ -127,7 +128,7 @@ function SearchBar(props: Props) {
|
||||
void onExitSearch();
|
||||
} else {
|
||||
setSearchStarted(true);
|
||||
props.inputRef.current.focus();
|
||||
focus('SearchBar::onSearchButtonClick', props.inputRef.current);
|
||||
props.dispatch({
|
||||
type: 'FOCUS_SET',
|
||||
field: 'globalSearch',
|
||||
|
@ -29,6 +29,7 @@ import { RuntimeProps } from './commands/focusElementSideBar';
|
||||
const { connect } = require('react-redux');
|
||||
import { renderFolders, renderTags } from '@joplin/lib/components/shared/side-menu-shared';
|
||||
import { getTrashFolderIcon, getTrashFolderId } from '@joplin/lib/services/trash';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
const Menu = bridge().Menu;
|
||||
@ -674,7 +675,7 @@ const SidebarComponent = (props: Props) => {
|
||||
id: focusItem.id,
|
||||
});
|
||||
|
||||
focusItem.ref.current.focus();
|
||||
focus('SideBar::onKeyDown', focusItem.ref.current);
|
||||
}
|
||||
|
||||
if (keyCode === 9) {
|
||||
|
@ -2,6 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
|
||||
import { AppState } from '../../../app.reducer';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'focusElementSideBar',
|
||||
@ -24,10 +25,10 @@ export const runtime = (props: RuntimeProps): CommandRuntime => {
|
||||
const item = props.getSelectedItem();
|
||||
if (item) {
|
||||
const anchorRef = props.anchorItemRefs.current[item.type][item.id];
|
||||
if (anchorRef) anchorRef.current.focus();
|
||||
if (anchorRef) focus('focusElementSideBar1', anchorRef.current);
|
||||
} else {
|
||||
const anchorRef = props.getFirstAnchorItemRef('folder');
|
||||
if (anchorRef) anchorRef.current.focus();
|
||||
if (anchorRef) focus('focusElementSideBar2', anchorRef.current);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -9,6 +9,7 @@ import useWebviewToPluginMessages from './hooks/useWebviewToPluginMessages';
|
||||
import useScriptLoader from './hooks/useScriptLoader';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import styled from 'styled-components';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
const logger = Logger.create('UserWebview');
|
||||
|
||||
@ -99,7 +100,7 @@ function UserWebview(props: Props, ref: any) {
|
||||
}
|
||||
},
|
||||
focus: function() {
|
||||
if (viewRef.current) viewRef.current.focus();
|
||||
if (viewRef.current) focus('UserWebView::focus', viewRef.current);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import WebviewController from '@joplin/lib/services/plugins/WebviewController';
|
||||
import UserWebview, { Props as UserWebviewProps } from './UserWebview';
|
||||
import UserWebviewDialogButtonBar from './UserWebviewDialogButtonBar';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
interface Props extends UserWebviewProps {
|
||||
@ -101,7 +102,7 @@ export default function UserWebviewDialog(props: Props) {
|
||||
// We focus the dialog once it's ready to make sure that the ESC/Enter
|
||||
// keyboard shortcuts are working.
|
||||
// https://github.com/laurent22/joplin/issues/4474
|
||||
if (webviewRef.current) webviewRef.current.focus();
|
||||
if (webviewRef.current) focus('UserWebviewDialog', webviewRef.current);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -2,7 +2,7 @@
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.tsx", "../lib/utils/focusHandler.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules",
|
||||
|
@ -11,6 +11,7 @@ import { _ } from '@joplin/lib/locale';
|
||||
import { EditorControl } from './types';
|
||||
import { useCallback } from 'react';
|
||||
import SelectionFormatting from '@joplin/editor/SelectionFormatting';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
interface LinkDialogProps {
|
||||
editorControl: EditorControl;
|
||||
@ -100,7 +101,7 @@ const EditLinkDialog = (props: LinkDialogProps) => {
|
||||
autoFocus
|
||||
|
||||
onSubmitEditing={() => {
|
||||
linkInputRef.current.focus();
|
||||
focus('EditLinkDialog::onSubmitEditing', linkInputRef.current);
|
||||
}}
|
||||
onChangeText={(text: string) => setLinkLabel(text)}
|
||||
/>
|
||||
|
@ -61,6 +61,7 @@ import { getDisplayParentTitle } from '@joplin/lib/services/trash';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
import pickDocument from '../../utils/pickDocument';
|
||||
import debounce from '../../utils/debounce';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
const urlUtils = require('@joplin/lib/urlUtils');
|
||||
|
||||
const emptyArray: any[] = [];
|
||||
@ -1328,13 +1329,8 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
|
||||
// Avoid writing `this.titleTextFieldRef.current` -- titleTextFieldRef may
|
||||
// be undefined.
|
||||
if (fieldToFocus === 'title' && this.titleTextFieldRef?.current) {
|
||||
this.titleTextFieldRef.current.focus();
|
||||
focus('Note::focusUpdate', this.titleTextFieldRef.current);
|
||||
}
|
||||
// if (fieldToFocus === 'body' && this.markdownEditorRef.current) {
|
||||
// if (this.markdownEditorRef.current) {
|
||||
// this.markdownEditorRef.current.focus();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private async folderPickerOptions_valueChanged(itemValue: any) {
|
||||
|
@ -11,6 +11,7 @@ import swapLine, { SwapLineDirection } from './swapLine';
|
||||
import duplicateLine from './duplicateLine';
|
||||
import sortSelectedLines from './sortSelectedLines';
|
||||
import { closeSearchPanel, findNext, findPrevious, openSearchPanel, replaceAll, replaceNext } from '@codemirror/search';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
export type EditorCommandFunction = (editor: EditorView, ...args: any[])=> void|any;
|
||||
|
||||
@ -22,7 +23,7 @@ const editorCommands: Record<EditorCommandType, EditorCommandFunction> = {
|
||||
[EditorCommandType.Undo]: undo,
|
||||
[EditorCommandType.Redo]: redo,
|
||||
[EditorCommandType.SelectAll]: selectAll,
|
||||
[EditorCommandType.Focus]: editor => editor.focus(),
|
||||
[EditorCommandType.Focus]: editor => focus('editorCommands::focus', editor),
|
||||
|
||||
[EditorCommandType.ToggleBolded]: toggleBolded,
|
||||
[EditorCommandType.ToggleItalicized]: toggleItalicized,
|
||||
|
41
packages/lib/utils/focusHandler.ts
Normal file
41
packages/lib/utils/focusHandler.ts
Normal file
@ -0,0 +1,41 @@
|
||||
// The purpose of this handler is to have all focus/blur calls go through the same place, which
|
||||
// makes it easier to log what happens. This is useful when one unknown component is stealing focus
|
||||
// from another component. Potentially it could also be used to resolve conflict situations when
|
||||
// multiple components try to set the focus at the same time.
|
||||
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
|
||||
const logger = Logger.create('setFocus');
|
||||
|
||||
enum ToggleFocusAction {
|
||||
Focus = 'focus',
|
||||
Blur = 'blur',
|
||||
}
|
||||
|
||||
interface FocusableElement {
|
||||
focus: ()=> void;
|
||||
blur: ()=> void;
|
||||
}
|
||||
|
||||
const toggleFocus = (source: string, element: FocusableElement, action: ToggleFocusAction) => {
|
||||
if (!element) {
|
||||
logger.warn(`Tried action "${action}" on an undefined element: ${source}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!element[action]) {
|
||||
logger.warn(`Element does not have a "${action}" method: ${source}`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Action "${action}" from "${source}"`);
|
||||
element[action]();
|
||||
};
|
||||
|
||||
export const focus = (source: string, element: any) => {
|
||||
toggleFocus(source, element, ToggleFocusAction.Focus);
|
||||
};
|
||||
|
||||
export const blur = (source: string, element: any) => {
|
||||
toggleFocus(source, element, ToggleFocusAction.Blur);
|
||||
};
|
@ -146,7 +146,8 @@ export default class PdfDocument {
|
||||
frame.contentWindow.onafterprint = () => {
|
||||
frame.remove();
|
||||
};
|
||||
frame.focus();
|
||||
console.warn('frame.focus() has been disabled!! Use focusHandler instead');
|
||||
// frame.focus();
|
||||
frame.contentWindow.print();
|
||||
};
|
||||
frame.src = this.url as string;
|
||||
|
Loading…
Reference in New Issue
Block a user