Skip to content

Commit

Permalink
Merge branch 'dev' into feat/server-ldap-self-signed-cert
Browse files Browse the repository at this point in the history
  • Loading branch information
laurent22 authored Dec 18, 2024
2 parents 8c43f80 + 3cba4ec commit e09a9b1
Show file tree
Hide file tree
Showing 44 changed files with 559 additions and 255 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ packages/app-cli/tests/tmp
packages/app-clipper/content_scripts/JSDOMParser.js
packages/app-clipper/content_scripts/Readability-readerable.js
packages/app-clipper/content_scripts/Readability.js
packages/app-clipper/content_scripts/clipperUtils.js
packages/app-clipper/dist
packages/app-clipper/icons
packages/app-clipper/popup/build
Expand Down
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ module.exports = {
allowEmptyReject: true,
}],
'no-throw-literal': ['error'],
'no-unused-expressions': ['error'],

// This rule should not be enabled since it matters in what order
// imports are done, in particular in relation to the shim.setReact
Expand Down
13 changes: 0 additions & 13 deletions .yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors

<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://buyyoutubviews.com"><img title="BYTV" width="256" src="https://joplinapp.org/images/sponsors/BYTV.png"/></a> <a href="https://casinoreviews.net"><img title="Casino Reviews" width="256" src="https://joplinapp.org/images/sponsors/CasinoReviews.png"/></a> <a href="https://useviral.com.br/"><img title="Comprar seguidores Instagram" width="256" src="https://joplinapp.org/images/sponsors/Useviral.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://buyyoutubviews.com"><img title="BYTV" width="256" src="https://joplinapp.org/images/sponsors/BYTV.png"/></a> <a href="https://casinoreviews.net"><img title="Casino Reviews" width="256" src="https://joplinapp.org/images/sponsors/CasinoReviews.png"/></a> <a href="https://useviral.com.br/"><img title="Comprar seguidores Instagram" width="256" src="https://joplinapp.org/images/sponsors/Useviral.png"/></a> <a href="https://ca.edubirdie.com/"><img title="Achieve academic success with Edubirdie — your trusted partner for expert writing assistance and resources!" width="256" src="https://joplinapp.org/images/sponsors/Edubirdie.png"/></a>
<!-- SPONSORS-ORG -->

* * *
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
"react-native-vosk@0.1.12": "patch:react-native-vosk@npm%3A0.1.12#./.yarn/patches/react-native-vosk-npm-0.1.12-76b1caaae8.patch",
"eslint": "patch:eslint@8.57.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
"app-builder-lib@24.4.0": "patch:app-builder-lib@npm%3A24.4.0#./.yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch",
"nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch",
"pdfjs-dist": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
"@react-native-community/slider": "patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<ul>
<li>
<img src=":/0123456789abcdef0123456789abcdef" width="10" height="11"/>
<ul>
<li>HTML images just before a sublist should have a closing &lt;img&gt; tag added.</li>
<li>Otherwise, when rendered to Markdown again, the subsequent list items will be rendered as HTML.</li>
</ul>
</li>
<li>
<img src=":/0123456789abcdef0123456789abcdef" width="10" height="11"/>
<br/>
<strong>A closing tag is also necessary if the image is followed by content on a new line.</strong>
</li>
<li>
<img src=":/0123456789abcdef0123456789abcdef"/>
<ul>
<li>Similar logic isn't necessary for images converted to Markdown.</li>
</ul>
</li>
<li>
<img src=":/0123456789abcdef0123456789abcdef" width="10" height="11"/>
</li>
<li>It also isn't necessary if the next item is at the same level as the image...</li>
<li>
...or if the image has text just before it.
<img src=":/0123456789abcdef0123456789abcdef" width="10" height="11"/>
<ul>
<li>
See <a href="https://github.com/laurent22/joplin/issues/11382">this issue</a> for details.
</li>
</ul>
</li>
</ul>
11 changes: 11 additions & 0 deletions packages/app-cli/tests/html_to_md/image_preserve_size_in_lists.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
- <img src=":/0123456789abcdef0123456789abcdef" width="10" height="11"></img>
- HTML images just before a sublist should have a closing &lt;img&gt; tag added.
- Otherwise, when rendered to Markdown again, the subsequent list items will be rendered as HTML.
- <img src=":/0123456789abcdef0123456789abcdef" width="10" height="11"></img>
**A closing tag is also necessary if the image is followed by content on a new line.**
- ![](:/0123456789abcdef0123456789abcdef)
- Similar logic isn't necessary for images converted to Markdown.
- <img src=":/0123456789abcdef0123456789abcdef" width="10" height="11">
- It also isn't necessary if the next item is at the same level as the image...
- ...or if the image has text just before it. <img src=":/0123456789abcdef0123456789abcdef" width="10" height="11">
- See [this issue](https://github.com/laurent22/joplin/issues/11382) for details.
1 change: 1 addition & 0 deletions packages/app-cli/tests/md_to_html/sanitize_21.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Should keep this comment: <!-- keep this &amp; that -->
1 change: 1 addition & 0 deletions packages/app-cli/tests/md_to_html/sanitize_21.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Should keep this comment: <!-- keep this &amp; that -->
2 changes: 1 addition & 1 deletion packages/app-desktop/gui/NoteContentPropertiesDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function countElements(text: string, wordSetter: Function, characterSetter: Func
characterSetter(counter.all);
characterNoSpaceSetter(counter.characters);
});
text === '' ? lineSetter(0) : lineSetter(text.split('\n').length);
lineSetter(text === '' ? 0 : text.split('\n').length);
}

function formatReadTime(readTimeMinutes: number) {
Expand Down
20 changes: 14 additions & 6 deletions packages/app-desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "3.2.4",
"version": "3.2.5",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
Expand Down Expand Up @@ -115,17 +115,25 @@
"icon": "../../Assets/LinuxIcons",
"category": "Office",
"desktop": {
"Icon": "joplin",
"MimeType": "x-scheme-handler/joplin;"
"entry": {
"Icon": "joplin",
"MimeType": "x-scheme-handler/joplin;"
}
},
"target": "AppImage"
"target": [
"AppImage",
"deb"
],
"executableName": "joplin",
"maintainer": "Joplin Team <no-reply@joplinapp.org>",
"artifactName": "Joplin-${version}.${ext}"
}
},
"homepage": "https://github.com/laurent22/joplin#readme",
"devDependencies": {
"7zip-bin": "5.2.0",
"@axe-core/playwright": "4.10.0",
"@electron/rebuild": "3.6.0",
"@electron/rebuild": "3.7.1",
"@joplin/default-plugins": "~3.2",
"@joplin/tools": "~3.2",
"@playwright/test": "1.45.3",
Expand All @@ -139,7 +147,7 @@
"@types/tesseract.js": "2.0.0",
"axios": "^1.7.7",
"electron": "32.2.0",
"electron-builder": "24.13.3",
"electron-builder": "26.0.0-alpha.7",
"glob": "10.4.5",
"gulp": "4.0.2",
"jest": "29.7.0",
Expand Down
23 changes: 23 additions & 0 deletions packages/app-desktop/utils/isSafeToOpen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,29 @@ const isSafeToOpen = async (path: string) => {
}
}


// The check `extname(path) === ''` is present to prevent the following security issue:
//
// Suppose that an attacker can rapidly change the type of a file (e.g. on a network drive or shared folder).
//
// - **Example 1**: On a network drive, if:
// 1. In a loop,
// - A folder `test.exe\\` is created, replacing the file `test.exe` if it exists.
// - After a brief delay, the folder is replaced the **file** `test.exe`.
// 4. Joplin calls `stat('network-drive/path/here/test.exe')` and gets **isDirectory: true**.
// 5. The folder is replaced with a file.
// 6. Joplin calls `openPath('network-drive/path/here/test.exe')` and executes `test.exe`.
// - **Example 2**: An example that doesn't rely on timings (but whether it works depends on how network drives are implemented):
// 1. An attacker creates a custom network file server where:
// - It's logged when a client calls `stat` on a particular path.
// - The first time `stat` is called on the path, it returns `directory`.
// - Subsequent times, `stat` returns `file`.
// 2. Joplin calls `stat('network-drive/path/here/file.exe')`.
// 3. The network drive returns `isDirectory: true`.
// 4. The network drive replaces the directory `file.exe` with an executable with the same name.
// 5. Joplin marks the path as safe to open.
// 6. Joplin calls `openPath('network-drive/path/here/file.exe')`.
// 7. This executes the executable from step 4.
if (extname(path) === '' && (await stat(path)).isDirectory()) {
return true;
}
Expand Down
9 changes: 8 additions & 1 deletion packages/app-mobile/components/ScreenHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,14 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade

try {
for (let i = 0; i < noteIds.length; i++) {
await Note.moveToFolder(noteIds[i], folderId);
await Note.moveToFolder(
noteIds[i],
folderId,
// By default, the note selection is preserved on mobile when a note is moved to
// a different folder. However, when moving notes from the note list, this shouldn't be
// the case:
{ dispatchOptions: { preserveSelection: false } },
);
}
} catch (error) {
alert(_n('This note could not be moved: %s', 'These notes could not be moved: %s', noteIds.length, error.message));
Expand Down
14 changes: 2 additions & 12 deletions packages/app-mobile/components/screens/Note/Note.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { isSupportedLanguage } from '../../../services/voiceTyping/vosk';
import { ChangeEvent as EditorChangeEvent, SelectionRangeChangeEvent, UndoRedoDepthChangeEvent } from '@joplin/editor/events';
import { join } from 'path';
import { Dispatch } from 'redux';
import { RefObject, useContext, useRef } from 'react';
import { RefObject, useContext } from 'react';
import { SelectionRange } from '../../NoteEditor/types';
import { getNoteCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import { AppState } from '../../../utils/types';
Expand Down Expand Up @@ -1615,19 +1615,9 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
// which can cause some bugs where previously set state to another note would interfere
// how the new note should be rendered
const NoteScreenWrapper = (props: Props) => {
const lastNonNullNoteIdRef = useRef(props.noteId);
if (props.noteId) {
lastNonNullNoteIdRef.current = props.noteId;
}

// This keeps the current note open even if it's no longer present in selectedNoteIds.
// This might happen, for example, if the selected note is moved to an unselected
// folder.
const noteId = lastNonNullNoteIdRef.current;

const dialogs = useContext(DialogContext);
return (
<NoteScreenComponent key={noteId} dialogs={dialogs} {...props} />
<NoteScreenComponent key={props.noteId} dialogs={dialogs} {...props} />
);
};

Expand Down
1 change: 1 addition & 0 deletions packages/app-mobile/components/screens/Notes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface Props {
showCompletedTodos: boolean;
noteSelectionEnabled: boolean;

selectedNoteIds: string[];
activeFolderId: string;
selectedFolderId: string;
selectedTagId: string;
Expand Down
58 changes: 39 additions & 19 deletions packages/app-mobile/components/screens/encryption-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import time from '@joplin/lib/time';
import { decryptedStatText, enableEncryptionConfirmationMessages, onSavePasswordClick, useInputMasterPassword, useInputPasswords, usePasswordChecker, useStats } from '@joplin/lib/components/EncryptionConfigScreen/utils';
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
import { State } from '@joplin/lib/reducer';
import { SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { masterKeyEnabled, SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { getDefaultMasterKey, setupAndDisableEncryption, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
import { useMemo, useState } from 'react';
import { Divider, List } from 'react-native-paper';
import shim from '@joplin/lib/shim';

interface Props {
Expand All @@ -34,8 +35,10 @@ const EncryptionConfigScreen = (props: Props) => {
const { passwordChecks, masterPasswordKeys } = usePasswordChecker(props.masterKeys, props.activeMasterKeyId, props.masterPassword, props.passwords);
const { inputPasswords, onInputPasswordChange } = useInputPasswords(props.passwords);
const { inputMasterPassword, onMasterPasswordSave, onMasterPasswordChange } = useInputMasterPassword(props.masterKeys, props.activeMasterKeyId);
const [showDisabledKeys, setShowDisabledKeys] = useState(false);

const mkComps = [];
const disabledMkComps = [];

const nonExistingMasterKeyIds = props.notLoadedMasterKeys.slice();

Expand Down Expand Up @@ -78,14 +81,18 @@ const EncryptionConfigScreen = (props: Props) => {
flex: 1,
padding: theme.margin,
},
disabledContainer: {
paddingLeft: theme.margin,
paddingRight: theme.margin,
},
};

return StyleSheet.create(styles);
}, [theme]);

const decryptedItemsInfo = props.encryptionEnabled ? <Text style={styles.normalText}>{decryptedStatText(stats)}</Text> : null;

const renderMasterKey = (_num: number, mk: MasterKeyEntity) => {
const renderMasterKey = (mk: MasterKeyEntity) => {
const theme = themeStyle(props.themeId);

const password = inputPasswords[mk.id] ? inputPasswords[mk.id] : '';
Expand Down Expand Up @@ -226,16 +233,19 @@ const EncryptionConfigScreen = (props: Props) => {
}
};



for (let i = 0; i < props.masterKeys.length; i++) {
for (let i = 0; i < props.masterKeys.filter(mk => masterKeyEnabled(mk)).length; i++) {
const mk = props.masterKeys[i];
mkComps.push(renderMasterKey(i + 1, mk));
mkComps.push(renderMasterKey(mk));

const idx = nonExistingMasterKeyIds.indexOf(mk.id);
if (idx >= 0) nonExistingMasterKeyIds.splice(idx, 1);
}

for (let i = 0; i < props.masterKeys.filter(mk => !masterKeyEnabled(mk)).length; i++) {
const mk = props.masterKeys[i];
disabledMkComps.push(renderMasterKey(mk));
}

const onToggleButtonClick = async () => {
if (props.encryptionEnabled) {
const ok = await shim.showConfirmationDialog(_('Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?'));
Expand Down Expand Up @@ -286,8 +296,8 @@ const EncryptionConfigScreen = (props: Props) => {
return (
<View style={rootStyle}>
<ScreenHeader title={_('Encryption Config')} />
<ScrollView style={styles.container}>
{
<ScrollView>
<View style={styles.container}>
<View style={{ backgroundColor: theme.warningBackgroundColor, paddingTop: 5, paddingBottom: 5, paddingLeft: 10, paddingRight: 10 }}>
<Text>{_('For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:')}</Text>
<TouchableOpacity
Expand All @@ -298,17 +308,27 @@ const EncryptionConfigScreen = (props: Props) => {
<Text>https://joplinapp.org/help/apps/sync/e2ee</Text>
</TouchableOpacity>
</View>
}

<Text style={styles.titleText}>{_('Status')}</Text>
<Text style={styles.normalText}>{_('Encryption is: %s', props.encryptionEnabled ? _('Enabled') : _('Disabled'))}</Text>
{decryptedItemsInfo}
{renderMasterPassword()}
{toggleButton}
{passwordPromptComp}
{mkComps}
{nonExistingMasterKeySection}
<View style={{ flex: 1, height: 20 }}></View>

<Text style={styles.titleText}>{_('Status')}</Text>
<Text style={styles.normalText}>{_('Encryption is: %s', props.encryptionEnabled ? _('Enabled') : _('Disabled'))}</Text>
{decryptedItemsInfo}
{renderMasterPassword()}
{toggleButton}
{passwordPromptComp}
{mkComps}
{nonExistingMasterKeySection}
</View>
<Divider />
<List.Accordion
title={_('Disabled keys')}
titleStyle={styles.titleText}
expanded={showDisabledKeys}
onPress={() => setShowDisabledKeys(st => !st)}
>
<View style={styles.disabledContainer}>
{disabledMkComps}
</View>
</List.Accordion>
</ScrollView>
</View>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/app-mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@joplin/react-native-saf-x": "~3.2",
"@joplin/renderer": "~3.2",
"@joplin/utils": "~3.2",
"@react-native-clipboard/clipboard": "1.14.1",
"@react-native-clipboard/clipboard": "1.14.2",
"@react-native-community/datetimepicker": "8.2.0",
"@react-native-community/geolocation": "3.3.0",
"@react-native-community/netinfo": "11.3.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/app-mobile/services/voiceTyping/vosk.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const startRecording = (vosk: Vosk, options: StartOptions): VoiceTypingSe
eventHandler.remove();
}

vosk.cleanup(),
vosk.cleanup();

state_ = State.Idle;

Expand Down
3 changes: 3 additions & 0 deletions packages/app-mobile/utils/appDefaultState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ const appDefaultState: AppState = {
showPanelsDialog: false,
newNoteAttachFileAction: null,
...defaultState,

// On mobile, it's possible to select notes that aren't in the selected folder/tag/etc.
allowSelectionInOtherFolders: true,
};
export default appDefaultState;
Loading

0 comments on commit e09a9b1

Please sign in to comment.