1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Merge remote-tracking branch 'origin/dev' into issue-8408

This commit is contained in:
Hubert 2023-07-25 11:38:36 -03:00
commit c88e147343
36 changed files with 449 additions and 322 deletions

View File

@ -0,0 +1,25 @@
diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
index 0f52b73c61625db2a3081c0950b6bdd2b06e3d40..b0fc3de4be0b3a26b638683613c63c783c2739bb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
@@ -38,7 +38,7 @@ import com.facebook.react.uimanager.common.ViewUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -151,7 +151,10 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
}
private class ConcurrentOperationQueue {
- private final Queue<UIThreadOperation> mQueue = new ConcurrentLinkedQueue<>();
+ // Patch: Use LinkedBlockingQueue instead of ConcurrentLinkedQueue.
+ // In some versions of Android, ConcurrentLinkedQueue is known to drop
+ // items, causing crashing. See https://github.com/laurent22/joplin/issues/8425
+ private final Queue<UIThreadOperation> mQueue = new LinkedBlockingQueue<>();
@Nullable private UIThreadOperation mPeekedOperation = null;
@AnyThread

View File

@ -0,0 +1,27 @@
diff --git a/android/src/main/java/com/swmansion/reanimated/NodesManager.java b/android/src/main/java/com/swmansion/reanimated/NodesManager.java
index e974f8eb827a35be4d7e5fa9b096af9387c595dd..bc9e5ff566c9484274e8eacefc88327a5ff30def 100644
--- a/android/src/main/java/com/swmansion/reanimated/NodesManager.java
+++ b/android/src/main/java/com/swmansion/reanimated/NodesManager.java
@@ -34,7 +34,7 @@ import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -80,7 +80,12 @@ public class NodesManager implements EventDispatcherListener {
private ReactApplicationContext mReactApplicationContext;
private RCTEventEmitter mCustomEventHandler;
private List<OnAnimationFrame> mFrameCallbacks = new ArrayList<>();
- private ConcurrentLinkedQueue<CopiedEvent> mEventQueue = new ConcurrentLinkedQueue<>();
+
+ // Patch: On some versions of Android, ConcurrentLinkedQueue is known to
+ // drop items. LinkedBlockingQueue is a potentially-slower alternative that
+ // should not drop items.
+ // See https://github.com/laurent22/joplin/issues/8425
+ private LinkedBlockingQueue<CopiedEvent> mEventQueue = new LinkedBlockingQueue<>();
public double currentFrameTimeMs;
public Set<String> uiProps = Collections.emptySet();
public Set<String> nativeProps = Collections.emptySet();

View File

@ -90,7 +90,7 @@
"@types/fs-extra": "11.0.1",
"eslint-plugin-github": "4.8.0",
"http-server": "14.1.1",
"node-gyp": "9.3.1",
"node-gyp": "9.4.0",
"nodemon": "2.0.22"
},
"packageManager": "yarn@3.5.0",
@ -99,6 +99,8 @@
"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@8.39.0": "patch:eslint@npm%3A8.39.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
"eslint@^8.13.0": "patch:eslint@npm%3A8.39.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"
"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",
"react-native@0.71.10": "patch:react-native@npm%3A0.71.10#./.yarn/patches/react-native-animation-fix/react-native-npm-0.71.10-f9c32562d8.patch",
"react-native-reanimated@3.3.0": "patch:react-native-reanimated@npm%3A3.3.0#./.yarn/patches/react-native-animation-fix/react-native-reanimated-npm-3.3.0-fb4272741c.patch"
}
}

View File

@ -1 +1 @@
&nbsp; &nbsp;**A test...**&nbsp;Test
&nbsp;  **A test...** Test

View File

@ -66,8 +66,7 @@ import syncDebugLog from '@joplin/lib/services/synchronizer/syncDebugLog';
import eventManager from '@joplin/lib/eventManager';
import path = require('path');
import { checkPreInstalledDefaultPlugins, installDefaultPlugins, setSettingsForDefaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
// import { runIntegrationTests } from '@joplin/lib/services/e2ee/ppkTestUtils';
import { initializeInboxFetcher, inboxFetcher } from '@joplin/lib/utils/inboxFetcher';
import userFetcher, { initializeUserFetcher } from '@joplin/lib/utils/userFetcher';
const pluginClasses = [
require('./plugins/GotoAnything').default,
@ -488,8 +487,8 @@ class Application extends BaseApplication {
shim.setInterval(() => { runAutoUpdateCheck(); }, 12 * 60 * 60 * 1000);
}
initializeInboxFetcher();
shim.setInterval(() => { void inboxFetcher(); }, 1000 * 60 * 60);
initializeUserFetcher();
shim.setInterval(() => { void userFetcher(); }, 1000 * 60 * 60);
this.updateTray();

View File

@ -25,7 +25,7 @@ const JoplinCloudConfigScreen = (props: JoplinCloudConfigScreenProps) => {
const mapStateToProps = (state: AppState) => {
return {
inboxEmail: state.settings['emailToNote.inboxEmail'],
inboxEmail: state.settings['sync.10.inboxEmail'],
};
};

View File

@ -18,7 +18,7 @@ export const runtime = (): CommandRuntime => {
if (!folder) throw new Error(`No such folder: ${folderId}`);
let deleteMessage = _('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', substrWithEllipsis(folder.title, 0, 32));
if (folderId === context.state.settings['emailToNote.inboxJopId']) {
if (folderId === context.state.settings['sync.10.inboxId']) {
deleteMessage = _('Delete the Inbox notebook?\n\nIf you delete the inbox notebook, any email that\'s recently been sent to it may be lost.');
}

View File

@ -599,6 +599,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
joplinInsert: { inline: 'ins', remove: 'all' },
joplinSub: { inline: 'sub', remove: 'all' },
joplinSup: { inline: 'sup', remove: 'all' },
code: { inline: 'code', remove: 'all', attributes: { spellcheck: false } },
},
setup: (editor: Editor) => {
editor.addCommand('joplinAttach', () => {
@ -697,7 +698,17 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
setEditorReady(true);
});
const preprocessContent = () => {
// Disable spellcheck for all inline code blocks.
const codeElements = editor.dom.doc.querySelectorAll('code.inline-code');
for (const code of codeElements) {
code.setAttribute('spellcheck', 'false');
}
};
editor.on('SetContent', () => {
preprocessContent();
props_onMessage.current({ channel: 'noteRenderComplete' });
});
},
@ -714,18 +725,10 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
// Set the initial content and load the plugin CSS and JS files
// -----------------------------------------------------------------------------------------
const loadDocumentAssets = (editor: any, pluginAssets: any[]) => {
// Note: The way files are cached is not correct because it assumes there's only one version
// of each file. However, when the theme change, a new CSS file, specific to the theme, is
// created. That file should not be loaded on top of the previous one, but as a replacement.
// Otherwise it would do this:
// - Try to load CSS for theme 1 => OK
// - Try to load CSS for theme 2 => OK
// - Try to load CSS for theme 1 => Skip because the file is in cache. As a result, theme 2
// incorrectly stay.
// The fix would be to make allAssets() return a name and a version for each asset. Then the loading
// code would check this and either append the CSS or replace.
const documentCssElements: Record<string, HTMLLinkElement> = {};
const documentScriptElements: Record<string, HTMLScriptElement> = {};
const loadDocumentAssets = (editor: any, pluginAssets: any[]) => {
const theme = themeStyle(props.themeId);
let docHead_: any = null;
@ -736,49 +739,72 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
return docHead_;
}
const cssFiles = [
const allCssFiles = [
`${bridge().vendorDir()}/lib/@fortawesome/fontawesome-free/css/all.min.css`,
`gui/note-viewer/pluginAssets/highlight.js/${theme.codeThemeCss}`,
].concat(
pluginAssets
.filter((a: any) => a.mime === 'text/css')
.map((a: any) => a.path)
).filter((path: string) => !loadedCssFiles_.includes(path));
);
const jsFiles = [].concat(
const allJsFiles = [].concat(
pluginAssets
.filter((a: any) => a.mime === 'application/javascript')
.map((a: any) => a.path)
).filter((path: string) => !loadedJsFiles_.includes(path));
);
for (const cssFile of cssFiles) loadedCssFiles_.push(cssFile);
for (const jsFile of jsFiles) loadedJsFiles_.push(jsFile);
// Remove all previously loaded files that aren't in the assets this time.
// Note: This is important to ensure that we properly change themes.
// See https://github.com/laurent22/joplin/issues/8520
for (const cssFile of loadedCssFiles_) {
if (!allCssFiles.includes(cssFile)) {
documentCssElements[cssFile]?.remove();
delete documentCssElements[cssFile];
}
}
for (const jsFile of loadedJsFiles_) {
if (!allJsFiles.includes(jsFile)) {
documentScriptElements[jsFile]?.remove();
delete documentScriptElements[jsFile];
}
}
const newCssFiles = allCssFiles.filter((path: string) => !loadedCssFiles_.includes(path));
const newJsFiles = allJsFiles.filter((path: string) => !loadedJsFiles_.includes(path));
loadedCssFiles_ = allCssFiles;
loadedJsFiles_ = allJsFiles;
// console.info('loadDocumentAssets: files to load', cssFiles, jsFiles);
if (cssFiles.length) {
for (const cssFile of cssFiles) {
const script = editor.dom.create('link', {
if (newCssFiles.length) {
for (const cssFile of newCssFiles) {
const style = editor.dom.create('link', {
rel: 'stylesheet',
type: 'text/css',
href: cssFile,
class: 'jop-tinymce-css',
});
docHead().appendChild(script);
documentCssElements[cssFile] = style;
docHead().appendChild(style);
}
}
if (jsFiles.length) {
if (newJsFiles.length) {
const editorElementId = editor.dom.uniqueId();
for (const jsFile of jsFiles) {
for (const jsFile of newJsFiles) {
const script = editor.dom.create('script', {
id: editorElementId,
type: 'text/javascript',
src: jsFile,
});
documentScriptElements[jsFile] = script;
docHead().appendChild(script);
}
}

View File

@ -96,7 +96,7 @@ interface Props {
onClose(): void;
shares: StateShare[];
shareUsers: Record<string, StateShareUser[]>;
isJoplinCloud: boolean;
canUseSharePermissions: boolean;
}
interface RecipientDeleteEvent {
@ -261,7 +261,7 @@ function ShareFolderDialog(props: Props) {
function renderAddRecipient() {
const disabled = shareState !== ShareState.Idle;
const dropdown = !props.isJoplinCloud ? null : <Dropdown className="permission-dropdown" options={permissionOptions} value={recipientPermissions} onChange={recipientPermissions_change}/>;
const dropdown = !props.canUseSharePermissions ? null : <Dropdown className="permission-dropdown" options={permissionOptions} value={recipientPermissions} onChange={recipientPermissions_change}/>;
return (
<StyledAddRecipient>
@ -306,7 +306,7 @@ function ShareFolderDialog(props: Props) {
const permission = shareUser.can_write ? 'can_read_and_write' : 'can_read';
const enabled = !recipientsBeingUpdated[shareUser.id];
const dropdown = !props.isJoplinCloud ? null : <Dropdown disabled={!enabled} className="permission-dropdown" value={permission} options={permissionOptions} variant={DropdownVariant.NoBorder} onChange={event => recipient_permissionChange(shareUser.id, event.value)}/>;
const dropdown = !props.canUseSharePermissions ? null : <Dropdown disabled={!enabled} className="permission-dropdown" value={permission} options={permissionOptions} variant={DropdownVariant.NoBorder} onChange={event => recipient_permissionChange(shareUser.id, event.value)}/>;
return (
<StyledRecipient key={shareUser.user.email} index={index}>
@ -407,7 +407,7 @@ const mapStateToProps = (state: State) => {
return {
shares: state.shareService.shares,
shareUsers: state.shareService.shareUsers,
isJoplinCloud: state.settings['sync.target'] === 10,
canUseSharePermissions: state.settings['sync.target'] === 10 && state.settings['sync.10.canUseSharePermissions'],
};
};

View File

@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.12.8",
"version": "2.12.9",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@ -161,7 +161,7 @@
"react": "18.2.0",
"react-datetime": "3.2.0",
"react-dom": "18.2.0",
"react-redux": "8.0.7",
"react-redux": "8.1.1",
"react-select": "5.7.3",
"react-toggle-button": "2.2.0",
"react-tooltip": "4.5.1",

View File

@ -31,6 +31,12 @@
# ./runForTesting.sh 1 createUsers,createData,reset,sync && ./runForTesting.sh 1a reset,sync && ./runForTesting.sh 1
# ./runForTesting.sh 1a
# ----------------------------------------------------------------------------------
# Team accounts:
# ----------------------------------------------------------------------------------
# ./runForTesting.sh 1 createTeams,createData,resetTeam,sync && ./runForTesting.sh 2 resetTeam,sync && ./runForTesting.sh 1
set -e
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
@ -54,6 +60,16 @@ if [ "$USER_NUM" = "1b" ]; then
USER_PROFILE_NUM=1b
fi
if [ "$USER_NUM" = "2a" ]; then
USER_NUM=2
USER_PROFILE_NUM=2a
fi
if [ "$USER_NUM" = "2b" ]; then
USER_NUM=2
USER_PROFILE_NUM=2b
fi
COMMANDS=($(echo $2 | tr "," "\n"))
PROFILE_DIR=~/.config/joplindev-desktop-$USER_PROFILE_NUM
SYNC_TARGET=10
@ -74,6 +90,10 @@ do
curl --data '{"action": "createUserDeletions"}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
elif [[ $CMD == "createTeams" ]]; then
curl --data '{"action": "createTeams"}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
elif [[ $CMD == "createData" ]]; then
echo 'mkbook "shared"' >> "$CMD_FILE"
@ -96,6 +116,16 @@ do
echo "config sync.$SYNC_TARGET.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
echo "config sync.$SYNC_TARGET.userContentPath http://joplinusercontent.local:22300" >> "$CMD_FILE"
fi
elif [[ $CMD == "resetTeam" ]]; then
USER_EMAIL="teamuser1-$USER_NUM@example.com"
rm -rf "$PROFILE_DIR"
echo "config keychain.supported 0" >> "$CMD_FILE"
echo "config sync.target $SYNC_TARGET" >> "$CMD_FILE"
echo "config sync.$SYNC_TARGET.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.$SYNC_TARGET.password 111111" >> "$CMD_FILE"
elif [[ $CMD == "e2ee" ]]; then

View File

@ -362,13 +362,13 @@ class ConfigScreenComponent extends BaseScreenComponent {
<View key="joplinCloud">
<View style={this.styles().settingContainerNoBottomBorder}>
<Text style={this.styles().settingText}>{_('Email to note')}</Text>
<Text style={{ fontWeight: 'bold' }}>{this.props.settings['emailToNote.inboxEmail']}</Text>
<Text style={{ fontWeight: 'bold' }}>{this.props.settings['sync.10.inboxEmail']}</Text>
</View>
{
this.renderButton(
'emailToNote.inboxEmail',
'sync.10.inboxEmail',
_('Copy to clipboard'),
() => Clipboard.setString(this.props.settings['emailToNote.inboxEmail']),
() => Clipboard.setString(this.props.settings['sync.10.inboxEmail']),
{ description }
)
}

View File

@ -528,6 +528,6 @@ export default connect((state: AppState) => {
isOnMobileData: state.isOnMobileData,
syncOnlyOverWifi: state.settings['sync.mobileWifiOnly'],
profileConfig: state.profileConfig,
inboxJopId: state.settings['emailToNote.inboxJopId'],
inboxJopId: state.settings['sync.10.inboxId'],
};
})(SideMenuContentComponent);

View File

@ -467,7 +467,7 @@ PODS:
- React-Core
- RNDateTimePicker (7.1.0):
- React-Core
- RNDeviceInfo (10.6.0):
- RNDeviceInfo (10.6.1):
- React-Core
- RNExitApp (1.1.0):
- React
@ -850,7 +850,7 @@ SPEC CHECKSUMS:
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
RNDateTimePicker: 7ecd54a97fc3749f38c3c89a171f6cbd52f3c142
RNDeviceInfo: 475a4c447168d0ad4c807e48ef5e0963a0f4eb1b
RNDeviceInfo: ab292735ad4fccc5f2aec0c773f7a7f03c7073ae
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688

View File

@ -53,11 +53,11 @@
"react-native-file-viewer": "2.1.5",
"react-native-fingerprint-scanner": "6.0.0",
"react-native-fs": "2.20.0",
"react-native-gesture-handler": "2.11.0",
"react-native-gesture-handler": "2.12.0",
"react-native-get-random-values": "1.9.0",
"react-native-image-picker": "5.4.2",
"react-native-image-resizer": "1.4.5",
"react-native-localize": "3.0.1",
"react-native-localize": "3.0.2",
"react-native-modal-datetime-picker": "15.0.1",
"react-native-paper": "5.8.0",
"react-native-popup-menu": "0.16.1",
@ -74,7 +74,7 @@
"react-native-vosk": "0.1.12",
"react-native-webview": "12.4.0",
"react-native-zip-archive": "6.0.9",
"react-redux": "8.0.7",
"react-redux": "8.1.1",
"redux": "4.2.1",
"rn-fetch-blob": "0.12.0",
"stream": "0.0.2",

View File

@ -117,7 +117,7 @@ import sensorInfo, { SensorInfo } from './components/biometrics/sensorInfo';
import { getCurrentProfile } from '@joplin/lib/services/profileConfig';
import { getDatabaseName, getProfilesRootDir, getResourceDir, setDispatch } from './services/profiles';
import { ReactNode } from 'react';
import { initializeInboxFetcher, inboxFetcher } from '@joplin/lib/utils/inboxFetcher';
import userFetcher, { initializeUserFetcher } from '@joplin/lib/utils/userFetcher';
import { parseShareCache } from '@joplin/lib/services/share/reducer';
import autodetectTheme, { onSystemColorSchemeChange } from './utils/autodetectTheme';
@ -665,8 +665,8 @@ async function initialize(dispatch: Function) {
reg.setupRecurrentSync();
initializeInboxFetcher();
PoorManIntervals.setInterval(() => { void inboxFetcher(); }, 1000 * 60 * 60);
initializeUserFetcher();
PoorManIntervals.setInterval(() => { void userFetcher(); }, 1000 * 60 * 60);
PoorManIntervals.setTimeout(() => {
void AlarmService.garbageCollect();

View File

@ -38,6 +38,8 @@ export interface DeleteOptions {
// sync, we don't need to track the deletion, because the operation doesn't
// need to applied again on next sync.
trackDeleted?: boolean;
disableReadOnlyCheck?: boolean;
}
class BaseModel {

View File

@ -1,5 +1,5 @@
import { ModelType, DeleteOptions } from '../BaseModel';
import { BaseItemEntity, NoteEntity } from '../services/database/types';
import { BaseItemEntity, DeletedItemEntity, NoteEntity } from '../services/database/types';
import Setting from './Setting';
import BaseModel from '../BaseModel';
import time from '../time';
@ -285,7 +285,7 @@ export default class BaseItem extends BaseModel {
});
}
if (needsReadOnlyChecks(this.modelType(), options.changeSource, this.syncShareCache)) {
if (needsReadOnlyChecks(this.modelType(), options.changeSource, this.syncShareCache, options.disableReadOnlyCheck)) {
const previousItems = await this.loadItemsByTypeAndIds(this.modelType(), ids, { fields: ['share_id', 'id'] });
checkIfItemsCanBeChanged(this.modelType(), options.changeSource, previousItems, this.syncShareCache);
}
@ -321,7 +321,7 @@ export default class BaseItem extends BaseModel {
// - Client 1 syncs with target 2 only => the note is *not* deleted from target 2 because no information
// that it was previously deleted exist (deleted_items entry has been deleted).
// The solution would be to permanently store the list of deleted items on each client.
public static deletedItems(syncTarget: number) {
public static deletedItems(syncTarget: number): Promise<DeletedItemEntity[]> {
return this.db().selectAll('SELECT * FROM deleted_items WHERE sync_target = ?', [syncTarget]);
}

View File

@ -80,6 +80,21 @@ export default class Folder extends BaseItem {
return this.db().exec(query);
}
public static async deleteAllByShareId(shareId: string, deleteOptions: DeleteOptions = null) {
const tableNameToClasses: Record<string, any> = {
'folders': Folder,
'notes': Note,
'resources': Resource,
};
for (const tableName of ['folders', 'notes', 'resources']) {
const ItemClass = tableNameToClasses[tableName];
const rows = await this.db().selectAll(`SELECT id FROM ${tableName} WHERE share_id = ?`, [shareId]);
const ids: string[] = rows.map(r => r.id);
await ItemClass.batchDelete(ids, deleteOptions);
}
}
public static async delete(folderId: string, options: DeleteOptions = null) {
options = {
deleteChildren: true,
@ -90,12 +105,16 @@ export default class Folder extends BaseItem {
if (!folder) return; // noop
if (options.deleteChildren) {
const childrenDeleteOptions: DeleteOptions = {
disableReadOnlyCheck: options.disableReadOnlyCheck,
};
const noteIds = await Folder.noteIds(folderId);
await Note.batchDelete(noteIds);
await Note.batchDelete(noteIds, childrenDeleteOptions);
const subFolderIds = await Folder.subFolderIds(folderId);
for (let i = 0; i < subFolderIds.length; i++) {
await Folder.delete(subFolderIds[i]);
await Folder.delete(subFolderIds[i], childrenDeleteOptions);
}
}
@ -762,7 +781,14 @@ export default class Folder extends BaseItem {
syncDebugLog.info('Folder Save:', o);
const savedFolder: FolderEntity = await super.save(o, options);
let savedFolder: FolderEntity = await super.save(o, options);
// Ensures that any folder added to the state has all the required
// properties, in particular "share_id" and "parent_id', which are
// required in various parts of the code.
if (!('share_id' in savedFolder) || !('parent_id' in savedFolder)) {
savedFolder = await this.load(savedFolder.id);
}
this.dispatch({
type: 'FOLDER_UPDATE_ONE',

View File

@ -21,6 +21,7 @@ export default class ItemChange extends BaseModel {
public static SOURCE_UNSPECIFIED = 1;
public static SOURCE_SYNC = 2;
public static SOURCE_DECRYPTION = 2; // CAREFUL - SAME ID AS SOURCE_SYNC!
public static SOURCE_SHARE_SERVICE = 4;
public static tableName() {
return 'item_changes';

View File

@ -1,4 +1,4 @@
import BaseModel, { ModelType } from '../BaseModel';
import BaseModel, { DeleteOptions, ModelType } from '../BaseModel';
import BaseItem from './BaseItem';
import ItemChange from './ItemChange';
import Setting from './Setting';
@ -744,7 +744,7 @@ export default class Note extends BaseItem {
return note;
}
public static async batchDelete(ids: string[], options: any = null) {
public static async batchDelete(ids: string[], options: DeleteOptions = null) {
ids = ids.slice();
while (ids.length) {

View File

@ -717,6 +717,12 @@ class Setting extends BaseModel {
secure: true,
},
'sync.10.inboxEmail': { value: '', type: SettingItemType.String, public: false },
'sync.10.inboxId': { value: '', type: SettingItemType.String, public: false },
'sync.10.canUseSharePermissions': { value: false, type: SettingItemType.Bool, public: false },
'sync.5.syncTargets': { value: {}, type: SettingItemType.Object, public: false },
'sync.resourceDownloadMode': {
@ -1714,10 +1720,6 @@ class Setting extends BaseModel {
label: () => _('Voice typing language files (URL)'),
section: 'note',
},
'emailToNote.inboxEmail': { value: '', type: SettingItemType.String, public: false },
'emailToNote.inboxJopId': { value: '', type: SettingItemType.String, public: false },
};
this.metadata_ = { ...this.buildInMetadata_ };

View File

@ -13,7 +13,8 @@ export interface ItemSlice {
// This function can be called to wrap any read-only-related code. It should be
// fast and allows an early exit for cases that don't apply, for example if not
// synchronising with Joplin Cloud or if not sharing any notebook.
export const needsReadOnlyChecks = (itemType: ModelType, changeSource: number, shareState: ShareState) => {
export const needsReadOnlyChecks = (itemType: ModelType, changeSource: number, shareState: ShareState, disableReadOnlyCheck = false) => {
if (disableReadOnlyCheck) return false;
if (Setting.value('sync.target') !== 10) return false;
if (changeSource === ItemChange.SOURCE_SYNC) return false;
if (!Setting.value('sync.userId')) return false;

View File

@ -33,4 +33,5 @@ export interface SaveOptions {
ignoreProvisionalFlag?: boolean;
dispatchUpdateAction?: boolean;
changeSource?: number;
disableReadOnlyCheck?: boolean;
}

View File

@ -1,5 +1,5 @@
import Note from '../../models/Note';
import { encryptionService, loadEncryptionMasterKey, msleep, resourceService, setupDatabaseAndSynchronizer, supportDir, switchClient } from '../../testing/test-utils';
import { createFolderTree, encryptionService, loadEncryptionMasterKey, msleep, resourceService, setupDatabaseAndSynchronizer, simulateReadOnlyShareEnv, supportDir, switchClient } from '../../testing/test-utils';
import ShareService from './ShareService';
import reducer, { defaultState } from '../../reducer';
import { createStore } from 'redux';
@ -15,6 +15,9 @@ import shim from '../../shim';
import Resource from '../../models/Resource';
import { readFile } from 'fs-extra';
import BaseItem from '../../models/BaseItem';
import ResourceService from '../ResourceService';
import Setting from '../../models/Setting';
import { ModelType } from '../../BaseModel';
interface TestShareFolderServiceOptions {
master_key_id?: string;
@ -239,5 +242,40 @@ describe('ShareService', () => {
Logger.globalLogger.setLevel(previousLogLevel);
});
it('should leave a shared folder', async () => {
const folder1 = await createFolderTree('', [
{
title: 'folder 1',
children: [
{
title: 'note 1',
},
{
title: 'note 2',
},
],
},
]);
const resourceService = new ResourceService();
await Folder.save({ id: folder1.id, share_id: '123456789' });
await Folder.updateAllShareIds(resourceService);
const cleanup = simulateReadOnlyShareEnv('123456789');
const shareService = testShareFolderService();
await shareService.leaveSharedFolder(folder1.id, 'somethingrandom');
expect(await Folder.count()).toBe(0);
expect(await Note.count()).toBe(0);
const deletedItems = await BaseItem.deletedItems(Setting.value('sync.target'));
expect(deletedItems.length).toBe(1);
expect(deletedItems[0].item_type).toBe(ModelType.Folder);
expect(deletedItems[0].item_id).toBe(folder1.id);
cleanup();
});
});

View File

@ -173,14 +173,21 @@ export default class ShareService {
// This is when a share recipient decides to leave the shared folder.
//
// In that case, we should only delete the folder but none of its children.
// Deleting the folder tells the server that we want to leave the share. The
// server will then proceed to delete all associated user_items. So
// eventually all the notebook content will also be deleted for the current
// user.
// In that case we delete the root folder. Deleting the folder tells the
// server that we want to leave the share.
//
// We don't delete the children here because that would delete them for the
// other share participants too.
// We also immediately delete the children, but we do not sync the changes
// otherwise it would delete the items for other users too.
//
// If we do not delete them now it would also cause all kind of issues with
// read-only shares, because the read-only status will be lost after the
// deletion of the root folder, which means various services may modify the
// data. The changes will then be rejected by the sync target and cause
// conflicts.
//
// We do not need to sync the children deletion, because the server will
// take care of deleting all associated user_items. So eventually all the
// notebook content will also be deleted for the current user.
//
// If `folderShareUserId` is provided, the function will check that the user
// does not own the share. It would be an error to leave such a folder
@ -191,7 +198,14 @@ export default class ShareService {
if (folderShareUserId === userId) throw new Error('Cannot leave own notebook');
}
await Folder.delete(folderId, { deleteChildren: false });
const folder = await Folder.load(folderId);
// We call this to make sure all items are correctly linked before we
// call deleteAllByShareId()
await Folder.updateAllShareIds(ResourceService.instance());
await Folder.delete(folderId, { deleteChildren: false, disableReadOnlyCheck: true });
await Folder.deleteAllByShareId(folder.share_id, { disableReadOnlyCheck: true, trackDeleted: false });
}
// Finds any folder that is associated with a share, but the user no longer

View File

@ -94,6 +94,11 @@ export function isSharedFolderOwner(state: RootState, folderId: string): boolean
}
export function isRootSharedFolder(folder: FolderEntity): boolean {
if (!('share_id' in folder) || !('parent_id' in folder)) {
logger.warn('Calling isRootSharedFolder without specifying share_id and parent_id:', folder);
return false;
}
return !!folder.share_id && !folder.parent_id;
}

View File

@ -1,30 +0,0 @@
import SyncTargetRegistry from '../SyncTargetRegistry';
import eventManager from '../eventManager';
import Setting from '../models/Setting';
import { reg } from '../registry';
export const inboxFetcher = async () => {
if (Setting.value('sync.target') !== SyncTargetRegistry.nameToId('joplinCloud')) {
return;
}
const syncTarget = reg.syncTarget();
const fileApi = await syncTarget.fileApi();
const api = fileApi.driver().api();
const owner = await api.exec('GET', `api/users/${api.userId}`);
if (owner.inbox) {
Setting.setValue('emailToNote.inboxJopId', owner.inbox.jop_id);
}
if (owner.inbox_email) {
Setting.setValue('emailToNote.inboxEmail', owner.inbox_email);
}
};
// Listen to the event only once
export const initializeInboxFetcher = () => {
eventManager.once('sessionEstablished', inboxFetcher);
};

View File

@ -0,0 +1,41 @@
import SyncTargetRegistry from '../SyncTargetRegistry';
import eventManager from '../eventManager';
import Setting from '../models/Setting';
import { reg } from '../registry';
import Logger from '../Logger';
const logger = Logger.create('userFetcher');
interface UserApiResponse {
inbox?: {
jop_id: string;
};
inbox_email?: string;
can_use_share_permissions?: number;
}
const userFetcher = async () => {
if (Setting.value('sync.target') !== SyncTargetRegistry.nameToId('joplinCloud')) {
return;
}
const syncTarget = reg.syncTarget();
const fileApi = await syncTarget.fileApi();
const api = fileApi.driver().api();
const owner: UserApiResponse = await api.exec('GET', `api/users/${api.userId}`);
logger.info('Got user:', owner);
Setting.setValue('sync.10.inboxId', owner.inbox ? owner.inbox.jop_id : '');
Setting.setValue('sync.10.inboxEmail', owner.inbox_email ? owner.inbox_email : '');
Setting.setValue('sync.10.canUseSharePermissions', !!owner.can_use_share_permissions);
};
// Listen to the event only once
export const initializeUserFetcher = () => {
eventManager.once('sessionEstablished', userFetcher);
};
export default userFetcher;

View File

@ -1,6 +1,6 @@
const utils = require('../utils');
const glob = require('glob');
const rootDir = utils.rootDir();
const { globSync } = require('@joplin/utils/fs');
module.exports = {
src: '',
@ -11,7 +11,7 @@ module.exports = {
//
// https://github.com/isaacs/node-glob/issues/371
const tsFiles = glob.sync('{**/*.ts,**/*.tsx}', {
const tsFiles = globSync('{**/*.ts,**/*.tsx}', {
cwd: rootDir,
ignore: [
'**/.git/**',
@ -32,7 +32,6 @@ module.exports = {
'packages/app-desktop/dist/**',
'packages/app-mobile/android/**',
'packages/app-mobile/ios/**',
// 'packages/fork-htmlparser2/**',
'packages/fork-sax/**',
'packages/lib/plugin_types/**',
'packages/server/**',
@ -52,29 +51,12 @@ module.exports = {
return `${s.join('.')}.js`;
});
// const ignoredMapFiles = tsFiles.map(f => {
// const s = f.split('.');
// s.pop();
// return `${s.join('.')}.js.map`;
// });
// const ignoredDefFiles = tsFiles.map(f => {
// const s = f.split('.');
// s.pop();
// return `${s.join('.')}.d.ts`;
// });
// const ignoredFiles = ignoredJsFiles.concat(ignoredMapFiles).concat(ignoredDefFiles);
const ignoredFiles = ignoredJsFiles;
ignoredFiles.sort();
const regex = /(# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD)[\s\S]*(# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD)/;
const replacement = `$1\n${ignoredFiles.join('\n')}\n$2`;
const replacement = `$1\n${ignoredJsFiles.join('\n')}\n$2`;
await Promise.all([
utils.replaceFileText(`${rootDir}/.gitignore`, regex, replacement),
utils.replaceFileText(`${rootDir}/.eslintignore`, regex, replacement),
// utils.replaceFileText(`${rootDir}/.ignore`, regex, replacement),
]);
},
};

View File

@ -27,7 +27,7 @@
"dayjs": "1.11.9",
"execa": "4.1.0",
"fs-extra": "11.1.1",
"gettext-parser": "6.0.0",
"gettext-parser": "7.0.1",
"glob": "10.2.7",
"markdown-it": "13.0.1",
"md5-file": "5.0.0",

View File

@ -1,4 +1,4 @@
import { repeat, isCodeBlockSpecialCase1, isCodeBlockSpecialCase2, isCodeBlock, getStyleProp } from './utilities'
import { repeat, isCodeBlockSpecialCase1, isCodeBlockSpecialCase2, isCodeBlock, getStyleProp, htmlEscapeLeadingNonbreakingSpace } from './utilities'
const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = (new Entities()).encode;
@ -25,6 +25,13 @@ rules.paragraph = {
filter: 'p',
replacement: function (content) {
// If the line starts with a nonbreaking space, replace it. By default, the
// markdown renderer removes leading non-HTML-escaped nonbreaking spaces. However,
// because the space is nonbreaking, we want to keep it.
// \u00A0 is a nonbreaking space.
const leadingNonbreakingSpace = /^\u{00A0}/ug;
content = content.replace(leadingNonbreakingSpace, '&nbsp;');
return '\n\n' + content + '\n\n'
}
}

View File

@ -36,7 +36,6 @@ export default function TurndownService (options) {
linkReferenceStyle: 'full',
anchorNames: [],
br: ' ',
nonbreakingSpace: '&nbsp;',
disableEscapeContent: false,
preformattedCode: false,
blankReplacement: function (content, node) {
@ -216,15 +215,10 @@ function replacementForNode (node) {
var whitespace = node.flankingWhitespace
if (whitespace.leading || whitespace.trailing) content = content.trim()
const replaceNonbreakingSpaces = space => {
// \u{00A0} is a nonbreaking space
return space.replace(/\u{00A0}/ug, this.options.nonbreakingSpace);
};
return (
replaceNonbreakingSpaces(whitespace.leading) +
replaceNonbreakingSpaces(rule.replacement(content, node, this.options)) +
replaceNonbreakingSpaces(whitespace.trailing)
whitespace.leading +
rule.replacement(content, node, this.options) +
whitespace.trailing
)
}

12
packages/utils/fs.ts Normal file
View File

@ -0,0 +1,12 @@
/* eslint-disable import/prefer-default-export */
import { GlobOptionsWithFileTypesFalse, sync } from 'glob';
// Wraps glob.sync but with good default options so that it works across
// platforms and with consistent sorting.
export const globSync = (pattern: string | string[], options: GlobOptionsWithFileTypesFalse) => {
let output = sync(pattern, options);
output = output.map(f => f.replace(/\\/g, '/'));
output.sort();
return output;
};

View File

@ -5,7 +5,8 @@
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/utils",
"exports": {
".": "./dist/index.js",
"./net": "./dist/net.js"
"./net": "./dist/net.js",
"./fs": "./dist/fs.js"
},
"publishConfig": {
"access": "public"
@ -21,6 +22,7 @@
"dependencies": {
"execa": "5.1.1",
"fs-extra": "11.1.1",
"glob": "10.2.7",
"node-fetch": "2.6.7"
},
"devDependencies": {

294
yarn.lock
View File

@ -3966,13 +3966,6 @@ __metadata:
languageName: node
linkType: hard
"@gar/promisify@npm:^1.1.3":
version: 1.1.3
resolution: "@gar/promisify@npm:1.1.3"
checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1
languageName: node
linkType: hard
"@github/browserslist-config@npm:^1.0.0":
version: 1.0.0
resolution: "@github/browserslist-config@npm:1.0.0"
@ -4487,7 +4480,7 @@ __metadata:
react: 18.2.0
react-datetime: 3.2.0
react-dom: 18.2.0
react-redux: 8.0.7
react-redux: 8.1.1
react-select: 5.7.3
react-test-renderer: 18.2.0
react-toggle-button: 2.2.0
@ -4590,11 +4583,11 @@ __metadata:
react-native-file-viewer: 2.1.5
react-native-fingerprint-scanner: 6.0.0
react-native-fs: 2.20.0
react-native-gesture-handler: 2.11.0
react-native-gesture-handler: 2.12.0
react-native-get-random-values: 1.9.0
react-native-image-picker: 5.4.2
react-native-image-resizer: 1.4.5
react-native-localize: 3.0.1
react-native-localize: 3.0.2
react-native-modal-datetime-picker: 15.0.1
react-native-paper: 5.8.0
react-native-popup-menu: 0.16.1
@ -4611,7 +4604,7 @@ __metadata:
react-native-vosk: 0.1.12
react-native-webview: 12.4.0
react-native-zip-archive: 6.0.9
react-redux: 8.0.7
react-redux: 8.1.1
react-test-renderer: 18.2.0
redux: 4.2.1
rn-fetch-blob: 0.12.0
@ -4970,7 +4963,7 @@ __metadata:
execa: 4.1.0
fs-extra: 11.1.1
gettext-extractor: 3.7.2
gettext-parser: 6.0.0
gettext-parser: 7.0.1
glob: 10.2.7
gulp: 4.0.2
html-entities: 1.4.0
@ -5042,6 +5035,7 @@ __metadata:
"@types/node-fetch": 2.6.3
execa: 5.1.1
fs-extra: 11.1.1
glob: 10.2.7
jest: 29.5.0
node-fetch: 2.6.7
ts-jest: 29.1.0
@ -6172,16 +6166,6 @@ __metadata:
languageName: node
linkType: hard
"@npmcli/fs@npm:^2.1.0":
version: 2.1.2
resolution: "@npmcli/fs@npm:2.1.2"
dependencies:
"@gar/promisify": ^1.1.3
semver: ^7.3.5
checksum: 405074965e72d4c9d728931b64d2d38e6ea12066d4fad651ac253d175e413c06fe4350970c783db0d749181da8fe49c42d3880bd1cbc12cd68e3a7964d820225
languageName: node
linkType: hard
"@npmcli/fs@npm:^3.1.0":
version: 3.1.0
resolution: "@npmcli/fs@npm:3.1.0"
@ -6229,16 +6213,6 @@ __metadata:
languageName: node
linkType: hard
"@npmcli/move-file@npm:^2.0.0":
version: 2.0.1
resolution: "@npmcli/move-file@npm:2.0.1"
dependencies:
mkdirp: ^1.0.4
rimraf: ^3.0.2
checksum: 52dc02259d98da517fae4cb3a0a3850227bdae4939dda1980b788a7670636ca2b4a01b58df03dd5f65c1e3cb70c50fa8ce5762b582b3f499ec30ee5ce1fd9380
languageName: node
linkType: hard
"@npmcli/node-gyp@npm:^3.0.0":
version: 3.0.0
resolution: "@npmcli/node-gyp@npm:3.0.0"
@ -11098,32 +11072,6 @@ __metadata:
languageName: node
linkType: hard
"cacache@npm:^16.1.0":
version: 16.1.3
resolution: "cacache@npm:16.1.3"
dependencies:
"@npmcli/fs": ^2.1.0
"@npmcli/move-file": ^2.0.0
chownr: ^2.0.0
fs-minipass: ^2.1.0
glob: ^8.0.1
infer-owner: ^1.0.4
lru-cache: ^7.7.1
minipass: ^3.1.6
minipass-collect: ^1.0.2
minipass-flush: ^1.0.5
minipass-pipeline: ^1.2.4
mkdirp: ^1.0.4
p-map: ^4.0.0
promise-inflight: ^1.0.1
rimraf: ^3.0.2
ssri: ^9.0.0
tar: ^6.1.11
unique-filename: ^2.0.0
checksum: d91409e6e57d7d9a3a25e5dcc589c84e75b178ae8ea7de05cbf6b783f77a5fae938f6e8fda6f5257ed70000be27a681e1e44829251bfffe4c10216002f8f14e6
languageName: node
linkType: hard
"cacache@npm:^17.0.0":
version: 17.1.3
resolution: "cacache@npm:17.1.3"
@ -12440,6 +12388,13 @@ __metadata:
languageName: node
linkType: hard
"content-type@npm:^1.0.5":
version: 1.0.5
resolution: "content-type@npm:1.0.5"
checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766
languageName: node
linkType: hard
"conventional-changelog-angular@npm:^5.0.3":
version: 5.0.13
resolution: "conventional-changelog-angular@npm:5.0.13"
@ -17274,7 +17229,7 @@ __metadata:
languageName: node
linkType: hard
"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0":
"fs-minipass@npm:^2.0.0":
version: 2.1.0
resolution: "fs-minipass@npm:2.1.0"
dependencies:
@ -17707,15 +17662,15 @@ __metadata:
languageName: node
linkType: hard
"gettext-parser@npm:6.0.0":
version: 6.0.0
resolution: "gettext-parser@npm:6.0.0"
"gettext-parser@npm:7.0.1":
version: 7.0.1
resolution: "gettext-parser@npm:7.0.1"
dependencies:
content-type: ^1.0.4
content-type: ^1.0.5
encoding: ^0.1.13
readable-stream: ^4.1.0
readable-stream: ^4.3.0
safe-buffer: ^5.2.1
checksum: e6938755d914e886c780a71e58390af0a5ea5698aab2777654733da4c4b96e02fcc9d40c50df0987e4d4a12bd6b99291a268805edb8c99a83a639606ab73e40e
checksum: 251174700e4c7a4b9d868acf3b0e233211724a40f779bd9869357f782b004abd1f0a587f6c9f4219fd24f468bbfc18ca654cdd6e375f19610bd99f1587046ffa
languageName: node
linkType: hard
@ -17961,20 +17916,6 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^8.0.1":
version: 8.0.1
resolution: "glob@npm:8.0.1"
dependencies:
fs.realpath: ^1.0.0
inflight: ^1.0.4
inherits: 2
minimatch: ^5.0.1
once: ^1.3.0
path-is-absolute: ^1.0.0
checksum: 7ac782f3ef1c08005884447479e68ceb0ad56997eb2003e1e9aefae71bad3cb48eb7c49190d3d6736f2ffcd8af4985d53a46831b3d5e0052cc5756822a38b61a
languageName: node
linkType: hard
"glob@npm:^8.0.3":
version: 8.0.3
resolution: "glob@npm:8.0.3"
@ -22605,30 +22546,6 @@ __metadata:
languageName: node
linkType: hard
"make-fetch-happen@npm:^10.0.3":
version: 10.2.1
resolution: "make-fetch-happen@npm:10.2.1"
dependencies:
agentkeepalive: ^4.2.1
cacache: ^16.1.0
http-cache-semantics: ^4.1.0
http-proxy-agent: ^5.0.0
https-proxy-agent: ^5.0.0
is-lambda: ^1.0.1
lru-cache: ^7.7.1
minipass: ^3.1.6
minipass-collect: ^1.0.2
minipass-fetch: ^2.0.3
minipass-flush: ^1.0.5
minipass-pipeline: ^1.2.4
negotiator: ^0.6.3
promise-retry: ^2.0.1
socks-proxy-agent: ^7.0.0
ssri: ^9.0.0
checksum: 2332eb9a8ec96f1ffeeea56ccefabcb4193693597b132cd110734d50f2928842e22b84cfa1508e921b8385cdfd06dda9ad68645fed62b50fff629a580f5fb72c
languageName: node
linkType: hard
"make-fetch-happen@npm:^11.0.0, make-fetch-happen@npm:^11.0.1, make-fetch-happen@npm:^11.0.3, make-fetch-happen@npm:^11.1.1":
version: 11.1.1
resolution: "make-fetch-happen@npm:11.1.1"
@ -24030,21 +23947,6 @@ __metadata:
languageName: node
linkType: hard
"minipass-fetch@npm:^2.0.3":
version: 2.1.2
resolution: "minipass-fetch@npm:2.1.2"
dependencies:
encoding: ^0.1.13
minipass: ^3.1.6
minipass-sized: ^1.0.3
minizlib: ^2.1.2
dependenciesMeta:
encoding:
optional: true
checksum: 3f216be79164e915fc91210cea1850e488793c740534985da017a4cbc7a5ff50506956d0f73bb0cb60e4fe91be08b6b61ef35101706d3ef5da2c8709b5f08f91
languageName: node
linkType: hard
"minipass-fetch@npm:^3.0.0":
version: 3.0.3
resolution: "minipass-fetch@npm:3.0.3"
@ -24790,27 +24692,7 @@ __metadata:
languageName: node
linkType: hard
"node-gyp@npm:9.3.1":
version: 9.3.1
resolution: "node-gyp@npm:9.3.1"
dependencies:
env-paths: ^2.2.0
glob: ^7.1.4
graceful-fs: ^4.2.6
make-fetch-happen: ^10.0.3
nopt: ^6.0.0
npmlog: ^6.0.0
rimraf: ^3.0.2
semver: ^7.3.5
tar: ^6.1.2
which: ^2.0.2
bin:
node-gyp: bin/node-gyp.js
checksum: b860e9976fa645ca0789c69e25387401b4396b93c8375489b5151a6c55cf2640a3b6183c212b38625ef7c508994930b72198338e3d09b9d7ade5acc4aaf51ea7
languageName: node
linkType: hard
"node-gyp@npm:^9.0.0, node-gyp@npm:latest":
"node-gyp@npm:9.4.0, node-gyp@npm:^9.0.0, node-gyp@npm:latest":
version: 9.4.0
resolution: "node-gyp@npm:9.4.0"
dependencies:
@ -27952,9 +27834,9 @@ __metadata:
languageName: node
linkType: hard
"react-native-gesture-handler@npm:2.11.0":
version: 2.11.0
resolution: "react-native-gesture-handler@npm:2.11.0"
"react-native-gesture-handler@npm:2.12.0":
version: 2.12.0
resolution: "react-native-gesture-handler@npm:2.12.0"
dependencies:
"@egjs/hammerjs": ^2.0.17
hoist-non-react-statics: ^3.3.0
@ -27964,7 +27846,7 @@ __metadata:
peerDependencies:
react: "*"
react-native: "*"
checksum: 2f1e5ab3e96473c034f90e7aecce7d66fca992b791317b16a951928a42487598d3d3237e6e711bcf3cdb065f5b37415d3a8f8872f9df17993c614b852e350cd5
checksum: 5147357b3212e269d0b8003e1be9e0993d0770f4880f1a8f52d2d61512c4569c48ec7b866d0a9ee44038785c29e0f84fbb99fd18a8e09552a25910c71602d788
languageName: node
linkType: hard
@ -28012,9 +27894,9 @@ __metadata:
languageName: node
linkType: hard
"react-native-localize@npm:3.0.1":
version: 3.0.1
resolution: "react-native-localize@npm:3.0.1"
"react-native-localize@npm:3.0.2":
version: 3.0.2
resolution: "react-native-localize@npm:3.0.2"
peerDependencies:
react: ">=18.1.0"
react-native: ">=0.70.0"
@ -28022,7 +27904,7 @@ __metadata:
peerDependenciesMeta:
react-native-macos:
optional: true
checksum: f8703405ea5cf3fcc04fbfb255853832d730e6bedfc4a758254888bd4fb0b5edfa17350dff0b51e5cf804c00819bba1be86c05c496cc2a1d1a1a005a19bb0b9d
checksum: 46abd6046dfb1fb5426f9e51971894e893556646206b7fa387c5eb9394a3f3b9ddce190bc94ffc9c51c65326f60bbad57219d8330b4a1dcfaf46dd2eb4ef43ae
languageName: node
linkType: hard
@ -28089,6 +27971,27 @@ __metadata:
languageName: node
linkType: hard
"react-native-reanimated@patch:react-native-reanimated@npm%3A3.3.0#./.yarn/patches/react-native-animation-fix/react-native-reanimated-npm-3.3.0-fb4272741c.patch::locator=root%40workspace%3A.":
version: 3.3.0
resolution: "react-native-reanimated@patch:react-native-reanimated@npm%3A3.3.0#./.yarn/patches/react-native-animation-fix/react-native-reanimated-npm-3.3.0-fb4272741c.patch::version=3.3.0&hash=13daee&locator=root%40workspace%3A."
dependencies:
"@babel/plugin-transform-object-assign": ^7.16.7
"@babel/preset-typescript": ^7.16.7
convert-source-map: ^2.0.0
invariant: ^2.2.4
peerDependencies:
"@babel/core": ^7.0.0-0
"@babel/plugin-proposal-nullish-coalescing-operator": ^7.0.0-0
"@babel/plugin-proposal-optional-chaining": ^7.0.0-0
"@babel/plugin-transform-arrow-functions": ^7.0.0-0
"@babel/plugin-transform-shorthand-properties": ^7.0.0-0
"@babel/plugin-transform-template-literals": ^7.0.0-0
react: "*"
react-native: "*"
checksum: 5d73a35e694ab6c37b5f12530e1acdac822f2cceef7d04dcc060cc4c9221f51cf6bb68ed16737b25e79086de9dca6cd322819c6d04d668c9b97526408e97db68
languageName: node
linkType: hard
"react-native-rsa-native@npm:2.0.5":
version: 2.0.5
resolution: "react-native-rsa-native@npm:2.0.5"
@ -28299,6 +28202,52 @@ __metadata:
languageName: node
linkType: hard
"react-native@patch:react-native@npm%3A0.71.10#./.yarn/patches/react-native-animation-fix/react-native-npm-0.71.10-f9c32562d8.patch::locator=root%40workspace%3A.":
version: 0.71.10
resolution: "react-native@patch:react-native@npm%3A0.71.10#./.yarn/patches/react-native-animation-fix/react-native-npm-0.71.10-f9c32562d8.patch::version=0.71.10&hash=d4015e&locator=root%40workspace%3A."
dependencies:
"@jest/create-cache-key-function": ^29.2.1
"@react-native-community/cli": 10.2.2
"@react-native-community/cli-platform-android": 10.2.0
"@react-native-community/cli-platform-ios": 10.2.1
"@react-native/assets": 1.0.0
"@react-native/normalize-color": 2.1.0
"@react-native/polyfills": 2.0.0
abort-controller: ^3.0.0
anser: ^1.4.9
base64-js: ^1.1.2
deprecated-react-native-prop-types: ^3.0.1
event-target-shim: ^5.0.1
invariant: ^2.2.4
jest-environment-node: ^29.2.1
jsc-android: ^250231.0.0
memoize-one: ^5.0.0
metro-react-native-babel-transformer: 0.73.9
metro-runtime: 0.73.9
metro-source-map: 0.73.9
mkdirp: ^0.5.1
nullthrows: ^1.1.1
pretty-format: ^26.5.2
promise: ^8.3.0
react-devtools-core: ^4.26.1
react-native-codegen: ^0.71.5
react-native-gradle-plugin: ^0.71.19
react-refresh: ^0.4.0
react-shallow-renderer: ^16.15.0
regenerator-runtime: ^0.13.2
scheduler: ^0.23.0
stacktrace-parser: ^0.1.3
use-sync-external-store: ^1.0.0
whatwg-fetch: ^3.0.0
ws: ^6.2.2
peerDependencies:
react: 18.2.0
bin:
react-native: cli.js
checksum: c8c3cd4abfe4031f2c91baa6b26e2a43421dc3a3b221fec408d67c788e477b90dc4c0b206c1a24f82d5d7dae319b5da8ce6a472e346b48936813001d8276a279
languageName: node
linkType: hard
"react-reconciler@npm:^0.26.2":
version: 0.26.2
resolution: "react-reconciler@npm:0.26.2"
@ -28312,9 +28261,9 @@ __metadata:
languageName: node
linkType: hard
"react-redux@npm:8.0.7":
version: 8.0.7
resolution: "react-redux@npm:8.0.7"
"react-redux@npm:8.1.1":
version: 8.1.1
resolution: "react-redux@npm:8.1.1"
dependencies:
"@babel/runtime": ^7.12.1
"@types/hoist-non-react-statics": ^3.3.1
@ -28323,7 +28272,6 @@ __metadata:
react-is: ^18.0.0
use-sync-external-store: ^1.0.0
peerDependencies:
"@reduxjs/toolkit": ^1 || ^2.0.0-beta.0
"@types/react": ^16.8 || ^17.0 || ^18.0
"@types/react-dom": ^16.8 || ^17.0 || ^18.0
react: ^16.8 || ^17.0 || ^18.0
@ -28331,8 +28279,6 @@ __metadata:
react-native: ">=0.59"
redux: ^4 || ^5.0.0-beta.0
peerDependenciesMeta:
"@reduxjs/toolkit":
optional: true
"@types/react":
optional: true
"@types/react-dom":
@ -28343,7 +28289,7 @@ __metadata:
optional: true
redux:
optional: true
checksum: d903aa79b12154258fd76b8e61fcf56f72123f69c31033b262805646371e23822cd0cd11d24194cda1e03569a7b1bf86e935cd57a9bab4523186804ed2742fac
checksum: 370676330727764d78f35e9c5a0ed0591d79482fe9b70fffcab4aa6bcccc6194e4f1ebd818b4b390351dea5557e70d3bd4d95d7a0ac9baa1f45d6bf2230ee713
languageName: node
linkType: hard
@ -28645,15 +28591,16 @@ __metadata:
languageName: node
linkType: hard
"readable-stream@npm:^4.1.0":
version: 4.2.0
resolution: "readable-stream@npm:4.2.0"
"readable-stream@npm:^4.3.0":
version: 4.4.2
resolution: "readable-stream@npm:4.4.2"
dependencies:
abort-controller: ^3.0.0
buffer: ^6.0.3
events: ^3.3.0
process: ^0.11.10
checksum: aa8447f781e6df90af78f6b0b9b9a77da2816dcf6c8220e7021c4de36e04f8129fed7ead81eac0baad2f42098209f9e7d7cd43169e1c156efcd2613828a37439
string_decoder: ^1.3.0
checksum: 6f4063763dbdb52658d22d3f49ca976420e1fbe16bbd241f744383715845350b196a2f08b8d6330f8e219153dff34b140aeefd6296da828e1041a7eab1f20d5e
languageName: node
linkType: hard
@ -29545,7 +29492,7 @@ __metadata:
lerna: 3.22.1
lint-staged: 13.2.3
madge: 6.1.0
node-gyp: 9.3.1
node-gyp: 9.4.0
nodemon: 2.0.22
npm-package-json-lint: 6.4.0
typescript: 5.0.2
@ -30837,15 +30784,6 @@ __metadata:
languageName: node
linkType: hard
"ssri@npm:^9.0.0":
version: 9.0.1
resolution: "ssri@npm:9.0.1"
dependencies:
minipass: ^3.1.1
checksum: fb58f5e46b6923ae67b87ad5ef1c5ab6d427a17db0bead84570c2df3cd50b4ceb880ebdba2d60726588272890bae842a744e1ecce5bd2a2a582fccd5068309eb
languageName: node
linkType: hard
"stack-trace@npm:0.0.10":
version: 0.0.10
resolution: "stack-trace@npm:0.0.10"
@ -31238,7 +31176,7 @@ __metadata:
languageName: node
linkType: hard
"string_decoder@npm:^1.1.1":
"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0":
version: 1.3.0
resolution: "string_decoder@npm:1.3.0"
dependencies:
@ -33264,15 +33202,6 @@ __metadata:
languageName: node
linkType: hard
"unique-filename@npm:^2.0.0":
version: 2.0.1
resolution: "unique-filename@npm:2.0.1"
dependencies:
unique-slug: ^3.0.0
checksum: 807acf3381aff319086b64dc7125a9a37c09c44af7620bd4f7f3247fcd5565660ac12d8b80534dcbfd067e6fe88a67e621386dd796a8af828d1337a8420a255f
languageName: node
linkType: hard
"unique-filename@npm:^3.0.0":
version: 3.0.0
resolution: "unique-filename@npm:3.0.0"
@ -33291,15 +33220,6 @@ __metadata:
languageName: node
linkType: hard
"unique-slug@npm:^3.0.0":
version: 3.0.0
resolution: "unique-slug@npm:3.0.0"
dependencies:
imurmurhash: ^0.1.4
checksum: 49f8d915ba7f0101801b922062ee46b7953256c93ceca74303bd8e6413ae10aa7e8216556b54dc5382895e8221d04f1efaf75f945c2e4a515b4139f77aa6640c
languageName: node
linkType: hard
"unique-slug@npm:^4.0.0":
version: 4.0.0
resolution: "unique-slug@npm:4.0.0"