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);