1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Mobile: Add quick actions (#2247)

* Super basic QuickAction is working!

* QuickAction successfully creates notes

* Update icons

* Update icons and support new to-do

* Update icons and support new to-do

* Fixed

* Extract QuickActions

* It works as long as you go out of the edit screen (saving is not sufficient)

* working? but still kinda buggy...

* Cleanup

* Cleanup

* Cleanup

* Use the same pattern as onJoplinLinkClick_

* Cleanup

* Cleanup

* Remove pluginAssets/index.js change

* manual Andoid linking

* Transition QuickActions.js to .ts

* Unstage QuickActions.js in favor of .ts

* Move QuickActions out of lib/

* Add comment about userInfo in QuickActions

* Remove redundant QuickActions file

* Remove pluginAssets/index to resolve conflict

* Update CONTRIBUTING.md to include test runner troubleshooting

* Add `npm run tsc` to Unit Tests docs

Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>
This commit is contained in:
Devon Zuegel 2020-02-18 15:52:36 -08:00 committed by GitHub
parent e6cbd8c8f8
commit eeb9999334
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1306 additions and 57 deletions

View File

@ -53,6 +53,7 @@ ReactNativeClient/lib/joplin-renderer/assets/
# Ignore files generated from TypeScript files
ElectronClient/app/gui/ShareNoteDialog.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/QuickActions.js
ReactNativeClient/PluginAssetsLoader.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js

1
.gitignore vendored
View File

@ -49,6 +49,7 @@ Tools/commit_hook.txt
# Ignore files generated from TypeScript files
ElectronClient/app/gui/ShareNoteDialog.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/QuickActions.js
ReactNativeClient/PluginAssetsLoader.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js

View File

@ -52,21 +52,32 @@ When submitting a pull request for a new feature or bug fix, please add unit tes
The tests are under CliClient/tests. To get them running, you first need to build the CLI app:
cd CliClient
npm i
./build.sh
```sh
npm run tsc # Build the .ts and .tsx files
cd CliClient
npm i
./build.sh
```
To run all the test units:
./run_test.sh
```sh
./run_test.sh
```
To run just one particular file:
./run_test.sh markdownUtils # Don't add the .js extension
```sh
./run_test.sh markdownUtils # Don't add the .js extension
```
To filter tests:
./run_test.sh "should handle conflict" # Will run all the test units that contain "should handle conflict" in their description
```sh
./run_test.sh "should handle conflict" # Will run all the test units that contain "should handle conflict" in their description
```
If you get the error `Cannot find module '/joplin/CliClient/node_modules/sqlite3/lib/binding/node-v79-darwin-x64/node_sqlite3.node'`, you may need to run `npm rebuild`.
## About abandoned pull requests

View File

@ -2929,7 +2929,6 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.4.tgz",
"integrity": "sha512-qHwPdGyGr9pOZBoSgUOuNPG20QYZVN00lFcxKQgjPUODSxVH7obQeLVVawa3B4cfSNtLIeczSzoy/xYA8XG5WQ==",
"dev": true,
"requires": {
"he": "1.1.1"
}

View File

@ -6952,6 +6952,36 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"param-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
"integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
"requires": {
"no-case": "^2.2.0"
}
},
"parse-glob": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
"integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
"dev": true,
"optional": true,
"requires": {
"glob-base": "^0.3.0",
"is-dotfile": "^1.0.0",
"is-extglob": "^1.0.0",
"is-glob": "^2.0.0"
},
"dependencies": {
"is-extglob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"dev": true,
"optional": true
},
"semver-diff": {
"version": "2.1.0",
@ -7653,7 +7683,8 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
"integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=",
"dev": true
"dev": true,
"optional": true
},
"repeat-string": {
"version": "1.6.1",

View File

@ -0,0 +1,50 @@
import {DeviceEventEmitter} from 'react-native';
import * as QuickActions from 'react-native-quick-actions';
const { _ } = require('lib/locale.js');
type TData = {
type: string
}
export default (dispatch: Function, folderId: string) => {
const userInfo = { url: '' };
QuickActions.setShortcutItems([
{type: 'New note', title: _('New note'), icon: 'Compose', userInfo},
{type: 'New to-do', title: _('New to-do'), icon: 'Add', userInfo},
]);
DeviceEventEmitter.addListener('quickActionShortcut', (data: TData) => {
// This dispatch is to momentarily go back to reset state, similar to what
// happens in onJoplinLinkClick_(). Easier to just go back, then go to the
// note since the Note screen doesn't handle reloading a different note.
//
// This hack is necessary because otherwise you get this problem:
// The first time you create a note from the quick-action menu, it works
// perfectly. But if you do it again immediately later, it re-opens the
// page to that first note you made rather than creating an entirely new
// note. If you navigate around enough (which I think changes the redux
// state sufficiently or something), then it'll work again.
dispatch({type: 'NAV_BACK'});
if (data.type === 'New note') {
dispatch({
type: 'NAV_GO',
noteId: null,
folderId,
routeName: 'Note',
itemType: 'note',
});
}
if (data.type === 'New to-do') {
dispatch({
type: 'NAV_GO',
noteId: null,
folderId,
routeName: 'Note',
itemType: 'todo',
});
}
});
};

View File

@ -180,6 +180,8 @@ android {
}
dependencies {
implementation project(':react-native-quick-actions')
implementation project(':@react-native-community_slider')
implementation "org.webkit:android-jsc:r241213"
compile project(':react-native-push-notification')
// implementation (project(':react-native-camera')) {
@ -219,6 +221,7 @@ dependencies {
implementation project(':react-native-version-info')
implementation project(':react-native-camera')
implementation "com.facebook.react:react-native:+"
implementation project(':react-native-quick-actions')
// implementation "com.google.android.gms:play-services-base:16.0.1" // For Firebase
// implementation 'me.leolin:ShortcutBadger:1.1.21@aar' // For Firebase - this line if you wish to use badge on Android

View File

@ -4,6 +4,9 @@ import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.reactNativeQuickActions.AppShortcutsPackage;
import com.reactnativecommunity.slider.ReactSliderPackage;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
@ -19,51 +22,74 @@ import android.database.CursorWindow;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new AppShortcutsPackage(),
new ReactSliderPackage(),
new RNCWebViewPackage(),
new ReactNativePushNotificationPackage(),
new ImageResizerPackage(),
new RNFileViewerPackage(),
new RNSecureRandomPackage(),
new ImagePickerPackage(),
new ReactNativeDocumentPicker(),
new RNFetchBlobPackage(),
new RNFSPackage(),
new SQLitePluginPackage(),
new VectorIconsPackage(),
new SharePackage(),
new RNCameraPackage(),
new RNVersionInfoPackage()
);
}
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public void onCreate() {
super.onCreate();
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
// To try to fix the error "Row too big to fit into CursorWindow"
// https://github.com/andpor/react-native-sqlite-storage/issues/364#issuecomment-526423153
// https://github.com/laurent22/joplin/issues/1767#issuecomment-515617991
try {
Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
@Override
public void onCreate() {
super.onCreate();
// To try to fix the error "Row too big to fit into CursorWindow"
// https://github.com/andpor/react-native-sqlite-storage/issues/364#issuecomment-526423153
// https://github.com/laurent22/joplin/issues/1767#issuecomment-515617991
try {
Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
field.setAccessible(true);
field.set(null, 50 * 1024 * 1024); //the 102400 is the new size added
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this); // Remove this line if you don't want Flipper enabled
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this); // Remove this line if you don't want Flipper enabled
}
/**
* Loads Flipper in React Native templates.
*

View File

@ -1,4 +1,22 @@
rootProject.name = 'Joplin'
include ':react-native-quick-actions'
project(':react-native-quick-actions').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-quick-actions/android')
include ':@react-native-community_slider'
project(':@react-native-community_slider').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/slider/android')
include ':react-native-webview'
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
include ':react-native-file-viewer'
project(':react-native-file-viewer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-file-viewer/android')
include ':react-native-securerandom'
project(':react-native-securerandom').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-securerandom/android')
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
include ':react-native-image-picker'
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':react-native-image-resizer'
project(':react-native-image-resizer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-resizer/android')
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
*/
#import "AppDelegate.h"
#import "RNQuickActionManager.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
@ -82,4 +83,8 @@ fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
#endif
}
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded)) completionHandler {
[RNQuickActionManager onQuickActionPress:shortcutItem completionHandler:completionHandler];
}
@end

View File

@ -131,7 +131,8 @@ class NoteScreenComponent extends BaseScreenComponent {
if (item.type_ === BaseModel.TYPE_NOTE) {
// Easier to just go back, then go to the note since
// the Note screen doesn't handle reloading a different note
// the Note screen doesn't handle reloading a different note.
// Similar to creating a new note via QuickActions.
this.props.dispatch({
type: 'NAV_BACK',

View File

@ -8,13 +8,11 @@
// console.disableYellowBox = true
import { YellowBox } from 'react-native';
import {YellowBox, AppRegistry} from 'react-native';
YellowBox.ignoreWarnings([
'Require cycle: node_modules/react-native-',
'Require cycle: node_modules/rn-fetch-blob',
]);
const { AppRegistry } = require('react-native');
const { Root } = require('./root.js');
function main() {

View File

@ -7441,6 +7441,11 @@
"version": "git+https://github.com/laurent22/react-native-push-notification.git#04d0746035d1fb058d8f52696121a0d5a29acbc4",
"from": "git+https://github.com/laurent22/react-native-push-notification.git"
},
"react-native-quick-actions": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/react-native-quick-actions/-/react-native-quick-actions-0.3.13.tgz",
"integrity": "sha512-Vz13a0+NV0mzCh/29tNt0qDzWPh8i2srTQW8eCSzGFDArnVm1COTOhTD0FY0hWHlxRY0ahvX+BlezTDvsyAuMA=="
},
"react-native-securerandom": {
"version": "1.0.0-rc.0",
"resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-1.0.0-rc.0.tgz",

View File

@ -65,6 +65,8 @@
"react-native-popup-dialog": "^0.9.35",
"react-native-popup-menu": "^0.10.0",
"react-native-push-notification": "git+https://github.com/laurent22/react-native-push-notification.git",
"react-native-quick-actions": "^0.3.13",
"react-native-share-extension": "^1.2.1",
"react-native-securerandom": "^1.0.0-rc.0",
"react-native-side-menu": "^1.1.3",
"react-native-sqlite-storage": "^4.1.0",

View File

@ -82,6 +82,7 @@ const EncryptionService = require('lib/services/EncryptionService');
const MigrationService = require('lib/services/MigrationService');
import PluginAssetsLoader from './PluginAssetsLoader';
import setUpQuickActions from './QuickActions';
let storeDispatch = function() {};
@ -215,9 +216,9 @@ const appReducer = (state = appDefaultState, action) => {
const currentRoute = state.route;
if (!historyGoingBack && historyCanGoBackTo(currentRoute, action)) {
// If the route *name* is the same (even if the other parameters are different), we
// overwrite the last route in the history with the current one. If the route name
// is different, we push a new history entry.
// If the route *name* is the same (even if the other parameters are different), we
// overwrite the last route in the history with the current one. If the route name
// is different, we push a new history entry.
if (currentRoute.routeName == action.routeName) {
// nothing
} else {
@ -534,6 +535,8 @@ async function initialize(dispatch) {
folderId: folder.id,
});
}
setUpQuickActions(dispatch, folderId);
} catch (error) {
alert(`Initialization error: ${error.message}`);
reg.logger().error('Initialization error:', error);

15
package-lock.json generated
View File

@ -109,14 +109,12 @@
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
},
"@types/react": {
"version": "16.9.16",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.16.tgz",
"integrity": "sha512-dQ3wlehuBbYlfvRXfF5G+5TbZF3xqgkikK7DWAsQXe2KnzV+kjD4W2ea+ThCrKASZn9h98bjjPzoTYzfRqyBkw==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
@ -131,6 +129,14 @@
"@types/react": "*"
}
},
"@types/react-native": {
"version": "0.61.4",
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.61.4.tgz",
"integrity": "sha512-RWU51dCIEjvgT0QuclgAha/P9fdAvnDzilhatx85LcTKTH2S3PSOUNZlbxwyZLMrqpCek5xsBOjSA5nOIFYq4A==",
"requires": {
"@types/react": "*"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.10.0.tgz",
@ -593,8 +599,7 @@
"csstype": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz",
"integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==",
"dev": true
"integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ=="
},
"date-fns": {
"version": "1.30.1",

View File

@ -35,5 +35,9 @@
"husky": "^3.0.2",
"lint-staged": "^9.2.1",
"typescript": "^3.7.3"
},
"dependencies": {
"@types/react-native": "^0.61.4",
"joplin-renderer": "^1.0.6"
}
}