1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-06-27 23:28:38 +02:00
This commit is contained in:
Laurent Cozic
2020-10-16 16:14:39 +01:00
parent e3a37ec2d6
commit 4e303be85f
24 changed files with 55 additions and 3319 deletions

View File

@ -6,7 +6,7 @@
"scripts": { "scripts": {
"test": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --fail-fast=true --config=tests/support/jasmine.json", "test": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --fail-fast=true --config=tests/support/jasmine.json",
"test-ci": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --config=tests/support/jasmine.json", "test-ci": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --config=tests/support/jasmine.json",
"postinstall": "npm run build && patch-package --patch-dir ../patches", "postinstall": "npm run build && patch-package --patch-dir ../patches/shared && patch-package --patch-dir ../patches/node",
"build": "gulp build", "build": "gulp build",
"start": "gulp build -L && node 'build/main.js' --stack-trace-enabled --log-level debug --env dev" "start": "gulp build -L && node 'build/main.js' --stack-trace-enabled --log-level debug --env dev"
}, },

View File

@ -8,7 +8,7 @@ describe('InMemoryCache', function() {
expect(cache.value('test')).toBe(undefined); expect(cache.value('test')).toBe(undefined);
expect(cache.value('test', 'default')).toBe('default'); expect(cache.value('test', 'default')).toBe('default');
cache.setValue('test', 'something'); cache.setValue('test', 'something');
expect(cache.value('test')).toBe('something'); expect(cache.value('test')).toBe('something');
@ -42,7 +42,7 @@ describe('InMemoryCache', function() {
it('should delete old records', async () => { it('should delete old records', async () => {
const cache = new InMemoryCache(5); const cache = new InMemoryCache(5);
cache.setValue('1', '1'); cache.setValue('1', '1');
cache.setValue('2', '2'); cache.setValue('2', '2');
cache.setValue('3', '3'); cache.setValue('3', '3');
@ -55,5 +55,5 @@ describe('InMemoryCache', function() {
expect(cache.value('1')).toBe(undefined); expect(cache.value('1')).toBe(undefined);
}); });
}); });

View File

@ -146,7 +146,7 @@ describe('services_rest_Api', function() {
})); }));
const noteId = response.id; const noteId = response.id;
{ {
const note = await Note.load(noteId); const note = await Note.load(noteId);
expect(note.latitude).toBe('48.73207100'); expect(note.latitude).toBe('48.73207100');
@ -154,7 +154,7 @@ describe('services_rest_Api', function() {
expect(note.altitude).toBe('21.0000'); expect(note.altitude).toBe('21.0000');
} }
await api.route('PUT', 'notes/' + noteId, null, JSON.stringify({ await api.route('PUT', `notes/${noteId}`, null, JSON.stringify({
latitude: '49', latitude: '49',
longitude: '-3', longitude: '-3',
altitude: '22', altitude: '22',

View File

@ -5,7 +5,7 @@
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"dist": "node_modules/.bin/electron-builder", "dist": "node_modules/.bin/electron-builder",
"build": "patch-package --patch-dir ../patches && gulp build", "build": "patch-package --patch-dir ../patches/shared && patch-package --patch-dir ../patches/node && gulp build",
"postinstall": "npm run build && gulp electronRebuild", "postinstall": "npm run build && gulp electronRebuild",
"start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools" "start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools"
}, },

View File

@ -1,114 +0,0 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
} from 'react-native';
import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<Header />
{global.HermesInternal == null ? null : (
<View style={styles.engine}>
<Text style={styles.footer}>Engine: Hermes</Text>
</View>
)}
<View style={styles.body}>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Step One</Text>
<Text style={styles.sectionDescription}>
Edit <Text style={styles.highlight}>App.js</Text> to change this
screen and then come back to see your edits.
</Text>
</View>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>See Your Changes</Text>
<Text style={styles.sectionDescription}>
<ReloadInstructions />
</Text>
</View>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Debug</Text>
<Text style={styles.sectionDescription}>
<DebugInstructions />
</Text>
</View>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Learn More</Text>
<Text style={styles.sectionDescription}>
Read the docs to discover what to do next:
</Text>
</View>
<LearnMoreLinks />
</View>
</ScrollView>
</SafeAreaView>
</>
);
};
const styles = StyleSheet.create({
scrollView: {
backgroundColor: Colors.lighter,
},
engine: {
position: 'absolute',
right: 0,
},
body: {
backgroundColor: Colors.white,
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
color: Colors.black,
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
color: Colors.dark,
},
highlight: {
fontWeight: '700',
},
footer: {
color: Colors.dark,
fontSize: 12,
fontWeight: '600',
padding: 4,
paddingRight: 12,
textAlign: 'right',
},
});
export default App;

View File

@ -1,14 +0,0 @@
/**
* @format
*/
import 'react-native';
import React from 'react';
import App from '../App';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('renders correctly', () => {
renderer.create(<App />);
});

View File

@ -1,3 +1,3 @@
module.exports = { module.exports = {
presets: ['module:metro-react-native-babel-preset'], presets: ['module:metro-react-native-babel-preset'],
}; };

View File

@ -6,8 +6,8 @@
// So there's basically still a one way flux: React => SQLite => Redux => React // So there's basically still a one way flux: React => SQLite => Redux => React
import {LogBox, AppRegistry} from 'react-native'; import { LogBox, AppRegistry } from 'react-native';
const {Root} = require('./root.js'); const { Root } = require('./root.js');
// Seems JavaScript developers love adding warnings everywhere, even when these warnings can't be fixed // Seems JavaScript developers love adding warnings everywhere, even when these warnings can't be fixed
// or don't really matter. Because we want important warnings to actually be fixed, we disable // or don't really matter. Because we want important warnings to actually be fixed, we disable
@ -17,7 +17,7 @@ LogBox.ignoreLogs([
// Happens for example in react-native-side-menu, but the package is discontinued // Happens for example in react-native-side-menu, but the package is discontinued
// and we should just switch to a different one (or do it without a package). // and we should just switch to a different one (or do it without a package).
'Animated: `useNativeDriver` was not specified. This is a required option and must be explicitly set to `true` or `false`', 'Animated: `useNativeDriver` was not specified. This is a required option and must be explicitly set to `true` or `false`',
// What's the point of printing warnings for non-user code. Of all the things that // What's the point of printing warnings for non-user code. Of all the things that
// are broken and unreliable in React Native, require cycles are just a minor annoyance // are broken and unreliable in React Native, require cycles are just a minor annoyance
// which shouldn't forever print warnings. // which shouldn't forever print warnings.
@ -29,7 +29,7 @@ LogBox.ignoreLogs([
'Require cycle: node_modules\\rn-fetch-blob', 'Require cycle: node_modules\\rn-fetch-blob',
'Require cycle: node_modules/aws-sdk', 'Require cycle: node_modules/aws-sdk',
'Require cycle: node_modules\\aws-sdk', 'Require cycle: node_modules\\aws-sdk',
// It's being updated over time and we don't need to see these warnings all the time // It's being updated over time and we don't need to see these warnings all the time
'Warning: componentWillReceiveProps has been renamed', 'Warning: componentWillReceiveProps has been renamed',
'Warning: componentWillUpdate has been renamed', 'Warning: componentWillUpdate has been renamed',

View File

@ -36,7 +36,7 @@ export default class Cache {
private checkExpiredRecords() { private checkExpiredRecords() {
const now = Date.now(); const now = Date.now();
for (let key in this.expirableKeys_) { for (const key in this.expirableKeys_) {
if (!this.records_[key]) { if (!this.records_[key]) {
delete this.expirableKeys_[key]; delete this.expirableKeys_[key];
} else { } else {
@ -68,7 +68,7 @@ export default class Cache {
this.records_[key] = { this.records_[key] = {
value: value, value: value,
expiredTime: ttl ? Date.now() + ttl : 0, expiredTime: ttl ? Date.now() + ttl : 0,
} };
const idx = this.recordKeyHistory_.indexOf(key); const idx = this.recordKeyHistory_.indexOf(key);
if (idx >= 0) this.recordKeyHistory_.splice(idx, 1); if (idx >= 0) this.recordKeyHistory_.splice(idx, 1);

View File

@ -337,16 +337,16 @@ class WebDavApi {
async exec(method, path = '', body = null, headers = null, options = null) { async exec(method, path = '', body = null, headers = null, options = null) {
headers = Object.assign({}, headers); headers = Object.assign({}, headers);
options = Object.assign({}, options); options = Object.assign({}, options);
if (!options.responseFormat) options.responseFormat = 'json'; if (!options.responseFormat) options.responseFormat = 'json';
if (!options.target) options.target = 'string'; if (!options.target) options.target = 'string';
const authToken = this.authToken(); const authToken = this.authToken();
if (authToken) headers['Authorization'] = `Basic ${authToken}`; if (authToken) headers['Authorization'] = `Basic ${authToken}`;
// That should not be needed, but it is required for React Native 0.63+ // That should not be needed, but it is required for React Native 0.63+
// https://github.com/facebook/react-native/issues/30176 // https://github.com/facebook/react-native/issues/30176
if (!headers['Content-Type']) { if (!headers['Content-Type']) {
if (method === 'PROPFIND') headers['Content-Type'] = 'text/xml'; if (method === 'PROPFIND') headers['Content-Type'] = 'text/xml';
if (method === 'PUT') headers['Content-Type'] = 'text/plain'; if (method === 'PUT') headers['Content-Type'] = 'text/plain';

View File

@ -29,7 +29,7 @@ interface Props {
export default function NoteBodyViewer(props:Props) { export default function NoteBodyViewer(props:Props) {
const theme = themeStyle(props.themeId); const theme = themeStyle(props.themeId);
const webViewStyle:any = useMemo(() => { const webViewStyle:any = useMemo(() => {
return { backgroundColor: theme.backgroundColor }; return { backgroundColor: theme.backgroundColor };
}, [theme.backgroundColor]); }, [theme.backgroundColor]);
@ -43,7 +43,7 @@ export default function NoteBodyViewer(props:Props) {
props.highlightedKeywords, props.highlightedKeywords,
props.noteResources, props.noteResources,
props.paddingBottom, props.paddingBottom,
props.noteHash props.noteHash
); );
const onResourceLongPress = useOnResourceLongPress( const onResourceLongPress = useOnResourceLongPress(
@ -64,7 +64,7 @@ export default function NoteBodyViewer(props:Props) {
}, [props.onLoadEnd]); }, [props.onLoadEnd]);
function onError() { function onError() {
reg.logger().error('WebView error') reg.logger().error('WebView error');
} }
// On iOS scalesPageToFit work like this: // On iOS scalesPageToFit work like this:
@ -108,4 +108,4 @@ export default function NoteBodyViewer(props:Props) {
<BackButtonDialogBox ref={dialogBoxRef}/> <BackButtonDialogBox ref={dialogBoxRef}/>
</View> </View>
); );
} }

View File

@ -4,7 +4,7 @@ const shared = require('lib/components/shared/note-screen-shared');
export default function useOnMessage(onCheckboxChange:Function, noteBody:string, onMarkForDownload:Function, onJoplinLinkClick:Function, onResourceLongPress:Function) { export default function useOnMessage(onCheckboxChange:Function, noteBody:string, onMarkForDownload:Function, onJoplinLinkClick:Function, onResourceLongPress:Function) {
return useCallback((event:any) => { return useCallback((event:any) => {
// Since RN 58 (or 59) messages are now escaped twice??? // Since RN 58 (or 59) messages are now escaped twice???
let msg = unescape(unescape(event.nativeEvent.data)); const msg = unescape(unescape(event.nativeEvent.data));
console.info('Got IPC message: ', msg); console.info('Got IPC message: ', msg);
@ -20,7 +20,7 @@ export default function useOnMessage(onCheckboxChange:Function, noteBody:string,
} else if (msg.startsWith('joplin:')) { } else if (msg.startsWith('joplin:')) {
onJoplinLinkClick(msg); onJoplinLinkClick(msg);
} else if (msg.startsWith('error:')) { } else if (msg.startsWith('error:')) {
console.error('Webview injected script error: ' + msg); console.error(`Webview injected script error: ${msg}`);
} }
}, [onCheckboxChange, noteBody, onMarkForDownload, onJoplinLinkClick, onResourceLongPress]); }, [onCheckboxChange, noteBody, onMarkForDownload, onJoplinLinkClick, onResourceLongPress]);
} }

View File

@ -9,7 +9,7 @@ const { dialogs } = require('lib/dialogs.js');
const Resource = require('lib/models/Resource.js'); const Resource = require('lib/models/Resource.js');
const Share = require('react-native-share').default; const Share = require('react-native-share').default;
export default function onResourceLongPress(onJoplinLinkClick:Function, dialogBoxRef:any) { export default function useOnResourceLongPress(onJoplinLinkClick:Function, dialogBoxRef:any) {
return useCallback(async (msg:string) => { return useCallback(async (msg:string) => {
try { try {
const resourceId = msg.split(':')[1]; const resourceId = msg.split(':')[1];

View File

@ -61,7 +61,7 @@ export default function useSource(noteBody:string, noteMarkupLanguage:number, th
// props changes, thus triggering a render. The **content** of this noteResources array however is not changed because // props changes, thus triggering a render. The **content** of this noteResources array however is not changed because
// it doesn't contain info about the resource download state. Because of that, if we were to use the markupToHtml() cache // it doesn't contain info about the resource download state. Because of that, if we were to use the markupToHtml() cache
// it wouldn't re-render at all. We don't need this cache in any way because this hook is only triggered when we know // it wouldn't re-render at all. We don't need this cache in any way because this hook is only triggered when we know
// something has changed. // something has changed.
markupToHtml().clearCache(noteMarkupLanguage); markupToHtml().clearCache(noteMarkupLanguage);
const result = await markupToHtml().render( const result = await markupToHtml().render(
@ -125,7 +125,7 @@ export default function useSource(noteBody:string, noteMarkupLanguage:number, th
</html> </html>
`; `;
const tempFile = `${Setting.value('resourceDir')}/NoteBodyViewer.html` const tempFile = `${Setting.value('resourceDir')}/NoteBodyViewer.html`;
await shim.fsDriver().writeFile(tempFile, html, 'utf8'); await shim.fsDriver().writeFile(tempFile, html, 'utf8');
if (cancelled) return; if (cancelled) return;
@ -135,7 +135,7 @@ export default function useSource(noteBody:string, noteMarkupLanguage:number, th
// //
// `baseUrl` is where the images will be loaded from. So images must use a path relative to resourceDir. // `baseUrl` is where the images will be loaded from. So images must use a path relative to resourceDir.
setSource({ setSource({
uri: 'file://' + tempFile + '?r=' + Math.round(Math.random() * 100000000), uri: `file://${tempFile}?r=${Math.round(Math.random() * 100000000)}`,
baseUrl: `file://${Setting.value('resourceDir')}/`, baseUrl: `file://${Setting.value('resourceDir')}/`,
}); });
@ -147,7 +147,7 @@ export default function useSource(noteBody:string, noteMarkupLanguage:number, th
// - Secondly with the source to actually render the note // - Secondly with the source to actually render the note
// This is necessary to prevent a race condition that could cause an ERR_ACCESS_DENIED error // This is necessary to prevent a race condition that could cause an ERR_ACCESS_DENIED error
// https://github.com/react-native-webview/react-native-webview/issues/656#issuecomment-551312436 // https://github.com/react-native-webview/react-native-webview/issues/656#issuecomment-551312436
if (isFirstRender) { if (isFirstRender) {
setIsFirstRender(false); setIsFirstRender(false);
setSource(undefined); setSource(undefined);
@ -158,8 +158,8 @@ export default function useSource(noteBody:string, noteMarkupLanguage:number, th
return () => { return () => {
cancelled = true; cancelled = true;
} };
}, [resourceLoadedTime, noteBody, noteMarkupLanguage, themeId, rendererTheme, highlightedKeywords, noteResources, noteHash, isFirstRender]); }, [resourceLoadedTime, noteBody, noteMarkupLanguage, themeId, rendererTheme, highlightedKeywords, noteResources, noteHash, isFirstRender]);
return { source, injectedJs }; return { source, injectedJs };
} }

View File

@ -60,7 +60,7 @@ class ActionButtonComponent extends React.Component {
renderIconMultiStates() { renderIconMultiStates() {
const button = this.props.buttons[this.state.buttonIndex]; const button = this.props.buttons[this.state.buttonIndex];
return <Icon name={button.icon} style={styles.actionButtonIcon} /> return <Icon name={button.icon} style={styles.actionButtonIcon} />;
} }
renderIcon() { renderIcon() {

View File

@ -322,7 +322,7 @@ class NoteScreenComponent extends BaseScreenComponent {
borderTopWidth: 1, borderTopWidth: 1,
borderBottomColor: theme.dividerColor, borderBottomColor: theme.dividerColor,
borderBottomWidth: 1, borderBottomWidth: 1,
} };
styles.titleContainer = { styles.titleContainer = {
flex: 0, flex: 0,
@ -855,7 +855,7 @@ class NoteScreenComponent extends BaseScreenComponent {
// //
// On Android, it will depend on the phone, but usually it will allow browing all files and photos. // On Android, it will depend on the phone, but usually it will allow browing all files and photos.
buttons.push({ text: _('Attach file'), id: 'attachFile' }); buttons.push({ text: _('Attach file'), id: 'attachFile' });
// Disabled on Android because it doesn't work due to permission issues, but enabled on iOS // Disabled on Android because it doesn't work due to permission issues, but enabled on iOS
// because that's only way to browse photos from the camera roll. // because that's only way to browse photos from the camera roll.
if (Platform.OS === 'ios') buttons.push({ text: _('Attach photo'), id: 'attachPhoto' }); if (Platform.OS === 'ios') buttons.push({ text: _('Attach photo'), id: 'attachPhoto' });

View File

@ -31,7 +31,7 @@ export default class AlarmServiceDriver {
// Returns -1 if could not be found // Returns -1 if could not be found
private alarmJoplinAlarmId(alarm:any):number { private alarmJoplinAlarmId(alarm:any):number {
if (!alarm.data) return -1; if (!alarm.data) return -1;
const m = alarm.data.match(/joplinNotificationId==>(\d+)/) const m = alarm.data.match(/joplinNotificationId==>(\d+)/);
return m ? Number(m[1]) : -1; return m ? Number(m[1]) : -1;
} }
@ -50,10 +50,10 @@ export default class AlarmServiceDriver {
const alarmNotifData = { const alarmNotifData = {
title: notification.title, title: notification.title,
message: notification.body ? notification.body : '-', // Required message: notification.body ? notification.body : '-', // Required
channel: "net.cozic.joplin.notification", channel: 'net.cozic.joplin.notification',
small_icon: "ic_launcher", small_icon: 'ic_launcher',
color: 'white', color: 'white',
data: { joplinNotificationId: notification.id + '' }, data: { joplinNotificationId: `${notification.id}` },
}; };
// ReactNativeAN expects a string as a date and it seems this utility // ReactNativeAN expects a string as a date and it seems this utility

View File

@ -6,12 +6,12 @@
*/ */
module.exports = { module.exports = {
transformer: { transformer: {
getTransformOptions: async () => ({ getTransformOptions: async () => ({
transform: { transform: {
experimentalImportSupport: false, experimentalImportSupport: false,
inlineRequires: false, inlineRequires: false,
}, },
}), }),
}, },
}; };

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
{ {
"name": "Joplin", "name": "Joplin",
"version": "0.0.1", "description": "Joplin for Mobile",
"license": "MIT",
"version": "0.8.0",
"private": true, "private": true,
"scripts": { "scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start --reset-cache", "start": "react-native start --reset-cache",
"test": "jest", "build": "gulp build",
"lint": "eslint ." "postinstall": "patch-package --patch-dir ../patches/shared && jetify && npm run build"
}, },
"dependencies": { "dependencies": {
"@react-native-community/clipboard": "^1.5.0", "@react-native-community/clipboard": "^1.5.0",
@ -96,21 +96,12 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.11.6", "@babel/core": "^7.11.6",
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@react-native-community/eslint-config": "^2.0.0",
"app-module-path": "^2.2.0", "app-module-path": "^2.2.0",
"babel-jest": "^26.5.2",
"eslint": "^7.11.0",
"execa": "^4.0.0", "execa": "^4.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"jest": "^26.5.3",
"jetifier": "^1.6.5", "jetifier": "^1.6.5",
"metro-react-native-babel-preset": "^0.63.0", "metro-react-native-babel-preset": "^0.63.0",
"patch-package": "^6.2.2", "patch-package": "^6.2.2"
"react-native-log-ios": "^1.5.0",
"react-test-renderer": "16.13.1"
},
"jest": {
"preset": "react-native"
} }
} }

1
patches/node/README.md Normal file
View File

@ -0,0 +1 @@
Patches that apply to the Node.js applications, i.e. the desktop and CLI apps.

1
patches/shared/README.md Normal file
View File

@ -0,0 +1 @@
Patches that apply to all apps, mobile, desktop and CLI.