From 6b2381767a1339f1df1120f8f6021f8b6197fa53 Mon Sep 17 00:00:00 2001 From: Tib Teng <661892+tiberiusteng@users.noreply.github.com> Date: Tue, 30 Jan 2024 15:21:49 +0900 Subject: [PATCH 1/5] Mobile: resolves #8639: implement callback url on android --- .../android/app/src/main/AndroidManifest.xml | 9 ++++ .../app-mobile/components/screens/Note.tsx | 12 +++++ .../app-mobile/components/screens/Notes.tsx | 29 ++++++++++- packages/app-mobile/root.tsx | 52 +++++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/packages/app-mobile/android/app/src/main/AndroidManifest.xml b/packages/app-mobile/android/app/src/main/AndroidManifest.xml index 3972acaf8a3..60022c03124 100644 --- a/packages/app-mobile/android/app/src/main/AndroidManifest.xml +++ b/packages/app-mobile/android/app/src/main/AndroidManifest.xml @@ -92,6 +92,15 @@ + + + + + + + + + diff --git a/packages/app-mobile/components/screens/Note.tsx b/packages/app-mobile/components/screens/Note.tsx index 57f8922a0e7..5986dd2027e 100644 --- a/packages/app-mobile/components/screens/Note.tsx +++ b/packages/app-mobile/components/screens/Note.tsx @@ -57,6 +57,7 @@ import { join } from 'path'; import { Dispatch } from 'redux'; import { RefObject } from 'react'; import { SelectionRange } from '../NoteEditor/types'; +import { getNoteCallbackUrl } from '@joplin/lib/callbackUrlUtils'; const urlUtils = require('@joplin/lib/urlUtils'); const emptyArray: any[] = []; @@ -1098,6 +1099,11 @@ class NoteScreenComponent extends BaseScreenComponent implements B Clipboard.setString(Note.markdownTag(note)); } + private copyExternalLink_onPress() { + const note = this.state.note; + Clipboard.setString(getNoteCallbackUrl(note.id)); + } + public sideMenuOptions() { const note = this.state.note; if (!note) return []; @@ -1305,6 +1311,12 @@ class NoteScreenComponent extends BaseScreenComponent implements B this.copyMarkdownLink_onPress(); }, }); + output.push({ + title: _('Copy external link'), + onPress: () => { + this.copyExternalLink_onPress(); + }, + }); } output.push({ title: _('Properties'), diff --git a/packages/app-mobile/components/screens/Notes.tsx b/packages/app-mobile/components/screens/Notes.tsx index 28869f0d488..6042feb8da9 100644 --- a/packages/app-mobile/components/screens/Notes.tsx +++ b/packages/app-mobile/components/screens/Notes.tsx @@ -8,7 +8,7 @@ import Tag from '@joplin/lib/models/Tag'; import Note from '@joplin/lib/models/Note'; import Setting from '@joplin/lib/models/Setting'; const { themeStyle } = require('../global-style.js'); -import { ScreenHeader } from '../ScreenHeader'; +import { ScreenHeader, MenuOptionType } from '../ScreenHeader'; import { _ } from '@joplin/lib/locale'; import ActionButton from '../ActionButton'; const { dialogs } = require('../../utils/dialogs.js'); @@ -18,6 +18,8 @@ const { BackButtonService } = require('../../services/back-button.js'); import { AppState } from '../../utils/types'; import { NoteEntity } from '@joplin/lib/services/database/types'; const { ALL_NOTES_FILTER_ID } = require('@joplin/lib/reserved-ids.js'); +const Clipboard = require('@react-native-community/clipboard').default; +import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils'; class NotesScreenComponent extends BaseScreenComponent { @@ -197,6 +199,29 @@ class NotesScreenComponent extends BaseScreenComponent { return this.folderPickerOptions_; } + public menuOptions() { + const output: MenuOptionType[] = []; + + if (this.props.notesParentType === 'Tag') { + output.push({ + title: _('Copy external link'), + onPress: () => { + Clipboard.setString(getTagCallbackUrl(this.props.selectedTagId)); + }, + }); + } + if (this.props.notesParentType === 'Folder') { + output.push({ + title: _('Copy external link'), + onPress: () => { + Clipboard.setString(getFolderCallbackUrl(this.props.selectedFolderId)); + }, + }); + } + + return output; + } + public render() { const parent = this.parentItem(); const theme = themeStyle(this.props.themeId); @@ -285,7 +310,7 @@ class NotesScreenComponent extends BaseScreenComponent { accessibilityElementsHidden={accessibilityHidden} importantForAccessibility={accessibilityHidden ? 'no-hide-descendants' : undefined} > - + {actionButtonComp} new Promise(res => res); + private callbackUrl: string|null = null; public constructor() { super(); @@ -794,6 +796,14 @@ class AppComponent extends React.Component { logger.info('Sharing: handleOpenURL_: Processing share data'); void this.handleShareData(); } + + if (isCallbackUrl(event.url)) { + logger.info('received callback url: ', event.url); + this.callbackUrl = event.url; + if (this.props.biometricsDone) { + void this.handleCallbackUrl(); + } + } }; this.handleNewShare_ = () => { @@ -952,6 +962,9 @@ class AppComponent extends React.Component { if (this.props.biometricsDone !== prevProps.biometricsDone && this.props.biometricsDone) { logger.info('Sharing: componentDidUpdate: biometricsDone'); void this.handleShareData(); + if (this.callbackUrl !== null) { + void this.handleCallbackUrl(); + } } } @@ -992,6 +1005,45 @@ class AppComponent extends React.Component { } } + private async handleCallbackUrl() { + const url = this.callbackUrl; + this.callbackUrl = null; + if (url === null) { + return; + } + + const { command, params } = parseCallbackUrl(url); + + switch (command) { + + case CallbackUrlCommand.OpenNote: + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Note', + noteId: params.id, + }); + break; + + case CallbackUrlCommand.OpenTag: + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Notes', + tagId: params.id, + }); + break; + + case CallbackUrlCommand.OpenFolder: + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Notes', + folderId: params.id, + }); + break; + + } + } + + private async handleScreenWidthChange_() { this.setState({ sideMenuWidth: this.getSideMenuWidth() }); } From 4e1729d49ee8589dcbd9af5131d33277c3dca853 Mon Sep 17 00:00:00 2001 From: Tib Teng <661892+tiberiusteng@users.noreply.github.com> Date: Tue, 30 Jan 2024 21:31:46 +0900 Subject: [PATCH 2/5] cleanup --- packages/app-mobile/root.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index 48dd868e75d..4283e789f80 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -795,9 +795,7 @@ class AppComponent extends React.Component { if (event.url === ShareExtension.shareURL && this.props.biometricsDone) { logger.info('Sharing: handleOpenURL_: Processing share data'); void this.handleShareData(); - } - - if (isCallbackUrl(event.url)) { + } else if (isCallbackUrl(event.url)) { logger.info('received callback url: ', event.url); this.callbackUrl = event.url; if (this.props.biometricsDone) { @@ -962,9 +960,7 @@ class AppComponent extends React.Component { if (this.props.biometricsDone !== prevProps.biometricsDone && this.props.biometricsDone) { logger.info('Sharing: componentDidUpdate: biometricsDone'); void this.handleShareData(); - if (this.callbackUrl !== null) { - void this.handleCallbackUrl(); - } + void this.handleCallbackUrl(); } } @@ -1043,7 +1039,6 @@ class AppComponent extends React.Component { } } - private async handleScreenWidthChange_() { this.setState({ sideMenuWidth: this.getSideMenuWidth() }); } From 37364c5d9b0d716961039a2cb0f894946e569bd1 Mon Sep 17 00:00:00 2001 From: Tib Teng <661892+tiberiusteng@users.noreply.github.com> Date: Tue, 6 Feb 2024 03:34:51 +0900 Subject: [PATCH 3/5] go back and close side menu before navigating to callback url --- packages/app-mobile/root.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index 4283e789f80..c4d821d00c2 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -1010,6 +1010,13 @@ class AppComponent extends React.Component { const { command, params } = parseCallbackUrl(url); + // adopted from app-mobile/utils/shareHandler.ts + // We go back one screen in case there's already a note open - + // if we don't do this, the dispatch below will do nothing + // (because routeName wouldn't change) + this.props.dispatch({ type: 'NAV_BACK' }); + this.props.dispatch({ type: 'SIDE_MENU_CLOSE' }); + switch (command) { case CallbackUrlCommand.OpenNote: From 1fa5809d2d8e366aaaad234a425a70ef7ffece9c Mon Sep 17 00:00:00 2001 From: Tib Teng <661892+tiberiusteng@users.noreply.github.com> Date: Mon, 4 Mar 2024 01:54:37 +0900 Subject: [PATCH 4/5] add Edit/Delete notebook menu items in right-top three dots menu --- .../app-mobile/components/screens/Notes.tsx | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/app-mobile/components/screens/Notes.tsx b/packages/app-mobile/components/screens/Notes.tsx index 6042feb8da9..31c8fd9231e 100644 --- a/packages/app-mobile/components/screens/Notes.tsx +++ b/packages/app-mobile/components/screens/Notes.tsx @@ -1,5 +1,5 @@ const React = require('react'); -import { AppState as RNAppState, View, StyleSheet, NativeEventSubscription } from 'react-native'; +import { AppState as RNAppState, View, StyleSheet, NativeEventSubscription, Alert } from 'react-native'; import { stateUtils } from '@joplin/lib/reducer'; import { connect } from 'react-redux'; import NoteList from '../NoteList'; @@ -217,6 +217,44 @@ class NotesScreenComponent extends BaseScreenComponent { Clipboard.setString(getFolderCallbackUrl(this.props.selectedFolderId)); }, }); + // menu items originally in long-pressing Notebook items in side menu + // app-mobile/components/side-menu-content.tsx + output.push({ + title: _('Edit notebook'), + onPress: () => { + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Folder', + folderId: this.props.selectedFolderId, + }); + }, + }); + output.push({ + title: _('Delete notebook'), + onPress: () => { + const folderDeletion = (message: string) => { + Alert.alert('', message, [ + { + text: _('OK'), + onPress: () => { + void Folder.delete(this.props.selectedFolderId); + }, + }, + { + text: _('Cancel'), + onPress: () => { }, + style: 'cancel', + }, + ]); + }; + if (this.props.selectedFolderId === this.props.inboxJopId) { + return folderDeletion( + _('Delete the Inbox notebook?\n\nIf you delete the inbox notebook, any email that\'s recently been sent to it may be lost.'), + ); + } + return folderDeletion(_('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', this.parentItem().title)); + }, + }); } return output; @@ -340,6 +378,7 @@ const NotesScreen = connect((state: AppState) => { themeId: state.settings.theme, noteSelectionEnabled: state.noteSelectionEnabled, notesOrder: stateUtils.notesOrder(state.settings), + inboxJopId: state.settings['sync.10.inboxId'], }; })(NotesScreenComponent as any); From fed843ccd47edd4101accb2f2091cddd7bb9a707 Mon Sep 17 00:00:00 2001 From: Tib Teng <661892+tiberiusteng@users.noreply.github.com> Date: Sat, 15 Jun 2024 03:50:36 +0900 Subject: [PATCH 5/5] remove menus from Notes screen --- .../app-mobile/components/screens/Notes.tsx | 70 +------------------ 1 file changed, 3 insertions(+), 67 deletions(-) diff --git a/packages/app-mobile/components/screens/Notes.tsx b/packages/app-mobile/components/screens/Notes.tsx index d3e227d8485..4de1ab756aa 100644 --- a/packages/app-mobile/components/screens/Notes.tsx +++ b/packages/app-mobile/components/screens/Notes.tsx @@ -1,5 +1,5 @@ const React = require('react'); -import { AppState as RNAppState, View, StyleSheet, NativeEventSubscription, Alert } from 'react-native'; +import { AppState as RNAppState, View, StyleSheet, NativeEventSubscription } from 'react-native'; import { stateUtils } from '@joplin/lib/reducer'; import { connect } from 'react-redux'; import NoteList from '../NoteList'; @@ -8,7 +8,7 @@ import Tag from '@joplin/lib/models/Tag'; import Note from '@joplin/lib/models/Note'; import Setting from '@joplin/lib/models/Setting'; import { themeStyle } from '../global-style'; -import { ScreenHeader, MenuOptionType } from '../ScreenHeader'; +import { ScreenHeader } from '../ScreenHeader'; import { _ } from '@joplin/lib/locale'; import ActionButton from '../ActionButton'; const { dialogs } = require('../../utils/dialogs.js'); @@ -18,8 +18,6 @@ const { BackButtonService } = require('../../services/back-button.js'); import { AppState } from '../../utils/types'; import { NoteEntity } from '@joplin/lib/services/database/types'; import { itemIsInTrash } from '@joplin/lib/services/trash'; -const Clipboard = require('@react-native-community/clipboard').default; -import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils'; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied class NotesScreenComponent extends BaseScreenComponent { @@ -203,67 +201,6 @@ class NotesScreenComponent extends BaseScreenComponent { return this.folderPickerOptions_; } - public menuOptions() { - const output: MenuOptionType[] = []; - - if (this.props.notesParentType === 'Tag') { - output.push({ - title: _('Copy external link'), - onPress: () => { - Clipboard.setString(getTagCallbackUrl(this.props.selectedTagId)); - }, - }); - } - if (this.props.notesParentType === 'Folder') { - output.push({ - title: _('Copy external link'), - onPress: () => { - Clipboard.setString(getFolderCallbackUrl(this.props.selectedFolderId)); - }, - }); - // menu items originally in long-pressing Notebook items in side menu - // app-mobile/components/side-menu-content.tsx - output.push({ - title: _('Edit notebook'), - onPress: () => { - this.props.dispatch({ - type: 'NAV_GO', - routeName: 'Folder', - folderId: this.props.selectedFolderId, - }); - }, - }); - output.push({ - title: _('Delete notebook'), - onPress: () => { - const folderDeletion = (message: string) => { - Alert.alert('', message, [ - { - text: _('OK'), - onPress: () => { - void Folder.delete(this.props.selectedFolderId); - }, - }, - { - text: _('Cancel'), - onPress: () => { }, - style: 'cancel', - }, - ]); - }; - if (this.props.selectedFolderId === this.props.inboxJopId) { - return folderDeletion( - _('Delete the Inbox notebook?\n\nIf you delete the inbox notebook, any email that\'s recently been sent to it may be lost.'), - ); - } - return folderDeletion(_('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', this.parentItem().title)); - }, - }); - } - - return output; - } - public render() { const parent = this.parentItem(); const theme = themeStyle(this.props.themeId); @@ -338,7 +275,7 @@ class NotesScreenComponent extends BaseScreenComponent { accessibilityElementsHidden={accessibilityHidden} importantForAccessibility={accessibilityHidden ? 'no-hide-descendants' : undefined} > - + {actionButtonComp} { themeId: state.settings.theme, noteSelectionEnabled: state.noteSelectionEnabled, notesOrder: stateUtils.notesOrder(state.settings), - inboxJopId: state.settings['sync.10.inboxId'], }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied })(NotesScreenComponent as any);