You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-13 00:10:37 +02:00
Desktop, Mobile: Improve focus handling
This commit is contained in:
@ -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/usePluginServiceRegistration.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useSearchMarkers.js
|
packages/app-desktop/gui/NoteEditor/utils/useSearchMarkers.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.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/NoteList2.js
|
||||||
packages/app-desktop/gui/NoteList/NoteListSource.js
|
|
||||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
||||||
packages/app-desktop/gui/NoteList/commands/index.js
|
packages/app-desktop/gui/NoteList/commands/index.js
|
||||||
packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.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/useContextMenu.js
|
||||||
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.test.js
|
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.test.js
|
||||||
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.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/NoteListItem.js
|
||||||
packages/app-desktop/gui/NoteListItem/utils/getNoteTitleHtml.js
|
packages/app-desktop/gui/NoteListItem/utils/getNoteTitleHtml.js
|
||||||
packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.test.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.test.js
|
||||||
packages/lib/utils/ActionLogger.js
|
packages/lib/utils/ActionLogger.js
|
||||||
packages/lib/utils/credentialFiles.js
|
packages/lib/utils/credentialFiles.js
|
||||||
|
packages/lib/utils/focusHandler.js
|
||||||
packages/lib/utils/ipc/RemoteMessenger.test.js
|
packages/lib/utils/ipc/RemoteMessenger.test.js
|
||||||
packages/lib/utils/ipc/RemoteMessenger.js
|
packages/lib/utils/ipc/RemoteMessenger.js
|
||||||
packages/lib/utils/ipc/TestMessenger.js
|
packages/lib/utils/ipc/TestMessenger.js
|
||||||
|
11
.eslintrc.js
11
.eslintrc.js
@ -101,6 +101,17 @@ module.exports = {
|
|||||||
'no-unneeded-ternary': 'error',
|
'no-unneeded-ternary': 'error',
|
||||||
'github/array-foreach': ['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
|
// 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/usePluginServiceRegistration.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useSearchMarkers.js
|
packages/app-desktop/gui/NoteEditor/utils/useSearchMarkers.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.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/NoteList2.js
|
||||||
packages/app-desktop/gui/NoteList/NoteListSource.js
|
|
||||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
||||||
packages/app-desktop/gui/NoteList/commands/index.js
|
packages/app-desktop/gui/NoteList/commands/index.js
|
||||||
packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.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/useContextMenu.js
|
||||||
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.test.js
|
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.test.js
|
||||||
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.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/NoteListItem.js
|
||||||
packages/app-desktop/gui/NoteListItem/utils/getNoteTitleHtml.js
|
packages/app-desktop/gui/NoteListItem/utils/getNoteTitleHtml.js
|
||||||
packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.test.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.test.js
|
||||||
packages/lib/utils/ActionLogger.js
|
packages/lib/utils/ActionLogger.js
|
||||||
packages/lib/utils/credentialFiles.js
|
packages/lib/utils/credentialFiles.js
|
||||||
|
packages/lib/utils/focusHandler.js
|
||||||
packages/lib/utils/ipc/RemoteMessenger.test.js
|
packages/lib/utils/ipc/RemoteMessenger.test.js
|
||||||
packages/lib/utils/ipc/RemoteMessenger.js
|
packages/lib/utils/ipc/RemoteMessenger.js
|
||||||
packages/lib/utils/ipc/TestMessenger.js
|
packages/lib/utils/ipc/TestMessenger.js
|
||||||
|
@ -441,6 +441,7 @@ class AppGui {
|
|||||||
if (cmd === 'activate') {
|
if (cmd === 'activate') {
|
||||||
const w = this.widget('mainWindow').focusedWidget;
|
const w = this.widget('mainWindow').focusedWidget;
|
||||||
if (w.name === 'folderList') {
|
if (w.name === 'folderList') {
|
||||||
|
// eslint-disable-next-line no-restricted-properties
|
||||||
this.widget('noteList').focus();
|
this.widget('noteList').focus();
|
||||||
} else if (w.name === 'noteList' || w.name === 'noteText') {
|
} else if (w.name === 'noteList' || w.name === 'noteText') {
|
||||||
this.processPromptCommand('edit $n');
|
this.processPromptCommand('edit $n');
|
||||||
|
@ -243,6 +243,7 @@ class AppComponent extends Component {
|
|||||||
if (!ref) break;
|
if (!ref) break;
|
||||||
lastRef = ref;
|
lastRef = ref;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-restricted-properties
|
||||||
if (lastRef) lastRef.focus();
|
if (lastRef) lastRef.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -428,6 +428,7 @@ export default class ElectronAppWrapper {
|
|||||||
if (!win) return;
|
if (!win) return;
|
||||||
if (win.isMinimized()) win.restore();
|
if (win.isMinimized()) win.restore();
|
||||||
win.show();
|
win.show();
|
||||||
|
// eslint-disable-next-line no-restricted-properties
|
||||||
win.focus();
|
win.focus();
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
const url = argv.find((arg) => isCallbackUrl(arg));
|
const url = argv.find((arg) => isCallbackUrl(arg));
|
||||||
|
@ -13,6 +13,7 @@ import Button from '../Button/Button';
|
|||||||
import bridge from '../../services/bridge';
|
import bridge from '../../services/bridge';
|
||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
import FolderIconBox from '../FolderIconBox';
|
import FolderIconBox from '../FolderIconBox';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
@ -46,7 +47,7 @@ export default function(props: Props) {
|
|||||||
}, [props.dispatch]);
|
}, [props.dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
titleInputRef.current.focus();
|
focus('Dialog::titleInputRef', titleInputRef.current);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
titleInputRef.current.select();
|
titleInputRef.current.select();
|
||||||
|
@ -32,6 +32,7 @@ import useStyles from '../utils/useStyles';
|
|||||||
import useContextMenu from '../utils/useContextMenu';
|
import useContextMenu from '../utils/useContextMenu';
|
||||||
import useWebviewIpcMessage from '../utils/useWebviewIpcMessage';
|
import useWebviewIpcMessage from '../utils/useWebviewIpcMessage';
|
||||||
import useEditorSearchHandler from '../utils/useEditorSearchHandler';
|
import useEditorSearchHandler from '../utils/useEditorSearchHandler';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
function markupRenderOptions(override: MarkupToHtmlOptions = null): MarkupToHtmlOptions {
|
function markupRenderOptions(override: MarkupToHtmlOptions = null): MarkupToHtmlOptions {
|
||||||
return { ...override };
|
return { ...override };
|
||||||
@ -142,7 +143,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
}
|
}
|
||||||
} else if (cmd.name === 'editor.focus') {
|
} else if (cmd.name === 'editor.focus') {
|
||||||
if (props.visiblePanes.indexOf('editor') >= 0) {
|
if (props.visiblePanes.indexOf('editor') >= 0) {
|
||||||
editorRef.current.focus();
|
focus('v5/CodeMirror::editor.focus', editorRef.current);
|
||||||
} else {
|
} else {
|
||||||
// If we just call focus() then the iframe is focused,
|
// If we just call focus() then the iframe is focused,
|
||||||
// but not its content, such that scrolling up / down
|
// but not its content, such that scrolling up / down
|
||||||
@ -188,7 +189,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
textItalic: () => wrapSelectionWithStrings('*', '*', _('emphasised text')),
|
textItalic: () => wrapSelectionWithStrings('*', '*', _('emphasised text')),
|
||||||
textLink: async () => {
|
textLink: async () => {
|
||||||
const url = await dialogs.prompt(_('Insert Hyperlink'));
|
const url = await dialogs.prompt(_('Insert Hyperlink'));
|
||||||
editorRef.current.focus();
|
focus('v5/CodeMirror::textLink', editorRef.current);
|
||||||
if (url) wrapSelectionWithStrings('[', `](${url})`);
|
if (url) wrapSelectionWithStrings('[', `](${url})`);
|
||||||
},
|
},
|
||||||
textCode: () => {
|
textCode: () => {
|
||||||
@ -699,30 +700,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
return output;
|
return output;
|
||||||
}, [styles.cellViewer, props.visiblePanes]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (!editorRef.current) return;
|
if (!editorRef.current) return;
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import Setting from '@joplin/lib/models/Setting';
|
|||||||
// import eventManager from '@joplin/lib/eventManager';
|
// import eventManager from '@joplin/lib/eventManager';
|
||||||
|
|
||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
// Based on http://pypl.github.io/PYPL.html
|
// Based on http://pypl.github.io/PYPL.html
|
||||||
const topLanguages = [
|
const topLanguages = [
|
||||||
@ -137,7 +138,7 @@ function Editor(props: EditorProps, ref: any) {
|
|||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||||
const editor_drop = useCallback((cm: any, _event: any) => {
|
const editor_drop = useCallback((cm: any, _event: any) => {
|
||||||
cm.focus();
|
focus('v5/Editor::editor_drop', cm);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const editor_drag = useCallback((cm: any, event: any) => {
|
const editor_drag = useCallback((cm: any, event: any) => {
|
||||||
|
@ -311,30 +311,6 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
return output;
|
return output;
|
||||||
}, [styles.cellViewer, props.visiblePanes]);
|
}, [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({
|
useEditorSearchHandler({
|
||||||
setLocalSearchResultCount: props.setLocalSearchResultCount,
|
setLocalSearchResultCount: props.setLocalSearchResultCount,
|
||||||
searchMarkers: props.searchMarkers,
|
searchMarkers: props.searchMarkers,
|
||||||
|
@ -8,6 +8,7 @@ import { EditorCommandType } from '@joplin/editor/types';
|
|||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
|
import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
|
||||||
import { MarkupLanguage } from '@joplin/renderer';
|
import { MarkupLanguage } from '@joplin/renderer';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
const logger = Logger.create('CodeMirror 6 commands');
|
const logger = Logger.create('CodeMirror 6 commands');
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ const useEditorCommands = (props: Props) => {
|
|||||||
},
|
},
|
||||||
textLink: async () => {
|
textLink: async () => {
|
||||||
const url = await dialogs.prompt(_('Insert Hyperlink'));
|
const url = await dialogs.prompt(_('Insert Hyperlink'));
|
||||||
editorRef.current.focus();
|
focus('useEditorCommands::textLink', editorRef.current);
|
||||||
if (url) wrapSelectionWithStrings(editorRef.current, '[', `](${url})`);
|
if (url) wrapSelectionWithStrings(editorRef.current, '[', `](${url})`);
|
||||||
},
|
},
|
||||||
insertText: (value: any) => editorRef.current.insertText(value),
|
insertText: (value: any) => editorRef.current.insertText(value),
|
||||||
@ -116,7 +117,7 @@ const useEditorCommands = (props: Props) => {
|
|||||||
},
|
},
|
||||||
'editor.focus': () => {
|
'editor.focus': () => {
|
||||||
if (props.visiblePanes.indexOf('editor') >= 0) {
|
if (props.visiblePanes.indexOf('editor') >= 0) {
|
||||||
editorRef.current.editor.focus();
|
focus('useEditorCommands::editor.focus', editorRef.current.editor);
|
||||||
} else {
|
} else {
|
||||||
// If we just call focus() then the iframe is focused,
|
// If we just call focus() then the iframe is focused,
|
||||||
// but not its content, such that scrolling up / down
|
// but not its content, such that scrolling up / down
|
||||||
|
@ -32,6 +32,7 @@ import markupRenderOptions from '../../utils/markupRenderOptions';
|
|||||||
import { DropHandler } from '../../utils/useDropHandler';
|
import { DropHandler } from '../../utils/useDropHandler';
|
||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
import useWebViewApi from './utils/useWebViewApi';
|
import useWebViewApi from './utils/useWebViewApi';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const { clipboard } = require('electron');
|
const { clipboard } = require('electron');
|
||||||
const supportedLocales = require('./supportedLocales');
|
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 }));
|
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, cmd.value, markupRenderOptions({ bodyOnly: true }));
|
||||||
editor.insertContent(result.html);
|
editor.insertContent(result.html);
|
||||||
} else if (cmd.name === 'editor.focus') {
|
} else if (cmd.name === 'editor.focus') {
|
||||||
editor.focus();
|
focus('TinyMCE::editor.focus', editor);
|
||||||
} else if (cmd.name === 'editor.execCommand') {
|
} else if (cmd.name === 'editor.execCommand') {
|
||||||
if (!('ui' in cmd.value)) cmd.value.ui = false;
|
if (!('ui' in cmd.value)) cmd.value.ui = false;
|
||||||
if (!('value' in cmd.value)) cmd.value.value = null;
|
if (!('value' in cmd.value)) cmd.value.value = null;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
import { MarkupToHtml } from '@joplin/renderer';
|
import { MarkupToHtml } from '@joplin/renderer';
|
||||||
import { TinyMceEditorEvents } from './types';
|
import { TinyMceEditorEvents } from './types';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
const taboverride = require('taboverride');
|
const taboverride = require('taboverride');
|
||||||
|
|
||||||
interface SourceInfo {
|
interface SourceInfo {
|
||||||
@ -13,7 +14,7 @@ interface SourceInfo {
|
|||||||
|
|
||||||
function dialogTextArea_keyDown(event: any) {
|
function dialogTextArea_keyDown(event: any) {
|
||||||
if (event.key === 'Tab') {
|
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 { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
export const declaration: CommandDeclaration = {
|
export const declaration: CommandDeclaration = {
|
||||||
name: 'focusElementNoteTitle',
|
name: 'focusElementNoteTitle',
|
||||||
@ -11,7 +12,7 @@ export const runtime = (comp: any): CommandRuntime => {
|
|||||||
return {
|
return {
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
if (!comp.titleInputRef.current) return;
|
if (!comp.titleInputRef.current) return;
|
||||||
comp.titleInputRef.current.focus();
|
focus('focusElementNoteTitle', comp.titleInputRef.current);
|
||||||
},
|
},
|
||||||
enabledCondition: 'oneNoteSelected',
|
enabledCondition: 'oneNoteSelected',
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
export const declaration: CommandDeclaration = {
|
export const declaration: CommandDeclaration = {
|
||||||
name: 'showLocalSearch',
|
name: 'showLocalSearch',
|
||||||
@ -13,7 +14,7 @@ export const runtime = (comp: any): CommandRuntime => {
|
|||||||
comp.editorRef.current.execCommand({ name: 'search' });
|
comp.editorRef.current.execCommand({ name: 'search' });
|
||||||
} else {
|
} else {
|
||||||
if (comp.noteSearchBarRef.current) {
|
if (comp.noteSearchBarRef.current) {
|
||||||
comp.noteSearchBarRef.current.focus();
|
focus('showLocalSearch', comp.noteSearchBarRef.current);
|
||||||
} else {
|
} else {
|
||||||
comp.setShowLocalSearch(true);
|
comp.setShowLocalSearch(true);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import { reg } from '@joplin/lib/registry';
|
|||||||
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
||||||
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
|
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
|
||||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
export interface OnLoadEvent {
|
export interface OnLoadEvent {
|
||||||
formNote: FormNote;
|
formNote: FormNote;
|
||||||
@ -191,7 +192,7 @@ export default function useFormNote(dependencies: HookDependencies) {
|
|||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (Setting.value(focusSettingName) === 'title') {
|
if (Setting.value(focusSettingName) === 'title') {
|
||||||
if (titleInputRef.current) titleInputRef.current.focus();
|
if (titleInputRef.current) focus('useFormNote::handleAutoFocus', titleInputRef.current);
|
||||||
} else {
|
} else {
|
||||||
if (editorRef.current) editorRef.current.execCommand({ name: 'editor.focus' });
|
if (editorRef.current) editorRef.current.execCommand({ name: 'editor.focus' });
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useState, useCallback, MutableRefObject, useEffect } from 'react';
|
import { useState, useCallback, MutableRefObject, useEffect } from 'react';
|
||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
import { SearchMarkers } from './useSearchMarkers';
|
import { SearchMarkers } from './useSearchMarkers';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
const CommandService = require('@joplin/lib/services/CommandService').default;
|
const CommandService = require('@joplin/lib/services/CommandService').default;
|
||||||
|
|
||||||
const logger = Logger.create('useNoteSearchBar');
|
const logger = Logger.create('useNoteSearchBar');
|
||||||
@ -36,7 +37,7 @@ export default function useNoteSearchBar({ noteSearchBarRef }: UseNoteSearchBarP
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showLocalSearch && noteSearchBarRef.current) {
|
if (showLocalSearch && noteSearchBarRef.current) {
|
||||||
noteSearchBarRef.current.focus();
|
focus('useNoteSearchBar', noteSearchBarRef.current);
|
||||||
}
|
}
|
||||||
}, [showLocalSearch, noteSearchBarRef]);
|
}, [showLocalSearch, noteSearchBarRef]);
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import useDragAndDrop from './utils/useDragAndDrop';
|
|||||||
import usePrevious from '../hooks/usePrevious';
|
import usePrevious from '../hooks/usePrevious';
|
||||||
import { itemIsInTrash } from '@joplin/lib/services/trash';
|
import { itemIsInTrash } from '@joplin/lib/services/trash';
|
||||||
import Folder from '@joplin/lib/models/Folder';
|
import Folder from '@joplin/lib/models/Folder';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
|
|
||||||
const commands = {
|
const commands = {
|
||||||
@ -159,7 +160,9 @@ const NoteList = (props: Props) => {
|
|||||||
makeItemIndexVisible(i);
|
makeItemIndexVisible(i);
|
||||||
if (doRefocus) {
|
if (doRefocus) {
|
||||||
const ref = itemRefs.current[id];
|
const ref = itemRefs.current[id];
|
||||||
if (ref) ref.focus();
|
if (ref) {
|
||||||
|
focus('NoteList::doRefocus', ref);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
import { useRef, useCallback, MutableRefObject } from 'react';
|
import { useRef, useCallback, MutableRefObject } from 'react';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
export type FocusNote = (noteId: string)=> void;
|
export type FocusNote = (noteId: string)=> void;
|
||||||
|
|
||||||
@ -16,14 +17,14 @@ const useFocusNote = (itemRefs: MutableRefObject<Record<string, HTMLDivElement>>
|
|||||||
if (focusItemIID.current) shim.clearInterval(focusItemIID.current);
|
if (focusItemIID.current) shim.clearInterval(focusItemIID.current);
|
||||||
focusItemIID.current = shim.setInterval(() => {
|
focusItemIID.current = shim.setInterval(() => {
|
||||||
if (itemRefs.current[noteId]) {
|
if (itemRefs.current[noteId]) {
|
||||||
itemRefs.current[noteId].focus();
|
focus('useFocusNote1', itemRefs.current[noteId]);
|
||||||
shim.clearInterval(focusItemIID.current);
|
shim.clearInterval(focusItemIID.current);
|
||||||
focusItemIID.current = null;
|
focusItemIID.current = null;
|
||||||
}
|
}
|
||||||
}, 10);
|
}, 10);
|
||||||
} else {
|
} else {
|
||||||
if (focusItemIID.current) shim.clearInterval(focusItemIID.current);
|
if (focusItemIID.current) shim.clearInterval(focusItemIID.current);
|
||||||
itemRefs.current[noteId].focus();
|
focus('useFocusNote2', itemRefs.current[noteId]);
|
||||||
}
|
}
|
||||||
}, [itemRefs]);
|
}, [itemRefs]);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import Note from '@joplin/lib/models/Note';
|
|||||||
import bridge from '../services/bridge';
|
import bridge from '../services/bridge';
|
||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
const Datetime = require('react-datetime').default;
|
const Datetime = require('react-datetime').default;
|
||||||
const { clipboard } = require('electron');
|
const { clipboard } = require('electron');
|
||||||
const formatcoords = require('formatcoords');
|
const formatcoords = require('formatcoords');
|
||||||
@ -77,7 +78,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
|
|
||||||
public componentDidUpdate() {
|
public componentDidUpdate() {
|
||||||
if (this.state.editedKey === null) {
|
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) {
|
if ((this.refs.editField as any).openCalendar) {
|
||||||
(this.refs.editField as any).openCalendar();
|
(this.refs.editField as any).openCalendar();
|
||||||
} else {
|
} else {
|
||||||
(this.refs.editField as any).focus();
|
focus('NotePropertiesDialog::editPropertyButtonClick', (this.refs.editField as any));
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@ -255,7 +256,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
public async cancelProperty() {
|
public async cancelProperty() {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||||
return new Promise((resolve: Function) => {
|
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({
|
this.setState({
|
||||||
editedKey: null,
|
editedKey: null,
|
||||||
editedValue: null,
|
editedValue: null,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { themeStyle } from '@joplin/lib/theme';
|
import { themeStyle } from '@joplin/lib/theme';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
@ -32,6 +33,8 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
this.previousButton_click = this.previousButton_click.bind(this);
|
this.previousButton_click = this.previousButton_click.bind(this);
|
||||||
this.nextButton_click = this.nextButton_click.bind(this);
|
this.nextButton_click = this.nextButton_click.bind(this);
|
||||||
this.closeButton_click = this.closeButton_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.focus = this.focus.bind(this);
|
||||||
|
|
||||||
this.backgroundColor = undefined;
|
this.backgroundColor = undefined;
|
||||||
@ -125,7 +128,7 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
(this.refs.searchInput as any).focus();
|
focus('NoteSearchBar::focus', this.refs.searchInput as any);
|
||||||
(this.refs.searchInput as any).select();
|
(this.refs.searchInput as any).select();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import PostMessageService, { MessageResponse, ResponderComponentType } from '@joplin/lib/services/PostMessageService';
|
import PostMessageService, { MessageResponse, ResponderComponentType } from '@joplin/lib/services/PostMessageService';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
// 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() {
|
public focus() {
|
||||||
if (this.webviewRef_.current) {
|
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 CreatableSelect from 'react-select/creatable';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
import makeAnimated from 'react-select/animated';
|
import makeAnimated from 'react-select/animated';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
defaultValue: any;
|
defaultValue: any;
|
||||||
@ -67,7 +68,7 @@ export default class PromptDialog extends React.Component<Props, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate() {
|
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;
|
this.focusInput_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import uuid from '@joplin/lib/uuid';
|
|||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
import Note from '@joplin/lib/models/Note';
|
import Note from '@joplin/lib/models/Note';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
|
import { blur, focus } from '@joplin/lib/utils/focusHandler';
|
||||||
const debounce = require('debounce');
|
const debounce = require('debounce');
|
||||||
const styled = require('styled-components').default;
|
const styled = require('styled-components').default;
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ function SearchBar(props: Props) {
|
|||||||
|
|
||||||
const onKeyDown = useCallback((event: any) => {
|
const onKeyDown = useCallback((event: any) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
if (document.activeElement) (document.activeElement as any).blur();
|
if (document.activeElement) blur('SearchBar::onKeyDown', document.activeElement as any);
|
||||||
void onExitSearch();
|
void onExitSearch();
|
||||||
}
|
}
|
||||||
}, [onExitSearch]);
|
}, [onExitSearch]);
|
||||||
@ -127,7 +128,7 @@ function SearchBar(props: Props) {
|
|||||||
void onExitSearch();
|
void onExitSearch();
|
||||||
} else {
|
} else {
|
||||||
setSearchStarted(true);
|
setSearchStarted(true);
|
||||||
props.inputRef.current.focus();
|
focus('SearchBar::onSearchButtonClick', props.inputRef.current);
|
||||||
props.dispatch({
|
props.dispatch({
|
||||||
type: 'FOCUS_SET',
|
type: 'FOCUS_SET',
|
||||||
field: 'globalSearch',
|
field: 'globalSearch',
|
||||||
|
@ -29,6 +29,7 @@ import { RuntimeProps } from './commands/focusElementSideBar';
|
|||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
import { renderFolders, renderTags } from '@joplin/lib/components/shared/side-menu-shared';
|
import { renderFolders, renderTags } from '@joplin/lib/components/shared/side-menu-shared';
|
||||||
import { getTrashFolderIcon, getTrashFolderId } from '@joplin/lib/services/trash';
|
import { getTrashFolderIcon, getTrashFolderId } from '@joplin/lib/services/trash';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
const { themeStyle } = require('@joplin/lib/theme');
|
const { themeStyle } = require('@joplin/lib/theme');
|
||||||
const bridge = require('@electron/remote').require('./bridge').default;
|
const bridge = require('@electron/remote').require('./bridge').default;
|
||||||
const Menu = bridge().Menu;
|
const Menu = bridge().Menu;
|
||||||
@ -674,7 +675,7 @@ const SidebarComponent = (props: Props) => {
|
|||||||
id: focusItem.id,
|
id: focusItem.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
focusItem.ref.current.focus();
|
focus('SideBar::onKeyDown', focusItem.ref.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyCode === 9) {
|
if (keyCode === 9) {
|
||||||
|
@ -2,6 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/
|
|||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
|
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
|
||||||
import { AppState } from '../../../app.reducer';
|
import { AppState } from '../../../app.reducer';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
export const declaration: CommandDeclaration = {
|
export const declaration: CommandDeclaration = {
|
||||||
name: 'focusElementSideBar',
|
name: 'focusElementSideBar',
|
||||||
@ -24,10 +25,10 @@ export const runtime = (props: RuntimeProps): CommandRuntime => {
|
|||||||
const item = props.getSelectedItem();
|
const item = props.getSelectedItem();
|
||||||
if (item) {
|
if (item) {
|
||||||
const anchorRef = props.anchorItemRefs.current[item.type][item.id];
|
const anchorRef = props.anchorItemRefs.current[item.type][item.id];
|
||||||
if (anchorRef) anchorRef.current.focus();
|
if (anchorRef) focus('focusElementSideBar1', anchorRef.current);
|
||||||
} else {
|
} else {
|
||||||
const anchorRef = props.getFirstAnchorItemRef('folder');
|
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 useScriptLoader from './hooks/useScriptLoader';
|
||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
const logger = Logger.create('UserWebview');
|
const logger = Logger.create('UserWebview');
|
||||||
|
|
||||||
@ -99,7 +100,7 @@ function UserWebview(props: Props, ref: any) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
focus: function() {
|
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 WebviewController from '@joplin/lib/services/plugins/WebviewController';
|
||||||
import UserWebview, { Props as UserWebviewProps } from './UserWebview';
|
import UserWebview, { Props as UserWebviewProps } from './UserWebview';
|
||||||
import UserWebviewDialogButtonBar from './UserWebviewDialogButtonBar';
|
import UserWebviewDialogButtonBar from './UserWebviewDialogButtonBar';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
const styled = require('styled-components').default;
|
const styled = require('styled-components').default;
|
||||||
|
|
||||||
interface Props extends UserWebviewProps {
|
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
|
// We focus the dialog once it's ready to make sure that the ESC/Enter
|
||||||
// keyboard shortcuts are working.
|
// keyboard shortcuts are working.
|
||||||
// https://github.com/laurent22/joplin/issues/4474
|
// https://github.com/laurent22/joplin/issues/4474
|
||||||
if (webviewRef.current) webviewRef.current.focus();
|
if (webviewRef.current) focus('UserWebviewDialog', webviewRef.current);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx", "../lib/utils/focusHandler.ts",
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"**/node_modules",
|
"**/node_modules",
|
||||||
|
@ -11,6 +11,7 @@ import { _ } from '@joplin/lib/locale';
|
|||||||
import { EditorControl } from './types';
|
import { EditorControl } from './types';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import SelectionFormatting from '@joplin/editor/SelectionFormatting';
|
import SelectionFormatting from '@joplin/editor/SelectionFormatting';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
|
|
||||||
interface LinkDialogProps {
|
interface LinkDialogProps {
|
||||||
editorControl: EditorControl;
|
editorControl: EditorControl;
|
||||||
@ -100,7 +101,7 @@ const EditLinkDialog = (props: LinkDialogProps) => {
|
|||||||
autoFocus
|
autoFocus
|
||||||
|
|
||||||
onSubmitEditing={() => {
|
onSubmitEditing={() => {
|
||||||
linkInputRef.current.focus();
|
focus('EditLinkDialog::onSubmitEditing', linkInputRef.current);
|
||||||
}}
|
}}
|
||||||
onChangeText={(text: string) => setLinkLabel(text)}
|
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 { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||||
import pickDocument from '../../utils/pickDocument';
|
import pickDocument from '../../utils/pickDocument';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
const urlUtils = require('@joplin/lib/urlUtils');
|
const urlUtils = require('@joplin/lib/urlUtils');
|
||||||
|
|
||||||
const emptyArray: any[] = [];
|
const emptyArray: any[] = [];
|
||||||
@ -1328,13 +1329,8 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
|
|||||||
// Avoid writing `this.titleTextFieldRef.current` -- titleTextFieldRef may
|
// Avoid writing `this.titleTextFieldRef.current` -- titleTextFieldRef may
|
||||||
// be undefined.
|
// be undefined.
|
||||||
if (fieldToFocus === 'title' && this.titleTextFieldRef?.current) {
|
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) {
|
private async folderPickerOptions_valueChanged(itemValue: any) {
|
||||||
|
@ -11,6 +11,7 @@ import swapLine, { SwapLineDirection } from './swapLine';
|
|||||||
import duplicateLine from './duplicateLine';
|
import duplicateLine from './duplicateLine';
|
||||||
import sortSelectedLines from './sortSelectedLines';
|
import sortSelectedLines from './sortSelectedLines';
|
||||||
import { closeSearchPanel, findNext, findPrevious, openSearchPanel, replaceAll, replaceNext } from '@codemirror/search';
|
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;
|
export type EditorCommandFunction = (editor: EditorView, ...args: any[])=> void|any;
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ const editorCommands: Record<EditorCommandType, EditorCommandFunction> = {
|
|||||||
[EditorCommandType.Undo]: undo,
|
[EditorCommandType.Undo]: undo,
|
||||||
[EditorCommandType.Redo]: redo,
|
[EditorCommandType.Redo]: redo,
|
||||||
[EditorCommandType.SelectAll]: selectAll,
|
[EditorCommandType.SelectAll]: selectAll,
|
||||||
[EditorCommandType.Focus]: editor => editor.focus(),
|
[EditorCommandType.Focus]: editor => focus('editorCommands::focus', editor),
|
||||||
|
|
||||||
[EditorCommandType.ToggleBolded]: toggleBolded,
|
[EditorCommandType.ToggleBolded]: toggleBolded,
|
||||||
[EditorCommandType.ToggleItalicized]: toggleItalicized,
|
[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.contentWindow.onafterprint = () => {
|
||||||
frame.remove();
|
frame.remove();
|
||||||
};
|
};
|
||||||
frame.focus();
|
console.warn('frame.focus() has been disabled!! Use focusHandler instead');
|
||||||
|
// frame.focus();
|
||||||
frame.contentWindow.print();
|
frame.contentWindow.print();
|
||||||
};
|
};
|
||||||
frame.src = this.url as string;
|
frame.src = this.url as string;
|
||||||
|
Reference in New Issue
Block a user