Compare commits

..

1 Commits

Author SHA1 Message Date
Ammar Ahmed
c353d429c4 mobile: fix material menu 2025-01-15 11:33:57 +05:00
312 changed files with 63140 additions and 18704 deletions

View File

@@ -14,8 +14,6 @@ runs:
apps/mobile/package-lock.json
apps/desktop/package-lock.json
apps/web/package-lock.json
apps/monograph/package-lock.json
apps/theme-builder/package-lock.json
extensions/web-clipper/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
@@ -26,4 +24,3 @@ runs:
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
packages/intl/package-lock.json

View File

@@ -4,7 +4,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CMAKE_C_COMPILER_LAUNCHER: ccache

View File

@@ -18,7 +18,7 @@ on:
jobs:
test:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

View File

@@ -37,7 +37,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -49,13 +49,6 @@ jobs:
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Setup environment
run: |
@@ -64,10 +57,6 @@ jobs:
- name: Generate desktop build
run: npx nx build:desktop @notesnook/web
- name: Build desktop bundle
working-directory: ./apps/desktop
run: npm run bundle
- name: Archive build artifact
uses: actions/upload-artifact@v4
with:
@@ -82,8 +71,7 @@ jobs:
- name: Package desktop build
run: |
cp -r ./apps/web/build ./apps/desktop/
zip -r ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip ./apps/desktop/build/
zip -r ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip ./apps/web/build/
echo "Build folder archived to ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip"
- name: Upload desktop build

View File

@@ -14,7 +14,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -133,7 +133,7 @@ jobs:
test-linux:
name: Test for Linux
needs: build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Check out Git repository

View File

@@ -19,7 +19,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -11,7 +11,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -14,7 +14,7 @@ on: workflow_dispatch
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
permissions:
packages: write
contents: read

View File

@@ -5,7 +5,7 @@ on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -5,7 +5,7 @@ on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -14,7 +14,7 @@ on:
jobs:
bench:
name: Bench
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -5,7 +5,7 @@ on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -19,7 +19,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

1
.gitignore vendored
View File

@@ -11,4 +11,3 @@ nx-cloud.env
site
node_modules.backup
.nx
*.patch

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.0.25",
"version": "3.0.23",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",
@@ -25,7 +25,7 @@
"repository": "https://github.com/streetwriters/notesnook",
"license": "GPL-3.0-or-later",
"dependencies": {
"@lingui/core": "5.1.2",
"@lingui/core": "4.11.4",
"@notesnook/intl": "file:../../packages/intl",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
@@ -33,10 +33,10 @@
"electron-trpc": "0.6.1",
"electron-updater": "^6.3.4",
"icojs": "^0.19.4",
"sqlite-better-trigram": "0.0.2",
"sqlite-better-trigram": "^0.0.2",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"zod": "3.23.8"
"zod": "^3.23.8"
},
"devDependencies": {
"@streetwriters/kysely": "^0.27.4",
@@ -45,15 +45,15 @@
"chokidar": "^4.0.1",
"electron": "^31.7.4",
"electron-builder": "^25.1.8",
"esbuild": "0.21.5",
"vitest": "2.1.8",
"esbuild": "^0.24.0",
"node-abi": "^3.68.0",
"node-gyp-build": "^4.8.2",
"playwright": "^1.48.2",
"prebuildify": "^6.0.1",
"slugify": "1.6.6",
"slugify": "^1.6.6",
"tree-kill": "^1.2.2",
"undici": "^6.19.8"
"undici": "^6.19.8",
"vitest": "^2.1.5"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"

View File

@@ -32,7 +32,6 @@ import { withErrorBoundry } from "./components/exception-handler";
import GlobalSafeAreaProvider from "./components/globalsafearea";
import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import { NotePreviewConfigure } from "./screens/note-preview-configure";
import { themeTrpcClient } from "./screens/settings/theme-selector";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
@@ -48,7 +47,7 @@ if (appLockEnabled || appLockMode !== "none") {
useUserStore.getState().lockApp(true);
}
const App = (props: { configureMode: "note-preview" }) => {
const App = () => {
useAppEvents();
//@ts-ignore
globalThis["IS_MAIN_APP_RUNNING"] = true;
@@ -62,7 +61,6 @@ const App = (props: { configureMode: "note-preview" }) => {
TipManager.init();
}, 100);
}, []);
return (
<View
style={{
@@ -92,11 +90,7 @@ const App = (props: { configureMode: "note-preview" }) => {
width: "100%"
}}
>
{props.configureMode === "note-preview" ? (
<NotePreviewConfigure />
) : (
<ApplicationHolder />
)}
<ApplicationHolder />
</GestureHandlerRootView>
<AppLockedOverlay />
</View>
@@ -109,8 +103,8 @@ let currTheme =
: SettingsService.getProperty("lighTheme");
useThemeEngineStore.getState().setTheme(currTheme);
export const withTheme = (Element: (props: any) => JSX.Element) => {
return function AppWithThemeProvider(props: any) {
export const withTheme = (Element: () => JSX.Element) => {
return function AppWithThemeProvider() {
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
state.colorScheme,
state.darkTheme,
@@ -129,14 +123,13 @@ export const withTheme = (Element: (props: any) => JSX.Element) => {
})
.then((theme) => {
if (theme) {
console.log(theme.version, "theme updated");
theme.colorScheme === "dark"
? useThemeStore.getState().setDarkTheme(theme)
: useThemeStore.getState().setLightTheme(theme);
}
})
.catch(() => {
/* empty */
});
.catch(console.log);
}, 1000);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -151,7 +144,7 @@ export const withTheme = (Element: (props: any) => JSX.Element) => {
return (
<I18nProvider i18n={i18n}>
<Element {...props} />
<Element />
</I18nProvider>
);
};

View File

@@ -126,6 +126,7 @@ export async function setAppLockVerificationCipher(appLockPassword: string) {
DatabaseLogger.info("setAppLockVerificationCipher");
} catch (e) {
DatabaseLogger.error(e);
console.log(e);
}
}
@@ -309,6 +310,7 @@ export async function getCryptoKey() {
return key;
} catch (e) {
console.log("getCryptoKey", e);
DatabaseLogger.error(e);
}
}

View File

@@ -39,6 +39,8 @@ export async function setupDatabase(password?: string) {
const key = await getDatabaseKey(password);
if (!key) throw new Error(strings.databaseSetupFailed());
console.log("Opening database with key:", !!key);
database.host({
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",

View File

@@ -27,9 +27,13 @@ import RNFetchBlob from "react-native-blob-util";
*/
export async function compressToBase64(path, type) {
const { width: screenWidth, scale } = Dimensions.get("window");
console.log("COMPRESSING TO BASE64...");
return new Promise((resolve) => {
console.log(path, "image path...");
Image.getSize(path, async (width) => {
console.log("image width", width);
const response = await ImageResizer.createResizedImage(
path,
screenWidth * scale,
@@ -51,12 +55,10 @@ export async function compressToBase64(path, type) {
: response.uri,
"base64"
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(response.uri.replace("file://", "")).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs
.unlink(response.uri.replace("file://", ""))
.catch(console.log);
resolve(base64);
});
@@ -78,8 +80,6 @@ export async function compressToFile(path, type) {
onlyScaleDown: true
}
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
return response.uri;
}

View File

@@ -83,9 +83,7 @@ export async function downloadAttachments(attachmentIds: string[]) {
const isCancelled = () => {
if (useAttachmentStore.getState().downloading?.[groupId]?.canceled) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
useAttachmentStore.getState().setDownloading({
groupId,
current: 0,
@@ -190,13 +188,9 @@ export async function downloadAttachments(attachmentIds: string[]) {
ToastManager.error(e as Error, "Error zipping attachments");
}
// Remove source & zip file from cache.
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
}
}
@@ -327,14 +321,10 @@ export default async function downloadAttachment(
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
.catch(() => {
/* empty */
});
.catch(console.log);
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
.catch(() => {
/* empty */
});
.catch(console.log);
}
useAttachmentStore.getState().setDownloading({

View File

@@ -123,9 +123,7 @@ export async function downloadFile(
cancelToken.cancel = async (reason) => {
useAttachmentStore.getState().remove(filename);
request.cancel();
RNFetchBlob.fs.unlink(tempFilePath).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
DatabaseLogger.log(`Download cancelled: ${reason} ${filename}`);
};
@@ -144,9 +142,7 @@ export async function downloadFile(
useAttachmentStore.getState().remove(filename);
if (await exists(originalFilePath)) {
await RNFetchBlob.fs.unlink(originalFilePath).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
}
await RNFetchBlob.fs.mv(tempFilePath, originalFilePath);
@@ -173,12 +169,8 @@ export async function downloadFile(
}
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(tempFilePath).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(originalFilePath).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
DatabaseLogger.error(e, "Download failed: ", {
url
});
@@ -196,7 +188,7 @@ export async function checkAttachment(hash: string) {
try {
const size = await getUploadedFileSize(hash);
console.log("File Size", size);
if (size === -1) return { success: true };
if (size === 0)

View File

@@ -60,9 +60,7 @@ export async function readEncrypted<TOutputFormat extends DataFormat>(
return output as Output<TOutputFormat>;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(path).catch(console.log);
DatabaseLogger.error(e);
}
}
@@ -92,9 +90,8 @@ export async function writeEncryptedBase64(
type: "url"
});
RNFetchBlob.fs.unlink(filepath).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(filepath).catch(console.log);
console.log("encrypted file output: ", output);
return {
...output,
@@ -109,9 +106,7 @@ export async function deleteFile(
await createCacheDir();
const localFilePath = cacheDir + `/${filename}`;
if (!requestOptions) {
RNFetchBlob.fs.unlink(localFilePath).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(localFilePath).catch(console.log);
return true;
}
@@ -122,9 +117,7 @@ export async function deleteFile(
const status = response.info().status;
const ok = status >= 200 && status < 300;
if (ok) {
RNFetchBlob.fs.unlink(localFilePath).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(localFilePath).catch(console.log);
}
return ok;
} catch (e) {
@@ -141,14 +134,10 @@ export async function clearFileStorage() {
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (const file of files) {
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(console.log);
}
for (const file of oldCache) {
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(console.log);
}
} catch (e) {
DatabaseLogger.error(e, "clearFileStorage");
@@ -172,19 +161,20 @@ export async function migrateFilesFromCache() {
const migratedFilesPath = cacheDir + "/.migrated_1";
const migrated = await RNFetchBlob.fs.exists(migratedFilesPath);
if (migrated) {
console.log("Files migrated already");
return;
}
const files = await RNFetchBlob.fs.ls(cacheDir);
console.log("Files to migrate:", files.join(","));
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (const file of oldCache) {
if (file.startsWith("org.") || file.startsWith("com.")) continue;
RNFetchBlob.fs
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
.catch(() => {
/* empty */
});
.catch(console.log);
console.log("Moved", file);
}
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
} catch (e) {
@@ -193,17 +183,13 @@ export async function migrateFilesFromCache() {
}
export async function clearCache() {
await RNFetchBlob.fs.unlink(cacheDir).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(cacheDir).catch(console.log);
await createCacheDir();
eSendEvent("cache-cleared");
}
export async function deleteCacheFileByPath(path: string) {
await RNFetchBlob.fs.unlink(path).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(path).catch(console.log);
}
export async function deleteCacheFileByName(name: string) {
@@ -212,21 +198,15 @@ export async function deleteCacheFileByName(name: string) {
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${name}`;
await RNFetchBlob.fs.unlink(appGroupPath).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(appGroupPath).catch(console.log);
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(console.log);
}
export async function deleteDCacheFiles() {
const files = await RNFetchBlob.fs.ls(cacheDir);
for (const file of files) {
if (file.includes("_dcache") || file.startsWith("NN_")) {
await RNFetchBlob.fs.unlink(file).catch(() => {
/* empty */
});
if (file.includes("_dcache")) {
await RNFetchBlob.fs.unlink(file).catch(console.log);
}
}
}
@@ -265,9 +245,7 @@ export async function exists(filename: string) {
);
RNFetchBlob.fs
.unlink(existsInAppGroup ? appGroupPath : path)
.catch(() => {
/* empty */
});
.catch(console.log);
return false;
}
@@ -303,7 +281,7 @@ export async function getCacheSize() {
await createCacheDir();
const stat = await RNFetchBlob.fs.lstat(`file://` + cacheDir);
let total = 0;
console.log("Total files", stat.length);
stat.forEach((file) => {
total += parseInt(file.size as unknown as string);
});

View File

@@ -115,7 +115,7 @@ export const FileSizeResult = {
Error: -1
};
export async function getUploadedFileSize(hash: string, retry = 0) {
export async function getUploadedFileSize(hash: string) {
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.tokenManager.getAccessToken();
@@ -123,24 +123,8 @@ export async function getUploadedFileSize(hash: string, retry = 0) {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
if (
!attachmentInfo.ok ||
attachmentInfo.headers?.get("content-length") === null
) {
if (retry < 3) {
DatabaseLogger.log(`Retrying file size check: ${hash}, ${retry}`);
return getUploadedFileSize(hash, retry + 1);
}
throw new Error(
`File size check failed: ${hash}, ${
attachmentInfo.status
}, ${attachmentInfo.headers?.get("content-length")}`
);
}
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length") as string
attachmentInfo.headers?.get("content-length") || "0"
);
return isNaN(contentLength) ? FileSizeResult.Empty : contentLength;
} catch (e) {

View File

@@ -17,7 +17,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function info(context: string, ...logs: unknown[]) {}
function info(context: string, ...logs: unknown[]) {
console.log(`${new Date().toLocaleDateString()}::info::${context}:`, ...logs);
}
function error(context: string, ...logs: unknown[]) {
console.log(

View File

@@ -41,9 +41,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
await sleep(500);
}
if (item.type === "link") {
Linking.openURL(item.data).catch(() => {
/* empty */
});
Linking.openURL(item.data).catch(console.log);
} else if (item.type === "promo") {
presentSheet({
component: (

View File

@@ -112,19 +112,14 @@ const AppLockedOverlay = () => {
if (
!biometricsAuthEnabled ||
!(await BiometricService.isBiometryAvailable())
) {
)
return;
}
biometricUnlockAwaitingUserInput.current = true;
if (Platform.OS === "android") {
const activityName = await NotesnookModule.getActivityName();
if (
activityName !== "MainActivity" &&
activityName !== "NotePreviewConfigureActivity"
)
return;
if (activityName !== "MainActivity") return;
}
useSettingStore.getState().setRequestBiometrics(true);
@@ -185,6 +180,7 @@ const AppLockedOverlay = () => {
DatabaseLogger.info("Locking app on entering foreground");
useUserStore.getState().lockApp(true);
}
if (
!(
biometricUnlockAwaitingUserInput.current ||

View File

@@ -182,17 +182,18 @@ const Actions = ({
relations
.map((relation) => relation.fromId)
.forEach(async (id) => {
useTabStore.getState().forEachNoteTab(id, async (tab) => {
const isFocused = useTabStore.getState().currentTab === tab.id;
const tab = useTabStore.getState().getTabForNote(id);
if (tab !== undefined) {
const isFocused = useTabStore.getState().currentTab === tab;
if (isFocused) {
eSendEvent(eOnLoadNote, {
item: await db.notes.note(id),
forced: true
});
} else {
editorController.current.commands.setLoading(true, tab.id);
editorController.current.commands.setLoading(true, tab);
}
});
}
});
close?.();
},

View File

@@ -528,9 +528,7 @@ export const AttachmentDialog = ({
setAttachments(results);
setLoading(false);
})
.catch(() => {
/* empty */
});
.catch(console.log);
}}
/>
)

View File

@@ -88,6 +88,7 @@ export const ChangePassword = () => {
await sleep(300);
eSendEvent(eOpenRecoveryKeyDialog);
} catch (e) {
console.log(e.stack);
setLoading(false);
ToastManager.show({
heading: strings.passwordChangeFailed(),

View File

@@ -70,7 +70,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
switch (step) {
case LoginSteps.emailAuth: {
const mfaInfo = await db.user.authenticateEmail(email.current);
console.log("email auth", mfaInfo);
if (mfaInfo) {
TwoFactorVerification.present(
async (mfa, callback) => {

View File

@@ -75,9 +75,9 @@ const BaseDialog = ({
background
}: BaseDialogProps) => {
const floating = useIsFloatingKeyboard();
const appState = useAppState();
const lockEvents = useRef(false);
const [internalVisible, setIntervalVisible] = useState(true);
const locked = useUserStore((state) => state.appLocked);
useEffect(() => {
return () => {
@@ -86,20 +86,26 @@ const BaseDialog = ({
}, []);
useEffect(() => {
if (locked) {
setIntervalVisible(false);
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
setIntervalVisible(true);
unsub();
setTimeout(() => {
lockEvents.current = false;
if (useUserStore.getState().disableAppLockRequests) return;
if (SettingsService.canLockAppInBackground()) {
if (appState === "background") {
setIntervalVisible(false);
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
setIntervalVisible(true);
unsub();
setTimeout(() => {
lockEvents.current = false;
});
}
});
}
});
}
}
}, [locked]);
}, [appState]);
const Wrapper = useSafeArea ? SafeAreaView : View;

View File

@@ -110,7 +110,6 @@ export const Dialog = ({ context = "global" }) => {
setChecked(false);
values.current.inputValue = undefined;
setVisible(false);
dialogInfo.onClose();
};
const onNegativePress = async () => {

View File

@@ -180,7 +180,7 @@ AttachImage.present = (response: ImageType[], context?: string) => {
close={close}
onAttach={(result) => {
resolved = true;
console.log("closing");
resolve(result);
close?.();
}}

View File

@@ -307,7 +307,9 @@ const PDFPreview = () => {
password={password}
maxScale={6}
onError={onError}
onPressLink={(uri) => {}}
onPressLink={(uri) => {
console.log(`Link pressed: ${uri}`);
}}
style={{
flex: 1,
width: width,

View File

@@ -62,7 +62,7 @@ export default function Progress() {
eSubscribeEvent(PROGRESS_EVENTS.start, (options: ProgressOptions) => {
setProgress(options.progress);
cancelCallback.current = options.cancelCallback;
console.log("options", options.fillBackground);
setData({
title: options.title,
paragraph: options.paragraph,

View File

@@ -336,7 +336,7 @@ export class VaultDialog extends Component {
);
});
}
console.log("VAULT UPDATED EVENT");
eSendEvent("vaultUpdated");
this.setState({
loading: false
@@ -406,6 +406,7 @@ export class VaultDialog extends Component {
} else {
await db.vault.add(this.state.note.id);
console.log("update note event...");
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.close();
@@ -542,6 +543,7 @@ export class VaultDialog extends Component {
this.close();
})
.catch((e) => {
console.log("Error", e);
this._takeErrorAction(e);
});
}

View File

@@ -78,10 +78,10 @@ class ExceptionHandler extends React.Component<{
}
export const withErrorBoundry = (Element: React.ElementType, name: string) => {
return function ErrorBoundary(props: any) {
return function ErrorBoundary() {
return (
<ExceptionHandler component={name}>
<Element {...props} />
<Element />
</ExceptionHandler>
);
};

View File

@@ -54,7 +54,7 @@ export const Header = ({
onSearch
}: {
onLeftMenuButtonPress?: () => void;
renderedInRoute?: RouteName;
renderedInRoute: RouteName;
id?: string;
title: string;
headerRightButtons?: HeaderRightButton[];

View File

@@ -41,7 +41,7 @@ export const RightMenus = ({
onSearch
}: {
headerRightButtons?: HeaderRightButton[];
renderedInRoute?: RouteName;
renderedInRoute: RouteName;
id?: string;
onPressDefaultRightButton?: () => void;
search?: boolean;

View File

@@ -41,7 +41,7 @@ export const Title = ({
isHiddenOnRender?: boolean;
accentColor?: string;
isBeta?: boolean;
renderedInRoute?: string;
renderedInRoute: string;
id?: string;
}) => {
const { colors } = useThemeColors();

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import { DataURL, getFileNameWithExtension } from "@notesnook/core";
import { DataURL } from "@notesnook/core";
import type { ImageAttributes } from "@notesnook/editor";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
@@ -34,11 +34,6 @@ import {
import BaseDialog from "../dialog/base-dialog";
import { IconButton } from "../ui/icon-button";
import { ProgressBarComponent } from "../ui/svg/lazy";
import RNFetchBlob from "react-native-blob-util";
import Share from "react-native-share";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useSettingStore } from "../../stores/use-setting-store";
const ImagePreview = () => {
const { colors } = useThemeColors("dialog");
@@ -46,8 +41,6 @@ const ImagePreview = () => {
const [image, setImage] = useState<string>();
const [loading, setLoading] = useState(false);
const imageRef = useRef<ImageAttributes>();
const insets = useGlobalSafeAreaInsets();
const [showHeader, setShowHeader] = useState(true);
useEffect(() => {
eSubscribeEvent("ImagePreview", open);
@@ -80,86 +73,16 @@ const ImagePreview = () => {
silent: true,
cache: true
});
if (!uri) {
setLoading(false);
return;
}
const attachment = await db.attachments.attachment(hash);
const path = `${cacheDir}/${
"NN_" + (await getFileNameWithExtension(hash, attachment?.mimeType))
}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, path).catch(() => {
/* empty */
});
const path = `${cacheDir}/${uri}`;
setImage("file://" + path);
setLoading(false);
}, 100);
};
const close = React.useCallback(() => {
image &&
RNFetchBlob.fs.unlink(image.replace("file://", "")).catch(() => {
/* empty */
});
const close = () => {
setImage(undefined);
setVisible(false);
}, [image]);
const renderHeader = React.useCallback(
() => (
<View
style={{
paddingTop: insets.top,
backgroundColor: "rgba(0,0,0,0.3)",
position: "absolute",
zIndex: 999,
display: showHeader ? "flex" : "none"
}}
>
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 50,
paddingHorizontal: 12,
gap: 10
}}
>
<IconButton
name="share"
color="white"
style={{
borderWidth: 0
}}
onPress={async () => {
useSettingStore
.getState()
.setAppDidEnterBackgroundForAction(true);
await Share.open({
url: image
}).catch(() => {
/* empty */
});
useSettingStore
.getState()
.setAppDidEnterBackgroundForAction(false);
}}
/>
<IconButton
name="close"
color="white"
onPress={() => {
close();
}}
/>
</View>
</View>
),
[close, image, insets.top, showHeader]
);
};
return (
visible && (
@@ -167,7 +90,6 @@ const ImagePreview = () => {
background="black"
animation="slide"
visible={true}
useSafeArea={false}
onRequestClose={close}
transparent
>
@@ -208,25 +130,43 @@ const ImagePreview = () => {
/>
</View>
) : (
<>
<ImageViewer
enableImageZoom={true}
renderIndicator={() => <></>}
enableSwipeDown
useNativeDriver
onSwipeDown={close}
saveToLocalByLongPress={false}
onClick={() => {
setShowHeader(!showHeader);
}}
renderHeader={renderHeader}
imageUrls={[
{
url: image as string
}
]}
/>
</>
<ImageViewer
enableImageZoom={true}
renderIndicator={() => <></>}
enableSwipeDown
useNativeDriver
onSwipeDown={close}
saveToLocalByLongPress={false}
renderHeader={() => (
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 50,
paddingHorizontal: 24,
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
marginTop: Platform.OS === "android" ? 30 : 0
}}
>
<IconButton
name="close"
color="white"
onPress={() => {
close();
}}
/>
</View>
)}
imageUrls={[
{
url: image as string
}
]}
/>
)}
</View>
</BaseDialog>

View File

@@ -76,8 +76,7 @@ const NoteItem = ({
}: NoteItemProps) => {
const isEditingNote = useTabStore(
(state) =>
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
item.id
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
);
const { colors } = useThemeColors();
const compactMode = useIsCompactModeEnabled(

View File

@@ -28,8 +28,7 @@ export const Filler = ({ item, color }: { item: Item; color?: string }) => {
const { colors } = useThemeColors();
const isEditingNote = useTabStore(
(state) =>
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
item.id
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
);
const [selected] = useIsSelected(item);

View File

@@ -153,7 +153,9 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
},
items?.cacheItem(index) ? 100 : 0
);
} catch (e) {}
} catch (e) {
console.log("Error", e);
}
})();
}, [index, items, refreshItem]);

View File

@@ -119,6 +119,7 @@ export const PricingPlans = ({
setLoading(false);
} catch (e) {
setLoading(false);
console.log("error getting sku", e);
}
}, [promo?.promoCode]);
@@ -170,6 +171,7 @@ export const PricingPlans = ({
});
return true;
} catch (e) {
console.log("PROMOCODE ERROR:", code, e);
return false;
}
};
@@ -229,6 +231,7 @@ export const PricingPlans = ({
});
} catch (e) {
setBuying(false);
console.log(e);
}
};

View File

@@ -141,7 +141,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
close();
eSendEvent(eOnLoadNote, {
item: item,
newTab: true
presistTab: true
});
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1);

View File

@@ -100,6 +100,7 @@ export const AddNotebookSheet = ({
eSendEvent(eOnNotebookUpdated, parent);
if (notebook) {
setImmediate(() => {
console.log(parent, notebook.id);
eSendEvent(eOnNotebookUpdated, notebook.id);
});
}

View File

@@ -90,12 +90,13 @@ export const NotebookItem = ({
for (const key in keys) {
nextState[key] = !state.initialState[key] ? undefined : "deselected";
}
console.log("Single item selection");
state.setSelection({
[item.id]: "selected",
...nextState
});
} else {
console.log("Multi item selection");
state.markAs(item, "selected");
}
};

View File

@@ -16,8 +16,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { EVENTS, Note } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { Note } from "@notesnook/core";
import { EVENTS } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { View } from "react-native";
@@ -25,18 +25,27 @@ import { FlatList } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import { useDBItem } from "../../../hooks/use-db-item";
import {
TabItem,
useTabStore
} from "../../../screens/editor/tiptap/use-tab-store";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../../screens/editor/tiptap/utils";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { eUnlockNote } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
type TabItem = {
id: number;
noteId?: string;
previewTab?: boolean;
locked?: boolean;
noteLocked?: boolean;
readonly?: boolean;
pinned?: boolean;
};
const TabItemComponent = (props: {
tab: TabItem;
@@ -44,7 +53,7 @@ const TabItemComponent = (props: {
close?: (ctx?: string | undefined) => void;
}) => {
const { colors } = useThemeColors();
const [item, update] = useDBItem(props.tab.session?.noteId, "note");
const [item, update] = useDBItem(props.tab.noteId, "note");
useEffect(() => {
const syncCompletedSubscription = db.eventManager?.subscribe(
@@ -76,11 +85,11 @@ const TabItemComponent = (props: {
onPress={() => {
if (!props.isFocused) {
useTabStore.getState().focusTab(props.tab.id);
if (props.tab.session?.locked) {
if (props.tab.locked) {
eSendEvent(eUnlockNote);
}
if (!props.tab.session?.noteId) {
if (!props.tab.noteId) {
setTimeout(() => {
editorController?.current?.commands?.focus(props.tab.id);
}, 300);
@@ -88,6 +97,11 @@ const TabItemComponent = (props: {
}
props.close?.();
}}
onLongPress={() => {
useTabStore.getState().updateTab(props.tab.id, {
previewTab: false
});
}}
>
<View
style={{
@@ -98,9 +112,9 @@ const TabItemComponent = (props: {
flexShrink: 1
}}
>
{props.tab.session?.noteLocked ? (
{props.tab.noteLocked ? (
<>
{props.tab.session?.locked ? (
{props.tab.locked ? (
<Icon size={SIZE.md} name="lock" />
) : (
<Icon size={SIZE.md} name="lock-open-outline" />
@@ -108,9 +122,7 @@ const TabItemComponent = (props: {
</>
) : null}
{props.tab.session?.readonly ? (
<Icon size={SIZE.md} name="pencil-lock" />
) : null}
{props.tab.readonly ? <Icon size={SIZE.md} name="pencil-lock" /> : null}
<Paragraph
color={
@@ -118,10 +130,15 @@ const TabItemComponent = (props: {
? colors.selected.paragraph
: colors.primary.paragraph
}
style={{
fontFamily: props.tab.previewTab
? "OpenSans-Italic"
: "OpenSans-Regular"
}}
numberOfLines={1}
size={SIZE.md}
>
{props.tab.session?.noteId
{props.tab.noteId
? item?.title || strings.untitledNote()
: strings.newNote()}
</Paragraph>
@@ -140,7 +157,8 @@ const TabItemComponent = (props: {
color={props.tab.pinned ? colors.primary.accent : colors.primary.icon}
onPress={() => {
useTabStore.getState().updateTab(props.tab.id, {
pinned: !props.tab.pinned
pinned: !props.tab.pinned,
previewTab: false
});
}}
top={0}
@@ -179,7 +197,6 @@ export default function EditorTabs({
}: {
close?: (ctx?: string | undefined) => void;
}) {
const { colors } = useThemeColors();
const [tabs, currentTab] = useTabStore((state) => [
state.tabs,
state.currentTab
@@ -216,13 +233,25 @@ export default function EditorTabs({
}}
>
<Heading size={SIZE.lg}>{strings.tabs()}</Heading>
<IconButton
<Button
onPress={() => {
useTabStore.getState().newTab();
setTimeout(() => {
editorController?.current?.commands?.focus(
useTabStore.getState().currentTab
);
}, 500);
close?.();
}}
name="plus"
color={colors.primary.accent}
title={strings.newTab()}
icon="plus"
style={{
flexDirection: "row",
justifyContent: "flex-start",
borderRadius: 100,
height: 35
}}
iconSize={SIZE.lg}
/>
</View>

View File

@@ -333,17 +333,13 @@ const ExportNotesSheet = ({
if (Platform.OS === "ios") {
Share.open({
url: result?.fileDir + result.fileName
}).catch(() => {
/* empty */
});
}).catch(console.log);
} else {
FileViewer.open(result.filePath, {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
} as any).catch(() => {
/* empty */
});
} as any).catch(console.log);
}
}}
/>

View File

@@ -62,9 +62,7 @@ export const ShareComponent = ({ uri, name, padding }) => {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
}).catch(() => {
/* empty */
});
}).catch(console.log);
}}
/>
</View>

View File

@@ -22,22 +22,24 @@ import {
VirtualizedGrouping,
createInternalLink
} from "@notesnook/core";
import type { LinkAttributes } from "@notesnook/editor";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { useDBItem } from "../../../hooks/use-db-item";
import { editorController } from "../../../screens/editor/tiptap/utils";
import { presentSheet } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import type { LinkAttributes } from "@notesnook/editor";
import {
EditorEvents,
editorController
} from "../../../screens/editor/tiptap/utils";
import { strings } from "@notesnook/intl";
const ListNoteItem = ({
id,
@@ -192,7 +194,7 @@ export default function LinkNote(props: {
}
: undefined
);
editorController.current?.postMessage(NativeEvents.resolve, {
editorController.current?.postMessage(EditorEvents.resolve, {
data: {
href: link,
title: selectedNote.title

View File

@@ -113,9 +113,11 @@ const ManageTagsSheet = (props: {
.sorted()
.then((items) => {
setTags(items);
console.log("searched tags");
});
} else {
db.tags.all.sorted(db.settings.getGroupOptions("tags")).then((items) => {
console.log("items loaded tags", items.placeholders.length);
setTags(items);
});
}

View File

@@ -191,17 +191,13 @@ class RecoveryKeySheet extends React.Component {
Share.open({
url: path,
failOnCancel: false
}).catch(() => {
/* empty */
});
}).catch(console.log);
} else {
FileViewer.open(path, {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
}).catch(() => {
/* empty */
});
}).catch(console.log);
}
} catch (e) {
console.error(e);

View File

@@ -273,7 +273,7 @@ const ListNoteItem = ({
listType,
noteInternalLinks.length
]);
console.log(noteInternalLinks);
const renderBlock = React.useCallback(
(block: ContentBlock) => (
<ListBlockItem
@@ -419,6 +419,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
id={index}
items={items}
onSelect={(note, blockId) => {
console.log(note.id, blockId);
eSendEvent(eOnLoadNote, {
item: note,
blockId: blockId

View File

@@ -38,7 +38,7 @@ const Sort = ({ type, screen }) => {
);
const updateGroupOptions = async (_groupOptions) => {
const groupType = screen === "Notes" ? "home" : type + "s";
console.log("updateGroupOptions for group", groupType, "in", screen);
await db.settings.setGroupOptions(groupType, _groupOptions);
setGroupOptions(_groupOptions);
setTimeout(() => {

View File

@@ -43,7 +43,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
let notes = version?.notes
? version.notes.replace("Thank you for using Notesnook!", "").split("- ")
: ["Bug fixes and performance improvements"];
notes = notes?.map((n) => n.replace(/\n|<br>/g, ""));
notes = notes?.map((n) => n.replace(/\n/g, ""));
const isGithubRelease = Config.GITHUB_RELEASE === "true";
const getSupportedAbi = () => {
@@ -178,20 +178,18 @@ export const Update = ({ version: appVersion, fwdRef }) => {
{version.body}
</Paragraph>
) : null}
{notes.map((item) =>
item && item !== "" ? (
<Paragraph
key={item}
color={colors.secondary.paragraph}
style={{
marginBottom: 5
}}
selectable
>
{item}
</Paragraph>
) : null
)}
{notes.map((item) => (
<Paragraph
key={item}
color={colors.secondary.paragraph}
style={{
marginBottom: 5
}}
selectable
>
{item}
</Paragraph>
))}
</ScrollView>
<Seperator />
<Button
@@ -217,9 +215,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
marginTop: 10
}}
onPress={() => {
Linking.openURL(GITHUB_PAGE_URL).catch(() => {
/* empty */
});
Linking.openURL(GITHUB_PAGE_URL).catch(console.log);
}}
>
{strings.readReleaseNotes()}

View File

@@ -263,22 +263,20 @@ export const Pressable = ({
const getStyle = useCallback(
({ pressed }: PressableStateCallbackType): ViewStyle | ViewStyle[] => [
{
backgroundColor:
pressed && !disabled
? RGB_Linear_Shade(alpha, hexToRGBA(selectedColor, opacity || 1))
: hexToRGBA(primaryColor, opacity || 1),
backgroundColor: pressed
? RGB_Linear_Shade(alpha, hexToRGBA(selectedColor, opacity || 1))
: hexToRGBA(primaryColor, opacity || 1),
width: "100%",
alignSelf: "center",
borderRadius: noborder ? 0 : br,
justifyContent: "center",
alignItems: "center",
marginBottom: 0,
borderColor:
pressed && !disabled
? customSelectedColor
? getColorLinearShade(customSelectedColor, 0.3, false)
: borderSelectedColor || borderColor
: borderColor || "transparent",
borderColor: pressed
? customSelectedColor
? getColorLinearShade(customSelectedColor, 0.3, false)
: borderSelectedColor || borderColor
: borderColor || "transparent",
borderWidth: borderWidth
},
style,
@@ -300,8 +298,7 @@ export const Pressable = ({
borderColor,
borderWidth,
style,
growFactor,
disabled
growFactor
]
);

View File

@@ -59,9 +59,8 @@ const SheetWrapper = ({
const smallTablet = deviceMode === "smallTablet";
const dimensions = useSettingStore((state) => state.dimensions);
const insets = useGlobalSafeAreaInsets();
const appState = useAppState();
const lockEvents = useRef(false);
const locked = useUserStore((state) => state.appLocked);
let width = dimensions.width > 600 ? 600 : 500;
const style = React.useMemo(() => {
@@ -100,21 +99,24 @@ const SheetWrapper = ({
};
useEffect(() => {
if (locked) {
const ref = fwdRef || localRef;
ref?.current?.hide();
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
ref?.current?.show();
unsub();
lockEvents.current = false;
}
});
if (useUserStore.getState().disableAppLockRequests) return;
if (SettingsService.canLockAppInBackground()) {
if (appState === "background") {
const ref = fwdRef || localRef;
ref?.current?.hide();
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
ref?.current?.show();
unsub();
lockEvents.current = false;
}
});
}
}
}
}, [locked, fwdRef]);
}, [appState, fwdRef]);
return (
<ScopedThemeProvider value="sheet">

View File

@@ -315,9 +315,7 @@ const trialstarted: { id: string; steps: TStep[] } = {
actionButton: {
text: strings.joinDiscord(),
action: () => {
Linking.openURL("https://discord.gg/zQBK97EE22").catch(() => {
/* empty */
});
Linking.openURL("https://discord.gg/zQBK97EE22").catch(console.log);
}
}
}
@@ -368,9 +366,7 @@ const Support = () => {
width: "90%"
}}
onPress={() => {
Linking.openURL("https://discord.gg/zQBK97EE22").catch(() => {
/* empty */
});
Linking.openURL("https://discord.gg/zQBK97EE22").catch(console.log);
}}
icon="discord"
type="secondary"
@@ -384,9 +380,7 @@ const Support = () => {
width: "90%"
}}
onPress={() => {
Linking.openURL("https://t.me/notesnook").catch(() => {
/* empty */
});
Linking.openURL("https://t.me/notesnook").catch(console.log);
}}
icon="telegram"
type="secondary"

View File

@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-inner-declarations */
import {
Color,
createInternalLink,
ItemReference,
Note,
Notebook,
Reminder,
Tag,
TrashItem,
VAULT_ERRORS
VAULT_ERRORS,
createInternalLink
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { DisplayedNotification } from "@notifee/react-native";
@@ -50,11 +50,11 @@ import ReminderSheet from "../components/sheets/reminder";
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import {
ToastManager,
eSendEvent,
eSubscribeEvent,
openVault,
presentSheet,
ToastManager
presentSheet
} from "../services/event-manager";
import Navigation from "../services/navigation";
import Notifications from "../services/notifications";
@@ -544,13 +544,14 @@ export const useActions = ({
const toggleReadyOnlyMode = async () => {
const currentReadOnly = (item as Note).readonly;
await db.notes.readonly(!currentReadOnly, item?.id);
useTabStore.getState().forEachNoteTab(item.id, (tab) => {
useTabStore.getState().updateTab(tab.id, {
session: {
readonly: !currentReadOnly
}
if (useTabStore.getState().hasTabForNote(item.id)) {
const tabId = useTabStore.getState().getTabForNote(item.id);
if (!tabId) return;
useTabStore.getState().updateTab(tabId, {
readonly: !currentReadOnly
});
});
}
Navigation.queueRoutesForUpdate();
close();
};

View File

@@ -50,17 +50,12 @@ import { MMKV } from "../common/database/mmkv";
import { endProgress, startProgress } from "../components/dialogs/progress";
import Migrate from "../components/sheets/migrate";
import NewFeature from "../components/sheets/new-feature";
import ReminderSheet from "../components/sheets/reminder";
import { Walkthrough } from "../components/walkthroughs";
import {
resetTabStore,
useTabStore
} from "../screens/editor/tiptap/use-tab-store";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import {
clearAppState,
editorController,
editorState,
setAppState
editorState
} from "../screens/editor/tiptap/utils";
import { useDragState } from "../screens/settings/editor/state";
import BackupService from "../services/backup";
@@ -99,6 +94,7 @@ import {
eLoginSessionExpired,
eOnLoadNote,
eOpenAnnouncementDialog,
eUnlockNote,
eUserLoggedIn,
refreshNotesPage
} from "../utils/events";
@@ -152,6 +148,7 @@ const onUploadedAttachmentProgress = (data: any) => {
};
const onUserSessionExpired = async () => {
console.log("LOGGED OUT USER....");
SettingsService.set({
sessionExpired: true
});
@@ -160,7 +157,6 @@ const onUserSessionExpired = async () => {
const onAppOpenedFromURL = async (event: { url: string }) => {
const url = event.url;
try {
if (url.startsWith("https://app.notesnook.com/account/verified")) {
await onUserEmailVerified();
@@ -170,25 +166,6 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
eSendEvent(eOnLoadNote, { newNote: true });
tabBarRef.current?.goToPage(1, false);
return;
} else if (url.startsWith("https://notesnook.com/open_note")) {
const id = new URL(url).searchParams.get("id");
if (id) {
const note = await db.notes.note(id);
if (note) {
eSendEvent(eOnLoadNote, {
item: note
});
tabBarRef.current?.goToPage(1, false);
}
}
} else if (url.startsWith("https://notesnook.com/open_reminder")) {
const id = new URL(url).searchParams.get("id");
if (id) {
const reminder = await db.reminders.reminder(id);
if (reminder) ReminderSheet.present(reminder);
}
} else if (url.startsWith("https://notesnook.com/new_reminder")) {
ReminderSheet.present();
}
} catch (e) {
console.error(e);
@@ -245,7 +222,6 @@ const onLogout = async (reason: string) => {
await PremiumService.setPremiumStatus();
await BiometricService.resetCredentials();
MMKV.clearStore();
resetTabStore();
clearAllStores();
setImmediate(() => {
refreshAllStores();
@@ -278,7 +254,9 @@ async function checkForShareExtensionLaunchedInBackground() {
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
MMKV.removeItem("shareExtensionOpened");
}
} catch (e) {}
} catch (e) {
console.log(e);
}
}
async function saveEditorState() {
@@ -524,9 +502,7 @@ export const useAppEvents = () => {
}
if (SettingsService.getProperty("offlineMode")) {
db.attachments.cacheAttachments().catch(() => {
/* empty */
});
db.attachments.cacheAttachments().catch(console.log);
}
}
}, []);
@@ -574,6 +550,7 @@ export const useAppEvents = () => {
});
}
} catch (e) {
console.log(e);
ToastManager.error(e as Error, "Error updating user", "global");
}
@@ -644,17 +621,22 @@ export const useAppEvents = () => {
EV.subscribe(EVENTS.vaultLocked, async () => {
// Lock all notes in all tabs...
for (const tab of useTabStore.getState().tabs) {
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
if (!noteId) continue;
const note = await db.notes.note(noteId);
const locked = note && (await db.vaults.itemExists(note));
if (locked) {
useTabStore.getState().updateTab(tab.id, {
session: {
locked: true,
noteLocked: true
}
locked: true
});
if (
tab.id === useTabStore.getState().currentTab &&
locked &&
!editorState().movedAway
) {
// Show unlock note screen.
eSendEvent(eUnlockNote);
}
}
}
}),
@@ -663,7 +645,6 @@ export const useAppEvents = () => {
const emitterSubscriptions = [
Linking.addEventListener("url", onAppOpenedFromURL),
SodiumEventEmitter.addListener(
"onSodiumProgress",
onFileEncryptionProgress
@@ -749,15 +730,13 @@ export const useAppEvents = () => {
setTimeout(() => {
sub = AppState.addEventListener("change", onAppStateChanged);
if (
refValues.current.initialUrl &&
!refValues.current.initialUrl?.includes("open_note")
refValues.current.initialUrl?.startsWith(
"https://app.notesnook.com/account/verified"
)
) {
onAppOpenedFromURL({
url: refValues.current.initialUrl!
});
onUserEmailVerified();
}
}, 1000);
refValues.current.removeInternetStateListener = NetInfo.addEventListener(
onInternetStateChanged
);
@@ -800,7 +779,9 @@ export const useAppEvents = () => {
useEffect(() => {
if (!appLocked && isAppLoading) {
initializeLogger()
.catch((e) => {})
.catch((e) => {
console.log(e);
})
.finally(() => {
//@ts-ignore
initializeDatabase();

View File

@@ -196,6 +196,7 @@ export const useTotalNotes = (type: "notebook" | "tag" | "color") => {
}
setTotalNotesById(totalNotesById);
});
console.log("useTotalNotes.getTotalNotes");
},
[type]
);

View File

@@ -31,7 +31,7 @@ export function useGroupOptions(type: any) {
const onUpdate = (groupType: string) => {
if (groupType !== type) return;
const options = db.settings?.getGroupOptions(type) as any;
console.log("useGroupOptions.onUpdate", type, options);
if (
groupOptions?.groupBy !== options.groupBy ||
groupOptions?.sortBy !== options.sortBy ||

View File

@@ -74,8 +74,10 @@ export const useNotebook = (
};
const onUpdate = (type: string) => {
console.log("event", type);
if (type !== "notebooks") return;
onRequestUpdate();
console.log("useNotebook.onUpdate", item?.id, Date.now());
};
eSubscribeEvent(eGroupOptionsUpdated, onUpdate);

View File

@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
@@ -45,6 +44,7 @@ import useNavigationStore from "../stores/use-navigation-store";
import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store";
import { rootNavigatorRef } from "../utils/global-refs";
import { strings } from "@notesnook/intl";
const NativeStack = createNativeStackNavigator();
const IntroStack = createNativeStackNavigator();
@@ -76,12 +76,11 @@ const _Tabs = () => {
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const height = useSettingStore((state) => state.dimensions.height);
const insets = useGlobalSafeAreaInsets();
const screenHeight = height - (50 + insets.top + insets.bottom);
React.useEffect(() => {
setTimeout(async () => {
setTimeout(() => {
useNavigationStore.getState().update(homepage);
}, 1000);
}, [homepage]);

View File

@@ -509,8 +509,7 @@ const onChangeTab = async (event) => {
});
} else {
if (
useTabStore.getState().getTab(useTabStore.getState().currentTab).session
?.locked
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
) {
eSendEvent(eUnlockNote);
}
@@ -524,15 +523,13 @@ const onChangeTab = async (event) => {
// Lock all tabs with locked notes...
for (const tab of useTabStore.getState().tabs) {
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
if (!noteId) continue;
const note = await db.notes.note(noteId);
const locked = note && (await db.vaults.itemExists(note));
if (locked) {
useTabStore.getState().updateTab(tab.id, {
session: {
locked: true
}
locked: true
});
}
}

View File

@@ -9,13 +9,13 @@
"@streetwriters/showdown": "^3.0.1-alpha.2",
"absolutify": "^0.1.0",
"buffer": "^6.0.3",
"dayjs": "^1.10.4",
"deprecated-react-native-prop-types": "^4.1.0",
"entities": "^3.0.1",
"fflate": "^0.7.3",
"html-to-text": "9.0.5",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"dayjs": "^1.11.13",
"react-native-actions-sheet": "0.9.7",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",
@@ -36,15 +36,10 @@
"@streetwriters/kysely": "^0.27.4",
"pathe": "1.1.2",
"react-native-format-currency": "0.0.5",
"@lingui/core": "5.1.2",
"@lingui/react": "5.1.2",
"@lingui/react": "4.11.2",
"@lingui/core": "4.11.2",
"react-native-check-version": "^1.3.0",
"react-native-material-menu": "^2.0.0",
"@trpc/client": "^10.45.2",
"@trpc/react-query": "^10.45.2",
"@trpc/server": "^10.45.2",
"@tanstack/react-query": "^4.36.1",
"async-mutex": "0.5.0"
"react-native-material-menu": "^2.0.0"
},
"sideEffects": false
}

View File

@@ -19,8 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable @typescript-eslint/no-var-requires */
import { i18n } from "@lingui/core";
import { strings } from "@notesnook/intl";
import React, {
forwardRef,
useCallback,
@@ -48,7 +46,6 @@ import {
eUnlockWithPassword
} from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import EditorOverlay from "./loading";
import { EDITOR_URI } from "./source";
import { EditorProps, useEditorType } from "./tiptap/types";
@@ -61,6 +58,9 @@ import {
openInternalLink,
randId
} from "./tiptap/utils";
import { tabBarRef } from "../../utils/global-refs";
import { strings } from "@notesnook/intl";
import { i18n } from "@lingui/core";
const style: ViewStyle = {
height: "100%",
@@ -203,13 +203,11 @@ const useLockedNoteHandler = () => {
useEffect(() => {
for (const tab of useTabStore.getState().tabs) {
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
if (!noteId) continue;
if (tabRef.current && tabRef.current.session?.noteLocked) {
if (tabRef.current && tabRef.current.noteLocked) {
useTabStore.getState().updateTab(tabRef.current.id, {
session: {
locked: true
}
locked: true
});
}
}
@@ -223,38 +221,33 @@ const useLockedNoteHandler = () => {
biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint
});
syncTabs("biometry");
syncTabs();
})();
}, [tab?.id]);
useEffect(() => {
const unlockWithBiometrics = async () => {
try {
if (!tabRef.current?.session?.noteLocked || !tabRef.current) return;
if (!tabRef.current?.noteLocked || !tabRef.current) return;
console.log("Trying to unlock with biometrics...");
const credentials = await BiometricService.getCredentials(
"Unlock note",
"Unlock note to open it in editor."
);
if (
credentials &&
credentials?.password &&
tabRef.current.session?.noteId
) {
if (credentials && credentials?.password && tabRef.current.noteId) {
const note = await db.vault.open(
tabRef.current.session?.noteId,
tabRef.current.noteId,
credentials?.password
);
eSendEvent(eOnLoadNote, {
item: note,
refresh: true
item: note
});
useTabStore.getState().updateTab(tabRef.current.id, {
locked: false
});
} else {
if (tabRef.current && tabRef.current.session?.locked) {
editorController.current?.commands.focusPassInput();
}
}
} catch (e) {
console.error(e);
@@ -268,7 +261,7 @@ const useLockedNoteHandler = () => {
password: string;
biometrics?: boolean;
}) => {
if (!tabRef.current?.session?.noteId || !tabRef.current) return;
if (!tabRef.current?.noteId || !tabRef.current) return;
if (!password || password.trim().length === 0) {
ToastManager.show({
heading: strings.passwordNotEntered(),
@@ -278,10 +271,7 @@ const useLockedNoteHandler = () => {
}
try {
const note = await db.vault.open(
tabRef.current?.session?.noteId,
password
);
const note = await db.vault.open(tabRef.current?.noteId, password);
if (enrollBiometrics && note) {
try {
const unlocked = await db.vault.unlock(password);
@@ -309,10 +299,13 @@ const useLockedNoteHandler = () => {
}
}
eSendEvent(eOnLoadNote, {
item: note,
refresh: true
item: note
});
useTabStore.getState().updateTab(tabRef.current.id, {
locked: false
});
} catch (e) {
console.log(e);
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error"
@@ -322,7 +315,7 @@ const useLockedNoteHandler = () => {
const unlock = () => {
if (
(tabRef.current?.session?.locked,
(tabRef.current?.locked,
useTabStore.getState().biometryAvailable &&
useTabStore.getState().biometryEnrolled &&
!editorState().movedAway)
@@ -331,9 +324,10 @@ const useLockedNoteHandler = () => {
unlockWithBiometrics();
}, 150);
} else {
console.log("Biometrics unavailable.", editorState().movedAway);
if (!editorState().movedAway) {
setTimeout(() => {
if (tabRef.current && tabRef.current?.session?.locked) {
if (tabRef.current && tabRef.current?.locked) {
editorController.current?.commands.focus(tabRef.current?.id);
}
}, 100);
@@ -348,13 +342,13 @@ const useLockedNoteHandler = () => {
}),
eSubscribeEvent(eUnlockWithPassword, onSubmit)
];
if (tabRef.current?.session?.locked && tabBarRef.current?.page() === 2) {
if (tabRef.current?.locked && tabBarRef.current?.page() === 2) {
unlock();
}
return () => {
subs.map((s) => s?.unsubscribe());
};
}, [tab?.id, tab?.session?.locked]);
}, [tab?.id, tab?.locked]);
return null;
};

View File

@@ -27,10 +27,10 @@ import WebView from "react-native-webview";
import { useRef } from "react";
import { EDITOR_URI } from "./source";
import { EditorMessage } from "./tiptap/types";
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
import { EventTypes } from "./tiptap/editor-events";
import { Attachment } from "@notesnook/editor";
import downloadAttachment from "../../common/filesystem/download-attachment";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { EditorEvents } from "./tiptap/utils";
import { useThemeColors } from "@notesnook/theme";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { db } from "../../common/database";
@@ -69,11 +69,12 @@ export function ReadonlyEditor(props: {
const data = event.nativeEvent.data;
const editorMessage = JSON.parse(data) as EditorMessage<any>;
if (editorMessage.type === EditorEvents.logger) {
if (editorMessage.type === EventTypes.logger) {
logger.info("[READONLY EDITOR LOG]", editorMessage.value);
}
if (editorMessage.type === EditorEvents.readonlyEditorLoaded) {
if (editorMessage.type === EventTypes.readonlyEditorLoaded) {
console.log("Readonly editor loaded.");
props.onLoad?.((content: { data: string; id: string }) => {
setTimeout(() => {
noteId.current = content.id;
@@ -86,9 +87,10 @@ export function ReadonlyEditor(props: {
setLoading(false);
}, 300);
});
} else if (editorMessage.type === EditorEvents.getAttachmentData) {
} else if (editorMessage.type === EventTypes.getAttachmentData) {
const attachment = (editorMessage.value as any).attachment as Attachment;
console.log("Getting attachment data:", attachment.hash, attachment.type);
downloadAttachment(attachment.hash, true, {
base64: attachment.type === "image",
text: attachment.type === "web-clip",
@@ -104,7 +106,7 @@ export function ReadonlyEditor(props: {
);
editorRef.current?.postMessage(
JSON.stringify({
type: NativeEvents.attachmentData,
type: EditorEvents.attachmentData,
value: {
resolverId: (editorMessage.value as any).resolverId,
data
@@ -113,9 +115,10 @@ export function ReadonlyEditor(props: {
);
})
.catch(() => {
console.log("Error downloading attachment data");
editorRef.current?.postMessage(
JSON.stringify({
type: NativeEvents.attachmentData,
type: EditorEvents.attachmentData,
data: {
resolverId: (editorMessage.value as any).resolverId,
data: undefined

View File

@@ -18,11 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note } from "@notesnook/core";
import type {
Attachment,
ImageAttributes,
LinkAttributes
} from "@notesnook/editor";
import type { Attachment } from "@notesnook/editor";
import type { ImageAttributes } from "@notesnook/editor";
import type { LinkAttributes } from "@notesnook/editor";
import { createRef, RefObject } from "react";
import { Platform } from "react-native";
import { EdgeInsets } from "react-native-safe-area-context";
@@ -39,6 +37,9 @@ async function call(webview: RefObject<WebView | undefined>, action?: Action) {
if (!webview.current || !action) return;
setImmediate(() => webview.current?.injectJavaScript(action.job));
const response = await getResponse(action.id);
// if (!response) {
// console.warn("webview job failed", action.id);
// }
return response ? response.value : response;
}
@@ -74,71 +75,110 @@ class Commands {
return call(this.ref, fn(job, name)) as Promise<T>;
}
async sendCommand<T>(command: string, ...args: any[]) {
return this.doAsync(
`response = globalThis.commands.${command}(${args
.map((arg) =>
typeof arg === "string" ? `"${arg}"` : JSON.stringify(arg)
)
.join(",")})`,
command
);
}
focus = async (tabId: string) => {
focus = async (tabId: number) => {
if (!this.ref.current) return;
const locked = useTabStore.getState().getTab(tabId)?.session?.locked;
if (Platform.OS === "android") {
//this.ref.current?.requestFocus();
setTimeout(async () => {
if (
locked &&
useTabStore.getState().biometryAvailable &&
useTabStore.getState().biometryEnrolled
)
return;
if (!this.ref) return;
textInput.current?.focus();
await this.sendCommand("focus", tabId, locked);
const locked = useTabStore.getState().getTab(tabId)?.locked;
await this.doAsync(
locked
? `editorControllers[${tabId}]?.focusPassInput();`
: `editors[${tabId}]?.commands.focus()`,
"focus"
);
this.ref?.current?.requestFocus();
}, 1);
} else {
await sleep(400);
await this.sendCommand("focus", tabId, locked);
await this.doAsync(`editors[${tabId}]?.commands.focus()`, "focus");
}
};
blur = async (tabId: string) => this.sendCommand("blur", tabId);
blur = async (tabId: number) =>
await this.doAsync(
`
const editor = editors[${tabId}];
const editorTitle = editorTitles[${tabId}];
typeof editor !== "undefined" && editor.commands.blur();
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current.blur();
editorControllers[${tabId}]?.blurPassInput();
clearContent = async (tabId: string) => {
`,
"blur"
);
clearContent = async (tabId: number) => {
this.previousSettings = null;
await this.sendCommand("clearContent", tabId);
await this.doAsync(
`
const editor = editors[${tabId}];
const editorController = editorControllers[${tabId}];
const editorTitle = editorTitles[${tabId}];
const statusBar = statusBars[${tabId}];
if (typeof editor !== "undefined") {
editor.commands.blur();
editor.commands.clearContent(false);
}
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current?.blur();
if (typeof editorController.content !== undefined) editorController.content.current = '';
editorController.onUpdate();
editorController.setTitle('');
if (typeof statusBar !== "undefined") {
statusBar.current.resetWords();
statusBar.current.set({date:"",saved:""});
}`,
"clearContent"
);
};
setSessionId = async (id: string | null) =>
await this.sendCommand("setSessionId", id);
await this.doAsync(`globalThis.sessionId = "${id}";`);
setStatus = async (
date: string | undefined,
saved: string,
tabId: string
tabId: number
) => {
this.sendCommand("setStatus", date, saved, tabId);
};
setPlaceholder = async (placeholder: string) => {};
setLoading = async (loading?: boolean, tabId?: string) => {
this.sendCommand(
"setLoading",
loading,
tabId === undefined ? useTabStore.getState().currentTab : tabId
await this.doAsync(
`
const statusBar = statusBars[${tabId}];
typeof statusBar !== "undefined" && statusBar.current.set({date:"${date}",saved:"${saved}"})`,
"setStatus"
);
};
setPlaceholder = async (placeholder: string) => {
// await this.doAsync(`
// const element = document.querySelector(".is-editor-empty");
// if (element) {
// element.setAttribute("data-placeholder","${placeholder}");
// }
// `);
};
setLoading = async (loading?: boolean, tabId?: number) => {
await this.doAsync(`
const editorController = editorControllers[${
tabId || useTabStore.getState().currentTab
}];
editorController.setLoading(${loading})
`);
};
setInsets = async (insets: EdgeInsets) => {
this.sendCommand("setInsets", insets);
await this.doAsync(`
if (typeof safeAreaController !== "undefined") {
safeAreaController.update(${JSON.stringify(insets)})
}
`);
};
updateSettings = async (settings?: Partial<Settings>) => {
@@ -147,7 +187,13 @@ class Commands {
...this.previousSettings,
...settings
};
this.sendCommand("updateSettings", settings);
await this.doAsync(`
if (typeof globalThis.settingsController !== "undefined") {
globalThis.settingsController.update(${JSON.stringify(
this.previousSettings
)})
}
`);
};
setSettings = async (settings?: Partial<Settings>) => {
@@ -160,63 +206,123 @@ class Commands {
return;
}
}
this.sendCommand("setSettings", settings);
await this.doAsync(`
if (typeof globalThis.settingsController !== "undefined") {
globalThis.settingsController.update(${JSON.stringify(settings)})
}
`);
};
setTags = async (note: Note | null | undefined) => {
if (!note) return;
useTabStore.getState().forEachNoteTab(note.id, async (tab) => {
const tabId = tab.id;
const tags = await db.relations.to(note, "tag").resolve();
await this.sendCommand("setTags", tabId, tags);
});
const tabId = useTabStore.getState().getTabForNote(note.id);
const tags = await db.relations.to(note, "tag").resolve();
await this.doAsync(
`
const tags = editorTags[${tabId}];
if (tags && tags.current) {
tags.current.setTags(${JSON.stringify(
tags.map((tag) => ({
title: tag.title,
alias: tag.title,
id: tag.id,
type: tag.type
}))
)});
}
`,
"setTags"
);
};
clearTags = async (tabId: string) => {
await this.sendCommand("clearTags", tabId);
clearTags = async (tabId: number) => {
await this.doAsync(
`
const tags = editorTags[${tabId}];
logger("info", Object.keys(editorTags), typeof editorTags[0]);
if (tags && tags.current) {
tags.current.setTags([]);
}
`,
"clearTags"
);
};
insertAttachment = async (attachment: Attachment, tabId: string) => {
await this.sendCommand("insertAttachment", attachment, tabId);
insertAttachment = async (attachment: Attachment, tabId: number) => {
await this.doAsync(
`const editor = editors[${tabId}];
editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})`
);
};
setAttachmentProgress = async (
attachmentProgress: Partial<Attachment>,
tabId: string
tabId: number
) => {
await this.sendCommand("setAttachmentProgress", attachmentProgress, tabId);
await this.doAsync(
`const editor = editors[${tabId}];
editor && editor.commands.updateAttachment(${JSON.stringify(
attachmentProgress
)}, {
preventUpdate: true,
query: (attachment) => {
return attachment.hash === "${attachmentProgress.hash}";
}
})`
);
};
insertImage = async (
image: Omit<ImageAttributes, "bloburl"> & {
dataurl: string;
},
tabId: string
tabId: number
) => {
await this.sendCommand("insertImage", image, tabId);
await this.doAsync(
`const editor = editors[${tabId}];
const image = toBlobURL("${image.dataurl}", "${image.hash}");
editor && editor.commands.insertImage({
...${JSON.stringify({
...image,
dataurl: undefined
})},
bloburl: image
})`
);
};
handleBack = async () => {
return this.sendCommand("handleBack");
return this.doAsync<boolean>(
'response = window.dispatchEvent(new Event("handleBackPress",{cancelable:true}));'
);
};
keyboardShown = async (keyboardShown: boolean) => {
return this.sendCommand("keyboardShown", keyboardShown);
return this.doAsync(`globalThis['keyboardShown']=${keyboardShown};`);
};
getTableOfContents = async () => {
const tabId = useTabStore.getState().currentTab;
return this.sendCommand("getTableOfContents", tabId);
return this.doAsync(`
response = editorControllers[${tabId}]?.getTableOfContents() || [];
`);
};
focusPassInput = async () => {
const tabId = useTabStore.getState().currentTab;
return this.sendCommand("focusPassInput", tabId);
return this.doAsync(`
response = editorControllers[${tabId}]?.focusPassInput() || [];
`);
};
blurPassInput = async () => {
const tabId = useTabStore.getState().currentTab;
return this.sendCommand("blurPassInput", tabId);
return this.doAsync(`
response = editorControllers[${tabId}]?.blurPassInput() || [];
`);
};
createInternalLink = async (
@@ -224,18 +330,30 @@ class Commands {
resolverId: string
) => {
if (!resolverId) return;
return this.sendCommand("createInternalLink", attributes, resolverId);
return this.doAsync(`
if (globalThis.pendingResolvers["${resolverId}"]) {
globalThis.pendingResolvers["${resolverId}"](${JSON.stringify(
attributes
)});
}`);
};
dismissCreateInternalLinkRequest = async (resolverId: string) => {
if (!resolverId) return;
return this.sendCommand("dismissCreateInternalLinkRequest", resolverId);
return this.doAsync(`
if (globalThis.pendingResolvers["${resolverId}"]) {
globalThis.pendingResolvers["${resolverId}"](undefined);
}
`);
};
scrollIntoViewById = async (id: string) => {
const tabId = useTabStore.getState().currentTab;
return this.sendCommand("scrollIntoViewById", id, tabId);
return this.doAsync(`
response = editorControllers[${tabId}]?.scrollIntoView("${id}") || [];
`);
};
//todo add replace image function
}
export default Commands;

View File

@@ -16,8 +16,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const EditorEvents = {
export const EventTypes = {
selection: "editor-event:selection",
content: "editor-event:content",
title: "editor-event:title",
@@ -50,9 +49,5 @@ export const EditorEvents = {
disableReadonlyMode: "editor-events:disable-readonly-mode",
readonlyEditorLoaded: "readonlyEditorLoaded",
error: "editorError",
dbLogger: "editor-events:dbLogger",
goBack: "editor-events:go-back",
goForward: "editor-events:go-forward",
saveScroll: "editor-events:save-scroll",
newNote: "editor-events:new-note"
} as const;
dbLogger: "editor-events:dbLogger"
};

View File

@@ -43,7 +43,6 @@ import { eCloseSheet } from "../../../utils/events";
import { useTabStore } from "./use-tab-store";
import { editorController, editorState } from "./utils";
import { strings } from "@notesnook/intl";
import { useUserStore } from "../../../stores/use-user-store";
const showEncryptionSheet = (file: DocumentPickerResponse) => {
presentSheet({
@@ -61,7 +60,7 @@ const santizeUri = (uri: string) => {
type PickerOptions = {
noteId?: string;
tabId?: string;
tabId?: number;
type: "image" | "camera" | "file";
reupload: boolean;
hash?: string;
@@ -132,6 +131,7 @@ const file = async (fileOptions: PickerOptions) => {
if (
fileOptions.tabId !== undefined &&
fileOptions.noteId &&
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
fileOptions.noteId
) {
@@ -194,7 +194,9 @@ const camera = async (options: PickerOptions) => {
options
);
})
.catch((e) => {});
.catch((e) => {
console.log("camera error: ", e);
});
} catch (e) {
ToastManager.show({
heading: (e as Error).message,
@@ -222,7 +224,9 @@ const gallery = async (options: PickerOptions) => {
options
)
)
.catch((e) => {});
.catch((e) => {
console.log("gallery error: ", e);
});
} catch (e) {
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
ToastManager.show({
@@ -230,19 +234,13 @@ const gallery = async (options: PickerOptions) => {
type: "error",
context: "global"
});
console.log("attachment error:", e);
}
};
const pick = async (options: PickerOptions) => {
if (!PremiumService.get()) {
const user = await db.user.getUser();
if (!user) {
ToastManager.show({
heading: strings.loginRequired(),
type: "error"
});
return;
}
if (editorState().isFocused) {
editorState().isFocused = true;
}
@@ -253,7 +251,6 @@ const pick = async (options: PickerOptions) => {
}
return;
}
useUserStore.getState().setDisableAppLockRequests(true);
if (options?.type.startsWith("image") || options?.type === "camera") {
if (options.type.startsWith("image")) {
gallery(options);
@@ -270,7 +267,6 @@ const handleImageResponse = async (
options: PickerOptions
) => {
const result = await AttachImage.present(response, options.context);
if (!result) return;
const compress = result.compress;
@@ -314,14 +310,18 @@ const handleImageResponse = async (
? fileName.replace(/HEIC|HEIF/, "jpeg")
: fileName;
console.log("attaching image...", fileName);
console.log("attaching file...");
if (!(await attachFile(uri, hash, image.mime, fileName, options))) return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
console.log("attaching image to note...");
if (
options.tabId !== undefined &&
useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId
) {
console.log("attaching image to note...");
editorController.current?.commands.insertImage(
{
hash: hash,
@@ -396,7 +396,7 @@ export async function attachFile(
} else {
encryptionInfo = { hash: hash };
}
console.log("FILE ENCRYPTED....", encryptionInfo);
await db.attachments.add(encryptionInfo);
return true;
} catch (e) {

View File

@@ -74,7 +74,7 @@ export type EditorMessage<T> = {
value: T;
type: string;
noteId: string;
tabId: string;
tabId: number;
resolverId?: string;
hasTimeout?: boolean;
};
@@ -86,7 +86,7 @@ export type SavePayload = {
type?: "tiptap";
sessionHistoryId?: number;
ignoreEdit: boolean;
tabId: string;
tabId: number;
pendingChanges?: boolean;
};
@@ -94,5 +94,4 @@ export type AppState = {
editing: boolean;
movedAway: boolean;
timestamp: number;
noteId?: string;
};

View File

@@ -21,10 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable @typescript-eslint/no-var-requires */
import { ItemReference } from "@notesnook/core";
import type { Attachment } from "@notesnook/editor";
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { getDefaultPresets } from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
import { strings } from "@notesnook/intl";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useCallback, useEffect, useRef } from "react";
import {
@@ -75,9 +72,11 @@ import {
import { openLinkInBrowser } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs";
import { useDragState } from "../../settings/editor/state";
import { EventTypes } from "./editor-events";
import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { editorState, openInternalLink } from "./utils";
import { EditorEvents, editorState, openInternalLink } from "./utils";
import { strings } from "@notesnook/intl";
const publishNote = async () => {
const user = useUserStore.getState().user;
@@ -102,7 +101,7 @@ const publishNote = async () => {
}
const noteId = useTabStore
.getState()
.getNoteIdForTab(useTabStore.getState().currentTab!);
.getNoteIdForTab(useTabStore.getState().currentTab);
if (noteId) {
const note = await db.notes?.note(noteId);
@@ -125,7 +124,7 @@ const publishNote = async () => {
const showActionsheet = async () => {
const noteId = useTabStore
.getState()
.getNoteIdForTab(useTabStore.getState().currentTab!);
.getNoteIdForTab(useTabStore.getState().currentTab);
if (noteId) {
const note = await db.notes?.note(noteId);
if (editorState().isFocused || editorState().isFocused) {
@@ -178,7 +177,7 @@ export const useEditorEvents = (
useEffect(() => {
const handleKeyboardDidShow: KeyboardEventListener = () => {
editor.commands.keyboardShown(true);
editor.postMessage(NativeEvents.keyboardShown, undefined);
editor.postMessage(EditorEvents.keyboardShown, undefined);
};
const handleKeyboardDidHide: KeyboardEventListener = () => {
editor.commands.keyboardShown(false);
@@ -248,7 +247,7 @@ export const useEditorEvents = (
}
editorState().currentlyEditing = false;
// editor.reset(); Notes remain open.
editor.commands?.blur(useTabStore.getState().currentTab!);
editor.commands?.blur(useTabStore.getState().currentTab);
setTimeout(async () => {
if (deviceMode !== "mobile" && fullscreen) {
if (fullscreen) {
@@ -269,6 +268,7 @@ export const useEditorEvents = (
}, [editor, deviceMode, fullscreen]);
const onHardwareBackPress = useCallback(() => {
console.log(tabBarRef.current?.page());
if (tabBarRef.current?.page() === 2) {
onBackPress();
return true;
@@ -353,25 +353,25 @@ export const useEditorEvents = (
const editorMessage = JSON.parse(data) as EditorMessage<any>;
if (editorMessage.hasTimeout && editorMessage.resolverId) {
editor.postMessage(NativeEvents.resolve, {
editor.postMessage(EditorEvents.resolve, {
data: true,
resolverId: editorMessage.resolverId
});
}
if (editorMessage.type === EditorEvents.load) {
if (editorMessage.type === EventTypes.load) {
DatabaseLogger.log("Editor is ready");
editor.onLoad();
return;
}
if (editorMessage.type === EditorEvents.back) {
if (editorMessage.type === EventTypes.back) {
return onBackPress();
}
if (
editorMessage.sessionId !== editor.sessionId.current &&
editorMessage.type !== NativeEvents.status
editorMessage.type !== EditorEvents.status
) {
return;
}
@@ -381,8 +381,8 @@ export const useEditorEvents = (
.getNoteIdForTab(editorMessage.tabId);
switch (editorMessage.type) {
case EditorEvents.content:
DatabaseLogger.log("EditorEvents.content");
case EventTypes.content:
DatabaseLogger.log("EventTypes.content");
editor.saveContent({
type: editorMessage.type,
content: editorMessage.value.html as string,
@@ -392,8 +392,8 @@ export const useEditorEvents = (
pendingChanges: editorMessage.value?.pendingChanges
});
break;
case EditorEvents.title:
DatabaseLogger.log("EditorEvents.title");
case EventTypes.title:
DatabaseLogger.log("EventTypes.title");
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value?.title as string,
@@ -403,10 +403,10 @@ export const useEditorEvents = (
pendingChanges: editorMessage.value?.pendingChanges
});
break;
case EditorEvents.logger:
case EventTypes.logger:
logger.info("[EDITOR LOG]", editorMessage.value);
break;
case EditorEvents.dbLogger:
case EventTypes.dbLogger:
if (editorMessage.value.error) {
DatabaseLogger.error(
editorMessage.value.error,
@@ -419,12 +419,12 @@ export const useEditorEvents = (
DatabaseLogger.info("[EDITOR_LOG]" + editorMessage.value.message);
}
break;
case EditorEvents.contentchange:
case EventTypes.contentchange:
editor.onContentChanged(editorMessage.noteId);
break;
case EditorEvents.selection:
case EventTypes.selection:
break;
case EditorEvents.reminders:
case EventTypes.reminders:
if (!noteId) {
ToastManager.show({
heading: strings.createNoteFirst(),
@@ -442,7 +442,7 @@ export const useEditorEvents = (
onAdd: () => ReminderSheet.present(undefined, note, true)
});
break;
case EditorEvents.newtag:
case EventTypes.newtag:
if (!noteId) {
ToastManager.show({
heading: strings.createNoteFirst(),
@@ -452,7 +452,7 @@ export const useEditorEvents = (
}
ManageTagsSheet.present([noteId]);
break;
case EditorEvents.tag:
case EventTypes.tag:
if (editorMessage.value) {
if (!noteId) return;
const note = await db.notes.note(noteId);
@@ -468,7 +468,7 @@ export const useEditorEvents = (
});
}
break;
case EditorEvents.filepicker:
case EventTypes.filepicker:
editorState().isAwaitingResult = true;
const { pick } = require("./picker").default;
pick({
@@ -480,14 +480,14 @@ export const useEditorEvents = (
editorState().isAwaitingResult = false;
}, 1000);
break;
case EditorEvents.download: {
case EventTypes.download: {
const downloadAttachment =
require("../../../common/filesystem/download-attachment").default;
downloadAttachment((editorMessage.value as Attachment)?.hash, true);
break;
}
case EditorEvents.getAttachmentData: {
case EventTypes.getAttachmentData: {
const attachment = (editorMessage.value as any)
?.attachment as Attachment;
@@ -507,14 +507,14 @@ export const useEditorEvents = (
!!data,
editorMessage.resolverId
);
editor.postMessage(NativeEvents.resolve, {
editor.postMessage(EditorEvents.resolve, {
resolverId: editorMessage.resolverId,
data
});
})
.catch((e) => {
DatabaseLogger.error(e);
editor.postMessage(NativeEvents.resolve, {
editor.postMessage(EditorEvents.resolve, {
resolverId: editorMessage.resolverId,
data: undefined
});
@@ -523,26 +523,26 @@ export const useEditorEvents = (
break;
}
case EditorEvents.pro:
case EventTypes.pro:
if (editor.state.current?.isFocused) {
editor.state.current.isFocused = true;
}
eSendEvent(eOpenPremiumDialog);
break;
case EditorEvents.monograph:
case EventTypes.monograph:
publishNote();
break;
case EditorEvents.properties:
case EventTypes.properties:
showActionsheet();
break;
case EditorEvents.scroll:
case EventTypes.scroll:
editorState().scrollPosition = editorMessage.value;
break;
case EditorEvents.fullscreen:
case EventTypes.fullscreen:
editorState().isFullscreen = true;
eSendEvent(eOpenFullscreenEditor);
break;
case EditorEvents.link:
case EventTypes.link:
if (editorMessage.value.startsWith("nn://")) {
openInternalLink(editorMessage.value);
console.log(
@@ -554,7 +554,7 @@ export const useEditorEvents = (
}
break;
case EditorEvents.previewAttachment: {
case EventTypes.previewAttachment: {
const hash = (editorMessage.value as Attachment)?.hash;
const attachment = await db.attachments?.attachment(hash);
if (!attachment) return;
@@ -565,41 +565,26 @@ export const useEditorEvents = (
}
break;
}
case EditorEvents.copyToClipboard: {
case EventTypes.copyToClipboard: {
Clipboard.setString(editorMessage.value as string);
break;
}
case EditorEvents.saveScroll: {
useTabStore.getState().updateTab(editorMessage.tabId, {
session: {
...editorMessage.value
}
});
break;
}
case EditorEvents.newNote: {
eSendEvent(eOnLoadNote, {
tabId: editorMessage.tabId,
newNote: true
});
break;
}
case EditorEvents.tabsChanged: {
case EventTypes.tabsChanged: {
// useTabStore.setState({
// tabs: (editorMessage.value as any)?.tabs,
// currentTab: (editorMessage.value as any)?.currentTab
// });
//
// console.log("Tabs updated");
break;
}
case EditorEvents.toc:
case EventTypes.toc:
TableOfContents.present(editorMessage.value);
break;
case EditorEvents.showTabs: {
case EventTypes.showTabs: {
EditorTabs.present();
break;
}
case EditorEvents.error: {
case EventTypes.error: {
presentSheet({
component: (
<Issue
@@ -611,15 +596,27 @@ export const useEditorEvents = (
});
break;
}
case EditorEvents.tabFocused: {
case EventTypes.tabFocused: {
console.log(
"Focused tab",
editorMessage.tabId,
editorMessage.noteId,
"Content:",
editorMessage.value
);
eSendEvent(eEditorTabFocused, editorMessage.tabId);
if (!editor.state.current.initialLoadCalled) break;
if (editorMessage.noteId) {
if (
(!editorMessage.value || editor.currentLoadingNoteId.current) &&
editorMessage.noteId
) {
if (!useSettingStore.getState().isAppLoading) {
const note = await db.notes.note(editorMessage.noteId);
if (note) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true,
tabId: editorMessage.tabId
});
}
@@ -631,6 +628,7 @@ export const useEditorEvents = (
if (note) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true,
tabId: editorMessage.tabId
});
}
@@ -641,7 +639,7 @@ export const useEditorEvents = (
break;
}
case EditorEvents.createInternalLink: {
case EventTypes.createInternalLink: {
LinkNote.present(
editorMessage.value.attributes,
editorMessage.resolverId as string
@@ -649,37 +647,25 @@ export const useEditorEvents = (
break;
}
case EditorEvents.unlock: {
case EventTypes.unlock: {
eSendEvent(eUnlockWithPassword, editorMessage.value);
break;
}
case EditorEvents.goBack: {
useTabStore.getState().goBack();
break;
}
case EditorEvents.goForward: {
useTabStore.getState().goForward();
break;
}
case EditorEvents.unlockWithBiometrics: {
case EventTypes.unlockWithBiometrics: {
eSendEvent(eUnlockWithBiometrics);
break;
}
case EditorEvents.disableReadonlyMode: {
case EventTypes.disableReadonlyMode: {
const noteId = editorMessage.value;
if (noteId) {
await db.notes.readonly(false, noteId);
editor.note.current[noteId] = await db.notes?.note(noteId);
useTabStore
.getState()
.updateTab(useTabStore.getState().currentTab!, {
session: {
readonly: false
}
.updateTab(useTabStore.getState().currentTab, {
readonly: false
});
setTimeout(() => {
Navigation.queueRoutesForUpdate();

View File

@@ -32,11 +32,8 @@ import {
isEncryptedContent,
isTrashItem
} from "@notesnook/core";
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { strings } from "@notesnook/intl";
import { useThemeEngineStore } from "@notesnook/theme";
import { Mutex } from "async-mutex";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import WebView from "react-native-webview";
import { DatabaseLogger, db } from "../../../common/database";
@@ -49,7 +46,6 @@ import {
eUnSubscribeEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { NotePreviewWidget } from "../../../services/note-preview-widget";
import Notifications from "../../../services/notifications";
import SettingsService from "../../../services/settings";
import { useSettingStore } from "../../../stores/use-setting-store";
@@ -66,10 +62,12 @@ import { sleep } from "../../../utils/time";
import { unlockVault } from "../../../utils/unlock-vault";
import { onNoteCreated } from "../../notes/common";
import Commands from "./commands";
import { EventTypes } from "./editor-events";
import { SessionHistory } from "./session-history";
import { EditorState, SavePayload } from "./types";
import { TabSessionItem, syncTabs, useTabStore } from "./use-tab-store";
import { syncTabs, useTabStore } from "./use-tab-store";
import {
EditorEvents,
clearAppState,
defaultState,
getAppState,
@@ -77,70 +75,11 @@ import {
isEditorLoaded,
post
} from "./utils";
import { Linking } from "react-native";
const loadNoteMutex = new Mutex();
type NoteWithContent = Note & {
content?: NoteContent<false>;
};
type LocalTabStateT = {
editedAt: number;
lastFocusedAt: number;
};
class LocalTabState {
state: Record<string, LocalTabStateT> = {};
noteEditedTime: Record<string, number> = {};
setEditTime(noteId: string, time: number) {
this.noteEditedTime[noteId] = time;
}
get(tabId: string) {
return this.state[tabId] || {};
}
set(tabId: string, state: Partial<LocalTabStateT>) {
this.state[tabId] = {
...this.state[tabId],
...state
};
}
clear(tabId: string) {
delete this.state[tabId];
}
reset() {
this.state = {};
}
needsRefresh(tabId: string, locked: boolean, readonly: boolean) {
const state = this.get(tabId);
const tabSession = useTabStore.getState().getTab(tabId)?.session;
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
if (
tabSession?.locked !== locked ||
tabSession?.readonly !== readonly ||
!noteId
) {
console.log("tab is refreshing...");
return true;
}
console.log(
"Tab refreshing...",
state.editedAt < this.noteEditedTime[noteId]
);
console.log(state.editedAt, this.noteEditedTime[noteId]);
return !state.editedAt || state.editedAt < this.noteEditedTime[noteId];
}
}
export const useEditor = (
editorId = "",
readonly?: boolean,
@@ -159,6 +98,7 @@ export const useEditor = (
isPreview?: boolean;
};
})
| null
| undefined
>
>({});
@@ -178,17 +118,15 @@ export const useEditor = (
const lastContentChangeTime = useRef<Record<string, number>>({});
const lock = useRef(false);
const currentLoadingNoteId = useRef<string>();
const lastTabFocused = useRef<string>();
const localTabState = useRef<LocalTabState>(new LocalTabState());
const loadingState = useRef<string>();
const lastTabFocused = useRef(0);
const blockIdRef = useRef<string>();
const postMessage = useCallback(
async <T>(type: string, data: T, tabId?: string, waitFor = 300) =>
async <T>(type: string, data: T, tabId?: number, waitFor = 300) =>
await post(
editorRef,
sessionIdRef.current,
tabId || useTabStore.getState().currentTab!,
typeof tabId !== "number" ? useTabStore.getState().currentTab : tabId,
type,
data,
waitFor
@@ -203,7 +141,7 @@ export const useEditor = (
}, [commands, insets, isDefaultEditor]);
useEffect(() => {
postMessage(NativeEvents.theme, theme);
postMessage(EditorEvents.theme, theme);
}, [theme, postMessage]);
useEffect(() => {
@@ -213,14 +151,15 @@ export const useEditor = (
}, [commands, tags]);
useEffect(() => {
const event = eSubscribeEvent(eEditorTabFocused, (tabId: string) => {
const event = eSubscribeEvent(eEditorTabFocused, (tabId) => {
console.log("Editot tab focus changed", lastTabFocused.current, tabId);
if (lastTabFocused.current !== tabId) lock.current = false;
lastTabFocused.current = tabId;
lastTabFocused.current = tabId as number;
});
return () => {
event?.unsubscribe();
};
}, []);
});
const overlay = useCallback(
(show: boolean, data = { type: "new" }) => {
@@ -249,11 +188,12 @@ export const useEditor = (
);
const reset = useCallback(
async (tabId: string, resetState = true, resetContent = true) => {
async (tabId: number, resetState = true, resetContent = true) => {
console.log("Resetting tab:", tabId);
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
if (noteId) {
currentNotes.current?.id && db.fs().cancel(noteId);
currentNotes.current[noteId] = undefined;
currentNotes.current[noteId] = null;
currentContents.current[noteId] = null;
editorSessionHistory.clearSession(noteId);
lastContentChangeTime.current[noteId] = 0;
@@ -261,11 +201,18 @@ export const useEditor = (
}
saveCount.current = 0;
currentLoadingNoteId.current = undefined;
loadingState.current = undefined;
lock.current = false;
resetContent && postMessage(NativeEvents.title, "", tabId);
resetContent && postMessage(EditorEvents.title, "", tabId);
resetContent && (await commands.clearContent(tabId));
resetContent && (await commands.clearTags(tabId));
useTabStore.getState().updateTab(tabId, {
noteId: undefined,
locked: false,
noteLocked: false,
readonly: false
});
},
[commands, editorSessionHistory, postMessage]
);
@@ -285,16 +232,6 @@ export const useEditor = (
try {
if (id && !(await db.notes?.note(id))) {
await reset(tabId);
useTabStore.getState().updateTab(tabId, {
session: {
noteId: undefined,
noteLocked: undefined,
locked: undefined,
readonly: undefined,
scrollTop: undefined,
selection: undefined
}
});
return;
}
let note = id ? await db.notes?.note(id) : undefined;
@@ -333,6 +270,13 @@ export const useEditor = (
};
}
// If note is edited, the tab becomes a persistent tab automatically.
if (useTabStore.getState().getTab(tabId)?.previewTab) {
useTabStore.getState().updateTab(tabId, {
previewTab: false
});
}
let saved = false;
setTimeout(() => {
if (saved) return;
@@ -367,9 +311,7 @@ export const useEditor = (
}
useTabStore.getState().updateTab(tabId, {
session: {
noteId: id
}
noteId: id
});
const defaultNotebook = db.settings.getDefaultNotebook();
@@ -384,7 +326,7 @@ export const useEditor = (
if (!noteData.title) {
postMessage(
NativeEvents.title,
EditorEvents.title,
currentNotes.current[id]?.title,
tabId
);
@@ -444,19 +386,12 @@ export const useEditor = (
id === useTabStore.getState().getCurrentNoteId() &&
pendingChanges
) {
postMessage(NativeEvents.title, title || note?.title, tabId);
postMessage(NativeEvents.html, data, tabId);
postMessage(EditorEvents.title, title || note?.title, tabId);
postMessage(EditorEvents.html, data, tabId);
currentNotes.current[id] = note;
}
saveCount.current++;
clearTimeout(timers.current.onsave);
timers.current.onsave = setTimeout(async () => {
if (!id || !note) return;
NotePreviewWidget.updateNote(id, note);
}, 500);
return id;
} catch (e) {
console.error(e);
@@ -494,211 +429,183 @@ export const useEditor = (
);
const loadNote = useCallback(
(event: {
async (event: {
item?: Note;
forced?: boolean;
newNote?: boolean;
tabId?: string;
tabId?: number;
blockId?: string;
session?: TabSessionItem;
newTab?: boolean;
refresh?: boolean;
presistTab?: boolean;
}) => {
loadNoteMutex.runExclusive(async () => {
if (!event) return;
if (event.blockId) {
blockIdRef.current = event.blockId;
if (!event) return;
console.log(event.item?.id, event?.item?.title, "loading note...");
if (event.blockId) {
blockIdRef.current = event.blockId;
}
state.current.currentlyEditing = true;
if (
!state.current.ready &&
(await isEditorLoaded(
editorRef,
sessionIdRef.current,
useTabStore.getState().currentTab
))
) {
state.current.ready = true;
}
if (event.newNote) {
useTabStore.getState().focusEmptyTab();
const tabId = useTabStore.getState().currentTab;
currentNotes.current && (await reset(tabId));
setTimeout(() => {
if (state.current?.ready && !state.current.movedAway)
commands.focus(tabId);
});
} else {
if (!event.item) {
overlay(false);
return;
}
console.log("LOADING NOTE", event.item.id);
const item = event.item;
const currentTab = useTabStore
.getState()
.getTab(useTabStore.getState().currentTab);
if (currentTab?.previewTab && item.id !== currentTab.noteId) {
await commands.setLoading(true, useTabStore.getState().currentTab);
}
const isLockedNote = await db.vaults.itemExists(
event.item as ItemReference
);
const tabLocked =
isLockedNote && !(event.item as NoteWithContent).content;
// If note was already opened in a tab, focus that tab.
if (typeof event.tabId !== "number") {
if (useTabStore.getState().hasTabForNote(event.item.id)) {
const tabId = useTabStore.getState().getTabForNote(event.item.id);
if (typeof tabId === "number") {
useTabStore.getState().updateTab(tabId, {
readonly: event.item.readonly || readonly,
locked: tabLocked,
noteLocked: isLockedNote
});
useTabStore.getState().focusTab(tabId);
setTimeout(() => {
if (blockIdRef.current) {
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;
}
}, 150);
}
console.log("Note already loaded, focusing the tab");
} else {
if (event.presistTab) {
// Open note in new tab.
useTabStore.getState().newTab({
readonly: event.item.readonly || readonly,
locked: tabLocked,
noteLocked: isLockedNote,
noteId: event.item.id,
previewTab: false
});
console.log("Opening note in new tab");
} else {
console.log("Opening note in preview tab");
// Otherwise we focus the preview tab or create one to open the note in.
useTabStore.getState().focusPreviewTab(event.item.id, {
readonly: event.item.readonly || readonly,
locked: tabLocked,
noteLocked: isLockedNote
});
}
}
} else {
if (lastTabFocused.current !== event.tabId) {
useTabStore.getState().focusTab(event.tabId);
}
}
const tabId = event.tabId || useTabStore.getState().currentTab;
if (lastTabFocused.current !== tabId) {
// if ((await waitForEvent(eEditorTabFocused, 1000)) !== tabId) {
// console.log("tab id did not match after focus in 1000ms");
// return;
// }
currentLoadingNoteId.current = item.id;
console.log("Waiting for tab to focus");
return;
}
state.current.movedAway = false;
state.current.currentlyEditing = true;
if (!tabLocked) {
await loadContent(item);
}
if (
!state.current.ready &&
(await isEditorLoaded(
editorRef,
sessionIdRef.current,
useTabStore.getState().currentTab!
))
currentNotes.current[item.id] &&
loadingState.current &&
currentContents.current[item.id]?.data &&
loadingState.current === currentContents.current[item.id]?.data
) {
state.current.ready = true;
// If note is already loading, return.
console.log("Note is already loading...");
return;
}
if (event.newNote && !currentLoadingNoteId.current) {
let tabId;
if (useTabStore.getState().tabs.length === 0) {
tabId = useTabStore.getState().newTab();
} else {
tabId = useTabStore.getState().currentTab;
await reset(tabId!, true, true);
if (
event.session?.noteId ||
useTabStore.getState().getTab(tabId!)?.session?.noteId
) {
useTabStore.getState().newTabSession(tabId!, {});
}
}
setTimeout(() => {
if (state.current?.ready && !state.current.movedAway)
commands.focus(tabId!);
});
} else {
if (!event.item) {
overlay(false);
return;
}
const item = event.item;
// If note is already open in a tab, focus that tab.
if (useTabStore.getState().hasTabForNote(item.id) && !event.newTab) {
const tabId = useTabStore.getState().getTabForNote(item.id);
const currentTab = useTabStore
.getState()
.getTab(useTabStore.getState().currentTab as string);
if (
currentTab?.session?.noteId !== item.id &&
tabId !== useTabStore.getState().currentTab
) {
useTabStore.getState().focusTab(tabId as string);
return;
}
}
const isLockedNote = await db.vaults.itemExists(
event.item as ItemReference
);
const tabLocked =
isLockedNote && !(event.item as NoteWithContent).content;
const tabId = event.tabId
? event.tabId
: useTabStore.getState().currentTab;
// Check if tab needs to be refreshed.
if (!event.newTab) {
if (
!event.refresh &&
tabId &&
event.item.id === useTabStore.getState().getNoteIdForTab(tabId) &&
!localTabState.current?.needsRefresh(
tabId,
isLockedNote,
item.readonly
)
) {
commands.setLoading(false, tabId);
return;
} else {
localTabState.current?.setEditTime(
item.id,
localTabState.current?.noteEditedTime[item.id] ||
item.dateEdited
);
localTabState.current?.set(tabId!, {
editedAt:
localTabState.current?.noteEditedTime[item.id] ||
item.dateEdited
});
}
}
currentLoadingNoteId.current = item.id;
// Show loading overlay if note is not already loaded.
if (
tabId &&
(event.item?.id !== useTabStore.getState().getNoteIdForTab(tabId) ||
!currentContents.current[event.item.id]?.data)
) {
await commands.setLoading(true, tabId);
}
const session: Partial<TabSessionItem> = event.session || {
noteId: event.item.id
};
session.noteLocked = isLockedNote;
session.locked = tabLocked;
session.readonly = item.readonly;
const tab = useTabStore.getState().getTab(tabId!);
if (useTabStore.getState().tabs.length === 0 || event.newTab) {
useTabStore.getState().newTab({
session: session
});
} else {
// A new session is created if the note is changed.
// If the note is already opened, the session is updated.
if (
!tab?.session ||
(event.item.id !== tab?.session?.noteId && tab?.session?.noteId)
) {
useTabStore.getState().newTabSession(tabId!, session);
} else {
useTabStore.getState().updateTab(tabId!, {
session: session
});
}
}
if (lastTabFocused.current !== tabId) return;
if (tabBarRef.current?.page() === 2) {
state.current.movedAway = false;
}
state.current.currentlyEditing = true;
if (!tabLocked) {
await loadContent(item);
} else {
commands.focus(tabId!);
}
lastContentChangeTime.current[item.id] = item.dateEdited;
if (!state.current.ready) {
currentNotes.current[item.id] = item;
if (!currentNotes.current[item.id]) return;
editorSessionHistory.newSession(item.id);
await commands.setStatus(
getFormattedDate(item.dateEdited, "date-time"),
"Saved",
tabId!
);
await postMessage(NativeEvents.title, item.title, tabId);
overlay(false);
await postMessage(
NativeEvents.html,
{
data: currentContents.current[item.id]?.data || "",
scrollTop: tab?.session?.scrollTop,
selection: tab?.session?.selection
},
tabId,
10000
);
setTimeout(() => {
if (blockIdRef.current) {
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;
}
}, 300);
await commands.setTags(item);
commands.setSettings();
setTimeout(() => {
if (currentLoadingNoteId.current === event.item?.id) {
currentLoadingNoteId.current = undefined;
}
}, 300);
return;
}
postMessage(NativeEvents.theme, theme);
});
lastContentChangeTime.current[item.id] = 0;
currentLoadingNoteId.current = item.id;
currentNotes.current[item.id] = item;
if (!currentNotes.current[item.id]) return;
editorSessionHistory.newSession(item.id);
await commands.setStatus(
getFormattedDate(item.dateEdited, "date-time"),
strings.saved(),
tabId
);
await postMessage(EditorEvents.title, item.title, tabId);
overlay(false);
loadingState.current = currentContents.current[item.id]?.data;
await postMessage(
EditorEvents.html,
currentContents.current[item.id]?.data || "",
tabId,
10000
);
setTimeout(() => {
if (blockIdRef.current) {
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;
}
}, 300);
loadingState.current = undefined;
await commands.setTags(item);
commands.setSettings();
setTimeout(() => {
if (currentLoadingNoteId.current === event.item?.id) {
currentLoadingNoteId.current = undefined;
}
}, 300);
}
postMessage(EditorEvents.theme, theme);
},
[
commands,
@@ -706,6 +613,7 @@ export const useEditor = (
loadContent,
overlay,
postMessage,
readonly,
reset,
theme
]
@@ -717,116 +625,97 @@ export const useEditor = (
isLocal?: boolean
) => {
try {
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
if (!data) return;
await (async () => {
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
if (!data) return;
if (isDeleted(data) || isTrashItem(data)) {
const tabId = useTabStore.getState().getTabForNote(data.id);
if (tabId !== undefined) {
await commands.clearContent(tabId);
useTabStore.getState().removeTab(tabId);
if (isDeleted(data) || isTrashItem(data)) {
const tabId = useTabStore.getState().getTabForNote(data.id);
if (tabId !== undefined) {
console.log("Removing tab");
await commands.clearContent(tabId);
useTabStore.getState().removeTab(tabId);
}
return;
}
return;
}
const noteId =
(data as ContentItem).type === "tiptap"
? (data as ContentItem).noteId
: data.id;
const noteId =
(data as ContentItem).type === "tiptap"
? (data as ContentItem).noteId
: data.id;
const note = data.type === "note" ? data : await db.notes?.note(noteId);
if (!useTabStore.getState().hasTabForNote(noteId)) return;
const tabId = useTabStore.getState().getTabForNote(noteId) as number;
lock.current = true;
const tab = useTabStore.getState().getTab(tabId);
// Handle this case where note was locked on another device and synced.
let locked = note
? await db.vaults.itemExists(note as ItemReference)
: false;
const note =
data.type === "note" ? data : await db.notes?.note(noteId);
if (data.type === "tiptap") {
locked = data.locked;
}
lock.current = true;
useTabStore.getState().forEachNoteTab(noteId, async (tab) => {
const tabId = tab.id;
let didUnlock = false;
// Handle this case where note was locked on another device and synced.
const locked = note
? await db.vaults.itemExists(note as ItemReference)
: false;
if (note) {
if (!locked && tab?.session?.noteLocked) {
if (tab?.session?.noteLocked || tab?.session?.locked) {
if (useTabStore.getState().currentTab !== tabId) {
localTabState.current?.set(tabId, {
editedAt: 0
if (!locked && tab?.noteLocked) {
// Note lock removed.
if (tab.locked) {
if (useTabStore.getState().currentTab === tabId) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true
});
}
didUnlock = true;
useTabStore.getState().updateTab(tabId, {
session: {
} else {
useTabStore.getState().updateTab(tabId, {
locked: false,
noteLocked: false
}
});
}
} else if (!tab?.session?.noteLocked && locked) {
useTabStore.getState().updateTab(tabId, {
session: {
locked: true,
noteLocked: true
});
commands.setLoading(true, tabId);
}
}
} else if (!tab?.noteLocked && locked) {
// Note lock added.
useTabStore.getState().updateTab(tabId, {
locked: true,
noteLocked: true
});
commands.clearContent(tabId);
if (useTabStore.getState().currentTab !== tabId) {
commands.clearContent(tabId);
commands.setLoading(true, tabId);
}
localTabState.current?.set(tabId, {
editedAt: 0
});
}
if (
currentNotes.current[noteId]?.title !== note.title ||
didUnlock
) {
postMessage(NativeEvents.title, note.title, tabId);
if (currentNotes.current[noteId]?.title !== note.title) {
postMessage(EditorEvents.title, note.title, tabId);
}
commands.setTags(note);
if (
currentNotes.current[noteId]?.dateEdited !== note.dateEdited ||
didUnlock
) {
if (currentNotes.current[noteId]?.dateEdited !== note.dateEdited) {
commands.setStatus(
getFormattedDate(note.dateEdited, "date-time"),
strings.saved(),
tabId as string
tabId as number
);
}
if (tab.session?.readonly !== note.readonly || didUnlock) {
useTabStore.getState().updateTab(tabId, {
session: {
readonly: note.readonly
}
});
}
useTabStore.getState().updateTab(tabId, {
readonly: note.readonly
});
}
if (data.type === "tiptap" && note && !isLocal) {
if (
lastContentChangeTime.current[noteId] >= data.dateModified &&
!didUnlock
) {
if (lastContentChangeTime.current[noteId] >= data.dateModified) {
return;
}
if (locked && isEncryptedContent(data)) {
const decryptedContent = await db.vault?.decryptContent(data);
if (!decryptedContent) {
useTabStore.getState().updateTab(tab.id, {
session: {
locked: true,
noteLocked: true
}
useTabStore.getState().updateTab(tabId, {
locked: true,
noteLocked: true
});
if (useTabStore.getState().currentTab !== tabId) {
commands.clearContent(tabId);
@@ -834,43 +723,26 @@ export const useEditor = (
}
} else {
await postMessage(
NativeEvents.updatehtml,
{
data: decryptedContent.data,
selection: tab.session?.selection,
scrollTop: tab.session?.scrollTop
},
EditorEvents.updatehtml,
decryptedContent.data,
tabId
);
currentContents.current[note.id] = decryptedContent;
}
} else {
const _nextContent = data.data;
if (
_nextContent === currentContents.current[note.id]?.data &&
!didUnlock
) {
if (_nextContent === currentContents.current[note.id]?.data) {
return;
}
lastContentChangeTime.current[note.id] = note.dateEdited;
console.log(tab.session?.selection);
await postMessage(
NativeEvents.updatehtml,
{
data: _nextContent,
selection: tab.session?.selection,
scrollTop: tab.session?.scrollTop
},
tabId
);
await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
if (!isEncryptedContent(data)) {
currentContents.current[note.id] =
data as UnencryptedContentItem;
}
}
}
});
})();
} catch (e) {
DatabaseLogger.error(e as Error, "Error when applying sync changes");
} finally {
@@ -906,7 +778,7 @@ export const useEditor = (
content?: string;
type: string;
ignoreEdit: boolean;
tabId: string;
tabId: number;
pendingChanges?: boolean;
}) => {
DatabaseLogger.log(
@@ -935,13 +807,9 @@ export const useEditor = (
if (noteId) {
lastContentChangeTime.current[noteId] = Date.now();
localTabState.current?.setEditTime(noteId, Date.now());
localTabState?.current?.set(tabId, {
editedAt: Date.now()
});
}
if (type === EditorEvents.content && noteId) {
if (type === EventTypes.content && noteId) {
currentContents.current[noteId as string] = {
data: content,
type: "tiptap",
@@ -991,9 +859,7 @@ export const useEditor = (
if (!appState) return;
state.current.isRestoringState = true;
state.current.currentlyEditing = true;
if (tabBarRef.current?.page() === 2) {
state.current.movedAway = false;
}
state.current.movedAway = false;
if (!state.current.editorStateRestored) {
state.current.isRestoringState = true;
@@ -1024,12 +890,9 @@ export const useEditor = (
!(await isEditorLoaded(
editorRef,
sessionIdRef.current,
useTabStore.getState().currentTab!
useTabStore.getState().currentTab
))
) {
localTabState.current?.set(useTabStore.getState().currentTab!, {
editedAt: 0
});
eSendEvent(eEditorReset, "onReady");
return false;
} else {
@@ -1041,52 +904,35 @@ export const useEditor = (
const onLoad = useCallback(async () => {
setTimeout(() => {
postMessage(NativeEvents.theme, theme);
postMessage(EditorEvents.theme, theme);
});
commands.setInsets(
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
);
await commands.setSettings();
localTabState.current?.set(useTabStore.getState().currentTab!, {
editedAt: 0
});
if (!state.current.ready) {
state.current.ready = true;
}
if (!state.current?.initialLoadCalled) {
const url = await Linking.getInitialURL();
let noteId = url && new URL(url).searchParams.get("id");
if (noteId) {
const note = await db.notes?.note(noteId);
if (note) {
loadNote({
item: note
});
}
tabBarRef.current?.goToPage(1);
} else {
noteId = useTabStore.getState().getCurrentNoteId() || null;
if (!noteId) {
loadNote({ newNote: true });
if (tabBarRef.current?.page() === 1) {
state.current.currentlyEditing = false;
}
} else {
const note = await db.notes.note(noteId);
if (note) {
loadNote({
item: note
});
}
}
state.current.initialLoadCalled = true;
const noteId = useTabStore.getState().getCurrentNoteId();
if (!noteId) {
loadNote({ newNote: true });
if (tabBarRef.current?.page() === 1) {
state.current.currentlyEditing = false;
}
} else if (state.current?.initialLoadCalled) {
const note = currentNotes.current[noteId];
if (note) {
loadNote({
item: note
});
}
overlay(false);
}
if (!state.current?.initialLoadCalled) {
state.current.initialLoadCalled = true;
}
overlay(false);
}, [
postMessage,
theme,

View File

@@ -16,27 +16,18 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
TabHistory as TabHistoryType,
TabSessionHistory
} from "@notesnook/common";
import { getId } from "@notesnook/core";
import { MMKVLoader } from "react-native-mmkv-storage";
import create from "zustand";
import { persist, StateStorage } from "zustand/middleware";
import { db } from "../../../common/database";
import { MMKV } from "../../../common/database/mmkv";
import { eSendEvent } from "../../../services/event-manager";
import { eOnLoadNote } from "../../../utils/events";
import { editorController } from "./utils";
class TabHistory {
history: string[];
class History {
history: number[];
constructor() {
this.history = [];
this.history = [0];
}
add(item: string) {
add(item: number) {
const index = this.history.findIndex((id) => item === id);
if (index !== -1) {
// Item already exists, move it to the top
@@ -45,19 +36,19 @@ class TabHistory {
this.history.unshift(item); // Add item to the beginning of the array
useTabStore.setState({
historyNew: this.history.slice()
tabHistory: this.history.slice()
});
return true; // Item added successfully
}
remove(id: string) {
remove(id: number) {
const index = this.history.findIndex((item) => item === id);
if (index >= -1 && index < this.history.length) {
const removedItem = this.history.splice(index, 1)[0];
return removedItem;
}
useTabStore.setState({
historyNew: this.history.slice()
tabHistory: this.history.slice()
});
return null; // Invalid index
}
@@ -68,7 +59,7 @@ class TabHistory {
return restoredItem;
}
useTabStore.setState({
historyNew: this.history.slice()
tabHistory: this.history.slice()
});
return null; // History is empty
}
@@ -78,360 +69,149 @@ class TabHistory {
}
}
export type TabSessionItem = {
id: string;
noteId?: string;
scrollTop?: number;
selection?: { to: number; from: number };
noteLocked?: boolean;
locked?: boolean;
readonly?: boolean;
};
const TabSessionStorageKV = new MMKVLoader()
.withInstanceID("tab-session-storage")
.disableIndexing()
.initialize();
class TabSessionStorage {
static storage: typeof TabSessionStorageKV = TabSessionStorageKV;
static get(id: string): TabSessionItem | null {
return TabSessionStorage.storage.getMap(id);
}
static set(id: string, session: TabSessionItem): void {
TabSessionStorage.storage.setMap(id, session);
}
static update(id: string, session: Partial<TabSessionItem>) {
if (!id) return;
const currentSession = TabSessionStorage.get(id);
const newSession = {
...currentSession,
...session,
id: currentSession?.id || session.id || id
};
TabSessionStorage.set(id, newSession as TabSessionItem);
return newSession;
}
static remove(id: string) {
TabSessionStorageKV.removeItem(id);
}
}
export function syncTabs(
type: "tabs" | "history" | "biometry" | "all" = "all"
) {
const data: Partial<TabStore> = {};
if (type === "tabs" || type === "all") {
data.tabs = useTabStore.getState().tabs;
data.currentTab = useTabStore.getState().currentTab;
}
if (type === "history" || type === "all") {
data.canGoBack = useTabStore.getState().canGoBack;
data.canGoForward = useTabStore.getState().canGoForward;
data.sessionId = useTabStore.getState().sessionId;
}
if (type === "biometry" || type === "all") {
data.biometryAvailable = useTabStore.getState().biometryAvailable;
data.biometryEnrolled = useTabStore.getState().biometryEnrolled;
}
editorController.current?.commands.doAsync(`
globalThis.tabStore?.setState(${JSON.stringify(data)});
`);
}
export const tabSessionHistory = new TabSessionHistory({
get() {
return useTabStore.getState();
},
set(state) {
useTabStore.setState({
...state
});
}
});
export type TabItem = {
id: string;
id: number;
noteId?: string;
previewTab?: boolean;
readonly?: boolean;
locked?: boolean;
noteLocked?: boolean;
pinned?: boolean;
needsRefresh?: boolean;
session?: Partial<TabSessionItem>;
};
const history = new TabHistory();
const history = new History();
export type TabStore = {
tabs: TabItem[];
currentTab: string;
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => void;
currentTab: number;
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => void;
focusPreviewTab: (
noteId: string,
options: Omit<Partial<TabItem>, "id">
) => void;
removeTab: (index: string) => void;
removeTab: (index: number) => void;
moveTab: (index: number, toIndex: number) => void;
newTab: (options?: Omit<Partial<TabItem>, "id">) => string;
focusTab: (id: string) => void;
getNoteIdForTab: (id: string) => string | undefined;
getTabForNote: (noteId: string) => string | undefined;
getTabsForNote: (noteId: string) => TabItem[];
forEachNoteTab: (noteId: string, cb: (tab: TabItem) => void) => void;
newTab: (options?: Omit<Partial<TabItem>, "id">) => void;
focusTab: (id: number) => void;
getNoteIdForTab: (id: number) => string | undefined;
getTabForNote: (noteId: string) => number | undefined;
hasTabForNote: (noteId: string) => boolean;
focusEmptyTab: () => void;
getCurrentNoteId: () => string | undefined;
getTab: (tabId: string) => TabItem | undefined;
newTabSession: (
id: string,
options?: Omit<Partial<TabSessionItem>, "id">
) => void;
historyNew: string[];
getTab: (tabId: number) => TabItem | undefined;
tabHistory: number[];
biometryAvailable?: boolean;
biometryEnrolled?: boolean;
tabSessionHistory: TabHistoryType;
goBack(): void;
goForward(): void;
loadSession: (id: string) => Promise<boolean>;
canGoBack?: boolean;
canGoForward?: boolean;
sessionId?: string;
};
const DEFAULT_TABS = {
tabs: [
{
id: "679da59a3924d4bd56d16d3f",
session: {
id: "679da5a5667a16db2353a062"
}
}
],
tabSessionHistory: {
"679da59a3924d4bd56d16d3f": {
backStack: ["679da5a5667a16db2353a062"],
forwardStack: [] as string[]
}
} as TabHistoryType,
historyNew: ["679da59a3924d4bd56d16d3f"],
currentTab: "679da59a3924d4bd56d16d3f"
} as TabStore;
function getId(id: number, tabs: TabItem[]): number {
const exists = tabs.find((t) => t.id === id);
if (exists) {
return getId(id + 1, tabs);
}
return id;
}
export function resetTabStore() {
useTabStore.setState({
...DEFAULT_TABS
});
TabSessionStorage.storage.clearStore();
export function syncTabs() {
editorController.current?.commands.doAsync(`
globalThis.tabStore?.setState({
tabs: ${JSON.stringify(useTabStore.getState().tabs)},
currentTab: ${useTabStore.getState().currentTab},
biometryAvailable: ${useTabStore.getState().biometryAvailable},
biometryEnrolled: ${useTabStore.getState().biometryEnrolled}
});
`);
}
export const useTabStore = create<TabStore>(
persist(
(set, get) => ({
...DEFAULT_TABS,
newTabSession: (
_id?: string,
options?: Omit<Partial<TabSessionItem>, "id">
) => {
const tabId = _id || (get().currentTab as string);
const sessionHistory = get().tabSessionHistory[tabId];
let oldSessionId: string | undefined = undefined;
if (sessionHistory) {
const allSessions = sessionHistory.backStack.concat(
sessionHistory.forwardStack
);
allSessions.forEach((id) => {
if (TabSessionStorage.get(id)?.noteId === options?.noteId) {
oldSessionId = id;
}
});
tabs: [
{
id: 0
}
const sessionId =
oldSessionId &&
tabSessionHistory.currentSessionId(tabId) === oldSessionId
? oldSessionId
: tabSessionHistory.add(tabId, oldSessionId);
let session: Partial<TabSessionItem>;
if (!oldSessionId) {
session = {
id: sessionId,
...options
};
TabSessionStorage.set(sessionId, session as TabSessionItem);
} else {
session = {
id: sessionId,
...TabSessionStorage.get(oldSessionId),
...options
};
}
const index = get().tabs.findIndex((t) => t.id === tabId);
if (index == -1) return;
const tabs = [...get().tabs];
tabs[index] = {
...tabs[index],
session: session
} as TabItem;
set({
tabs: tabs
});
syncTabs();
},
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => {
],
tabHistory: [0],
history: new History(),
currentTab: 0,
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => {
if (!options) return;
const index = get().tabs.findIndex((t) => t.id === id);
if (index == -1) return;
const tabs = [...get().tabs];
const sessionId =
options.session?.id || (tabs[index].session?.id as string);
const updatedSession = !options.session
? tabs[index].session
: TabSessionStorage.update(sessionId, options.session);
tabs[index] = {
...tabs[index],
...options,
session: updatedSession
} as TabItem;
...options
};
set({
tabs: tabs
});
syncTabs();
},
goBack: async () => {
const currentTab = get().currentTab;
if (!currentTab) return;
if (!tabSessionHistory.canGoBack(currentTab)) return;
const id = tabSessionHistory.back(currentTab) as string;
const sessionLoaded = await get().loadSession(id);
if (!sessionLoaded) {
tabSessionHistory.remove(currentTab, id);
TabSessionStorage.remove(id);
if (!tabSessionHistory.canGoBack(currentTab)) {
tabSessionHistory.forward(currentTab);
syncTabs();
} else {
return get().goBack();
}
} else {
syncTabs();
}
},
goForward: async () => {
const currentTab = get().currentTab;
if (!currentTab) return;
if (!tabSessionHistory.canGoForward(currentTab)) return;
const id = tabSessionHistory.forward(currentTab) as string;
if (!(await get().loadSession(id))) {
tabSessionHistory.remove(currentTab, id);
TabSessionStorage.remove(id);
if (!tabSessionHistory.canGoForward(currentTab)) {
tabSessionHistory.back(currentTab);
syncTabs();
} else {
return get().goForward();
}
} else {
syncTabs();
}
},
loadSession: async (id: string) => {
const session = TabSessionStorage.get(id);
if (!session) return false;
const note = session?.noteId
? await db.notes.note(session?.noteId)
: undefined;
if (note) {
const isLocked = await db.vaults.itemExists(note);
session.noteLocked = isLocked;
session.locked = isLocked;
session.readonly = note.readonly;
} else if (session.noteId) {
console.log("Failed to load session...");
return false;
}
eSendEvent(eOnLoadNote, {
item: note,
newNote: !note,
tabId: get().currentTab,
session: session
});
return true;
},
focusPreviewTab: (
noteId: string,
options: Omit<Partial<TabItem>, "id" | "noteId">
) => {},
removeTab: (id: string) => {
) => {
const index = get().tabs.findIndex((t) => t.previewTab);
if (index === -1)
return get().newTab({
noteId,
previewTab: true,
...options
});
const tabs = [...get().tabs];
tabs[index] = {
...tabs[index],
...options,
previewTab: true,
noteId: noteId
};
console.log("focus preview", noteId);
set({
tabs: tabs
});
get().focusTab(tabs[index].id);
},
removeTab: (id: number) => {
const index = get().tabs.findIndex((t) => t.id === id);
if (index > -1) {
const isFocused = id === get().currentTab;
const nextTabs = get().tabs.slice();
nextTabs.splice(index, 1);
history.remove(id);
const tabSessions = tabSessionHistory.getTabHistory(id);
tabSessions.back.forEach((id) => TabSessionStorage.remove(id));
tabSessions.forward.forEach((id) => TabSessionStorage.remove(id));
tabSessionHistory.clearStackForTab(id);
if (nextTabs.length === 0) {
const id = getId();
set({
tabs: [{ id: id }]
nextTabs.push({
id: 0
});
get().newTabSession(id, {});
get().focusTab(id);
} else {
set({
tabs: nextTabs
});
if (isFocused) {
const lastTab = history.restoreLast();
if (lastTab) get().focusTab(lastTab);
}
}
syncTabs();
set({
tabs: nextTabs
});
get().focusTab(
isFocused ? history.restoreLast() || 0 : get().currentTab
);
}
},
newTab: (options) => {
const id = getId();
const id = getId(get().tabs.length, get().tabs);
const nextTabs = [
...get().tabs,
{
id: id,
...options
}
];
set({
tabs: [
...get().tabs,
{
id: id,
...options
}
]
tabs: nextTabs
});
get().newTabSession(id, options?.session || {});
get().focusTab(id);
return id;
},
focusEmptyTab: () => {
const index = get().tabs.findIndex((t) => !t.session?.noteId);
const index = get().tabs.findIndex((t) => !t.noteId);
if (index === -1) return get().newTab();
console.log("focus empty tab", get().tabs[index]);
get().focusTab(get().tabs[index].id);
},
@@ -444,46 +224,38 @@ export const useTabStore = create<TabStore>(
syncTabs();
},
focusTab: (id: string) => {
focusTab: (id: number) => {
console.log(history.getHistory(), id);
history.add(id);
set({
currentTab: id,
canGoBack: tabSessionHistory.canGoBack(id),
canGoForward: tabSessionHistory.canGoForward(id),
sessionId: tabSessionHistory.currentSessionId(id)
currentTab: id
});
syncTabs();
},
getNoteIdForTab: (id: string) => {
return get().tabs.find((t) => t.id === id)?.session?.noteId;
getNoteIdForTab: (id: number) => {
return get().tabs.find((t) => t.id === id)?.noteId;
},
hasTabForNote: (noteId: string) => {
return !!get().tabs.find((t) => t.session?.noteId === noteId);
return (
typeof get().tabs.find((t) => t.noteId === noteId)?.id === "number"
);
},
getTabForNote: (noteId: string) => {
return get().tabs.find((t) => t.session?.noteId === noteId)?.id;
},
getTabsForNote(noteId: string) {
return get().tabs.filter((t) => t.session?.noteId === noteId);
},
forEachNoteTab: (noteId: string, cb: (tab: TabItem) => void) => {
const tabs = get().tabs.filter((t) => t.session?.noteId === noteId);
tabs.forEach(cb);
return get().tabs.find((t) => t.noteId === noteId)?.id;
},
getCurrentNoteId: () => {
return get().tabs.find((t) => t.id === get().currentTab)?.session
?.noteId;
return get().tabs.find((t) => t.id === get().currentTab)?.noteId;
},
getTab: (tabId) => {
return get().tabs.find((t) => t.id === tabId);
}
}),
{
name: "tabs-storage-v5",
name: "tabs-storage",
getStorage: () => MMKV as unknown as StateStorage,
onRehydrateStorage: () => {
return (state) => {
history.history = state?.historyNew || [];
history.history = state?.tabHistory.slice() || [];
};
}
}

View File

@@ -31,8 +31,6 @@ import { eOnLoadNote } from "../../../utils/events";
import { NotesnookModule } from "../../../utils/notesnook-module";
import { AppState, EditorState, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
export const textInput = createRef<TextInput>();
export const editorController =
createRef<useEditorType>() as MutableRefObject<useEditorType>;
@@ -48,6 +46,19 @@ export function editorState() {
return editorController.current?.state.current || defaultState;
}
export const EditorEvents = {
html: "native:html",
updatehtml: "native:updatehtml",
title: "native:title",
theme: "native:theme",
titleplaceholder: "native:titleplaceholder",
logger: "native:logger",
status: "native:status",
keyboardShown: "native:keyboardShown",
attachmentData: "native:attachment-data",
resolve: "native:resolve"
};
export function randId(prefix: string) {
return Math.random()
.toString(36)
@@ -61,15 +72,15 @@ export function makeSessionId(id?: string) {
export async function isEditorLoaded(
ref: RefObject<WebView>,
sessionId: string,
tabId: string
tabId: number
) {
return await post(ref, sessionId, tabId, NativeEvents.status);
return await post(ref, sessionId, tabId, EditorEvents.status);
}
export async function post<T>(
ref: RefObject<WebView>,
sessionId: string,
tabId: string,
tabId: number,
type: string,
value: T | null = null,
waitFor = 300
@@ -124,6 +135,7 @@ export const waitForEvent = async (
};
eSubscribeEvent(type, callback);
setTimeout(() => {
console.log("return..");
eUnSubscribeEvent(type, callback);
resolve(false);
}, waitFor);
@@ -146,9 +158,6 @@ const canRestoreAppState = (appState: AppState) => {
};
let appState: AppState | undefined;
export function setAppState(state: AppState) {
appState = state;
}
export function getAppState() {
if (appState && canRestoreAppState(appState)) return appState as AppState;
const json = NotesnookModule.getAppState();
@@ -174,7 +183,7 @@ export async function openInternalLink(url: string) {
if (!data?.id) return false;
if (
data.id ===
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab!)
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
) {
if (data.params?.blockId) {
setTimeout(() => {

View File

@@ -1,169 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { FlatList, TouchableOpacity, View } from "react-native";
import { db } from "../../common/database";
import { Header } from "../../components/header";
import Input from "../../components/ui/input";
import Paragraph from "../../components/ui/typography/paragraph";
import { useDBItem } from "../../hooks/use-db-item";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useSettingStore } from "../../stores/use-setting-store";
import { NotesnookModule } from "../../utils/notesnook-module";
const NoteItem = (props: {
id: string | number;
items?: VirtualizedGrouping<Note>;
}) => {
const { colors } = useThemeColors();
const [item] = useDBItem(props.id, "note", props.items);
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {
const widgetId = NotesnookModule.getWidgetId();
NotesnookModule.setString(
"appPreview",
String(widgetId),
JSON.stringify(item)
);
setTimeout(() => {
NotesnookModule.saveAndFinish();
});
}}
style={{
flexDirection: "column",
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
justifyContent: "center",
paddingVertical: 12,
minHeight: 45
}}
>
{!item ? null : (
<View
style={{
flexDirection: "row",
paddingHorizontal: 12
}}
>
<View
style={{
flexDirection: "column"
}}
>
<Paragraph
numberOfLines={1}
style={{
color: colors.primary.paragraph,
fontSize: 15
}}
>
{item.title}
</Paragraph>
</View>
</View>
)}
</TouchableOpacity>
);
};
export const NotePreviewConfigure = () => {
const [items, setItems] = useState<VirtualizedGrouping<Note>>();
const loading = useSettingStore((state) => state.isAppLoading);
const bounceRef = React.useRef<NodeJS.Timeout>();
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
useEffect(() => {
useSettingStore.getState().setDeviceMode("mobile");
if (loading) return;
db.notes.all.sorted(db.settings.getGroupOptions("notes")).then((notes) => {
setItems(notes);
});
}, [loading]);
const renderItem = React.useCallback(
({ index }: { item: boolean; index: number }) => {
return <NoteItem id={index} items={items} />;
},
[items]
);
return (
<View
style={{
backgroundColor: colors.primary.background,
flex: 1
}}
>
<Header
canGoBack
title="Select a note"
onLeftMenuButtonPress={() => {
NotesnookModule.cancelAndFinish();
}}
/>
<View
style={{
paddingHorizontal: 12,
paddingTop: 6
}}
>
<Input
placeholder="Search for notes"
containerStyle={{
height: 50
}}
onChangeText={(value) => {
bounceRef.current = setTimeout(() => {
if (!value) {
db.notes.all
.sorted(db.settings.getGroupOptions("notes"))
.then((notes) => {
setItems(notes);
});
return;
}
db.lookup
.notes(value)
.sorted()
.then((notes) => {
setItems(notes);
});
}, 500);
}}
/>
<FlatList
data={items?.placeholders}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="interactive"
renderItem={renderItem}
windowSize={1}
ListFooterComponent={<View style={{ height: 200 }} />}
/>
</View>
</View>
);
};

View File

@@ -284,13 +284,14 @@ NotebookScreen.navigate = async (item: Notebook, canGoBack?: boolean) => {
focusedRouteId == item?.id
) {
// Update the route in place instead
console.log("Updating existing route in place");
eSendEvent(eUpdateNotebookRoute, {
item: item,
title: item.title,
canGoBack: canGoBack
});
} else {
console.log("Pushing new notebook route");
// Push a new route
Navigation.push("Notebook", {
title: item.title,

View File

@@ -59,6 +59,7 @@ ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
const { focusedRouteId } = useNavigationStore.getState();
if (focusedRouteId === item.id) {
console.log("ColoredNotes.navigate: route already focused for color");
return;
}

View File

@@ -171,6 +171,7 @@ const NotesPage = ({
setLoadingNotes(false);
})
.catch((e) => {
console.log("Error loading notes", params.current?.title, e, e.stack);
setLoadingNotes(false);
});
}

View File

@@ -60,6 +60,7 @@ TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
const { focusedRouteId } = useNavigationStore.getState();
if (focusedRouteId === item.id) {
console.log("TaggedNotes.navigate: route already focused for tag");
return;
}

View File

@@ -160,7 +160,9 @@ export default function DebugLogs() {
type: "success"
});
}
} catch (e) {}
} catch (e) {
console.log(e);
}
}, [currentLog?.logs]);
const copyLogs = React.useCallback(() => {

View File

@@ -54,9 +54,7 @@ export const Licenses = () => {
}}
onPress={() => {
if (!item.link) return;
Linking.openURL(item.link).catch(() => {
/* empty */
});
Linking.openURL(item.link).catch(console.log);
}}
>
<Heading size={SIZE.sm}>{item.name}</Heading>

View File

@@ -33,6 +33,7 @@ import { strings } from "@notesnook/intl";
export const FontPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.defaultFontFamily,
updateValue: (item) => {
console.log(item.id);
SettingsService.set({
defaultFontFamily: item.id
});

View File

@@ -148,7 +148,7 @@ const restoreBackup = async (options: {
let count = 0;
await db.transaction(async () => {
let passwordOrKey: PasswordOrKey | undefined = undefined;
let passwordOrKey: PasswordOrKey;
for (const path of extractedBackupFiles) {
if (path === ".nnbackup" || path === "attachments") continue;
@@ -166,7 +166,7 @@ const restoreBackup = async (options: {
passwordOrKey = !isEncryptedBackup
? ({} as PasswordOrKey)
: passwordOrKey || (await withPassword());
: await withPassword();
if (
isEncryptedBackup &&
@@ -197,6 +197,7 @@ const restoreBackup = async (options: {
const attachment = await db.attachments.attachment(hash as string);
if (!attachment) continue;
console.log("Saving attachment file", hash);
await deleteCacheFileByName(hash);
await RNFetchBlob.fs.cp(
`${zipOutputFolder}/attachments/${hash}`,
@@ -207,13 +208,9 @@ const restoreBackup = async (options: {
progress: strings.cleaningUp()
});
// Remove files from cache
RNFetchBlob.fs.unlink(zipOutputFolder).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
if (Platform.OS === "android" || deleteBackupFile) {
RNFetchBlob.fs.unlink(filePath).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(filePath).catch(console.log);
}
} else {
updateProgress({

View File

@@ -23,7 +23,7 @@ import {
StackActions,
useNavigation
} from "@react-navigation/native";
import React, { useEffect, useRef, useState } from "react";
import React, { useRef, useState } from "react";
import { ActivityIndicator, TextInput, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ToggleSwitch from "toggle-switch-react-native";
@@ -44,12 +44,7 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
const settings = useSettingStore((state) => state.settings);
const navigation = useNavigation<NavigationProp<RouteParams>>();
const current = item.useHook && item.useHook(item);
const [isHidden, setIsHidden] = useState(
item.hidden && item.hidden(item.property || current)
);
const [isDisabled, setIsDisabled] = useState(
item.disabled && item.disabled(item.property || current)
);
const isHidden = item.hidden && item.hidden(item.property || current);
const inputRef = useRef<TextInput>(null);
const [loading, setLoading] = useState(false);
@@ -67,13 +62,7 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
SettingsService.set({
[item.property]: nextValue
});
setImmediate(() => {
item.onChange?.(nextValue);
item.hidden &&
setIsHidden(item.hidden && item.hidden(item.property || current));
item.disabled &&
setIsDisabled(item.disabled && item.disabled(item.property || current));
});
setImmediate(() => item.onChange?.(nextValue));
};
const styles =
@@ -103,14 +92,9 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
}
};
useEffect(() => {
setIsHidden(item.hidden && item.hidden(item.property || current));
setIsDisabled(item.disabled && item.disabled(item.property || current));
}, [current, item]);
return isHidden ? null : (
<Pressable
disabled={item.type === "component" || isDisabled}
disabled={item.type === "component"}
style={{
width: "100%",
alignItems: "center",
@@ -118,12 +102,10 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: 20,
opacity: isDisabled ? 0.5 : 1,
borderRadius: 0,
...styles
}}
onPress={async () => {
if (isDisabled) return;
switch (item.type) {
case "screen":
{
@@ -226,7 +208,6 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
});
item.inputProperties?.onSubmitEditing?.(e);
}}
editable={!isDisabled}
onChangeText={(text) => {
SettingsService.set({
[item.property as string]: text
@@ -259,7 +240,6 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
name="minus"
color={colors.primary.icon}
onPress={() => {
if (isDisabled) return;
const rawValue = SettingsService.get()[
item.property as keyof SettingStore["settings"]
] as string;
@@ -282,7 +262,6 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
onChangeInputSelectorValue(e.nativeEvent.text);
item.inputProperties?.onSubmitEditing?.(e);
}}
editable={!isDisabled}
onChangeText={(text) => {
onChangeInputSelectorValue(text);
item.inputProperties?.onSubmitEditing?.(text as any);
@@ -308,7 +287,6 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
name="plus"
color={colors.primary.icon}
onPress={() => {
if (isDisabled) return;
const rawValue = SettingsService.get()[
item.property as keyof SettingStore["settings"]
] as string;

View File

@@ -389,12 +389,7 @@ export const settingsGroups: SettingSection[] = [
useHook: () => {
const [cacheSize, setCacheSize] = React.useState(0);
React.useEffect(() => {
filesystem
.getCacheSize()
.then(setCacheSize)
.catch(() => {
/* empty */
});
filesystem.getCacheSize().then(setCacheSize).catch(console.log);
const sub = eSubscribeEvent("cache-cleared", () => {
setCacheSize(0);
});
@@ -451,7 +446,7 @@ export const settingsGroups: SettingSection[] = [
"local",
"partial"
);
if (result?.error) throw result.error as Error;
if (result.error) throw result.error as Error;
} catch (e) {
DatabaseLogger.error(e);
const error = e;
@@ -537,7 +532,7 @@ export const settingsGroups: SettingSection[] = [
endProgress();
} catch (e) {
endProgress();
console.log(e);
ToastManager.error(
e as Error,
strings.failedToDeleteAccount(),
@@ -573,9 +568,7 @@ export const settingsGroups: SettingSection[] = [
}
PremiumService.verify(() => {
SettingsService.setProperty("offlineMode", true);
db.attachments.cacheAttachments().catch(() => {
/* empty */
});
db.attachments.cacheAttachments().catch(console.log);
});
}
},
@@ -751,12 +744,8 @@ export const settingsGroups: SettingSection[] = [
description: strings.clearDefaultNotebookDesc(),
modifer: () => {
db.settings.setDefaultNotebook(undefined);
ToastManager.show({
heading: strings.defaultNotebookCleared(),
type: "success"
});
},
disabled: () => !db.settings.getDefaultNotebook()
hidden: () => !db.settings.getDefaultNotebook()
}
]
},
@@ -780,10 +769,6 @@ export const settingsGroups: SettingSection[] = [
description: strings.resetToolbarDesc(),
modifer: () => {
useDragState.getState().setPreset("default");
ToastManager.show({
heading: strings.toolbarReset(),
type: "success"
});
}
},
{
@@ -1474,9 +1459,7 @@ export const settingsGroups: SettingSection[] = [
name: strings.joinTelegram(),
description: strings.joinTelegramDesc(),
modifer: () => {
Linking.openURL("https://t.me/notesnook").catch(() => {
/* empty */
});
Linking.openURL("https://t.me/notesnook").catch(console.log);
}
},
{
@@ -1496,9 +1479,7 @@ export const settingsGroups: SettingSection[] = [
description: strings.followOnXDesc(),
icon: "twitter",
modifer: () => {
Linking.openURL("https://twitter.com/notesnook").catch(() => {
/* empty */
});
Linking.openURL("https://twitter.com/notesnook").catch(console.log);
}
},
{
@@ -1506,9 +1487,7 @@ export const settingsGroups: SettingSection[] = [
name: strings.joinDiscord(),
icon: "discord",
modifer: async () => {
Linking.openURL("https://discord.gg/zQBK97EE22").catch(() => {
/* empty */
});
Linking.openURL("https://discord.gg/zQBK97EE22").catch(console.log);
},
description: strings.joinDiscordDesc()
}

View File

@@ -47,7 +47,6 @@ export type SettingSection = {
maxInputValue?: number;
onVerify?: () => Promise<boolean>;
hideHeader?: boolean;
disabled?: (current: unknown) => boolean;
};
export type SettingsGroup = {

View File

@@ -25,7 +25,6 @@ import {
import { DatabaseLogger, db, setupDatabase } from "../common/database";
import { deleteDCacheFiles } from "../common/filesystem/io";
import { useUserStore } from "../stores/use-user-store";
import { NotePreviewWidget } from "./note-preview-widget";
import Notifications from "./notifications";
import SettingsService from "./settings";
@@ -86,6 +85,7 @@ const task = async (event: { taskId: string; timeout: boolean }) => {
const taskId = event.taskId;
const isTimeout = event.timeout; // <-- true when your background-time has expired.
if (isTimeout) {
console.log(`BACKGROUND FETCH TIMEOUT: ${taskId}`);
BackgroundFetch.finish(taskId);
return;
}
@@ -112,13 +112,12 @@ async function onBackgroundSyncStarted() {
useUserStore.getState().setSyncing(false);
}
await Notifications.setupReminders();
NotePreviewWidget.updateNotes();
deleteDCacheFiles();
DatabaseLogger.info("BACKGROUND SYNC COMPLETE");
} catch (e) {
useUserStore.getState().setSyncing(false);
DatabaseLogger.error(e as Error);
console.log("BACKGROUND SYNC ERROR", (e as Error).message);
}
}
@@ -143,6 +142,7 @@ const onBoot = async () => {
DatabaseLogger.info("BOOT TASK COMPLETE");
} catch (e) {
DatabaseLogger.error(e as Error);
console.log(e);
}
};

View File

@@ -30,11 +30,7 @@ import { DatabaseLogger, db } from "../common/database";
import filesystem, { FileStorage } from "../common/filesystem";
import { cacheDir, copyFileAsync } from "../common/filesystem/utils";
import { presentDialog } from "../components/dialog/functions";
import {
endProgress,
startProgress,
updateProgress
} from "../components/dialogs/progress";
import { endProgress, updateProgress } from "../components/dialogs/progress";
import { eCloseSheet } from "../utils/events";
import { sleep } from "../utils/time";
import { ToastManager, eSendEvent, presentSheet } from "./event-manager";
@@ -124,17 +120,13 @@ async function presentBackupCompleteSheet(backupFilePath: string) {
Share.open({
url: backupFilePath,
failOnCancel: false
}).catch(() => {
/* empty */
});
}).catch(console.log);
} else {
FileViewer.open(backupFilePath, {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
} as any).catch(() => {
/* empty */
});
} as any).catch(console.log);
}
},
actionText: strings.share()
@@ -159,7 +151,6 @@ async function updateNextBackupTime(type: "full" | "partial") {
[type === "full" ? "lastFullBackupDate" : "lastBackupDate"]: Date.now()
});
}
let backupRunning = false;
/**
* @param {boolean=} progress
* @param {string=} context
@@ -169,23 +160,9 @@ async function run(
progress = false,
context?: string,
backupType: "full" | "partial" = "partial"
): Promise<{
path?: string;
error?: Error;
report?: boolean;
}> {
if (backupRunning) {
if (progress) {
startProgress({
title: strings.backingUpData(backupType),
paragraph: strings.backupDataDesc(),
progress: "Backup in progress...",
canHideProgress: true
});
}
return {};
}
backupRunning = true;
) {
console.log("Creating backup:", backupType, progress, context);
const androidBackupDirectory = (await checkBackupDirExists(
false,
context
@@ -213,11 +190,10 @@ async function run(
);
if (progress) {
startProgress({
presentSheet({
title: strings.backingUpData(backupType),
paragraph: strings.backupDataDesc(),
progress: "Preparing backup...",
canHideProgress: true
progress: true
});
}
@@ -227,9 +203,7 @@ async function run(
? `${path}/${backupFileName}.nnbackupz`
: `${cacheDir}/${backupFileName}.nnbackupz`;
await RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
await RNFetchBlob.fs.mkdir(zipSourceFolder);
const attachmentsDir = zipSourceFolder + "/attachments";
@@ -239,7 +213,6 @@ async function run(
try {
const user = await db.user.getUser();
DatabaseLogger.info(`Backup started: ${backupType}`);
for await (const file of db.backup.export({
type: "mobile",
encrypt: SettingsService.get().encryptedBackup && !!user,
@@ -267,16 +240,12 @@ async function run(
}
}
DatabaseLogger.info(`Backup complete: ${backupType}. Creating zip file...`);
updateProgress({
progress: "Creating backup zip file..."
});
await zip(zipSourceFolder, zipOutputFile);
DatabaseLogger.info(`Backup zip file created: ${zipOutputFile}`);
if (Platform.OS === "android") {
// Move the zip to user selected directory.
const file = await ScopedStorage.createFile(
@@ -284,18 +253,19 @@ async function run(
`${backupFileName}.nnbackupz`,
"application/nnbackupz"
);
DatabaseLogger.info("Copying backup file to user selected directory...");
console.log("Copying zip file...");
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
DatabaseLogger.info("Backup saved to user selected directory.");
console.log("Copied zip file...");
path = file.uri;
} else {
path = zipOutputFile;
}
cleanupAfterBackup(zipSourceFolder, zipOutputFile);
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
}
updateNextBackupTime(backupType || "partial");
@@ -306,8 +276,6 @@ async function run(
const canShowCompletionStatus =
progress && SettingsService.get().showBackupCompleteSheet;
backupRunning = false;
if (context) {
return {
path: path
@@ -325,11 +293,11 @@ async function run(
type: "success",
context: "global"
});
return {
path: path
};
} catch (e) {
backupRunning = false;
ToastManager.error(e as Error, strings.backupFailed(), context || "global");
if (
@@ -340,34 +308,28 @@ async function run(
return run(progress, context, backupType);
}
cleanupAfterBackup(zipSourceFolder, zipOutputFile);
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
}
DatabaseLogger.error(e);
await sleep(300);
if (progress) {
endProgress();
}
return {
error: e as Error,
error: e,
report: true
};
}
}
function cleanupAfterBackup(zipSourceFolder: string, zipOutputFile: string) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
/* empty */
});
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(() => {
/* empty */
});
}
}
async function checkBackupRequired(
type: "daily" | "off" | "useroff" | "weekly" | "monthly" | "never",
lastBackupDateType: "lastBackupDate" | "lastFullBackupDate" = "lastBackupDate"
) {
console.log(lastBackupDateType, type);
if (type === "off" || type === "useroff" || type === "never" || !type) return;
const now = Date.now();
const lastBackupDate = SettingsService.getProperty(lastBackupDateType) as
@@ -400,7 +362,9 @@ const checkAndRun = async () => {
if (await checkBackupRequired(settings?.reminder)) {
try {
await run();
} catch (e) {}
} catch (e) {
console.log(e);
}
}
};

View File

@@ -123,6 +123,7 @@ export type PresentSheetOptions = {
};
export function presentSheet(data: Partial<PresentSheetOptions>) {
console.log("PRESENTING...");
eSendEvent(eOpenSheet, data);
}

View File

@@ -85,7 +85,7 @@ async function resolveFileFunctions(
`/export_${Date.now()}`
);
await RNFetchBlob.fs.mkdir(exportCacheFolder).catch((e) => {});
await RNFetchBlob.fs.mkdir(exportCacheFolder).catch((e) => console.log(e));
const mkdir = async (dir: string) => {
const folder = join(exportCacheFolder, dir);
@@ -96,7 +96,7 @@ async function resolveFileFunctions(
const writeFile = async (path: string, result: string) => {
const cacheFilePath = join(exportCacheFolder, path);
console.log(cacheFilePath, result.length);
await RNFetchBlob.fs.writeFile(
cacheFilePath,
result,
@@ -194,7 +194,7 @@ async function bulkExport(
try {
await exportNoteToFile(item, type, mkdir, writeFile);
} catch (e) {
/* empty */
console.log(item.type, e);
}
} else if (item.type === "attachment") {
currentAttachmentProgress += 1;
@@ -202,11 +202,11 @@ async function bulkExport(
try {
await exportAttachmentToFile(item, mkdir, cacheFolder);
} catch (e) {
/* empty */
console.log(item.path, e);
}
}
}
console.log(cacheFolder);
return createZip(totalNotes, cacheFolder, type, path, callback);
}
@@ -240,7 +240,7 @@ async function exportNote(
noteItem = item;
await exportNoteToFile(item, type, mkdir, writeFile);
} catch (e) {
/* empty */
console.log("exportNoteToFile", item.type, e);
}
} else if (item.type === "attachment") {
currentAttachmentProgress += 1;
@@ -249,12 +249,13 @@ async function exportNote(
hasAttachments = true;
await exportAttachmentToFile(item, mkdir, cacheFolder);
} catch (e) {
/* empty */
console.log("exportAttachmentToFile", item.path, e);
}
}
}
if (!hasAttachments) {
console.log("creating file...");
return createFile(noteItem as ExportableNote, type, path, cacheFolder);
} else {
return createZip(1, cacheFolder, type, path, callback);
@@ -332,10 +333,10 @@ async function createFile(
filePath = originalPath.replace(`${ext}`, "") + "_" + id + ext;
id++;
}
console.log("path", filePath);
await RNFetchBlob.fs.mv(exportedFile, filePath);
}
console.log("file moved...");
return {
filePath: filePath,
fileDir: path,

View File

@@ -1,76 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import { db } from "../common/database";
import ReminderSheet from "../components/sheets/reminder";
import { setAppState } from "../screens/editor/tiptap/utils";
import { eOnLoadNote } from "../utils/events";
import { tabBarRef } from "../utils/global-refs";
import { NotesnookModule } from "../utils/notesnook-module";
import { eSendEvent } from "./event-manager";
const launchIntent = Platform.OS === "ios" ? {} : NotesnookModule.getIntent();
let used = false;
let launched = false;
export const IntentService = {
getLaunchIntent() {
if (used || Platform.OS === "ios") return null;
used = true;
return launchIntent;
},
onLaunch() {
if (launched || Platform.OS === "ios") return;
launched = true;
if (launchIntent["com.streetwriters.notesnook.OpenNoteId"]) {
setAppState({
movedAway: false,
editing: true,
timestamp: Date.now(),
noteId: launchIntent["com.streetwriters.notesnook.OpenNoteId"]
});
}
},
async onAppStateChanged() {
if (Platform.OS === "ios") return;
try {
const intent = NotesnookModule.getIntent();
if (intent["com.streetwriters.notesnook.OpenNoteId"]) {
const note = await db.notes.note(
intent["com.streetwriters.notesnook.OpenNoteId"]
);
if (note) {
eSendEvent(eOnLoadNote, {
item: note
});
tabBarRef.current?.goToPage(1, false);
}
} else if (intent["com.streetwriters.notesnook.OpenReminderId"]) {
const reminder = await db.reminders.reminder(
intent["com.streetwriters.notesnook.OpenReminderId"]
);
if (reminder) ReminderSheet.present(reminder);
} else if (intent["com.streetwriters.notesnook.NewReminder"]) {
ReminderSheet.present();
}
} catch (e) {
/* empty */
}
}
};

View File

@@ -122,6 +122,7 @@ function queueRoutesForUpdate(...routesToUpdate: RouteName[]) {
}
function navigate<T extends RouteName>(screen: T, params?: RouteParams[T]) {
console.log(`Navigation.navigate ${screen} route`);
rootNavigatorRef.current?.navigate(screen as any, params);
}
@@ -130,14 +131,17 @@ function goBack() {
}
function push<T extends RouteName>(screen: T, params: RouteParams[T]) {
console.log(`Navigation.push ${screen} route`);
rootNavigatorRef.current?.dispatch(StackActions.push(screen as any, params));
}
function replace<T extends RouteName>(screen: T, params: RouteParams[T]) {
console.log(`Navigation.replace ${screen} route`);
rootNavigatorRef.current?.dispatch(StackActions.replace(screen, params));
}
function popToTop() {
console.log(`Navigation.popToTop`);
rootNavigatorRef.current?.dispatch(StackActions.popToTop());
}

View File

@@ -1,45 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note } from "@notesnook/core";
import { db } from "../common/database";
import { NotesnookModule } from "../utils/notesnook-module";
import { Platform } from "react-native";
let timer: NodeJS.Timeout;
export const NotePreviewWidget = {
updateNotes: () => {
if (Platform.OS !== "android") return;
clearTimeout(timer);
timer = setTimeout(async () => {
const noteIds = await NotesnookModule.getWidgetNotes();
for (const id of noteIds) {
const newNote = await db.notes.note(id);
if (!newNote) continue;
NotesnookModule.updateWidgetNote(id, JSON.stringify(newNote));
}
}, 500);
},
updateNote: async (id: string, note: Note) => {
if (Platform.OS !== "android") return;
if (id && (await NotesnookModule.hasWidgetNote(id))) {
NotesnookModule.updateWidgetNote(id, JSON.stringify(note));
}
}
};

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { isReminderActive, Reminder } from "@notesnook/core";
import { Reminder } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import notifee, {
AndroidStyle,
@@ -43,7 +43,6 @@ import { useRelationStore } from "../stores/use-relation-store";
import { useReminderStore } from "../stores/use-reminder-store";
import { useSettingStore } from "../stores/use-setting-store";
import { useUserStore } from "../stores/use-user-store";
import { eOnLoadNote } from "../utils/events";
import { tabBarRef } from "../utils/global-refs";
import { convertNoteToText } from "../utils/note-to-text";
import { NotesnookModule } from "../utils/notesnook-module";
@@ -52,7 +51,6 @@ import { DDS } from "./device-detection";
import { eSendEvent } from "./event-manager";
import Navigation from "./navigation";
import SettingsService from "./settings";
import { getFormattedReminderTime } from "@notesnook/common";
let pinned: DisplayedNotification[] = [];
@@ -122,7 +120,7 @@ const onEvent = async ({ type, detail }: Event) => {
await scheduleNotification(reminder);
}
}
updateRemindersForWidget();
return;
}
if (type === EventType.PRESS) {
@@ -143,7 +141,7 @@ const onEvent = async ({ type, detail }: Event) => {
}
editorState().movedAway = false;
const noteId = notification?.id;
console.log("NOTE ID", noteId);
loadNote(noteId as string, true);
}
@@ -169,7 +167,6 @@ const onEvent = async ({ type, detail }: Event) => {
);
useRelationStore.getState().update();
useReminderStore.getState().refresh();
updateRemindersForWidget();
break;
}
case "REMINDER_DISABLE": {
@@ -187,7 +184,6 @@ const onEvent = async ({ type, detail }: Event) => {
);
useRelationStore.getState().update();
useReminderStore.getState().refresh();
updateRemindersForWidget();
break;
}
case strings.unpin(): {
@@ -239,9 +235,11 @@ const onEvent = async ({ type, detail }: Event) => {
try {
if (!globalThis["IS_MAIN_APP_RUNNING" as never]) {
await db.sync({ type: "send", force: false });
} else {
console.log("main app running, skipping sync");
}
} catch (e) {
/* empty */
console.log(e, (e as Error).stack);
}
}
Navigation.queueRoutesForUpdate();
@@ -251,35 +249,6 @@ const onEvent = async ({ type, detail }: Event) => {
}
};
type ReminderWithFormattedTime = Reminder & {
formattedTime?: string;
};
async function updateRemindersForWidget() {
if (Platform.OS === "ios") return;
const reminders: ReminderWithFormattedTime[] = await db.reminders?.all.items(
undefined,
{
sortBy: "dueDate",
sortDirection: "asc"
}
);
const activeReminders = [];
if (!reminders) return;
for (const reminder of reminders) {
if (isReminderActive(reminder)) {
reminder.formattedTime = getFormattedReminderTime(reminder);
activeReminders.push(reminder);
}
}
NotesnookModule.setString(
"appPreview",
"remindersList",
JSON.stringify(activeReminders)
);
NotesnookModule.updateReminderWidget();
}
async function setupIOSCategories() {
try {
if (Platform.OS === "ios") {
@@ -319,7 +288,7 @@ async function setupIOSCategories() {
}
}
} catch (e) {
/* empty */
console.log("ERROR in setupIOSCategories", e);
}
}
@@ -429,9 +398,8 @@ async function scheduleNotification(
trigger
);
}
updateRemindersForWidget();
} catch (e) {
/* empty */
console.log("Schedule notification", e);
}
}
@@ -450,10 +418,19 @@ async function loadNote(id: string, jump: boolean) {
})
);
const isLocked = await db.vaults.itemExists({
type: "note",
id: id
});
const tab = useTabStore.getState().getTabForNote(id);
if (useTabStore.getState().currentTab !== tab) {
eSendEvent(eOnLoadNote, {
note: note
if (tab !== undefined) {
useTabStore.getState().focusTab(tab);
} else {
useTabStore.getState().focusPreviewTab(id, {
noteId: id,
readonly: note.readonly,
noteLocked: isLocked
});
}
}
@@ -565,7 +542,7 @@ async function displayNotification({
}
});
} catch (e) {
/* empty */
console.log(e);
}
}
@@ -966,7 +943,6 @@ async function setupReminders(checkNeedsScheduling = false) {
trigger.notification.id &&
notifee.cancelTriggerNotification(trigger.notification.id as string)
);
updateRemindersForWidget();
}
async function pinNote(id: string) {
@@ -987,7 +963,7 @@ async function pinNote(id: string) {
id: note.id
});
} catch (e) {
/* empty */
console.log(e);
}
}
const Events = {
@@ -1012,8 +988,7 @@ const Notifications = {
getChannelId,
isNotePinned,
pinNote,
Events,
updateRemindersForWidget
Events
};
export default Notifications;

View File

@@ -76,7 +76,9 @@ async function setPremiumStatus() {
products = await RNIap.getSubscriptions({
skus: itemSkus
});
} catch (e) {}
} catch (e) {
console.log("subscriptions: ", e);
}
if (premiumStatus === 0 && !__DEV__) {
SettingsService.reset();
}
@@ -297,6 +299,7 @@ const subscriptions = {
}
};
console.log("Subscription.verify", requestData);
try {
let result = await fetch(
__DEV__
@@ -304,7 +307,7 @@ const subscriptions = {
: "https://payments.streetwriters.co/apple/verify",
requestData
);
console.log("Subscribed", result);
let text = await result.text();
if (!result.ok) {
@@ -315,7 +318,9 @@ const subscriptions = {
} else {
await subscriptions.clear(subscription);
}
} catch (e) {}
} catch (e) {
console.log("subscription error", e);
}
}
}
},

Some files were not shown because too many files have changed in this diff Show More