mirror of https://github.com/laurent22/joplin.git
Chore: Refactor Note.tsx and note-screen-shared.tsx to improve type safety (#9467)
parent
b70589ef56
commit
27a0959f30
|
@ -22,7 +22,7 @@ import Folder from '@joplin/lib/models/Folder';
|
||||||
const Clipboard = require('@react-native-community/clipboard').default;
|
const Clipboard = require('@react-native-community/clipboard').default;
|
||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const { BackButtonService } = require('../../services/back-button.js');
|
const { BackButtonService } = require('../../services/back-button.js');
|
||||||
import NavService from '@joplin/lib/services/NavService';
|
import NavService, { OnNavigateCallback as OnNavigateCallback } from '@joplin/lib/services/NavService';
|
||||||
import BaseModel from '@joplin/lib/BaseModel';
|
import BaseModel from '@joplin/lib/BaseModel';
|
||||||
import ActionButton from '../ActionButton';
|
import ActionButton from '../ActionButton';
|
||||||
const { fileExtension, safeFileExtension } = require('@joplin/lib/path-utils');
|
const { fileExtension, safeFileExtension } = require('@joplin/lib/path-utils');
|
||||||
|
@ -34,17 +34,17 @@ const { Checkbox } = require('../checkbox.js');
|
||||||
import { _, currentLocale } from '@joplin/lib/locale';
|
import { _, currentLocale } from '@joplin/lib/locale';
|
||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
||||||
const { BaseScreenComponent } = require('../base-screen');
|
import { BaseScreenComponent } from '../base-screen';
|
||||||
const { themeStyle, editorFont } = require('../global-style.js');
|
const { themeStyle, editorFont } = require('../global-style.js');
|
||||||
const { dialogs } = require('../../utils/dialogs.js');
|
const { dialogs } = require('../../utils/dialogs.js');
|
||||||
const DialogBox = require('react-native-dialogbox').default;
|
const DialogBox = require('react-native-dialogbox').default;
|
||||||
import ImageResizer from '@bam.tech/react-native-image-resizer';
|
import ImageResizer from '@bam.tech/react-native-image-resizer';
|
||||||
import shared from '@joplin/lib/components/shared/note-screen-shared';
|
import shared, { BaseNoteScreenComponent } from '@joplin/lib/components/shared/note-screen-shared';
|
||||||
import { Asset, ImagePickerResponse, launchImageLibrary } from 'react-native-image-picker';
|
import { Asset, ImagePickerResponse, launchImageLibrary } from 'react-native-image-picker';
|
||||||
import SelectDateTimeDialog from '../SelectDateTimeDialog';
|
import SelectDateTimeDialog from '../SelectDateTimeDialog';
|
||||||
import ShareExtension from '../../utils/ShareExtension.js';
|
import ShareExtension from '../../utils/ShareExtension.js';
|
||||||
import CameraView from '../CameraView';
|
import CameraView from '../CameraView';
|
||||||
import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types';
|
import { FolderEntity, NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types';
|
||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
import ImageEditor from '../NoteEditor/ImageEditor/ImageEditor';
|
import ImageEditor from '../NoteEditor/ImageEditor/ImageEditor';
|
||||||
import promptRestoreAutosave from '../NoteEditor/ImageEditor/promptRestoreAutosave';
|
import promptRestoreAutosave from '../NoteEditor/ImageEditor/promptRestoreAutosave';
|
||||||
|
@ -54,6 +54,8 @@ import { voskEnabled } from '../../services/voiceTyping/vosk';
|
||||||
import { isSupportedLanguage } from '../../services/voiceTyping/vosk.android';
|
import { isSupportedLanguage } from '../../services/voiceTyping/vosk.android';
|
||||||
import { ChangeEvent as EditorChangeEvent, UndoRedoDepthChangeEvent } from '@joplin/editor/events';
|
import { ChangeEvent as EditorChangeEvent, UndoRedoDepthChangeEvent } from '@joplin/editor/events';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { RefObject } from 'react';
|
||||||
const urlUtils = require('@joplin/lib/urlUtils');
|
const urlUtils = require('@joplin/lib/urlUtils');
|
||||||
|
|
||||||
const emptyArray: any[] = [];
|
const emptyArray: any[] = [];
|
||||||
|
@ -114,20 +116,84 @@ const pickDocument = async (multiple: boolean): Promise<SelectedDocument[]> => {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NoteScreenComponent extends BaseScreenComponent {
|
interface Props {
|
||||||
|
provisionalNoteIds: string[];
|
||||||
|
dispatch: Dispatch;
|
||||||
|
noteId: string;
|
||||||
|
useEditorBeta: boolean;
|
||||||
|
themeId: number;
|
||||||
|
editorFontSize: number;
|
||||||
|
editorFont: number; // e.g. Setting.FONT_MENLO
|
||||||
|
showSideMenu: boolean;
|
||||||
|
searchQuery: string[];
|
||||||
|
ftsEnabled: boolean;
|
||||||
|
highlightedWords: string[];
|
||||||
|
noteHash: string;
|
||||||
|
toolbarEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
note: any;
|
||||||
|
mode: 'view'|'edit';
|
||||||
|
readOnly: boolean;
|
||||||
|
folder: FolderEntity|null;
|
||||||
|
lastSavedNote: any;
|
||||||
|
isLoading: boolean;
|
||||||
|
titleTextInputHeight: number;
|
||||||
|
alarmDialogShown: boolean;
|
||||||
|
heightBumpView: number;
|
||||||
|
noteTagDialogShown: boolean;
|
||||||
|
fromShare: boolean;
|
||||||
|
showCamera: boolean;
|
||||||
|
showImageEditor: boolean;
|
||||||
|
imageEditorResource: ResourceEntity;
|
||||||
|
imageEditorResourceFilepath: string;
|
||||||
|
noteResources: Record<string, ResourceEntity>;
|
||||||
|
newAndNoTitleChangeNoteId: boolean|null;
|
||||||
|
|
||||||
|
HACK_webviewLoadingState: number;
|
||||||
|
|
||||||
|
undoRedoButtonState: {
|
||||||
|
canUndo: boolean;
|
||||||
|
canRedo: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
voiceTypingDialogShown: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteScreenComponent extends BaseScreenComponent<Props, State> implements BaseNoteScreenComponent {
|
||||||
// This isn't in this.state because we don't want changing scroll to trigger
|
// This isn't in this.state because we don't want changing scroll to trigger
|
||||||
// a re-render.
|
// a re-render.
|
||||||
private lastBodyScroll: number|undefined = undefined;
|
private lastBodyScroll: number|undefined = undefined;
|
||||||
|
|
||||||
|
private saveActionQueues_: any;
|
||||||
|
private doFocusUpdate_: boolean;
|
||||||
|
private styles_: any;
|
||||||
|
private editorRef: any;
|
||||||
|
private titleTextFieldRef: RefObject<TextInput>;
|
||||||
|
private navHandler: OnNavigateCallback;
|
||||||
|
private backHandler: ()=> Promise<boolean>;
|
||||||
|
private undoRedoService_: UndoRedoService;
|
||||||
|
private noteTagDialog_closeRequested: any;
|
||||||
|
private onJoplinLinkClick_: any;
|
||||||
|
private refreshResource: (resource: any, noteBody?: string)=> Promise<void>;
|
||||||
|
private selection: any;
|
||||||
|
private menuOptionsCache_: Record<string, any>;
|
||||||
|
private focusUpdateIID_: any;
|
||||||
|
private folderPickerOptions_: any;
|
||||||
|
public dialogbox: any;
|
||||||
|
|
||||||
public static navigationOptions(): any {
|
public static navigationOptions(): any {
|
||||||
return { header: null };
|
return { header: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
public constructor() {
|
public constructor(props: Props) {
|
||||||
super();
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
note: Note.new(),
|
note: Note.new(),
|
||||||
mode: 'view',
|
mode: 'view',
|
||||||
|
readOnly: false,
|
||||||
folder: null,
|
folder: null,
|
||||||
lastSavedNote: null,
|
lastSavedNote: null,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
@ -140,6 +206,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||||
showImageEditor: false,
|
showImageEditor: false,
|
||||||
imageEditorResource: null,
|
imageEditorResource: null,
|
||||||
noteResources: {},
|
noteResources: {},
|
||||||
|
imageEditorResourceFilepath: null,
|
||||||
|
newAndNoTitleChangeNoteId: null,
|
||||||
|
|
||||||
// HACK: For reasons I can't explain, when the WebView is present, the TextInput initially does not display (It's just a white rectangle with
|
// HACK: For reasons I can't explain, when the WebView is present, the TextInput initially does not display (It's just a white rectangle with
|
||||||
// no visible text). It will only appear when tapping it or doing certain action like selecting text on the webview. The bug started to
|
// no visible text). It will only appear when tapping it or doing certain action like selecting text on the webview. The bug started to
|
||||||
|
@ -164,8 +232,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||||
|
|
||||||
this.doFocusUpdate_ = false;
|
this.doFocusUpdate_ = false;
|
||||||
|
|
||||||
this.saveButtonHasBeenShown_ = false;
|
|
||||||
|
|
||||||
this.styles_ = {};
|
this.styles_ = {};
|
||||||
|
|
||||||
this.editorRef = React.createRef();
|
this.editorRef = React.createRef();
|
||||||
|
@ -341,7 +407,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async undoRedo(type: string) {
|
private async undoRedo(type: 'undo'|'redo') {
|
||||||
const undoState = await this.undoRedoService_[type](this.undoState());
|
const undoState = await this.undoRedoService_[type](this.undoState());
|
||||||
if (!undoState) return;
|
if (!undoState) return;
|
||||||
|
|
||||||
|
@ -811,7 +877,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||||
|
|
||||||
this.setState({ note: newNote });
|
this.setState({ note: newNote });
|
||||||
|
|
||||||
this.refreshResource(resource, newNote.body);
|
void this.refreshResource(resource, newNote.body);
|
||||||
|
|
||||||
this.scheduleSave();
|
this.scheduleSave();
|
||||||
|
|
||||||
|
@ -1281,8 +1347,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||||
let fieldToFocus = this.state.note.is_todo ? 'title' : 'body';
|
let fieldToFocus = this.state.note.is_todo ? 'title' : 'body';
|
||||||
if (this.state.mode === 'view') fieldToFocus = '';
|
if (this.state.mode === 'view') fieldToFocus = '';
|
||||||
|
|
||||||
if (fieldToFocus === 'title' && this.refs.titleTextField) {
|
if (fieldToFocus === 'title' && this.titleTextFieldRef.current) {
|
||||||
this.refs.titleTextField.focus();
|
this.titleTextFieldRef.current.focus();
|
||||||
}
|
}
|
||||||
// if (fieldToFocus === 'body' && this.markdownEditorRef.current) {
|
// if (fieldToFocus === 'body' && this.markdownEditorRef.current) {
|
||||||
// if (this.markdownEditorRef.current) {
|
// if (this.markdownEditorRef.current) {
|
||||||
|
@ -1504,8 +1570,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||||
const showSaveButton = false; // this.state.mode === 'edit' || this.isModified() || this.saveButtonHasBeenShown_;
|
const showSaveButton = false; // this.state.mode === 'edit' || this.isModified() || this.saveButtonHasBeenShown_;
|
||||||
const saveButtonDisabled = true;// !this.isModified();
|
const saveButtonDisabled = true;// !this.isModified();
|
||||||
|
|
||||||
if (showSaveButton) this.saveButtonHasBeenShown_ = true;
|
|
||||||
|
|
||||||
const titleContainerStyle = isTodo ? this.styles().titleContainerTodo : this.styles().titleContainer;
|
const titleContainerStyle = isTodo ? this.styles().titleContainerTodo : this.styles().titleContainer;
|
||||||
|
|
||||||
const dueDate = Note.dueDateObject(note);
|
const dueDate = Note.dueDateObject(note);
|
||||||
|
@ -1514,7 +1578,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||||
<View style={titleContainerStyle}>
|
<View style={titleContainerStyle}>
|
||||||
{isTodo && <Checkbox style={this.styles().checkbox} checked={!!Number(note.todo_completed)} onChange={this.todoCheckbox_change} />}
|
{isTodo && <Checkbox style={this.styles().checkbox} checked={!!Number(note.todo_completed)} onChange={this.todoCheckbox_change} />}
|
||||||
<TextInput
|
<TextInput
|
||||||
ref="titleTextField"
|
ref={this.titleTextFieldRef}
|
||||||
underlineColorAndroid="#ffffff00"
|
underlineColorAndroid="#ffffff00"
|
||||||
autoCapitalize="sentences"
|
autoCapitalize="sentences"
|
||||||
style={this.styles().titleTextInput}
|
style={this.styles().titleTextInput}
|
||||||
|
|
|
@ -12,17 +12,27 @@ import { itemIsReadOnlySync, ItemSlice } from '../../models/utils/readOnly';
|
||||||
import ItemChange from '../../models/ItemChange';
|
import ItemChange from '../../models/ItemChange';
|
||||||
import BaseItem from '../../models/BaseItem';
|
import BaseItem from '../../models/BaseItem';
|
||||||
|
|
||||||
|
export interface BaseNoteScreenComponent {
|
||||||
|
props: any;
|
||||||
|
state: any;
|
||||||
|
setState: (newState: any)=> void;
|
||||||
|
|
||||||
|
scheduleFocusUpdate(): void;
|
||||||
|
attachFile(asset: any, fileType: any): void;
|
||||||
|
lastLoadedNoteId_?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Shared {
|
interface Shared {
|
||||||
noteExists?: (noteId: string)=> Promise<boolean>;
|
noteExists?: (noteId: string)=> Promise<boolean>;
|
||||||
handleNoteDeletedWhileEditing_?: (note: NoteEntity)=> Promise<NoteEntity>;
|
handleNoteDeletedWhileEditing_?: (note: NoteEntity)=> Promise<NoteEntity>;
|
||||||
saveNoteButton_press?: (comp: any, folderId: string, options: any)=> Promise<void>;
|
saveNoteButton_press?: (comp: BaseNoteScreenComponent, folderId: string, options: any)=> Promise<void>;
|
||||||
saveOneProperty?: (comp: any, name: string, value: any)=> void;
|
saveOneProperty?: (comp: BaseNoteScreenComponent, name: string, value: any)=> void;
|
||||||
noteComponent_change?: (comp: any, propName: string, propValue: any)=> void;
|
noteComponent_change?: (comp: BaseNoteScreenComponent, propName: string, propValue: any)=> void;
|
||||||
clearResourceCache?: ()=> void;
|
clearResourceCache?: ()=> void;
|
||||||
attachedResources?: (noteBody: string)=> Promise<any>;
|
attachedResources?: (noteBody: string)=> Promise<any>;
|
||||||
isModified?: (comp: any)=> boolean;
|
isModified?: (comp: BaseNoteScreenComponent)=> boolean;
|
||||||
initState?: (comp: any)=> void;
|
initState?: (comp: BaseNoteScreenComponent)=> Promise<void>;
|
||||||
toggleIsTodo_onPress?: (comp: any)=> void;
|
toggleIsTodo_onPress?: (comp: BaseNoteScreenComponent)=> void;
|
||||||
toggleCheckboxRange?: (ipcMessage: string, noteBody: string)=> any;
|
toggleCheckboxRange?: (ipcMessage: string, noteBody: string)=> any;
|
||||||
toggleCheckbox?: (ipcMessage: string, noteBody: string)=> string;
|
toggleCheckbox?: (ipcMessage: string, noteBody: string)=> string;
|
||||||
installResourceHandling?: (refreshResourceHandler: any)=> void;
|
installResourceHandling?: (refreshResourceHandler: any)=> void;
|
||||||
|
@ -54,7 +64,7 @@ shared.handleNoteDeletedWhileEditing_ = async (note: NoteEntity) => {
|
||||||
return Note.load(newNote.id);
|
return Note.load(newNote.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
shared.saveNoteButton_press = async function(comp: any, folderId: string = null, options: any = null) {
|
shared.saveNoteButton_press = async function(comp: BaseNoteScreenComponent, folderId: string = null, options: any = null) {
|
||||||
options = { autoTitle: true, ...options };
|
options = { autoTitle: true, ...options };
|
||||||
|
|
||||||
const releaseMutex = await saveNoteMutex_.acquire();
|
const releaseMutex = await saveNoteMutex_.acquire();
|
||||||
|
@ -150,7 +160,7 @@ shared.saveNoteButton_press = async function(comp: any, folderId: string = null,
|
||||||
releaseMutex();
|
releaseMutex();
|
||||||
};
|
};
|
||||||
|
|
||||||
shared.saveOneProperty = async function(comp: any, name: string, value: any) {
|
shared.saveOneProperty = async function(comp: BaseNoteScreenComponent, name: string, value: any) {
|
||||||
let note = { ...comp.state.note };
|
let note = { ...comp.state.note };
|
||||||
|
|
||||||
const recreatedNote = await shared.handleNoteDeletedWhileEditing_(note);
|
const recreatedNote = await shared.handleNoteDeletedWhileEditing_(note);
|
||||||
|
@ -167,7 +177,7 @@ shared.saveOneProperty = async function(comp: any, name: string, value: any) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
shared.noteComponent_change = function(comp: any, propName: string, propValue: any) {
|
shared.noteComponent_change = function(comp: BaseNoteScreenComponent, propName: string, propValue: any) {
|
||||||
const newState: any = {};
|
const newState: any = {};
|
||||||
|
|
||||||
const note = { ...comp.state.note };
|
const note = { ...comp.state.note };
|
||||||
|
@ -211,14 +221,14 @@ shared.attachedResources = async function(noteBody: string) {
|
||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
|
|
||||||
shared.isModified = function(comp: any) {
|
shared.isModified = function(comp: BaseNoteScreenComponent) {
|
||||||
if (!comp.state.note || !comp.state.lastSavedNote) return false;
|
if (!comp.state.note || !comp.state.lastSavedNote) return false;
|
||||||
const diff = BaseModel.diffObjects(comp.state.lastSavedNote, comp.state.note);
|
const diff = BaseModel.diffObjects(comp.state.lastSavedNote, comp.state.note);
|
||||||
delete diff.type_;
|
delete diff.type_;
|
||||||
return !!Object.getOwnPropertyNames(diff).length;
|
return !!Object.getOwnPropertyNames(diff).length;
|
||||||
};
|
};
|
||||||
|
|
||||||
shared.initState = async function(comp: any) {
|
shared.initState = async function(comp: BaseNoteScreenComponent) {
|
||||||
const isProvisionalNote = comp.props.provisionalNoteIds.includes(comp.props.noteId);
|
const isProvisionalNote = comp.props.provisionalNoteIds.includes(comp.props.noteId);
|
||||||
|
|
||||||
const note = await Note.load(comp.props.noteId);
|
const note = await Note.load(comp.props.noteId);
|
||||||
|
@ -268,7 +278,7 @@ shared.initState = async function(comp: any) {
|
||||||
comp.lastLoadedNoteId_ = note.id;
|
comp.lastLoadedNoteId_ = note.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
shared.toggleIsTodo_onPress = function(comp: any) {
|
shared.toggleIsTodo_onPress = function(comp: BaseNoteScreenComponent) {
|
||||||
const newNote = Note.toggleIsTodo(comp.state.note);
|
const newNote = Note.toggleIsTodo(comp.state.note);
|
||||||
const newState = { note: newNote };
|
const newState = { note: newNote };
|
||||||
comp.setState(newState);
|
comp.setState(newState);
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
export type OnNavigateCallback = ()=> Promise<boolean>;
|
||||||
|
|
||||||
export default class NavService {
|
export default class NavService {
|
||||||
|
|
||||||
// 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
|
||||||
public static dispatch: Function = () => {};
|
public static dispatch: Function = () => {};
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
private static handlers_: OnNavigateCallback[] = [];
|
||||||
private static handlers_: Function[] = [];
|
|
||||||
|
|
||||||
public static async go(routeName: string) {
|
public static async go(routeName: string) {
|
||||||
if (this.handlers_.length) {
|
if (this.handlers_.length) {
|
||||||
|
@ -15,10 +16,11 @@ export default class NavService {
|
||||||
type: 'NAV_GO',
|
type: 'NAV_GO',
|
||||||
routeName: routeName,
|
routeName: routeName,
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
public static addHandler(handler: Function) {
|
public static addHandler(handler: OnNavigateCallback) {
|
||||||
for (let i = this.handlers_.length - 1; i >= 0; i--) {
|
for (let i = this.handlers_.length - 1; i >= 0; i--) {
|
||||||
const h = this.handlers_[i];
|
const h = this.handlers_[i];
|
||||||
if (h === handler) return;
|
if (h === handler) return;
|
||||||
|
@ -28,7 +30,7 @@ export default class NavService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
public static removeHandler(hanlder: Function) {
|
public static removeHandler(hanlder: OnNavigateCallback) {
|
||||||
for (let i = this.handlers_.length - 1; i >= 0; i--) {
|
for (let i = this.handlers_.length - 1; i >= 0; i--) {
|
||||||
const h = this.handlers_[i];
|
const h = this.handlers_[i];
|
||||||
if (h === hanlder) this.handlers_.splice(i, 1);
|
if (h === hanlder) this.handlers_.splice(i, 1);
|
||||||
|
|
Loading…
Reference in New Issue