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