diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87c4bd501c..29bde61e90 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ Coding style is enforced by a pre-commit hook that runs eslint. This hook is ins For new React components, please use [React Hooks](https://reactjs.org/docs/hooks-intro.html). For new code in general, please use TypeScript. Even if you are modifying a file that was originally in JavaScript you should ideally convert it first to TypeScript before modifying it. Doing so is relatively easy and it helps maintain code quality. -For changes made to the Desktop client that affect the user interface, refer to `packages/app-desktop/theme.ts` for all styling information. The goal is to create a consistent user interface to allow for easy navigation of Joplin's various features and improve the overall user experience. +For changes made to the Desktop and mobile clients that affect the user interface, refer to `packages/lib/theme.ts` for all styling information. The goal is to create a consistent user interface to allow for easy navigation of Joplin's various features and improve the overall user experience. ## Automated tests diff --git a/packages/app-mobile/components/screens/Note.tsx b/packages/app-mobile/components/screens/Note.tsx index d591a7b91d..c3bd7005f3 100644 --- a/packages/app-mobile/components/screens/Note.tsx +++ b/packages/app-mobile/components/screens/Note.tsx @@ -161,8 +161,8 @@ class NoteScreenComponent extends BaseScreenComponent { this.onJoplinLinkClick_ = async (msg: string) => { try { - if (msg.indexOf('joplin://') === 0) { - const resourceUrlInfo = urlUtils.parseResourceUrl(msg); + const resourceUrlInfo = urlUtils.parseResourceUrl(msg); + if (resourceUrlInfo) { const itemId = resourceUrlInfo.itemId; const item = await BaseItem.loadItemById(itemId); if (!item) throw new Error(_('No item with ID %s', itemId)); diff --git a/packages/lib/models/BaseItem.ts b/packages/lib/models/BaseItem.ts index 52401b56bd..24eeb65509 100644 --- a/packages/lib/models/BaseItem.ts +++ b/packages/lib/models/BaseItem.ts @@ -418,7 +418,7 @@ export default class BaseItem extends BaseModel { const share = item.share_id ? await this.shareService().shareById(item.share_id) : null; const serialized = await ItemClass.serialize(item, shownKeys); - if (!getEncryptionEnabled() || !ItemClass.encryptionSupported() || !itemCanBeEncrypted(item)) { + if (!getEncryptionEnabled() || !ItemClass.encryptionSupported() || !itemCanBeEncrypted(item, share)) { // Normally not possible since itemsThatNeedSync should only return decrypted items if (item.encryption_applied) throw new JoplinError('Item is encrypted but encryption is currently disabled', 'cannotSyncEncrypted'); return serialized; diff --git a/packages/lib/models/Note.test.ts b/packages/lib/models/Note.test.ts index 4ffdfbd7f0..a42e8ec25e 100644 --- a/packages/lib/models/Note.test.ts +++ b/packages/lib/models/Note.test.ts @@ -411,3 +411,37 @@ describe('models/Note', function() { })); }); + +describe('models/Note_replacePaths', function() { + + function testResourceReplacment(body: string, pathsToTry: string[], expected: string) { + expect(Note['replaceResourceExternalToInternalLinks_'](pathsToTry, body)).toBe(expected); + } + test('Basic replacement', () => { + const body = '![image.png](file:///C:Users/Username/resources/849eae4dade045298c107fc706b6d2bc.png?t=1655192326803)'; + const pathsToTry = ['file:///C:Users/Username/resources']; + const expected = '![image.png](:/849eae4dade045298c107fc706b6d2bc)'; + testResourceReplacment(body, pathsToTry, expected); + }); + + test('Replacement with spaces', () => { + const body = '![image.png](file:///C:Users/Username%20with%20spaces/resources/849eae4dade045298c107fc706b6d2bc.png?t=1655192326803)'; + const pathsToTry = ['file:///C:Users/Username with spaces/resources']; + const expected = '![image.png](:/849eae4dade045298c107fc706b6d2bc)'; + testResourceReplacment(body, pathsToTry, expected); + }); + + test('Replacement with Non-ASCII', () => { + const body = '![image.png](file:///C:Users/UsernameWith%C3%A9%C3%A0%C3%B6/resources/849eae4dade045298c107fc706b6d2bc.png?t=1655192326803)'; + const pathsToTry = ['file:///C:Users/UsernameWithéàö/resources']; + const expected = '![image.png](:/849eae4dade045298c107fc706b6d2bc)'; + testResourceReplacment(body, pathsToTry, expected); + }); + + test('Replacement with Non-ASCII and spaces', () => { + const body = '![image.png](file:///C:Users/Username%20With%20%C3%A9%C3%A0%C3%B6/resources/849eae4dade045298c107fc706b6d2bc.png?t=1655192326803)'; + const pathsToTry = ['file:///C:Users/Username With éàö/resources']; + const expected = '![image.png](:/849eae4dade045298c107fc706b6d2bc)'; + testResourceReplacment(body, pathsToTry, expected); + }); +}); diff --git a/packages/lib/models/Note.ts b/packages/lib/models/Note.ts index 5cd3941616..aa087e29ce 100644 --- a/packages/lib/models/Note.ts +++ b/packages/lib/models/Note.ts @@ -182,7 +182,7 @@ export default class Note extends BaseItem { const resourceDir = toForwardSlashes(Setting.value('resourceDir')); - let pathsToTry = []; + const pathsToTry = []; if (options.useAbsolutePaths) { pathsToTry.push(`file://${resourceDir}`); pathsToTry.push(`file:///${resourceDir}`); @@ -192,6 +192,13 @@ export default class Note extends BaseItem { pathsToTry.push(Resource.baseRelativeDirectoryPath()); } + body = Note.replaceResourceExternalToInternalLinks_(pathsToTry, body); + return body; + } + + private static replaceResourceExternalToInternalLinks_(pathsToTry: string[], body: string) { + // This is a moved to a separate function for the purpose of testing only + // We support both the escaped and unescaped versions because both // of those paths are valid: // @@ -202,7 +209,7 @@ export default class Note extends BaseItem { const temp = []; for (const p of pathsToTry) { temp.push(p); - temp.push(markdownUtils.escapeLinkUrl(p)); + temp.push(encodeURI(p)); } pathsToTry = temp; @@ -227,7 +234,6 @@ export default class Note extends BaseItem { // Handles joplin://af0edffa4a60496bba1b0ba06b8fb39a body = body.replace(/\(joplin:\/\/([a-zA-Z0-9]{32})\)/g, '(:/$1)'); } - // this.logger().debug('replaceResourceExternalToInternalLinks result', body); return body; diff --git a/packages/lib/models/Resource.ts b/packages/lib/models/Resource.ts index 5cd523bb94..d3028313c7 100644 --- a/packages/lib/models/Resource.ts +++ b/packages/lib/models/Resource.ts @@ -207,7 +207,7 @@ export default class Resource extends BaseItem { const share = resource.share_id ? await this.shareService().shareById(resource.share_id) : null; - if (!getEncryptionEnabled() || !itemCanBeEncrypted(resource as any)) { + if (!getEncryptionEnabled() || !itemCanBeEncrypted(resource as any, share)) { // Normally not possible since itemsThatNeedSync should only return decrypted items if (resource.encryption_blob_encrypted) throw new Error('Trying to access encrypted resource but encryption is currently disabled'); return { path: plainTextPath, resource: resource }; diff --git a/packages/lib/models/utils/itemCanBeEncrypted.ts b/packages/lib/models/utils/itemCanBeEncrypted.ts index 28acd47903..3b5829eacd 100644 --- a/packages/lib/models/utils/itemCanBeEncrypted.ts +++ b/packages/lib/models/utils/itemCanBeEncrypted.ts @@ -1,5 +1,14 @@ import { BaseItemEntity } from '../../services/database/types'; +import { StateShare } from '../../services/share/reducer'; -export default function(resource: BaseItemEntity): boolean { - return !resource.is_shared; +export default function(item: BaseItemEntity, share: StateShare): boolean { + // Note has been published - currently we don't encrypt + if (item.is_shared) return false; + + // Item has been shared with user, but sharee is not encrypting his notes, + // so we shouldn't encrypt it either. Otherwise sharee will not be able to + // view the note anymore. https://github.com/laurent22/joplin/issues/6645 + if (item.share_id && (!share || !share.master_key_id)) return false; + + return true; } diff --git a/packages/lib/services/share/ShareService.test.ts b/packages/lib/services/share/ShareService.test.ts index 53fd99e2a4..0985ff2dca 100644 --- a/packages/lib/services/share/ShareService.test.ts +++ b/packages/lib/services/share/ShareService.test.ts @@ -140,6 +140,7 @@ describe('ShareService', function() { expect(await MasterKey.count()).toBe(1); let { folder, note, resource } = await testShareFolder(shareService); + await Folder.updateAllShareIds(resourceService()); // The share service should automatically create a new encryption key // specifically for that shared folder diff --git a/packages/react-native-saf-x/jest.config.js b/packages/react-native-saf-x/jest.config.js deleted file mode 100644 index 85bd07e9f0..0000000000 --- a/packages/react-native-saf-x/jest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -// Sync object -/** @type {import('@jest/types').Config.InitialOptions} */ -const config = { - preset: 'react-native', - modulePathIgnorePatterns: [ - '/example/node_modules', - '/lib/', - ], -}; - -module.exports = config; diff --git a/packages/react-native-saf-x/package.json b/packages/react-native-saf-x/package.json index 0275662e60..0e07bb0668 100644 --- a/packages/react-native-saf-x/package.json +++ b/packages/react-native-saf-x/package.json @@ -15,13 +15,9 @@ "react-native-saf-x.podspec", "!lib/typescript/example", "!android/build", - "!ios/build", - "!**/__tests__", - "!**/__fixtures__", - "!**/__mocks__" + "!ios/build" ], "scripts": { - "test": "jest", "linter-precommit": "yarn --cwd ../../ eslint --resolve-plugins-relative-to . --fix packages/react-native-saf-x/**/*.{js,ts,tsx}", "tsc": "tsc --project tsconfig.json" }, @@ -42,10 +38,8 @@ }, "devDependencies": { "@babel/core": "^7.12.9", - "@types/jest": "^26.0.15", "@types/react": "^16.9.55", "@types/react-native": "^0.64.4", - "jest": "^26.6.3", "react": "17.0.2", "react-native": "0.66.1", "typescript": "^4.0.5" diff --git a/packages/react-native-saf-x/tsconfig.json b/packages/react-native-saf-x/tsconfig.json index ac666d99dd..c45636b832 100644 --- a/packages/react-native-saf-x/tsconfig.json +++ b/packages/react-native-saf-x/tsconfig.json @@ -6,8 +6,6 @@ ], "exclude": [ "**/node_modules", - "tests/support/**/*", - "tests-build/**/*", "build/**/*", ], } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 107140ce5d..485f5c25f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3517,10 +3517,8 @@ __metadata: resolution: "@joplin/react-native-saf-x@workspace:packages/react-native-saf-x" dependencies: "@babel/core": ^7.12.9 - "@types/jest": ^26.0.15 "@types/react": ^16.9.55 "@types/react-native": ^0.64.4 - jest: ^26.6.3 react: 17.0.2 react-native: 0.66.1 typescript: ^4.0.5