Compare commits

..

1 Commits

Author SHA1 Message Date
Ammar Ahmed
5294131370 mobile: fix loading empty conent in editor on tablets 2024-01-26 23:00:48 +05:00
168 changed files with 1564 additions and 3015 deletions

View File

@@ -1,4 +1,4 @@
name: Publish @notesnook/android
name: Publish @notesnook/mobile
on: workflow_dispatch
@@ -33,14 +33,13 @@ jobs:
- name: Sign app bundle for Playstore release
id: sign_app
uses: r0adkll/sign-android-release@master
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Publish to Playstore
@@ -66,7 +65,6 @@ jobs:
alias: ${{ secrets.PUBLIC_ALIAS }}
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Rename apk files

View File

@@ -18,21 +18,6 @@ on:
required: true
default: true
description: "Publish on GitHub releases?"
build-windows:
type: boolean
required: true
default: true
description: "Build for Windows?"
build-linux:
type: boolean
required: true
default: true
description: "Build for Linux?"
build-mac:
type: boolean
required: true
default: true
description: "Build for macOS?"
jobs:
build:
@@ -86,7 +71,6 @@ jobs:
build-macos:
name: Build for macOS
needs: build
if: inputs.build-mac
runs-on: macos-12
steps:
@@ -179,7 +163,6 @@ jobs:
build-linux:
name: Build for Linux
needs: build
if: inputs.build-linux
runs-on: ubuntu-22.04
steps:
@@ -238,22 +221,12 @@ jobs:
build-windows:
name: Build for Windows
needs: build
if: inputs.build-windows
runs-on: windows-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Install AzureCodeSigning
run: Install-Module -Name AzureCodeSigning -RequiredVersion 0.3.0 -Force -Repository PSGallery
shell: pwsh
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -274,9 +247,6 @@ jobs:
- name: Publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
run: |
if ($${{ inputs.publish-github }} -eq $true) {
yarn electron-builder --win --publish always

View File

@@ -1,67 +0,0 @@
name: Publish @notesnook/ios
on: workflow_dispatch
jobs:
build:
runs-on: macos-13
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=mobile
- name: Build packages
run: npx nx run @notesnook/mobile:build
- name: Install Pods
run: npm run prepare:ios
- name: Build iOS App
uses: yukiarrr/ios-build-action@v1.11.2
with:
bundle-identifier: org.streetwriters.notesnook
scheme: Notesnook
configuration: "Release"
export-options: apps/mobile/native/ios/ExportOptions.plist
project-path: apps/mobile/native/ios/Notesnook.xcodeproj
workspace-path: apps/mobile/native/ios/Notesnook.xcworkspace
update-targets: |
Notesnook
Make Note
NotesWidgetExtension
disable-targets: Notesnook-tvOS,Notesnook-tvOSTests,NotesnookTests
code-signing-identity: Apple Distribution
team-id: ${{ secrets.APPLE_TEAM_ID }}
p12-base64: ${{ secrets.APPLE_CERTIFICATE_P12 }}
certificate-password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }}
app-store-connect-api-key-issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
app-store-connect-api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
app-store-connect-api-key-base64: ${{ secrets.APPSTORE_CONNECT_API_KEY_BASE64 }}
output-path: Notesnook.ipa
mobileprovision-base64: |
${{ secrets.APPLE_MOBILE_PROVISION_APP }}
${{ secrets.APPLE_MOBILE_PROVISION_SHARE }}
${{ secrets.APPLE_MOBILE_PROVISION_WIDGET }}
- name: 'Upload app to TestFlight'
uses: apple-actions/upload-testflight-build@v1
with:
app-path: Notesnook.ipa
issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
api-key-id: ${{ secrets.API_KEY_ID }}
api-private-key: ${{ secrets.API_KEY }}
- name: Upload to Github
uses: actions/upload-artifact@v2
with:
name: Notesnook.ipa
path: Notesnook.ipa

View File

@@ -1,11 +1,10 @@
name: Publish @notesnook/web Beta
on:
on:
workflow_dispatch:
push:
branches:
- master
- '!v3-beta'
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
@@ -23,7 +22,6 @@ on:
- synchronize
branches:
- master
- '!v3-beta'
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"

View File

@@ -1,40 +0,0 @@
name: Publish @notesnook/web v3 Beta
on:
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate beta build
run: npm run build:beta:web
- name: Publish to Cloudflare Pages
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
id: pages
with:
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
cloudflare-project-name: notesnook-v3-app
directory: ./apps/web/build
github-token: ${{ secrets.GITHUB_TOKEN }}
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}

3
.gitignore vendored
View File

@@ -9,5 +9,4 @@ nx-cloud.env
.env.local
.nyc_output
site
node_modules.backup
.nx
node_modules.backup

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "2.6.16",
"version": "2.6.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "2.6.16",
"version": "2.6.14",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.6.16",
"version": "2.6.14",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -135,10 +135,6 @@
]
}
],
"signingHashAlgorithms": [
"sha256"
],
"sign": "./sign.js",
"icon": "assets/icons/app.ico"
},
"portable": {

View File

@@ -1,60 +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/>.
*/
const { writeFileSync, rmSync, readFileSync } = require("fs");
const { execSync } = require("child_process");
const { relative, join } = require("path");
module.exports = async function (configuration) {
const Endpoint = "https://weu.codesigning.azure.net";
const CodeSigningAccountName = "Notesnook";
const CertificateProfileName = "Notesnook";
const FileDigest = configuration.hash.toUpperCase();
const TimestampRfc3161 = "http://timestamp.acs.microsoft.com";
const TimestampDigest = configuration.hash.toUpperCase();
const Description = "The Notesnook app";
const DescriptionUrl = "https://notesnook.com/";
const FilesCatalog = createCatalog(configuration.path);
const command = `Invoke-AzureCodeSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
console.debug("Signing", configuration.path, "using command", command);
psexec(command);
console.debug("Signed", configuration.path);
deleteCatalog();
};
function createCatalog(path) {
writeFileSync("_catalog", relative(__dirname, path));
return join(__dirname, "_catalog");
}
function deleteCatalog() {
rmSync("_catalog");
}
function psexec(cmd) {
return execSync(cmd, {
env: process.env,
stdio: "inherit",
shell: "pwsh"
});
}

View File

@@ -54,14 +54,6 @@ export const osIntegrationRouter = t.router({
config.zoomFactor = factor;
}),
proxyRules: t.procedure.query(() => config.proxyRules),
setProxyRules: t.procedure
.input(z.string().optional())
.mutation(({ input: proxyRules }) => {
globalThis.window?.webContents.session.setProxy({ proxyRules });
config.proxyRules = proxyRules || "";
}),
privacyMode: t.procedure.query(() => config.privacyMode),
setPrivacyMode: t.procedure
.input(z.object({ enabled: z.boolean() }))

View File

@@ -118,13 +118,5 @@ export const spellCheckerRouter = t.router({
.mutation(({ input: { enabled } }) => {
globalThis.window?.webContents.session.setSpellCheckerEnabled(enabled);
config.isSpellCheckerEnabled = enabled;
}),
words: t.procedure.query(() =>
globalThis.window?.webContents.session.listWordsInSpellCheckerDictionary()
),
deleteWord: t.procedure.input(z.string()).mutation(({ input: word }) => {
globalThis.window?.webContents.session.removeWordFromSpellCheckerDictionary(
word
);
})
})
});

View File

@@ -112,7 +112,6 @@ async function createWindow() {
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
"http://dictionaries.notesnook.com/"
);
mainWindow.webContents.session.setProxy({ proxyRules: config.proxyRules });
mainWindow.once("closed", () => {
globalThis.window = null;

View File

@@ -25,11 +25,7 @@ Type=Application
Version=${app.getVersion()}
Name=${app.getName()}
Comment=${app.getName()} startup script
Exec=${
process.env.APPIMAGE
? `${process.env.APPIMAGE}${hidden ? " --hidden" : ""}`
: `${process.execPath}${hidden ? " --hidden" : ""}`
}
Exec=${process.execPath}${hidden ? " --hidden" : ""}
StartupNotify=false
Terminal=false`;

View File

@@ -41,8 +41,7 @@ export const config = {
isSpellCheckerEnabled: true,
zoomFactor: 1,
theme: nativeTheme.themeSource,
automaticUpdates: true,
proxyRules: ""
automaticUpdates: true
};
type ConfigKey = keyof typeof config;

View File

@@ -131,17 +131,6 @@ function setupMenu() {
);
}
if (params.mediaType === "image")
menu.append(
new MenuItem({
id: "copy-image",
label: "Copy Image",
click() {
globalThis.window?.webContents.copyImageAt(params.x, params.y);
}
})
);
if (params.isEditable)
menu.append(
new MenuItem({

View File

@@ -17,12 +17,13 @@ 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 "@azure/core-asynciterator-polyfill";
import SettingsService from "./services/settings";
import {
THEME_COMPATIBILITY_VERSION,
useThemeEngineStore
} from "@notesnook/theme";
import React, { useEffect } from "react";
import { I18nManager, View } from "react-native";
import { View } from "react-native";
import "react-native-gesture-handler";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
@@ -31,16 +32,11 @@ import { withErrorBoundry } from "./components/exception-handler";
import GlobalSafeAreaProvider from "./components/globalsafearea";
import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import { themeTrpcClient } from "./screens/settings/theme-selector";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
import { TipManager } from "./services/tip-manager";
import { useThemeStore } from "./stores/use-theme-store";
import { useUserStore } from "./stores/use-user-store";
I18nManager.allowRTL(false);
I18nManager.forceRTL(false);
I18nManager.swapLeftAndRightInRTL(false);
import { themeTrpcClient } from "./screens/settings/theme-selector";
SettingsService.checkOrientation();
const App = () => {

View File

@@ -34,8 +34,7 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
import { createCacheDir, exists } from "./io";
import { IOS_APPGROUPID } from "../../utils/constants";
import { createCacheDir } from "./io";
export const FileDownloadStatus = {
Success: 1,
@@ -182,9 +181,7 @@ export default async function downloadAttachment(
silent: false,
cache: false,
throwError: false,
groupId: undefined,
base64: false,
text: false
groupId: undefined
}
) {
await createCacheDir();
@@ -215,17 +212,10 @@ export default async function downloadAttachment(
options.groupId || attachment.metadata.hash,
attachment.metadata.hash
);
if (!(await exists(attachment.metadata.hash))) {
if (
!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))
)
return;
}
if (options.base64 || options.text) {
return await db.attachments.read(
attachment.metadata.hash,
options.base64 ? "base64" : "text"
);
}
let filename = getFileNameWithExtension(
attachment.metadata.filename,
@@ -243,8 +233,7 @@ export default async function downloadAttachment(
mime: attachment.metadata.type,
fileName: options.cache ? undefined : filename,
uri: options.cache ? undefined : folder.uri,
chunkSize: attachment.chunkSize,
appGroupId: IOS_APPGROUPID
chunkSize: attachment.chunkSize
};
let fileUri = await Sodium.decryptFile(

View File

@@ -36,13 +36,9 @@ export async function readEncrypted(filename, key, cipherData) {
}
const attachment = db.attachments.attachment(filename);
const isPng = !attachment.metadata.type
? false
: /(png)/g.test(attachment?.metadata.type);
const isJpeg = !attachment.metadata.type
? false
: /(jpeg|jpg)/g.test(attachment?.metadata.type);
const isPng = /(png)/g.test(attachment?.metadata.type);
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
console.log("decrypting....");
let output = await Sodium.decryptFile(
key,
{

View File

@@ -48,7 +48,7 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { formatBytes } from "@notesnook/common";
const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
const Actions = ({ attachment, setAttachments, fwdRef }) => {
const { colors } = useThemeColors();
const contextId = attachment.metadata.hash;
const [filename, setFilename] = useState(attachment.metadata.filename);
@@ -64,7 +64,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
name: "Download",
onPress: async () => {
if (currentProgress) {
await db.fs.cancel(attachment.metadata.hash);
await db.fs.cancel(attachment.metadata.hash, "download");
useAttachmentStore.getState().remove(attachment.metadata.hash);
}
downloadAttachment(attachment.metadata.hash, false);
@@ -117,7 +117,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
});
}
setAttachments();
setAttachments([...db.attachments.all]);
setLoading({
name: null
});
@@ -140,7 +140,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
filename: value
});
setFilename(value);
setAttachments();
setAttachments([...db.attachments.all]);
}
},
positiveText: "Rename"
@@ -152,8 +152,8 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
name: "Delete",
onPress: async () => {
await db.attachments.remove(attachment.metadata.hash, false);
setAttachments();
close();
setAttachments([...db.attachments.all]);
eSendEvent(eCloseSheet, contextId);
},
icon: "delete-outline"
}
@@ -362,13 +362,8 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
Actions.present = (attachment, set, context) => {
presentSheet({
context: context,
component: (ref, close) => (
<Actions
fwdRef={ref}
setAttachments={set}
close={close}
attachment={attachment}
/>
component: (ref) => (
<Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
)
});
};

View File

@@ -132,7 +132,7 @@ export const AttachmentItem = ({
activeOpacity={0.9}
onPress={() => {
if (encryption || !pressable) return;
db.fs.cancel(attachment.metadata.hash);
db.fs.cancel(attachment.metadata.hash, currentProgress.type);
setCurrentProgress(null);
}}
style={{

View File

@@ -49,7 +49,7 @@ export const AttachmentDialog = ({ note }) => {
const { height } = useSettingStore((state) => state.dimensions);
const [attachments, setAttachments] = useState(
note
? db.attachments.ofNote(note?.id, "all")
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])]
);
@@ -59,8 +59,8 @@ export const AttachmentDialog = ({ note }) => {
const [currentFilter, setCurrentFilter] = useState("all");
const onChangeText = (text) => {
const attachments = note?.id
? db.attachments.ofNote(note?.id, "all")
const attachments = note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
attachmentSearchValue.current = text;
@@ -68,7 +68,7 @@ export const AttachmentDialog = ({ note }) => {
!attachmentSearchValue.current ||
attachmentSearchValue.current === ""
) {
setAttachments(filterAttachments(currentFilter));
setAttachments([...attachments]);
}
clearTimeout(searchTimer.current);
searchTimer.current = setTimeout(() => {
@@ -77,16 +77,13 @@ export const AttachmentDialog = ({ note }) => {
attachmentSearchValue.current
);
if (results.length === 0) return;
setAttachments(filterAttachments(currentFilter, results));
setAttachments(results);
}, 300);
};
const renderItem = ({ item }) => (
<AttachmentItem
setAttachments={() => {
setAttachments(filterAttachments(currentFilter));
}}
setAttachments={setAttachments}
attachment={item}
context="attachments-list"
/>
@@ -136,13 +133,11 @@ export const AttachmentDialog = ({ note }) => {
}
];
const filterAttachments = (type, _attachments) => {
const attachments = _attachments
? _attachments
: note
? db.attachments.ofNote(note?.id, "all")
const filterAttachments = (type) => {
const attachments = note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
isDocument;
switch (type) {
case "all":
return attachments;

View File

@@ -26,15 +26,12 @@ import {
ToastEvent
} from "../../services/event-manager";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
import { eCloseSheet } from "../../utils/events";
import DialogHeader from "../dialog/dialog-header";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { Notice } from "../ui/notice";
import Seperator from "../ui/seperator";
import { Dialog } from "../dialog";
import BackupService from "../../services/backup";
import { sleep } from "../../utils/time";
export const ChangePassword = () => {
const passwordInputRef = useRef();
@@ -67,9 +64,6 @@ export const ChangePassword = () => {
}
setLoading(true);
try {
const result = await BackupService.run(false, "change-password-dialog");
if (!result) throw new Error("Failed to create backup");
await db.user.clearSessions();
await db.user.changePassword(oldPassword.current, password.current);
ToastEvent.show({
@@ -79,8 +73,6 @@ export const ChangePassword = () => {
});
setLoading(false);
eSendEvent(eCloseSheet);
await sleep(300);
eSendEvent(eOpenRecoveryKeyDialog);
} catch (e) {
setLoading(false);
ToastEvent.show({
@@ -100,7 +92,6 @@ export const ChangePassword = () => {
padding: 12
}}
>
<Dialog context="change-password-dialog" />
<DialogHeader
title="Change password"
paragraph="Enter your old and new passwords"
@@ -138,14 +129,7 @@ export const ChangePassword = () => {
/>
<Notice
text={`Changing password is an irreversible process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection.`}
type="alert"
/>
<View style={{ height: 10 }} />
<Notice
text={`Once your password is changed, please make sure to save the new account recovery key.`}
text="Changing password is a non-undoable process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection."
type="alert"
/>

View File

@@ -40,10 +40,7 @@ import { Button } from "../ui/button";
export const Dialog = ({ context = "global" }) => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [checked, setChecked] = useState(false);
const values = useRef({
inputValue: undefined
});
const [inputValue, setInputValue] = useState(null);
const inputRef = useRef();
const [dialogInfo, setDialogInfo] = useState({
title: "",
@@ -79,8 +76,7 @@ export const Dialog = ({ context = "global" }) => {
if (dialogInfo.positivePress) {
inputRef.current?.blur();
let result = await dialogInfo.positivePress(
values.current.inputValue || dialogInfo.defaultValue,
checked
inputValue || dialogInfo.defaultValue
);
if (result === false) {
return;
@@ -95,16 +91,14 @@ export const Dialog = ({ context = "global" }) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
setDialogInfo(data);
setChecked(false);
values.current.inputValue = data.defaultValue;
setVisible(true);
setInputValue(data.defaultValue);
},
[context]
);
const hide = () => {
setChecked(false);
values.current.inputValue = undefined;
setInputValue(null);
setVisible(false);
};
@@ -112,6 +106,7 @@ export const Dialog = ({ context = "global" }) => {
if (dialogInfo.onClose) {
await dialogInfo.onClose();
}
hide();
};
@@ -164,7 +159,7 @@ export const Dialog = ({ context = "global" }) => {
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(value) => {
values.current.inputValue = value;
setInputValue(value);
}}
testID="input-value"
secureTextEntry={dialogInfo.secureTextEntry}
@@ -181,10 +176,10 @@ export const Dialog = ({ context = "global" }) => {
<>
<Button
onPress={() => {
setChecked(!checked);
setInputValue(!inputValue);
}}
icon={
checked
inputValue
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
@@ -192,10 +187,9 @@ export const Dialog = ({ context = "global" }) => {
justifyContent: "flex-start"
}}
height={35}
iconSize={20}
width="100%"
title={dialogInfo.check.info}
type={checked ? dialogInfo.check.type : "gray"}
type={inputValue ? dialogInfo.check.type : "gray"}
/>
</>
) : null}

View File

@@ -101,7 +101,7 @@ export const SectionHeader = React.memo(
alignItems: "center"
}}
>
{index === 0 ? (
{index === 0 && (
<>
<Button
onPress={() => {
@@ -109,6 +109,7 @@ export const SectionHeader = React.memo(
component: <Sort screen={screen} type={type} />
});
}}
hidden={screen === "Reminders"}
tooltipText="Change sorting of items in list"
fwdRef={sortRef}
title={groupBy}
@@ -159,7 +160,7 @@ export const SectionHeader = React.memo(
size={SIZE.lg - 2}
/>
</>
) : null}
)}
</View>
</View>
);

View File

@@ -101,7 +101,6 @@ const SheetProvider = ({ context = "global" }) => {
setVisible(false);
setData(null);
}}
keyboardHandlerDisabled={data?.keyboardHandlerDisabled}
bottomPadding={!data.noBottomPadding}
enableGesturesInScrollView={
typeof data.enableGesturesInScrollView === "undefined"

View File

@@ -43,7 +43,6 @@ import Paragraph from "../../ui/typography/paragraph";
import { eSendEvent } from "../../../services/event-manager";
import { eCloseSheet } from "../../../utils/events";
import { requestInAppReview } from "../../../services/app-review";
import { Dialog } from "../../dialog";
const ExportNotesSheet = ({ notes, update }) => {
const { colors } = useThemeColors();
@@ -158,8 +157,6 @@ const ExportNotesSheet = ({ notes, update }) => {
</>
) : null}
<Dialog context="export-notes" />
<View style={styles.buttonContainer}>
{!exporting && !complete ? (
actions.map((item) => (
@@ -349,8 +346,7 @@ ExportNotesSheet.present = (notes, allNotes) => {
notes={allNotes ? db.notes.all : notes}
update={update}
/>
),
keyboardHandlerDisabled: true
)
});
};

View File

@@ -37,7 +37,7 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { requestInAppReview } from "../../../services/app-review";
const PublishNoteSheet = ({ note: item }) => {
const PublishNoteSheet = ({ note: item, update }) => {
const { colors } = useThemeColors();
const actionSheetRef = useRef();
@@ -48,7 +48,8 @@ const PublishNoteSheet = ({ note: item }) => {
const [note, setNote] = useState(item);
const [publishing, setPublishing] = useState(false);
const publishUrl =
note && `https://monogr.ph/${db?.monographs.monograph(note?.id)}`;
note &&
`https://monograph.notesnook.com/${db?.monographs.monograph(note?.id)}`;
const isPublished = note && db?.monographs.isPublished(note?.id);
const pwdInput = useRef();
const passwordValue = useRef();

View File

@@ -17,19 +17,17 @@ 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 { getFormattedDate } from "@notesnook/common";
import { EVENTS } from "@notesnook/core/dist/common";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import DocumentPicker, {
DocumentPickerResponse
} from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { unzip } from "react-native-zip-archive";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
import {
ToastEvent,
eSubscribeEvent,
@@ -37,6 +35,7 @@ import {
} from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { initialize } from "../../../stores";
import { useThemeColors } from "@notesnook/theme";
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
@@ -47,6 +46,9 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { unzip } from "react-native-zip-archive";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
@@ -182,7 +184,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file",
paragraph: "Please enter password of this backup file to restore it",
positiveText: "Restore",
secureTextEntry: true,
onClose: () => {
@@ -190,17 +192,10 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
resolve(undefined);
},
negativeText: "Cancel",
positivePress: async (password, isEncryptionKey) => {
resolve({
value: password,
isEncryptionKey
});
positivePress: async (password) => {
resolve(password);
resolved = true;
return true;
},
check: {
info: "Use encryption key",
type: "transparent"
}
});
});
@@ -240,8 +235,8 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}
};
const restoreBackup = async (backup, password, key) => {
await db.backup.import(backup, password, key);
const restoreBackup = async (backup, password) => {
await db.backup.import(backup, password);
await db.initCollections();
initialize();
ToastEvent.show({
@@ -284,7 +279,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}
let password;
let key;
console.log(`Found ${backupFiles?.length} files to restore from backup`);
for (const path of backupFiles) {
@@ -295,16 +289,10 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
if (parsed.encrypted && !password) {
console.log("Backup is encrypted...", "requesting password");
const { value, isEncryptionKey } = await withPassword();
if (isEncryptionKey) {
key = value;
} else {
password = value;
}
if (!password && !key) throw new Error("Failed to decrypt backup");
password = await withPassword();
if (!password) throw new Error("Failed to decrypt backup");
}
await db.backup.import(parsed, password, key);
await db.backup.import(parsed, password);
console.log("Imported", path);
}
// Remove files from cache
@@ -335,19 +323,10 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
async function restoreFromNNBackup(backup) {
try {
if (backup.data.iv && backup.data.salt) {
const { value, isEncryptionKey } = await withPassword();
let key;
let password;
if (isEncryptionKey) {
key = value;
} else {
password = value;
}
if (key || password) {
const password = await withPassword();
if (password) {
try {
await restoreBackup(backup, password, key);
await restoreBackup(backup, password);
close();
setRestoring(false);
} catch (e) {

View File

@@ -88,14 +88,10 @@ const Sort = ({ type, screen }) => {
? groupOptions.groupBy === "abc" ||
groupOptions.sortBy === "title"
? "A - Z"
: groupOptions.sortBy === "dueDate"
? "Earliest first"
: "Old - New"
: groupOptions.groupBy === "abc" ||
groupOptions.sortBy === "title"
? "Z - A"
: groupOptions.sortBy === "dueDate"
? "Latest first"
: "New - Old"
}
icon={
@@ -146,7 +142,6 @@ const Sort = ({ type, screen }) => {
/>
) : (
Object.keys(SORT).map((item) =>
(item === "dueDate" && screen !== "Reminders") ||
(item === "title" && groupOptions.groupBy !== "none") ||
((screen !== "Tags" || screen !== "Reminders") &&
item === "dateModified") ||

View File

@@ -37,11 +37,11 @@ const SheetWrapper = ({
onOpen,
closeOnTouchBackdrop = true,
onHasReachedTop,
keyboardMode,
overlay,
overlayOpacity = 0.3,
enableGesturesInScrollView = false,
bottomPadding = true,
keyboardHandlerDisabled
bottomPadding = true
}) => {
const localRef = useRef(null);
const { colors } = useThemeColors("sheet");
@@ -121,9 +121,8 @@ const SheetWrapper = ({
initialOffsetFromBottom={1}
onPositionChanged={onHasReachedTop}
closeOnTouchBackdrop={closeOnTouchBackdrop}
keyboardHandlerEnabled={
keyboardHandlerDisabled ? false : sheetKeyboardHandler
}
keyboardMode={keyboardMode}
keyboardHandlerEnabled={sheetKeyboardHandler}
closeOnPressBack={closeOnTouchBackdrop}
indicatorColor={colors.secondary.background}
onOpen={_onOpen}

View File

@@ -550,6 +550,15 @@ export const useActions = ({ close = () => null, item }) => {
}
async function exportNote() {
if (item.locked) {
ToastEvent.show({
heading: "Note is locked",
type: "error",
message: "Locked notes cannot be exported",
context: "local"
});
return;
}
ExportNotesSheet.present([item]);
}

View File

@@ -623,20 +623,15 @@ export const useAppEvents = () => {
}, [loading, onUserUpdated]);
const initializeDatabase = useCallback(async () => {
try {
if (!db.isInitialized) {
RNBootSplash.hide({ fade: true });
DatabaseLogger.info("Initializing database");
await db.init();
}
if (IsDatabaseMigrationRequired()) return;
initialize();
setLoading(false);
Walkthrough.init();
} catch (e) {
DatabaseLogger.error(e);
ToastEvent.error(e, "Error initializing database", "global");
if (!db.isInitialized) {
RNBootSplash.hide({ fade: true });
DatabaseLogger.info("Initializing database");
await db.init();
}
if (IsDatabaseMigrationRequired()) return;
initialize();
setLoading(false);
Walkthrough.init();
}, [IsDatabaseMigrationRequired]);
useEffect(() => {

View File

@@ -31,8 +31,7 @@
"url": "^0.11.0",
"validator": "^13.5.2",
"zustand": "^3.6.0",
"katex": "0.16.2",
"@readme/data-urls": "3.0.0"
"katex": "0.16.2"
},
"sideEffects": false
}

View File

@@ -45,7 +45,6 @@ import { EditorProps, useEditorType } from "./tiptap/types";
import { useEditor } from "./tiptap/use-editor";
import { useEditorEvents } from "./tiptap/use-editor-events";
import { editorController } from "./tiptap/utils";
import { useThemeColors } from "@notesnook/theme";
const style: ViewStyle = {
height: "100%",
@@ -94,6 +93,35 @@ const Editor = React.memo(
get: () => editor
}));
const onMediaDownloaded = useCallback(
({
hash,
groupId,
src,
attachmentType
}: {
hash: string;
groupId: string;
src: string;
attachmentType: string;
}) => {
if (groupId !== editor.note.current?.id) return;
editorController.current.markImageLoaded(hash);
if (attachmentType === "webclip") {
editor.commands.updateWebclip({
hash: hash,
src: src
});
} else {
editor.commands.updateImage({
hash: hash,
dataurl: src
});
}
},
[editor.commands, editor.note]
);
const onError = useCallback(() => {
renderKey.current =
renderKey.current === `editor-0` ? `editor-1` : `editor-0`;
@@ -102,11 +130,16 @@ const Editor = React.memo(
}, [editor]);
useEffect(() => {
const sub = [eSubscribeEvent("webview_reset", onError)];
const sub = [
eSubscribeEvent("webview_reset", onError),
EV.subscribe(EVENTS.mediaAttachmentDownloaded, onMediaDownloaded)
];
return () => {
sub.forEach((s) => s.unsubscribe());
EV.unsubscribe(EVENTS.mediaAttachmentDownloaded, onMediaDownloaded);
};
}, [onError]);
}, [onError, onMediaDownloaded]);
useLayoutEffect(() => {
setImmediate(() => {
@@ -177,7 +210,6 @@ export default Editor;
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
const readonly = useEditorStore((state) => state.readonly);
const keyboard = useKeyboard();
const { colors } = useThemeColors();
const onPress = async () => {
if (editor.note.current) {
@@ -193,7 +225,7 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
name="pencil-lock"
type="grayBg"
onPress={onPress}
color={colors.primary.accent}
color="accent"
customStyle={{
position: "absolute",
bottom: 60,

View File

@@ -17,7 +17,10 @@ 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 type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
import type {
Attachment,
AttachmentProgress
} from "@notesnook/editor/dist/extensions/attachment/index";
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
import { createRef, RefObject } from "react";
import { Platform } from "react-native";
@@ -200,16 +203,11 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
);
};
setAttachmentProgress = async (attachmentProgress: Partial<Attachment>) => {
setAttachmentProgress = async (attachmentProgress: AttachmentProgress) => {
await this.doAsync(
`editor && editor.commands.updateAttachment(${JSON.stringify(
`editor && editor.commands.setAttachmentProgress(${JSON.stringify(
attachmentProgress
)}, {
preventUpdate: true,
query: (attachment) => {
return attachment.hash === "${attachmentProgress.hash}";
}
})`
)})`
);
};

View File

@@ -36,6 +36,5 @@ export const EventTypes = {
contentchange: "editor-event:content-change",
reminders: "editor-event:reminders",
previewAttachment: "editor-event:preview-attachment",
copyToClipboard: "editor-events:copy-to-clipboard",
getAttachmentData: "editor-events:get-attachment-data"
copyToClipboard: "editor-events:copy-to-clipboard"
};

View File

@@ -53,7 +53,6 @@ export type Settings = {
dateFormat: string;
timeFormat: string;
fontScale: number;
markdownShortcuts: boolean;
};
export type EditorProps = {
@@ -98,7 +97,6 @@ export type SavePayload = {
type?: Content["type"];
sessionId?: string | null;
sessionHistoryId?: number;
ignoreEdit: boolean;
};
export type AppState = {

View File

@@ -66,7 +66,6 @@ import { EditorMessage, EditorProps, useEditorType } from "./types";
import { EditorEvents, editorState } from "./utils";
import { useNoteStore } from "../../../stores/use-notes-store";
import SettingsService from "../../../services/settings";
import downloadAttachment from "../../../common/filesystem/download-attachment";
const publishNote = async (editor: useEditorType) => {
const user = useUserStore.getState().user;
@@ -128,8 +127,6 @@ const showActionsheet = async (editor: useEditorType) => {
}
};
type ContentMessage = { html: string; ignoreEdit: boolean };
export const useEditorEvents = (
editor: useEditorType,
{ readonly: editorPropReadonly, noHeader, noToolbar }: Partial<EditorProps>
@@ -157,9 +154,6 @@ export const useEditorEvents = (
const defaultFontFamily = useSettingStore(
(state) => state.settings.defaultFontFamily
);
const markdownShortcuts = useSettingStore(
(state) => state.settings.markdownShortcuts
);
const tools = useDragState((state) => state.data);
@@ -205,8 +199,7 @@ export const useEditorEvents = (
fontFamily: SettingsService.get().defaultFontFamily,
dateFormat: db.settings?.getDateFormat(),
timeFormat: db.settings?.getTimeFormat(),
fontScale,
markdownShortcuts
fontScale
});
}, [
fullscreen,
@@ -226,8 +219,7 @@ export const useEditorEvents = (
dateFormat,
timeFormat,
loading,
fontScale,
markdownShortcuts
fontScale
]);
const onBackPress = useCallback(async () => {
@@ -338,16 +330,14 @@ export const useEditorEvents = (
if (editorMessage.type === EventTypes.content) {
editor.saveContent({
type: editorMessage.type,
content: (editorMessage.value as ContentMessage).html,
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit,
content: editorMessage.value as string,
forSessionId: editorMessage.sessionId
});
} else if (editorMessage.type === EventTypes.title) {
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value as string,
forSessionId: editorMessage.sessionId,
ignoreEdit: false
forSessionId: editorMessage.sessionId
});
}
@@ -426,44 +416,6 @@ export const useEditorEvents = (
break;
}
case 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",
silent: true,
groupId: editor.note.current?.id,
cache: true
} as any)
.then((data: any) => {
console.log(
"Got attachment data:",
!!data,
(editorMessage.value as any).resolverId
);
editor.postMessage(EditorEvents.attachmentData, {
resolverId: (editorMessage.value as any).resolverId,
data
});
})
.catch(() => {
console.log("Error downloading attachment data");
editor.postMessage(EditorEvents.attachmentData, {
resolverId: (editorMessage.value as any).resolverId,
data: undefined
});
});
break;
}
case EventTypes.pro:
if (editor.state.current?.isFocused) {
editor.state.current.isFocused = true;

View File

@@ -78,6 +78,7 @@ export const useEditor = (
const saveCount = useRef(0);
const lastContentChangeTime = useRef<number>(0);
const lock = useRef(false);
const loadedImages = useRef<{ [name: string]: boolean }>({});
const lockedSessionId = useRef<string>();
const loadingState = useRef<string>();
@@ -128,9 +129,12 @@ export const useEditor = (
const reset = useCallback(
async (resetState = true, resetContent = true) => {
currentNote.current?.id && db.fs?.cancel(currentNote.current.id);
currentNote.current?.id &&
db.fs?.cancel(currentNote.current.id, "download");
currentNote.current = null;
loadedImages.current = {};
currentContent.current = null;
clearTimeout(timers.current["loading-images"]);
sessionHistoryId.current = undefined;
saveCount.current = 0;
loadingState.current = undefined;
@@ -162,8 +166,7 @@ export const useEditor = (
data,
type,
sessionId: currentSessionId,
sessionHistoryId: currentSessionHistoryId,
ignoreEdit
sessionHistoryId: currentSessionHistoryId
}: SavePayload) => {
if (
readonly ||
@@ -195,12 +198,6 @@ export const useEditor = (
};
noteData.title = title;
if (ignoreEdit) {
console.log("Ignoring edits...");
noteData.dateEdited = note?.dateEdited;
}
if (data) {
noteData.content = {
data: data,
@@ -290,6 +287,58 @@ export const useEditor = (
}
}, []);
const getMediaToLoad = (previousContent?: string) => {
if (!currentNote.current?.id) return [];
const previousAttachments =
previousContent?.matchAll(/data-hash="(.+?)"/gm) || [];
const attachments =
currentContent.current?.data?.matchAll(/data-hash="(.+?)"/gm) || [];
const media: string[] = [];
const oldMatches = Array.from(previousAttachments).map((match) => match[1]);
const matches = Array.from(attachments).map((match) => match[1]);
for (let i = 0; i < matches.length; i++) {
const currentHash = matches[i];
const oldHash = oldMatches[i];
if (currentHash !== oldHash) {
media.push(currentHash);
loadedImages.current[currentHash] = false;
}
}
return media;
};
const markImageLoaded = (hash: string) => {
const attachment = loadedImages.current[hash];
if (typeof attachment === "boolean") {
loadedImages.current[hash] = true;
}
};
const loadImages = useCallback((previousContent?: string) => {
if (!currentNote.current?.id) return;
const timerId = "loading-images";
clearTimeout(timers.current[timerId]);
timers.current[timerId] = setTimeout(() => {
if (!currentNote.current?.id) return;
if (currentNote.current?.content?.isPreview) {
db.content?.downloadMedia(
currentNote.current?.id,
currentNote.current.content,
true
);
} else {
const media = getMediaToLoad(previousContent);
if (media.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, media);
}
}
}, 1000);
}, []);
const loadNote = useCallback(
async (
item: Omit<NoteType, "type"> & {
@@ -376,9 +425,18 @@ export const useEditor = (
}
}, 300);
overlay(false);
loadImages();
}
},
[commands, isDefaultEditor, loadContent, overlay, postMessage, reset]
[
commands,
isDefaultEditor,
loadContent,
loadImages,
overlay,
postMessage,
reset
]
);
const lockNoteWithVault = useCallback((note: NoteType) => {
@@ -408,6 +466,8 @@ export const useEditor = (
lock.current = true;
const previousContent = currentContent.current?.data;
if (data.type === "tiptap") {
if (!currentNote.current.locked && isContentEncrypted) {
lockNoteWithVault(note);
@@ -440,8 +500,18 @@ export const useEditor = (
}
lock.current = false;
if (data.type === "tiptap") {
loadImages(previousContent);
db.eventManager.subscribe(
EVENTS.syncCompleted,
() => {
loadImages(previousContent);
},
true
);
}
},
[lockNoteWithVault, postMessage, commands]
[loadImages, lockNoteWithVault, postMessage, commands]
);
useEffect(() => {
@@ -461,14 +531,12 @@ export const useEditor = (
title,
content,
type,
forSessionId,
ignoreEdit
forSessionId
}: {
title?: string;
content?: string;
type: string;
forSessionId: string;
ignoreEdit: boolean;
}) => {
if (lock.current || lockedSessionId.current === forSessionId) return;
lastContentChangeTime.current = Date.now();
@@ -499,8 +567,7 @@ export const useEditor = (
type: "tiptap",
sessionId: forSessionId,
id: noteId,
sessionHistoryId: sessionHistoryId.current,
ignoreEdit
sessionHistoryId: sessionHistoryId.current
};
withTimer(
noteId || "newnote",
@@ -518,7 +585,7 @@ export const useEditor = (
}
saveNote(params);
},
ignoreEdit ? 0 : 150
150
);
},
[withTimer, onChange, saveNote]
@@ -622,6 +689,7 @@ export const useEditor = (
saveContent,
onContentChanged,
editorId: editorId,
markImageLoaded,
overlay,
postMessage
};

View File

@@ -50,8 +50,7 @@ export const EditorEvents: { [name: string]: string } = {
titleplaceholder: "native:titleplaceholder",
logger: "native:logger",
status: "native:status",
keyboardShown: "native:keyboardShown",
attachmentData: "native:attachment-data"
keyboardShown: "native:keyboardShown"
};
export function randId(prefix: string) {

View File

@@ -115,7 +115,7 @@ export const EditorWrapper = ({ width }) => {
style={{ height: 1, padding: 0, width: 1, position: "absolute" }}
blurOnSubmit={false}
/>
{/* <ProgressBar /> */}
<ProgressBar />
<Editor key="editor" withController={true} />
</KeyboardAvoidingViewIOS>
)}

View File

@@ -69,10 +69,10 @@ export const TrashIntervalPicker = createSettingsPicker({
db.settings.setTrashCleanupInterval(item);
},
formatValue: (item) => {
return item === -1 ? "Never" : item === 1 ? "Daily" : item + " days";
return item === -1 ? "Never" : item + " days";
},
getItemKey: (item) => item.toString(),
options: [-1, 1, 7, 30, 365],
options: [-1, 7, 30, 365],
compareValue: (current, item) => current === item,
premium: true
});

View File

@@ -584,13 +584,6 @@ export const settingsGroups: SettingSection[] = [
component: "title-format",
description: "Customize the formatting for new note title",
type: "component"
},
{
id: "toggle-markdown",
name: "Markdown shortcuts",
property: "markdownShortcuts",
description: "Toggle markdown in the editor",
type: "switch"
}
]
}

View File

@@ -65,11 +65,7 @@ const PLACEHOLDER_DATA = (trashCleanupInterval = 7) => ({
paragraph:
trashCleanupInterval === -1
? "Set automatic trash cleanup interval from Settings > Behaviour > Clean trash interval."
: `Trash gets automatically cleaned up ${
trashCleanupInterval === 1
? "daily."
: `after ${trashCleanupInterval} days.`
}`,
: `Items in the trash will be permanently deleted after after ${trashCleanupInterval} days.`,
button: null,
loading: "Loading trash items"
});

View File

@@ -213,12 +213,10 @@ async function run(progress, context) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
updateNextBackupTime();
let showBackupCompleteSheet =
progress && SettingsService.get().showBackupCompleteSheet;
let showBackupCompleteSheet = SettingsService.get().showBackupCompleteSheet;
if (context) return path;
await sleep(300);
if (showBackupCompleteSheet) {
presentBackupCompleteSheet(backupFilePath);
} else {
@@ -235,7 +233,7 @@ async function run(progress, context) {
return path;
} catch (e) {
await sleep(300);
progress && eSendEvent(eCloseSheet);
eSendEvent(eCloseSheet);
ToastEvent.error(e, "Backup failed!");
return null;
}

View File

@@ -18,19 +18,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { decode, EntityLevel } from "entities";
import { zipSync } from "fflate";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import RNHTMLtoPDF from "react-native-html-to-pdf-lite";
import * as ScopedStorage from "react-native-scoped-storage";
import { zip } from "react-native-zip-archive";
import RNFetchBlob from "react-native-blob-util";
import { DatabaseLogger, db } from "../common/database/index";
import Storage from "../common/database/storage";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
import { sanitizeFilename } from "@notesnook/common";
import { presentDialog } from "../components/dialog/functions";
import { useSettingStore } from "../stores/use-setting-store";
import BiometicService from "./biometrics";
import { ToastEvent } from "./event-manager";
const MIMETypes = {
txt: "text/plain",
@@ -101,8 +99,8 @@ async function save(path, data, fileName, extension) {
return uri || path;
}
async function makeHtml(note, content) {
let html = await db.notes.note(note.id).export("html", content);
async function makeHtml(note) {
let html = await db.notes.note(note.id).export("html");
html = decode(html, {
level: EntityLevel.HTML
});
@@ -113,25 +111,23 @@ async function makeHtml(note, content) {
*
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
*/
async function exportAs(type, note, bulk, content) {
async function exportAs(type, note, bulk) {
let data;
switch (type) {
case "html":
{
data = await makeHtml(note, content);
data = await makeHtml(note);
}
break;
case "md":
data = await db.notes.note(note.id).export("md", content);
data = await db.notes.note(note.id).export("md");
break;
case "md-frontmatter":
data = await db.notes
.note(note.id)
.export("md-frontmatter", content?.data);
data = await db.notes.note(note.id).export("md-frontmatter");
break;
case "pdf":
{
let html = await makeHtml(note, content);
let html = await makeHtml(note);
let fileName = sanitizeFilename(note.title + Date.now(), {
replacement: "_"
});
@@ -157,84 +153,21 @@ async function exportAs(type, note, bulk, content) {
}
break;
case "txt":
{
data = await db.notes?.note(note.id).export("txt", content);
}
data = await convertNoteToText(note);
break;
}
return data;
}
async function unlockVault() {
let biometry = await BiometicService.isBiometryAvailable();
let fingerprint = await BiometicService.hasInternetCredentials("nn_vault");
if (biometry && fingerprint) {
let credentials = await BiometicService.getCredentials(
"Unlock vault",
"Unlock vault to export locked notes"
);
if (credentials) {
return db.vault.unlock(credentials.password);
}
}
useSettingStore.getState().setSheetKeyboardHandler(false);
return new Promise((resolve) => {
setImmediate(() => {
presentDialog({
context: "export-notes",
input: true,
secureTextEntry: true,
positiveText: "Unlock",
title: "Unlock vault",
paragraph: "Some exported notes are locked, Unlock to export them",
inputPlaceholder: "Enter password",
positivePress: async (value) => {
const unlocked = await db.vault.unlock(value);
if (!unlocked) {
ToastEvent.show({
heading: "Invalid password",
message: "Please enter a valid password",
type: "error",
context: "local"
});
return false;
}
resolve(unlocked);
useSettingStore.getState().setSheetKeyboardHandler(true);
return true;
},
onClose: () => {
resolve(false);
useSettingStore.getState().setSheetKeyboardHandler(true);
}
});
});
});
}
/**
*
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
*/
async function exportNote(note, type) {
let content;
if (note.locked) {
try {
let unlocked = await unlockVault();
if (!unlocked) return null;
const unlockedNote = await db.vault.open(note.id);
content = unlockedNote.content;
} catch (e) {
DatabaseLogger.error(e);
}
}
let path = await getPath(FolderNames[type]);
if (!path) return;
let result = await exportAs(type, note, false, content);
let result = await exportAs(type, note);
if (!result) return null;
let fileName = sanitizeFilename(note.title + Date.now(), {
replacement: "_"
@@ -262,6 +195,11 @@ function copyFileAsync(source, dest) {
});
}
function zipsync(results) {
let data = zipSync(results);
return Buffer.from(data.buffer).toString("base64");
}
function getUniqueFileName(fileName, results) {
const chunks = fileName.split(".");
const ext = chunks.pop();
@@ -283,108 +221,19 @@ async function bulkExport(notes, type, callback) {
let path = await getPath(FolderNames[type]);
if (!path) return;
const exportCacheFolder =
RNFetchBlob.fs.dirs.CacheDir + `/export_${Date.now()}`;
await RNFetchBlob.fs.mkdir(exportCacheFolder).catch((e) => console.log(e));
const mkdir = async (dir) => {
const folder = `${exportCacheFolder}/${dir}`;
if (!(await RNFetchBlob.fs.exists(folder))) {
await RNFetchBlob.fs.mkdir(folder);
}
};
const writeFile = async (path, result) => {
const cacheFilePath = exportCacheFolder + path;
await RNFetchBlob.fs.writeFile(
cacheFilePath,
result,
type === "pdf" ? "base64" : "utf8"
);
};
const results = {};
for (var i = 0; i < notes.length; i++) {
try {
await sleep(1);
let note = notes[i];
let content;
if (note.locked) {
try {
let unlocked = !db.vault.unlocked ? await unlockVault() : true;
if (!unlocked) {
continue;
}
const unlockedNote = await db.vault.open(note.id);
content = unlockedNote.content;
} catch (e) {
DatabaseLogger.error(e);
continue;
}
}
let result = await exportAs(type, note, true, content);
if (note.locked) continue;
let result = await exportAs(type, note);
let fileName = sanitizeFilename(note.title, {
replacement: "_"
});
if (result) {
const notebooks = [
...(db.relations
?.to({ id: note.id, type: "note" }, "notebook")
.map((notebook) => ({
title: notebook.title
})) || []),
...(note.notebooks || []).map((ref) => {
const notebook = db.notebooks?.notebook(ref.id);
const topics = notebook?.topics.all || [];
return {
title: notebook?.title,
topics: ref.topics
.map((topicId) => topics.find((topic) => topic.id === topicId))
.filter(Boolean)
};
})
];
for (const notebook of notebooks) {
results[notebook.title] = results[notebook.title] || {};
await mkdir(notebook.title);
if (notebook.topics && notebook.topics.length) {
for (const topic of notebook.topics) {
results[notebook.title][topic.title] =
results[notebook.title][topic.title] || {};
await mkdir(`${notebook.title}/${topic.title}`);
const exportedNoteName = getUniqueFileName(
fileName + `.${type}`,
results[notebook.title][topic.title]
);
results[notebook.title][topic.title][exportedNoteName] = true;
writeFile(
`/${notebook.title}/${topic.title}/${exportedNoteName}`,
result
);
}
} else {
const exportedNoteName = getUniqueFileName(
fileName + `.${type}`,
results[notebook.title]
);
results[notebook.title][exportedNoteName] = true;
writeFile(`/${notebook.title}/${exportedNoteName}`, result);
}
}
if (!notebooks.length) {
const exportedNoteName = getUniqueFileName(
fileName + `.${type}`,
results
);
results[exportedNoteName] = true;
writeFile(`/${exportedNoteName}`, result);
}
results[getUniqueFileName(fileName + `.${type}`, results)] =
Buffer.from(result, type === "pdf" ? "base64" : "utf-8");
}
callback(`${i + 1}/${notes.length}`);
} catch (e) {
@@ -395,24 +244,26 @@ async function bulkExport(notes, type, callback) {
try {
callback("zipping");
const zipOutputPath =
Platform.OS === "ios"
? path + `/${fileName}`
: RNFetchBlob.fs.dirs.CacheDir + `/${fileName}`;
await zip(exportCacheFolder, zipOutputPath);
await sleep(1);
const zipped = zipsync(results);
callback("saving to disk");
if (Platform.OS === "android") {
await sleep(1);
if (Platform.OS === "ios") {
RNFetchBlob.fs.writeFile(path + `/${fileName}`, zipped, "base64");
} else {
const templFile = RNFetchBlob.fs.dirs.CacheDir + `/${fileName}`;
await RNFetchBlob.fs.writeFile(templFile, zipped, "base64");
const file = await ScopedStorage.createFile(
path,
fileName,
"application/zip"
);
path = file.uri;
await copyFileAsync("file://" + zipOutputPath, path);
await RNFetchBlob.fs.unlink(zipOutputPath);
await copyFileAsync("file://" + templFile, path);
await RNFetchBlob.fs.unlink(templFile);
callback();
}
RNFetchBlob.fs.unlink(exportCacheFolder);
} catch (e) {
DatabaseLogger.error(e);
}

View File

@@ -262,7 +262,7 @@ const onEvent = async ({ type, detail }: Event) => {
if (status.isInternetReachable) {
try {
if (!globalThis["IS_MAIN_APP_RUNNING" as never]) {
await db.sync({ type: "send", force: false });
await db.sync(false, false);
} else {
console.log("main app running, skipping sync");
}
@@ -904,7 +904,7 @@ async function pinNote(id: string) {
const note = db.notes?.note(id as string) as any;
let text = await convertNoteToText(note as any, false);
if (!text) text = "";
const html = text.replace(/\n/g, "<br />");
let html = text.replace(/\n/g, "<br />");
Notifications.displayNotification({
title: note.title,
message: note.headline || text,

View File

@@ -79,11 +79,7 @@ const run = async (
try {
await BackgroundSync.doInBackground(async () => {
try {
await db.sync({
type: full ? "full" : "send",
force: forced,
lastSyncTime
});
await db.sync(full, forced, lastSyncTime);
} catch (e) {
error = e;
}

View File

@@ -68,7 +68,8 @@ export const useAttachmentStore = create<AttachmentStore>((set, get) => ({
if (!progress) return;
editorController.current?.commands.setAttachmentProgress({
hash: hash,
progress: 100
progress: 100,
type: progress[hash]?.type || "download"
});
progress[hash] = null;
set({ progress: { ...progress } });
@@ -79,11 +80,10 @@ export const useAttachmentStore = create<AttachmentStore>((set, get) => ({
progress[hash] = { sent, total, hash, recieved, type };
const progressPercentage =
type === "upload" ? sent / total : recieved / total;
editorController.current?.commands.setAttachmentProgress({
hash: hash,
//@ts-ignore
progress: Math.round(Math.max(progressPercentage * 100, 0))
progress: Math.round(Math.max(progressPercentage * 100, 0)),
type: type
});
set({ progress: { ...progress } });
},

View File

@@ -32,10 +32,7 @@ export const useReminderStore = create<ReminderStore>((set) => ({
reminders: [],
setReminders: () => {
set({
reminders: groupReminders(
(db.reminders?.all as Reminder[]) || [],
db.settings?.getGroupOptions("reminders")
)
reminders: groupReminders((db.reminders?.all as Reminder[]) || [])
});
},
cleareReminders: () => set({ reminders: [] })

View File

@@ -74,7 +74,6 @@ export type Settings = {
colorScheme: "dark" | "light";
lighTheme: ThemeDefinition;
darkTheme: ThemeDefinition;
markdownShortcuts?: boolean;
};
type DimensionsType = {
@@ -155,8 +154,7 @@ export const defaultSettings: SettingStore["settings"] = {
defaultFontSize: 16,
colorScheme: "light",
lighTheme: ThemeLight,
darkTheme: ThemeDark,
markdownShortcuts: true
darkTheme: ThemeDark
};
export const useSettingStore = create<SettingStore>((set, get) => ({

View File

@@ -41,8 +41,7 @@ export const SORT = {
dateModified: "Date modified",
dateEdited: "Date edited",
dateCreated: "Date created",
title: "Title",
dueDate: "Due date"
title: "Title"
};
export const itemSkus = [

View File

@@ -36,7 +36,7 @@ function confirmDeleteAllNotes(items, type, context) {
}?`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: (_inputValue, value) => {
positivePress: (value) => {
setTimeout(() => {
resolve({ delete: true, deleteNotes: value });
});

View File

@@ -21,7 +21,7 @@ import Sodium from "@ammarahmed/react-native-sodium";
import { isImage } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { DatabaseLogger, db } from "../common/database";
import { db } from "../common/database";
import { IOS_APPGROUPID } from "./constants";
const santizeUri = (uri) => {
@@ -57,7 +57,6 @@ export async function attachFile(uri, hash, type, filename, options) {
return true;
} catch (e) {
if (Platform.OS === "ios") RNFetchBlob.fs.unlink(uri).catch(console.log);
DatabaseLogger.error(e, "Attach file error");
console.log("attach file error: ", e);
return false;
}
@@ -108,31 +107,26 @@ async function createNotes(bundle) {
uri: uri,
type: "cache"
});
const attached = await attachFile(uri, hash, file.type, file.name, {
await attachFile(uri, hash, file.type, file.name, {
type: "cache",
id: id,
appGroupId: IOS_APPGROUPID
});
let content = ``;
if (attached) {
if (isImage(file.type)) {
content = `<img data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" />`;
} else {
content = `<p><span data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" data-size="${file.size}" /></p>`;
}
const rawContent = await db.content.raw(
db.notes.note(id).data?.contentId
);
await db.notes.add({
id: id,
content: {
type: "tiptap",
data: rawContent?.data ? rawContent?.data + content : content
},
sessionId: sessionId
});
if (isImage(file.type)) {
content = `<img data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" />`;
} else {
content = `<p><span data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" data-size="${file.size}" /></p>`;
}
const rawContent = await db.content.raw(db.notes.note(id).data?.contentId);
await db.notes.add({
id: id,
content: {
type: "tiptap",
data: rawContent?.data ? rawContent?.data + content : content
},
sessionId: sessionId
});
}
}

View File

@@ -111,7 +111,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 2084
versionCode 2074
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -5,7 +5,7 @@
<!-- Customize your theme here. -->
<item name="android:windowDisablePreview">true</item>
<item name="android:editTextBackground">@drawable/edit_text</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:datePickerDialogTheme">@style/DialogDatePicker.Theme</item>
</style>
@@ -20,7 +20,6 @@
<style name="BootTheme" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/bootsplash_background</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/bootsplash_logo_dark</item>
<item name="android:navigationBarColor">@color/bootsplash_background</item>
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
@@ -32,7 +31,6 @@
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:editTextBackground">@drawable/edit_text</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="DialogDatePicker.Theme" parent="Theme.AppCompat.Light.Dialog">

View File

@@ -5,7 +5,7 @@
<!-- Customize your theme here. -->
<item name="android:windowDisablePreview">true</item>
<item name="android:editTextBackground">@drawable/edit_text</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:datePickerDialogTheme">@style/DialogDatePicker.Theme</item>
</style>
@@ -20,7 +20,6 @@
<style name="BootTheme" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/bootsplash_background</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/bootsplash_logo</item>
<item name="android:navigationBarColor">@color/bootsplash_background</item>
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
@@ -32,7 +31,6 @@
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:editTextBackground">@drawable/edit_text</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="DialogDatePicker.Theme" parent="Theme.AppCompat.Light.Dialog">

View File

@@ -1,10 +1,3 @@
- Support for exporting locked notes
- Maintain directory structure in exports based on Notebooks/Topics
- Match color of navigation bar in sheets with app theme
- Improved image loading and show progress when loading images
- Support for setting trash interval to daily
- Fix android app UI issues with RTL languages
- Fix pasting links in editor
- Other Bug fixes and performance improvements
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>provisioningProfiles</key>
<dict>
<key>org.streetwriters.notesnook</key>
<string>Notesnook App Distribution 2024</string>
<key>org.streetwriters.notesnook.notewidget</key>
<string>Notesnook Widget Distribution 2024</string>
<key>org.streetwriters.notesnook.share</key>
<string>Notesnook Share Distribution 2024</string>
</dict>
</dict>
</plist>

View File

@@ -14,13 +14,13 @@
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2DCD954D1E0B4F2C00145EB5 /* NotesnookTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* NotesnookTests.m */; };
6510626F27042891009661C3 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */; };
6510627627042895009661C3 /* OpenSans-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */; };
6510E6D72877215700DACAA9 /* build.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 6510E6D62877215700DACAA9 /* build.bundle */; };
6515C42F2580AA3000E83E39 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6515C42E2580AA2F00E83E39 /* StoreKit.framework */; };
6517B7C12B6838EB0079FF37 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7BE2B6838EB0079FF37 /* OpenSans-Regular.ttf */; };
6517B7C22B6838EB0079FF37 /* OpenSans-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7BF2B6838EB0079FF37 /* OpenSans-SemiBold.ttf */; };
6517B7C32B6838EB0079FF37 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */; };
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */; };
6593E4A3281C345400492C50 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6593E4A2281C345400492C50 /* AppDelegate.mm */; };
659670B22A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */; };
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */ = {isa = PBXBuildFile; fileRef = 659BE46625E11A5100E05671 /* notesnook-text.png */; };
65AA857925E6DDEC00772A01 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857825E6DDEC00772A01 /* WidgetKit.framework */; };
65AA857B25E6DDEC00772A01 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857A25E6DDEC00772A01 /* SwiftUI.framework */; };
@@ -110,12 +110,12 @@
4C4AF691C324D95337F2FB0A /* libPods-Make Note.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Make Note.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6510E6D62877215700DACAA9 /* build.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = build.bundle; path = "../../../../packages/editor-mobile/build.bundle"; sourceTree = "<group>"; };
6515C42E2580AA2F00E83E39 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
6517B7BE2B6838EB0079FF37 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Regular.ttf"; path = "../../../../packages/editor-mobile/public/fonts/OpenSans-Regular.ttf"; sourceTree = "<group>"; };
6517B7BF2B6838EB0079FF37 /* OpenSans-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-SemiBold.ttf"; path = "../../../../packages/editor-mobile/public/fonts/OpenSans-SemiBold.ttf"; sourceTree = "<group>"; };
6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Bold.ttf"; path = "../../../../packages/editor-mobile/public/fonts/OpenSans-Bold.ttf"; sourceTree = "<group>"; };
6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = BootSplash.storyboard; path = Notesnook/BootSplash.storyboard; sourceTree = "<group>"; };
6529D2B0257B4A2900B49BC3 /* NotesnookDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = NotesnookDebug.entitlements; path = Notesnook/NotesnookDebug.entitlements; sourceTree = "<group>"; };
6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-SemiBold.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-SemiBold.ttf"; sourceTree = "<group>"; };
6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Regular.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-Regular.ttf"; sourceTree = "<group>"; };
6593E4A2281C345400492C50 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Notesnook/AppDelegate.mm; sourceTree = "<group>"; };
659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Bold.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-Bold.ttf"; sourceTree = "<group>"; };
659BE46625E11A5100E05671 /* notesnook-text.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "notesnook-text.png"; path = "Notesnook/Images.xcassets/notesnook-text.png"; sourceTree = "<group>"; };
65A7F34F255689AD00699170 /* Notesnook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Notesnook.entitlements; path = Notesnook/Notesnook.entitlements; sourceTree = "<group>"; };
65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotesWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -293,13 +293,13 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */,
6517B7BE2B6838EB0079FF37 /* OpenSans-Regular.ttf */,
6517B7BF2B6838EB0079FF37 /* OpenSans-SemiBold.ttf */,
65C400DD2A80B68D00AA3DF5 /* MaterialCommunityIcons.ttf */,
65C149862A61151B005C40F1 /* extension.bundle */,
6510E6D62877215700DACAA9 /* build.bundle */,
65EC5B71272A7EE200FB3748 /* NotesWidgetExtensionDebug.entitlements */,
6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */,
659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */,
6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */,
65D145C429DC30470056FE7D /* MaterialCommunityIcons.ttf */,
13B07FAE1A68108700A75B9A /* Notesnook */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
@@ -542,12 +542,12 @@
buildActionMask = 2147483647;
files = (
65C400DE2A80B68D00AA3DF5 /* MaterialCommunityIcons.ttf in Resources */,
6510626F27042891009661C3 /* OpenSans-Regular.ttf in Resources */,
659670B22A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
6517B7C32B6838EB0079FF37 /* OpenSans-Bold.ttf in Resources */,
6517B7C22B6838EB0079FF37 /* OpenSans-SemiBold.ttf in Resources */,
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */,
6510E6D72877215700DACAA9 /* build.bundle in Resources */,
6517B7C12B6838EB0079FF37 /* OpenSans-Regular.ttf in Resources */,
6510627627042895009661C3 /* OpenSans-SemiBold.ttf in Resources */,
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -997,7 +997,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2066;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1071,7 +1071,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1102,7 +1102,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2066;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1176,7 +1176,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1335,7 +1335,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2066;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1347,7 +1347,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1378,7 +1378,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2066;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1391,7 +1391,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1421,7 +1421,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2066;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1495,7 +1495,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1526,7 +1526,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2066;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1601,7 +1601,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "2.6.15",
"version": "2.6.14",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "2.6.15",
"version": "2.6.14",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -62,8 +62,7 @@
"@microsoft/signalr": "^7.0.10",
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/logger": "file:../logger",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"@streetwriters/showdown": "^3.0.2-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"entities": "^4.3.1",
@@ -82,7 +81,7 @@
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@types/showdown": "^2.0.0",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"cross-env": "^7.0.3",
@@ -118,7 +117,7 @@
"@tiptap/extension-font-family": "2.1.12",
"@tiptap/extension-history": "2.1.12",
"@tiptap/extension-horizontal-rule": "2.1.12",
"@tiptap/extension-link": "^2.1.16",
"@tiptap/extension-link": "2.1.12",
"@tiptap/extension-list-keymap": "2.1.12",
"@tiptap/extension-placeholder": "2.1.12",
"@tiptap/extension-subscript": "2.1.12",
@@ -249,7 +248,6 @@
"dependencies": {
"@flyerhq/react-native-link-preview": "^1.6.0",
"@mdi/js": "^6.7.96",
"@readme/data-urls": "3.0.0",
"@streetwriters/showdown": "^3.0.1-alpha.2",
"absolutify": "^0.1.0",
"buffer": "^6.0.3",
@@ -7097,14 +7095,6 @@
"nanoid": "^3.1.23"
}
},
"node_modules/@readme/data-urls": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@readme/data-urls/-/data-urls-3.0.0.tgz",
"integrity": "sha512-b0L7VWqbLZqOSSAFUrxS5ZwUfec35WDsAwwCH481vYnhk0dWO3nvmNVNCbP8CY4cXqwL1W4uCAnhDz+CUmXM3g==",
"engines": {
"node": ">=18"
}
},
"node_modules/@sayem314/react-native-keep-awake": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@sayem314/react-native-keep-awake/-/react-native-keep-awake-1.2.0.tgz",
@@ -26841,12 +26831,11 @@
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/crypto": "file:../crypto",
"@notesnook/logger": "file:../logger",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"@streetwriters/showdown": "^3.0.2-alpha",
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@types/showdown": "^2.0.0",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"async-mutex": "^0.3.2",
@@ -26893,7 +26882,7 @@
"@tiptap/extension-font-family": "2.1.12",
"@tiptap/extension-history": "2.1.12",
"@tiptap/extension-horizontal-rule": "2.1.12",
"@tiptap/extension-link": "^2.1.16",
"@tiptap/extension-link": "2.1.12",
"@tiptap/extension-list-keymap": "2.1.12",
"@tiptap/extension-placeholder": "2.1.12",
"@tiptap/extension-subscript": "2.1.12",
@@ -26982,7 +26971,6 @@
"requires": {
"@flyerhq/react-native-link-preview": "^1.6.0",
"@mdi/js": "^6.7.96",
"@readme/data-urls": "3.0.0",
"@streetwriters/showdown": "^3.0.1-alpha.2",
"absolutify": "^0.1.0",
"buffer": "^6.0.3",
@@ -27652,11 +27640,6 @@
"nanoid": "^3.1.23"
}
},
"@readme/data-urls": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@readme/data-urls/-/data-urls-3.0.0.tgz",
"integrity": "sha512-b0L7VWqbLZqOSSAFUrxS5ZwUfec35WDsAwwCH481vYnhk0dWO3nvmNVNCbP8CY4cXqwL1W4uCAnhDz+CUmXM3g=="
},
"@sayem314/react-native-keep-awake": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@sayem314/react-native-keep-awake/-/react-native-keep-awake-1.2.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.6.17",
"version": "2.6.15",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -10,7 +10,6 @@
"scripts": {
"postinstall": "patch-package && node ./scripts/optimize-fonts.mjs",
"run-android": "cd native && react-native run-android --active-arch-only",
"build": "echo 'No build script defined'",
"run-ios": "cd native && react-native run-ios",
"start": "cd native && react-native start",
"repack": "cd native && react-native webpack-start",
@@ -45,4 +44,4 @@
"react-native": "0.72.0",
"react-native-iap": "12.11.0"
}
}
}

View File

@@ -295,7 +295,7 @@ const ShareView = ({ quicknote = false }) => {
try {
if (!globalThis["IS_MAIN_APP_RUNNING"]) {
await db.sync({ type: "send", force: false });
await db.sync(false, false);
} else {
console.log("main app running, skipping sync");
}

View File

@@ -241,8 +241,8 @@ export class NoteContextMenuModel extends BaseProperties {
);
if (format === "html") {
return content
.replace(/(name="created-at" content=")(.+?)"/, '$1xxx"')
.replace(/(name="updated-at" content=")(.+?)"/, '$1xxx"');
.replace(/(name="created-on" content=")(.+?)"/, '$1xxx"')
.replace(/(name="last-edited-on" content=")(.+?)"/, '$1xxx"');
}
return content;
}

View File

@@ -8,8 +8,8 @@
content="This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1"
/>
<title>Test 1 - Notesnook</title>
<meta name="created-at" content="xxx" />
<meta name="updated-at" content="xxx" />
<meta name="created-at" content="22-12-2023 11:33 AM" />
<meta name="updated-at" content="22-12-2023 11:33 AM" />

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "2.6.16",
"version": "2.6.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "2.6.16",
"version": "2.6.14",
"license": "GPL-3.0-or-later",
"dependencies": {
"@aws-sdk/util-base64-browser": "^3.208.0",
@@ -215,8 +215,7 @@
"@microsoft/signalr": "^7.0.10",
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/logger": "file:../logger",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"@streetwriters/showdown": "^3.0.2-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"entities": "^4.3.1",
@@ -235,7 +234,7 @@
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@types/showdown": "^2.0.0",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"cross-env": "^7.0.3",
@@ -280,7 +279,7 @@
"@tiptap/extension-font-family": "2.1.12",
"@tiptap/extension-history": "2.1.12",
"@tiptap/extension-horizontal-rule": "2.1.12",
"@tiptap/extension-link": "^2.1.16",
"@tiptap/extension-link": "2.1.12",
"@tiptap/extension-list-keymap": "2.1.12",
"@tiptap/extension-placeholder": "2.1.12",
"@tiptap/extension-subscript": "2.1.12",
@@ -439,7 +438,7 @@
},
"../desktop": {
"name": "@notesnook/desktop",
"version": "2.6.15",
"version": "2.6.14",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "2.6.16",
"version": "2.6.14",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -20,11 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { lazify } from "../utils/lazify";
import { db } from "./db";
async function download(hash: string, groupId?: string) {
async function download(hash: string) {
const attachment = db.attachments?.attachment(hash);
if (!attachment) return;
const downloadResult = await db.fs?.downloadFile(
groupId || attachment.metadata.hash,
attachment.metadata.hash,
attachment.metadata.hash,
attachment.chunkSize,
attachment.metadata
@@ -61,12 +61,8 @@ type OutputTypeToReturnType = {
export async function downloadAttachment<
TType extends "blob" | "base64" | "text",
TOutputType = OutputTypeToReturnType[TType]
>(
hash: string,
type: TType,
groupId?: string
): Promise<TOutputType | undefined> {
const response = await download(hash, groupId);
>(hash: string, type: TType): Promise<TOutputType | undefined> {
const response = await download(hash);
if (!response) return;
const { attachment, key } = response;

View File

@@ -381,6 +381,13 @@ function getDialogData(type: string) {
subtitle: "Please enter your vault password to continue.",
positiveButtonText: "Unlock"
};
case "ask_backup_password":
return {
title: "Encrypted backup",
subtitle:
"Please enter the password to decrypt and restore this backup.",
positiveButtonText: "Restore"
};
case "change_account_password":
return {
title: "Change account password",
@@ -456,17 +463,6 @@ export function showPasswordDialog(
));
}
export function showBackupPasswordDialog(
validate: (outputs: {
password?: string;
key?: string;
}) => boolean | Promise<boolean>
) {
return showDialog("BackupPasswordDialog", (Dialog, perform) => (
<Dialog onClose={() => perform(false)} validate={validate} />
));
}
export function showRecoveryKeyDialog() {
return showDialog("RecoveryKeyDialog", (Dialog, perform) => (
<Dialog onDone={() => perform(true)} />

View File

@@ -21,11 +21,6 @@ import { TaskManager } from "./task-manager";
import { ExportStream } from "../utils/streams/export-stream";
import { createZipStream } from "../utils/streams/zip-stream";
import { createWriteStream } from "../utils/stream-saver";
import { showToast } from "../utils/toast";
import Vault from "./vault";
import { db } from "./db";
import Note from "@notesnook/core/dist/models/note";
import { sanitizeFilename } from "@notesnook/common";
export async function exportToPDF(
title: string,
@@ -79,91 +74,12 @@ export async function exportNotes(
title: "Exporting notes",
subtitle: "Please wait while your notes are exported.",
action: async (report) => {
try {
let progress = 0;
await createNoteStream(noteIds)
.pipeThrough(
new TransformStream<Note, Note>({
transform(note, controller) {
controller.enqueue(note);
report({
total: noteIds.length,
current: progress++,
text: `Exporting "${note?.title || "Unknown note"}"`
});
}
})
)
.pipeThrough(new ExportStream(format))
.pipeThrough(createZipStream())
.pipeTo(await createWriteStream("notes.zip"));
return true;
} catch (e) {
if (e instanceof Error) showToast("error", e.message);
}
return false;
await new ExportStream(noteIds, format, undefined, (c, text) =>
report({ total: noteIds.length, current: c, text })
)
.pipeThrough(createZipStream())
.pipeTo(await createWriteStream("notes.zip"));
return true;
}
});
}
function createNoteStream(noteIds: string[]) {
let i = 0;
return new ReadableStream<Note>({
start() {},
async pull(controller) {
const noteId = noteIds[i++];
if (!noteId) controller.close();
else controller.enqueue(db.notes?.note(noteId));
},
async cancel(reason) {
throw new Error(reason);
}
});
}
const FORMAT_TO_EXT = {
pdf: "pdf",
md: "md",
txt: "txt",
html: "html",
"md-frontmatter": "md"
} as const;
export async function exportNote(
note: Note,
format: keyof typeof FORMAT_TO_EXT,
disableTemplate = false
) {
if (!db.vault?.unlocked && note.data.locked && !(await Vault.unlockVault())) {
showToast("error", `Skipping note "${note.title}" as it is locked.`);
return false;
}
const rawContent = note.data.contentId
? await db.content?.raw(note.data.contentId)
: undefined;
const content =
rawContent &&
!rawContent.deleted &&
(typeof rawContent.data === "object"
? await db.vault?.decryptContent(rawContent)
: rawContent);
const exported = await note
.export(format === "pdf" ? "html" : format, content, !disableTemplate)
.catch((e: Error) => {
console.error(note.data, e);
showToast("error", `Failed to export note "${note.title}": ${e.message}`);
return false as const;
});
if (typeof exported === "boolean" && !exported) return false;
const filename = sanitizeFilename(note.title, { replacement: "-" });
const ext = FORMAT_TO_EXT[format];
return {
filename: [filename, ext].join("."),
content: exported
};
}

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
showBackupPasswordDialog,
showFeatureDialog,
showPasswordDialog,
showReminderDialog
@@ -27,8 +26,7 @@ import Config from "../utils/config";
import { hashNavigate, getCurrentHash } from "../navigation";
import { db } from "./db";
import { sanitizeFilename } from "@notesnook/common";
import { useStore as useUserStore } from "../stores/user-store";
import { useStore as useSettingStore } from "../stores/setting-store";
import { store as userstore } from "../stores/user-store";
import { showToast } from "../utils/toast";
import { SUBSCRIPTION_STATUS } from "./constants";
import { readFile, showFilePicker } from "../utils/file-picker";
@@ -80,17 +78,8 @@ export async function introduceFeatures() {
export const DEFAULT_CONTEXT = { colors: [], tags: [], notebook: {} };
export async function createBackup() {
const { isLoggedIn } = useUserStore.getState();
const { encryptBackups, toggleEncryptBackups } = useSettingStore.getState();
if (!isLoggedIn && encryptBackups) toggleEncryptBackups();
const verified = encryptBackups || (await verifyAccount());
if (!verified) {
showToast("error", "Could not create a backup: user verification failed.");
return;
}
const encryptedBackups = isLoggedIn && encryptBackups;
const encryptBackups =
userstore.get().isLoggedIn && Config.get("encryptBackups", false);
const filename = sanitizeFilename(
`notesnook-backup-${getFormattedDate(Date.now())}`
@@ -112,7 +101,7 @@ export async function createBackup() {
await new ReadableStream({
start() {},
async pull(controller) {
for await (const file of db.backup!.export("web", encryptedBackups)) {
for await (const file of db.backup!.export("web", encryptBackups)) {
report({
text: `Saving chunk ${file.path}`
});
@@ -161,9 +150,9 @@ export async function restoreBackupFile(backupFile: File) {
const backup = JSON.parse(await readFile(backupFile));
if (backup.data.iv && backup.data.salt) {
await showBackupPasswordDialog(async ({ password, key }) => {
if (!password && !key) return false;
const error = await restoreWithProgress(backup, password, key);
await showPasswordDialog("ask_backup_password", async ({ password }) => {
if (!password) return false;
const error = await restoreWithProgress(backup, password);
return !error;
});
} else {
@@ -177,7 +166,6 @@ export async function restoreBackupFile(backupFile: File) {
type: "modal",
action: async (report) => {
let cachedPassword: string | undefined = undefined;
let cachedKey: string | undefined = undefined;
// const { read, totalFiles } = await Reader(backupFile);
const entries: ZipEntry[] = [];
let filesProcessed = 0;
@@ -195,20 +183,20 @@ export async function restoreBackupFile(backupFile: File) {
for (const entry of entries) {
const backup = JSON.parse(await entry.text());
if (backup.encrypted) {
if (!cachedPassword && !cachedKey) {
const result = await showBackupPasswordDialog(
async ({ password, key }) => {
if (!password && !key) return false;
await db.backup?.import(backup, password, key);
if (!cachedPassword) {
const result = await showPasswordDialog(
"ask_backup_password",
async ({ password }) => {
if (!password) return false;
await db.backup?.import(backup, password);
cachedPassword = password;
cachedKey = key;
return true;
}
);
if (!result) break;
} else await db.backup?.import(backup, cachedPassword, cachedKey);
} else await db.backup?.import(backup, cachedPassword);
} else {
await db.backup?.import(backup);
await db.backup?.import(backup, null);
}
report({
@@ -228,8 +216,7 @@ export async function restoreBackupFile(backupFile: File) {
async function restoreWithProgress(
backup: Record<string, unknown>,
password?: string,
key?: string
password?: string
) {
return await TaskManager.startTask<Error | void>({
title: "Restoring backup",
@@ -256,7 +243,7 @@ async function restoreWithProgress(
);
report({ text: `Restoring...` });
return restore(backup, password, key);
return restore(backup, password);
}
});
}
@@ -283,7 +270,7 @@ export function totalSubscriptionConsumed(user: User) {
export async function showUpgradeReminderDialogs() {
if (IS_TESTING) return;
const user = useUserStore.getState().user;
const user = userstore.get().user;
if (!user || !user.subscription || user.subscription?.expiry === 0) return;
const consumed = totalSubscriptionConsumed(user);
@@ -296,13 +283,9 @@ export async function showUpgradeReminderDialogs() {
}
}
async function restore(
backup: Record<string, unknown>,
password?: string,
key?: string
) {
async function restore(backup: Record<string, unknown>, password?: string) {
try {
await db.backup?.import(backup, password, key);
await db.backup?.import(backup, password);
showToast("success", "Backup restored!");
} catch (e) {
logger.error(e as Error, "Could not restore the backup");

View File

@@ -94,21 +94,24 @@ class Vault {
}
static lockNote(id) {
return db.vault
.add(id)
.then(() => true)
.catch(({ message }) => {
switch (message) {
case db.vault.ERRORS.noVault:
return Vault.createVault().then(() => Vault.lockNote(id));
case db.vault.ERRORS.vaultLocked:
return Vault.unlockVault().then(() => Vault.lockNote(id));
default:
showToast("error", message);
console.error(message);
return false;
}
});
return new Promise(function lock(resolve) {
db.vault
.add(id)
.then(resolve)
.catch(({ message }) => {
switch (message) {
case db.vault.ERRORS.noVault:
return Vault.createVault();
case db.vault.ERRORS.vaultLocked:
return Vault.unlockVault();
default:
showToast("error", message);
console.error(message);
return false;
}
})
.then((result) => result && lock(resolve));
});
}
static askPassword(action) {

View File

@@ -54,7 +54,7 @@ import { ScopedThemeProvider } from "../theme-provider";
import { Lightbox } from "../lightbox";
import { Allotment } from "allotment";
import { showToast } from "../../utils/toast";
import { debounce, getFormattedDate } from "@notesnook/common";
import { getFormattedDate } from "@notesnook/common";
const PDFPreview = React.lazy(() => import("../pdf-preview"));
@@ -72,12 +72,11 @@ type DocumentPreview = {
function onEditorChange(
noteId: string | undefined,
sessionId: number,
content: string,
ignoreEdit: boolean
content: string
) {
if (!content) return;
editorstore.get().saveSessionContent(noteId, sessionId, ignoreEdit, {
editorstore.get().saveSessionContent(noteId, sessionId, {
type: "tiptap",
data: content
});
@@ -136,7 +135,26 @@ export default function EditorManager({
if (result) item.data = result.data;
else EV.publish(EVENTS.vaultLocked);
}
const oldHashes = editorInstance.current.getMediaHashes();
editorInstance.current.updateContent(item.data as string);
const newHashes = editorInstance.current.getMediaHashes();
const hashesToLoad = newHashes.filter(
(hash, index) => hash !== oldHashes[index]
);
if (appstore.get().isSyncing()) {
db.eventManager.subscribe(
EVENTS.syncCompleted,
async () => {
await db.attachments?.downloadMedia(id, hashesToLoad);
},
true
);
} else {
await db.attachments?.downloadMedia(id, hashesToLoad);
}
} else if (isNote) {
if (!locked && item.locked) return EV.publish(EVENTS.vaultLocked);
@@ -162,6 +180,18 @@ export default function EditorManager({
setTimestamp(Date.now());
}, []);
const loadMedia = useCallback(async () => {
if (previewSession.current) {
await db.content?.downloadMedia(
noteId,
previewSession.current.content,
true
);
} else if (noteId && editorstore.get().session.content) {
await db.attachments?.downloadMedia(noteId);
}
}, [noteId]);
useEffect(() => {
if (!isNewSession) return;
@@ -207,7 +237,6 @@ export default function EditorManager({
/>
)}
<Editor
id={noteId}
nonce={timestamp}
content={() =>
previewSession.current?.content?.data ||
@@ -218,6 +247,7 @@ export default function EditorManager({
options={{
readonly: isReadonly || isPreviewSession,
onRequestFocus: () => toggleProperties(false),
onLoadMedia: loadMedia,
focusMode: isFocusMode,
isMobile: isMobile || isTablet
}}
@@ -321,9 +351,9 @@ type EditorOptions = {
readonly?: boolean;
focusMode?: boolean;
onRequestFocus?: () => void;
onLoadMedia?: () => void;
};
type EditorProps = {
id: string | number;
content: () => string | undefined;
nonce?: number;
options?: EditorOptions;
@@ -331,9 +361,8 @@ type EditorProps = {
onPreviewDocument?: (preview: DocumentPreview) => void;
};
export function Editor(props: EditorProps) {
const { id, content, nonce, options, onContentChange, onPreviewDocument } =
props;
const { readonly, headless, isMobile } = options || {
const { content, nonce, options, onContentChange, onPreviewDocument } = props;
const { readonly, headless, onLoadMedia, isMobile } = options || {
headless: false,
readonly: false,
focusMode: false,
@@ -346,16 +375,40 @@ export function Editor(props: EditorProps) {
useEffect(() => {
const event = AppEventManager.subscribe(
AppEvents.UPDATE_ATTACHMENT_PROGRESS,
({ hash, loaded, total }: AttachmentProgress) => {
({ hash, loaded, total, type }: AttachmentProgress) => {
editor.current?.sendAttachmentProgress(
hash,
type,
Math.round((loaded / total) * 100)
);
}
);
const mediaAttachmentDownloadedEvent = EV.subscribe(
EVENTS.mediaAttachmentDownloaded,
({
groupId,
hash,
attachmentType,
src
}: {
groupId?: string;
attachmentType: "image" | "webclip" | "generic";
hash: string;
src: string;
}) => {
if (groupId?.startsWith("monograph")) return;
if (attachmentType === "image") {
editor.current?.loadImage(hash, src);
} else if (attachmentType === "webclip") {
editor.current?.loadWebClip(hash, src);
}
}
);
return () => {
event.unsubscribe();
mediaAttachmentDownloadedEvent.unsubscribe();
};
}, [editor]);
@@ -371,6 +424,7 @@ export function Editor(props: EditorProps) {
corsHost: Config.get("corsProxy", "https://cors.notesnook.com")
}}
onLoad={() => {
if (onLoadMedia) onLoadMedia();
if (nonce && nonce > 0) setIsLoading(false);
}}
onContentChange={onContentChange}
@@ -383,11 +437,8 @@ export function Editor(props: EditorProps) {
const container = document.getElementById("dialogContainer");
if (!(container instanceof HTMLElement)) return;
const dataurl = await downloadAttachment(
hash,
"base64",
id.toString()
);
const dataurl =
data.bloburl || (await downloadAttachment(hash, "base64"));
if (!dataurl)
return showToast("error", "This image cannot be previewed.");
@@ -404,7 +455,7 @@ export function Editor(props: EditorProps) {
);
} else if (attachment && onPreviewDocument) {
onPreviewDocument({ hash });
const blob = await downloadAttachment(hash, "blob", id.toString());
const blob = await downloadAttachment(hash, "blob");
if (!blob) return;
onPreviewDocument({ url: URL.createObjectURL(blob), hash });
}
@@ -416,13 +467,6 @@ export function Editor(props: EditorProps) {
editor.current?.attachFile(file);
});
}}
onGetAttachmentData={(attachment) => {
return downloadAttachment(
attachment.hash,
attachment.type === "web-clip" ? "text" : "base64",
id.toString()
);
}}
onAttachFile={async (file) => {
const result = await attachFile(file);
if (!result) return;
@@ -445,38 +489,6 @@ function EditorChrome(
isMobile: false
};
const editorMargins = useStore((store) => store.editorMargins);
const editorContainerRef = useRef<HTMLElement>(null);
const editorScrollRef = useRef<HTMLElement>(null);
useEffect(() => {
if (!editorScrollRef.current) return;
function onResize(
entries: ResizeObserverEntry[],
_observer: ResizeObserver
) {
const editor = editorContainerRef.current?.querySelector(
".ProseMirror"
) as HTMLElement | undefined;
const parent = editorScrollRef.current?.getBoundingClientRect();
const child = editorContainerRef.current?.getBoundingClientRect();
if (!parent || !child || !editor || entries.length <= 0) return;
const CONTAINER_MARGIN = 30;
const negativeSpace = Math.abs(
parent.left - child.left - CONTAINER_MARGIN
);
editor.style.marginLeft = `-${negativeSpace}px`;
editor.style.marginRight = `-${negativeSpace}px`;
editor.style.paddingLeft = `${negativeSpace}px`;
editor.style.paddingRight = `${negativeSpace}px`;
}
const observer = new ResizeObserver(debounce(onResize, 500));
observer.observe(editorScrollRef.current);
return () => {
observer.disconnect();
};
}, []);
if (headless) return <>{children}</>;
@@ -501,12 +513,10 @@ function EditorChrome(
<Toolbar />
<FlexScrollContainer
scrollRef={editorScrollRef}
className="editorScroll"
style={{ display: "flex", flexDirection: "column", flex: 1 }}
>
<Flex
ref={editorContainerRef}
variant="columnFill"
className="editor"
sx={{
@@ -515,7 +525,7 @@ function EditorChrome(
width: "100%"
}}
pl={6}
pr={6}
pr={2}
onClick={onRequestFocus}
>
{!isMobile && (
@@ -570,7 +580,7 @@ function PreviewModeNotice(props: PreviewModeNoticeProps) {
async (cancelled: boolean) => {
const { id, sessionId } = editorstore.get().session;
if (!cancelled) {
await editorstore.saveSessionContent(id, sessionId, false, content);
await editorstore.saveSessionContent(id, sessionId, content);
}
onDiscard();
},

View File

@@ -25,7 +25,6 @@ import { TaskManager } from "../../common/task-manager";
import { isUserPremium } from "../../hooks/use-is-user-premium";
import { showToast } from "../../utils/toast";
import { showFilePicker } from "../../utils/file-picker";
import { Attachment } from "@notesnook/editor";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
@@ -81,54 +80,33 @@ export async function reuploadAttachment(
}
/**
* @param {File} file
* @param {File} selectedFile
* @returns
*/
async function pickFile(
file: File,
options?: AddAttachmentOptions
): Promise<Attachment | undefined> {
async function pickFile(selectedFile: File, options?: AddAttachmentOptions) {
try {
if (file.size > FILE_SIZE_LIMIT)
if (selectedFile.size > FILE_SIZE_LIMIT)
throw new Error("File too big. You cannot add files over 500 MB.");
if (!file) return;
if (!selectedFile) return;
const hash = await addAttachment(file, options);
return {
type: "file",
filename: file.name,
hash,
mime: file.type,
size: file.size
};
return await addAttachment(selectedFile, undefined, options);
} catch (e) {
showToast("error", `${(e as Error).message}`);
}
}
/**
* @param {File} file
* @param {File} selectedImage
* @returns
*/
async function pickImage(
file: File,
options?: AddAttachmentOptions
): Promise<Attachment | undefined> {
async function pickImage(selectedImage: File, options?: AddAttachmentOptions) {
try {
if (file.size > IMAGE_SIZE_LIMIT)
if (selectedImage.size > IMAGE_SIZE_LIMIT)
throw new Error("Image too big. You cannot add images over 50 MB.");
if (!file) return;
if (!selectedImage) return;
const hash = await addAttachment(file, options);
const dimensions = await getImageDimensions(file);
return {
type: "image",
filename: file.name,
hash,
mime: file.type,
size: file.size,
...dimensions
};
const dataurl = await toDataURL(selectedImage);
return await addAttachment(selectedImage, dataurl, options);
} catch (e) {
showToast("error", (e as Error).message);
}
@@ -140,6 +118,12 @@ async function getEncryptionKey(): Promise<SerializedKey> {
return key;
}
async function toDataURL(file: File): Promise<string> {
const buffer = await file.arrayBuffer();
const base64 = Buffer.from(buffer).toString("base64");
return `data:${file.type};base64,${base64}`;
}
export type AttachmentProgress = {
hash: string;
type: "encrypt" | "download" | "upload";
@@ -147,6 +131,15 @@ export type AttachmentProgress = {
loaded: number;
};
export type Attachment = {
hash: string;
filename: string;
mime: string;
size: number;
dataurl?: string;
bloburl?: string;
};
type AddAttachmentOptions = {
expectedFileHash?: string;
showProgress?: boolean;
@@ -155,8 +148,9 @@ type AddAttachmentOptions = {
async function addAttachment(
file: File,
dataurl: string | undefined,
options: AddAttachmentOptions = {}
): Promise<string> {
): Promise<Attachment> {
const { default: FS } = await import("../../interfaces/fs");
const { expectedFileHash, showProgress = true } = options;
let forceWrite = options.forceWrite;
@@ -171,7 +165,7 @@ async function addAttachment(
`Please select the same file for reuploading. Expected hash ${expectedFileHash} but got ${hash}.`
);
const exists = db.attachments?.attachment(hash);
const exists = db.attachments?.exists(hash);
if (!forceWrite && exists) {
forceWrite = (await FS.getUploadedFileSize(hash)) <= 0;
}
@@ -187,13 +181,19 @@ async function addAttachment(
...output,
hash,
hashType,
filename: exists?.metadata.filename || file.name,
type: exists?.metadata.type || file.type,
filename: file.name,
type: file.type,
key
});
}
return hash;
return {
hash: hash,
filename: file.name,
mime: file.type,
size: file.size,
dataurl
};
};
const result = showProgress
@@ -204,10 +204,7 @@ async function addAttachment(
return result;
}
function withProgress<T>(
file: File,
action: () => Promise<T>
): Promise<T | Error> {
function withProgress<T>(file: File, action: () => Promise<T>): Promise<T> {
return TaskManager.startTask({
type: "modal",
title: "Encrypting attachment",
@@ -229,19 +226,3 @@ function withProgress<T>(
}
});
}
function getImageDimensions(file: File) {
return new Promise<{ width: number; height: number } | undefined>(
(resolve) => {
const img = new Image();
img.onload = () => {
const { naturalWidth: width, naturalHeight: height } = img;
resolve({ width, height });
};
img.onerror = () => {
resolve(undefined);
};
img.src = URL.createObjectURL(file);
}
);
}

View File

@@ -34,11 +34,11 @@ import {
getTotalWords,
countWords,
getFontById,
TiptapOptions,
Attachment
TiptapOptions
} from "@notesnook/editor";
import { Box, Flex } from "@theme-ui/components";
import { PropsWithChildren, useEffect, useMemo, useRef, useState } from "react";
import { Attachment } from "./picker";
import { IEditor } from "./types";
import {
useConfigureEditor,
@@ -57,12 +57,12 @@ import { store as editorstore } from "../../stores/editor-store";
import { ScopedThemeProvider } from "../theme-provider";
import { writeText } from "clipboard-polyfill";
import { useStore as useThemeStore } from "../../stores/theme-store";
import { toBlobURL } from "@notesnook/editor/dist/utils/downloader";
type OnChangeHandler = (
id: string | undefined,
sessionId: number,
content: string,
ignoreEdit: boolean
content: string
) => void;
type TipTapProps = {
editorContainer: HTMLElement;
@@ -72,7 +72,6 @@ type TipTapProps = {
onInsertAttachment?: (type: AttachmentType) => void;
onDownloadAttachment?: (attachment: Attachment) => void;
onPreviewAttachment?: (attachment: Attachment) => void;
onGetAttachmentData?: (attachment: Attachment) => Promise<string | undefined>;
onAttachFile?: (file: File) => void;
onFocus?: () => void;
content?: () => string | undefined;
@@ -93,7 +92,6 @@ function save(
editor: Editor,
content: Fragment,
preventSave: boolean,
ignoreEdit: boolean,
onChange?: OnChangeHandler
) {
configureEditor({
@@ -107,7 +105,7 @@ function save(
if (preventSave) return;
const html = getHTMLFromFragment(content, editor.schema);
onChange?.(noteId, sessionId, html, ignoreEdit);
onChange?.(noteId, sessionId, html);
}
const deferredSave = debounceWithId(save, SAVE_INTERVAL);
@@ -119,7 +117,6 @@ function TipTap(props: TipTapProps) {
onInsertAttachment,
onDownloadAttachment,
onPreviewAttachment,
onGetAttachmentData,
onAttachFile,
onContentChange,
onFocus = () => {},
@@ -141,9 +138,6 @@ function TipTap(props: TipTapProps) {
);
const dateFormat = useSettingsStore((store) => store.dateFormat);
const timeFormat = useSettingsStore((store) => store.timeFormat);
const markdownShortcuts = useSettingsStore(
(store) => store.markdownShortcuts
);
const { toolbarConfig } = useToolbarConfig();
const { isSearching, toggleSearch } = useSearch();
@@ -183,7 +177,6 @@ function TipTap(props: TipTapProps) {
}
}
},
enableInputRules: markdownShortcuts,
downloadOptions,
doubleSpacedLines,
dateFormat,
@@ -217,8 +210,7 @@ function TipTap(props: TipTapProps) {
onUpdate: ({ editor, transaction }) => {
onContentChange?.();
const preventSave = transaction.getMeta("preventSave") as boolean;
const ignoreEdit = transaction.getMeta("ignoreEdit") as boolean;
const preventSave = transaction?.getMeta("preventSave") as boolean;
const { id, sessionId } = editorstore.get().session;
const content = editor.state.doc.content;
deferredSave(
@@ -228,7 +220,6 @@ function TipTap(props: TipTapProps) {
editor as Editor,
content,
preventSave || !editor.isEditable,
ignoreEdit,
onChange
);
},
@@ -291,17 +282,9 @@ function TipTap(props: TipTapProps) {
onOpenLink: (url) => {
window.open(url, "_blank");
return true;
},
getAttachmentData: onGetAttachmentData
}
};
}, [
readonly,
nonce,
doubleSpacedLines,
dateFormat,
timeFormat,
markdownShortcuts
]);
}, [readonly, nonce, doubleSpacedLines, dateFormat, timeFormat]);
const editor = useTiptap(
tiptapOptions,
@@ -437,6 +420,14 @@ function toIEditor(editor: Editor): IEditor {
}),
undo: () => editor.current?.commands.undo(),
redo: () => editor.current?.commands.redo(),
getMediaHashes: () => {
if (!editor.current) return [];
const hashes: string[] = [];
editor.current.state.doc.descendants((n) => {
if (typeof n.attrs.hash === "string") hashes.push(n.attrs.hash);
});
return hashes;
},
updateContent: (content) => {
const { from, to } = editor.state.selection;
editor.current
@@ -452,17 +443,27 @@ function toIEditor(editor: Editor): IEditor {
})
.run();
},
attachFile: (file: Attachment) =>
file.type === "image"
? editor.current?.commands.insertImage(file)
: editor.current?.commands.insertAttachment(file),
sendAttachmentProgress: (hash, progress) =>
editor.current?.commands.updateAttachment(
{
progress
},
{ query: (a) => a.hash === hash, preventUpdate: true }
)
attachFile: (file: Attachment) => {
if (file.dataurl) {
editor.current?.commands.insertImage({
...file,
bloburl: toBlobURL(file.dataurl, file.hash)
});
} else editor.current?.commands.insertAttachment(file);
},
loadWebClip: (hash, src) =>
editor.current?.commands.updateWebClip({ hash }, { src }),
loadImage: (hash, dataurl) =>
editor.current?.commands.updateImage(
{ hash },
{ hash, bloburl: toBlobURL(dataurl, hash), preventUpdate: true }
),
sendAttachmentProgress: (hash, type, progress) =>
editor.current?.commands.setAttachmentProgress({
hash,
type,
progress
})
};
}

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 { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { Button, Flex, Text } from "@theme-ui/components";
import {
Published,
@@ -63,7 +63,6 @@ function Toolbar() {
const theme = useThemeStore((store) => store.colorScheme);
const toggleNightMode = useThemeStore((store) => store.toggleColorScheme);
const [isTitleVisible, setIsTitleVisible] = useState(false);
const onScrollTitleRef = useRef(null);
const monographs = useMonographStore((store) => store.monographs);
const { canRedo, canUndo, redo, undo } = useHistory();
@@ -93,10 +92,6 @@ function Toolbar() {
};
}, [isTitleVisible]);
useEffect(() => {
if (onScrollTitleRef.current) onScrollTitleRef.current.value = title;
}, [title]);
const tools = useMemo(
() => [
{
@@ -221,7 +216,6 @@ function Toolbar() {
}}
/>
<AnimatedInput
ref={onScrollTitleRef}
variant="clean"
ml={[2, 2, 0]}
initial={{

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 { Attachment } from "@notesnook/editor";
import { Attachment } from "./picker";
export type NoteStatistics = {
words: {
@@ -33,7 +33,14 @@ export interface IEditor {
}) => void;
undo: () => void;
redo: () => void;
getMediaHashes: () => string[];
updateContent: (content: string) => void;
attachFile: (file: Attachment) => void;
sendAttachmentProgress: (hash: string, progress: number) => void;
loadWebClip: (hash: string, html: string) => void;
loadImage: (hash: string, src: string) => void;
sendAttachmentProgress: (
hash: string,
type: "download" | "upload" | "encrypt",
progress: number
) => void;
}

View File

@@ -56,27 +56,22 @@ type GroupingMenuOptions = {
isUngrouped: boolean;
};
const groupByMenu: (options: GroupingMenuOptions) => MenuItem | null = (
options
) =>
options.groupingKey === "reminders"
? null
: {
type: "button",
key: "groupBy",
title: "Group by",
icon: GroupBy.path,
menu: {
items: map(options, [
{ key: "none", title: "None" },
{ key: "default", title: "Default" },
{ key: "year", title: "Year" },
{ key: "month", title: "Month" },
{ key: "week", title: "Week" },
{ key: "abc", title: "A - Z" }
])
}
};
const groupByMenu: (options: GroupingMenuOptions) => MenuItem = (options) => ({
type: "button",
key: "groupBy",
title: "Group by",
icon: GroupBy.path,
menu: {
items: map(options, [
{ key: "none", title: "None" },
{ key: "default", title: "Default" },
{ key: "year", title: "Year" },
{ key: "month", title: "Month" },
{ key: "week", title: "Week" },
{ key: "abc", title: "A - Z" }
])
}
});
const orderByMenu: (options: GroupingMenuOptions) => MenuItem = (options) => ({
type: "button",
@@ -95,20 +90,12 @@ const orderByMenu: (options: GroupingMenuOptions) => MenuItem = (options) => ({
{
key: "asc",
title:
options.groupOptions.sortBy === "title"
? "A - Z"
: options.groupOptions.sortBy === "dueDate"
? "Earliest first"
: "Oldest - newest"
options.groupOptions.sortBy === "title" ? "A - Z" : "Oldest - newest"
},
{
key: "desc",
title:
options.groupOptions.sortBy === "title"
? "Z - A"
: options.groupOptions.sortBy === "dueDate"
? "Latest first"
: "Newest - oldest"
options.groupOptions.sortBy === "title" ? "Z - A" : "Newest - oldest"
}
])
}
@@ -143,11 +130,6 @@ const sortByMenu: (options: GroupingMenuOptions) => MenuItem = (options) => ({
title: "Date modified",
isHidden: options.groupingKey !== "tags"
},
{
key: "dueDate",
title: "Due date",
isHidden: options.groupingKey !== "reminders"
},
{
key: "title",
title: "Title",
@@ -349,26 +331,26 @@ function GroupHeader(props: GroupHeaderProps) {
isUngrouped: false,
refresh
};
const groupBy = groupByMenu({
...menuOptions,
parentKey: "groupBy"
});
const menuItems = [
orderByMenu({
...menuOptions,
parentKey: "sortDirection"
}),
sortByMenu({
...menuOptions,
parentKey: "sortBy"
})
];
if (groupBy) menuItems.push(groupBy);
openMenu(menuItems, {
title: groupBy ? "Group & sort" : "Sort"
});
openMenu(
[
orderByMenu({
...menuOptions,
parentKey: "sortDirection"
}),
sortByMenu({
...menuOptions,
parentKey: "sortBy"
}),
groupByMenu({
...menuOptions,
parentKey: "groupBy"
})
],
{
title: "Group & sort"
}
);
}}
/>
)}

View File

@@ -84,7 +84,7 @@ function IconTag(props: IconTagProps) {
>
<Icon
size={11}
color={highlight ? "accent" : "icon"}
// color={styles?.icon?.color || (highlight ? "primary" : "icon")}
sx={{ ...styles?.icon, flexShrink: 0 }}
/>
<Text

View File

@@ -70,7 +70,7 @@ import { hashNavigate, navigate } from "../../navigation";
import { showPublishView } from "../publish-view";
import IconTag from "../icon-tag";
import { COLORS } from "../../common/constants";
import { exportNote, exportNotes, exportToPDF } from "../../common/export";
import { exportNotes } from "../../common/export";
import { Multiselect } from "../../common/multi-select";
import { store as selectionStore } from "../../stores/selection-store";
import {
@@ -86,7 +86,7 @@ import {
ReferencesWithDateEdited
} from "../list-container/types";
import { SchemeColors } from "@notesnook/theme";
import FileSaver from "file-saver";
import Vault from "../../common/vault";
type NoteProps = {
tags: Item[];
@@ -369,9 +369,10 @@ const menuItems: (note: any, items?: any[]) => MenuItem[] = (
onClick: async () => {
const { unlock, lock } = store.get();
if (!note.locked) {
if (await lock(note.id))
showToast("success", "Note locked successfully!");
} else if (await unlock(note.id)) {
await lock(note.id);
showToast("success", "Note locked successfully!");
} else {
await unlock(note.id);
showToast("success", "Note unlocked successfully!");
}
},
@@ -421,12 +422,7 @@ const menuItems: (note: any, items?: any[]) => MenuItem[] = (
isDisabled: !isSynced,
icon: Print.path,
onClick: async () => {
const item = db.notes?.note(note);
if (!item) return;
const result = await exportNote(item, "pdf");
if (!result) return;
await exportToPDF(note.title, result.content);
await exportNotes("pdf", [note.id]);
}
},
{
@@ -460,24 +456,7 @@ const menuItems: (note: any, items?: any[]) => MenuItem[] = (
// ? "Multiple notes cannot be exported as PDF."
// : false,
isPro: format.type !== "txt",
onClick: async () => {
if (ids.length === 1) {
const item = db.notes?.note(note);
if (!item) return;
const result = await exportNote(item, format.type);
if (!result) return;
if (format.type === "pdf")
return exportToPDF(note.title, result.content);
return FileSaver.saveAs(
new Blob([new TextEncoder().encode(result.content)]),
result.filename
);
}
await exportNotes(format.type, ids);
}
onClick: () => exportNotes(format.type, ids)
}))
},
multiSelect: true,
@@ -727,11 +706,17 @@ async function copyNote(noteId: string, format: "md" | "txt") {
try {
const note = db.notes?.note(noteId);
if (!note) throw new Error("No note with this id exists.");
if (note?.data.locked && !(await Vault.unlockVault()))
throw new Error("Please unlock this note to copy it.");
const result = await exportNote(note, format, true);
if (!result) return;
const rawContent = await db.content?.raw(note.data.contentId);
const content = note?.data.locked
? await db.vault?.decryptContent(rawContent)
: rawContent;
await navigator.clipboard.writeText(result.content);
const text = await note.export(format, content, false);
if (!text) throw new Error(`Could not convert note to ${format}.`);
await navigator.clipboard.writeText(text);
showToast("success", "Copied!");
} catch (e) {
if (e instanceof Error)

View File

@@ -125,7 +125,7 @@ function PublishView(props) {
variant="body"
as="a"
target="_blank"
href={`https://monogr.ph/${publishId}`}
href={`https://monograph.notesnook.com/${publishId}`}
sx={{
textOverflow: "ellipsis",
whiteSpace: "nowrap",
@@ -134,13 +134,13 @@ function PublishView(props) {
mr: 2
}}
>
{`https://monogr.ph/${publishId}`}
{`https://monograph.notesnook.com/${publishId}`}
</Text>
<Button
variant="anchor"
className="copyPublishLink"
onClick={() => {
writeText(`https://monogr.ph/${publishId}`);
writeText(`https://monograph.notesnook.com/${publishId}`);
}}
>
<Copy size={20} color="accent" />

View File

@@ -85,7 +85,6 @@ function Reminder(props: ReminderProps) {
gap: 1
}}
>
{reminder.disabled ? null : <PriorityIcon size={14} />}
{reminder.disabled ? (
<IconTag icon={ReminderOff} text={"Disabled"} testId={"disabled"} />
) : (
@@ -96,6 +95,7 @@ function Reminder(props: ReminderProps) {
testId={"reminder-time"}
/>
)}
{reminder.disabled ? null : <PriorityIcon size={14} />}
{reminder.mode === "repeat" && reminder.recurringMode && (
<IconTag
icon={Refresh}

View File

@@ -54,7 +54,6 @@ type FlexScrollContainerProps = {
id?: string;
className?: string;
style?: React.CSSProperties;
scrollRef?: React.Ref<HTMLElement>;
} & MacScrollbarProps;
export function FlexScrollContainer({
@@ -62,13 +61,11 @@ export function FlexScrollContainer({
children,
style,
className,
scrollRef,
...restProps
}: PropsWithChildren<FlexScrollContainerProps>) {
return (
<MacScrollbar
{...restProps}
ref={scrollRef}
id={id}
className={className}
style={style}

View File

@@ -1,115 +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 { useState } from "react";
import { Perform } from "../common/dialog-controller";
import Field from "../components/field";
import Dialog from "../components/dialog";
import { Box, Button } from "@theme-ui/components";
export type PromptDialogProps = {
onClose: Perform;
validate: (outputs: {
password?: string;
key?: string;
}) => boolean | Promise<boolean>;
};
export default function BackupPasswordDialog(props: PromptDialogProps) {
const { onClose, validate } = props;
const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const [isEncryptionKey, setIsEncryptionKey] = useState(false);
return (
<Dialog
isOpen={true}
title={"Encrypted backup"}
description={
"Please enter the password to decrypt and restore this backup."
}
onClose={() => onClose(false)}
positiveButton={{
form: "backupPasswordForm",
type: "submit",
loading: isLoading,
disabled: isLoading,
text: "Restore"
}}
negativeButton={{ text: "Cancel", onClick: () => onClose(false) }}
>
<Box
id="backupPasswordForm"
as="form"
onSubmit={async (e) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
try {
setIsLoading(true);
setError(undefined);
const key = formData.get("key") as string;
const password = formData.get("password") as string;
if (!key && !password) return;
if (await validate({ key, password })) {
onClose(true);
} else {
setError("Wrong password.");
}
} catch (e) {
setError((e as Error).message);
} finally {
setIsLoading(false);
}
}}
>
{isEncryptionKey ? (
<Field
required
autoFocus
data-test-id="dialog-key"
label="Encryption key"
type="password"
id="key"
name="key"
/>
) : (
<Field
required
autoFocus
data-test-id="dialog-password"
label="Password"
type="password"
autoComplete="current-password"
id="password"
name="password"
/>
)}
</Box>
<Button variant="anchor" onClick={() => setIsEncryptionKey((s) => !s)}>
{isEncryptionKey
? "Don't have encryption key? Use password."
: "Forgot password? Use encryption key."}
</Button>
</Dialog>
);
}

View File

@@ -89,37 +89,7 @@ const features: Record<FeatureKeys, Feature> = {
)
}
]
: [
{
title: "Sort reminders by due date",
subtitle:
"You can now sort your reminders by due date to quickly see which reminders are upcoming."
},
{
title: "Restore backups using encryption key",
subtitle:
"If you forget your password but still have your recovery key, you can now use that to restore your backups."
},
{
title: "Disable markdown in editor",
subtitle:
"If you find automatic markdown shortcuts annoying, you can now turn those off from Settings > Editor > Disable markdown shortcuts."
},
{
title: "Improved password reset",
subtitle:
"Resetting password should now be much more reliable and safer."
},
...(IS_DESKTOP_APP
? [
{
title: "Proxy settings",
subtitle:
"Desktop app now support setting a custom proxy url to route all network through it. Useful if you are behind a firewall and would like to bypass it."
}
]
: [])
],
: [],
cta: {
title: "Got it",
icon: Checkmark,

View File

@@ -52,9 +52,6 @@ const MigrationDialog = React.lazy(() => import("./migration-dialog"));
const EmailChangeDialog = React.lazy(() => import("./email-change-dialog"));
const AddTagsDialog = React.lazy(() => import("./add-tags-dialog"));
const ThemeDetailsDialog = React.lazy(() => import("./theme-details-dialog"));
const BackupPasswordDialog = React.lazy(
() => import("./backup-password-dialog")
);
export const Dialogs = {
AddNotebookDialog,
@@ -82,6 +79,5 @@ export const Dialogs = {
EmailChangeDialog,
AddTagsDialog,
SettingsDialog,
ThemeDetailsDialog,
BackupPasswordDialog
ThemeDetailsDialog
};

View File

@@ -63,11 +63,7 @@ function PasswordDialog(props) {
loading: isLoading,
disabled: isLoading
}}
negativeButton={{
text: "Cancel",
onClick: () => props.onClose(false),
disabled: isLoading
}}
negativeButton={{ text: "Cancel", onClick: () => props.onClose(false) }}
>
<Box
id="passwordForm"

View File

@@ -19,12 +19,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { SettingsGroup } from "./types";
import { useStore as useUserStore } from "../../stores/user-store";
import { createBackup, verifyAccount } from "../../common";
import { verifyAccount } from "../../common";
import {
show2FARecoveryCodesDialog,
showMultifactorDialog,
showPasswordDialog,
showRecoveryKeyDialog
showPasswordDialog
} from "../../common/dialog-controller";
import { db } from "../../common/db";
import { showToast } from "../../utils/toast";
@@ -46,22 +45,19 @@ export const AuthenticationSettings: SettingsGroup[] = [
title: "Change password",
variant: "secondary",
action: async () => {
await createBackup();
const result = await showPasswordDialog(
"change_account_password",
async (data) => {
await db.user?.clearSessions();
return (
(await db.user?.changePassword(
db.user?.changePassword(
data.oldPassword,
data.newPassword
)) || false
) || false
);
}
);
if (result) {
showToast("success", "Account password changed!");
await showRecoveryKeyDialog();
}
if (result) showToast("success", "Account password changed!");
}
}
]

View File

@@ -43,7 +43,17 @@ export const BackupExportSettings: SettingsGroup[] = [
{
type: "button",
title: "Create backup",
action: createBackup,
action: async () => {
if (
!useUserStore.getState().isLoggedIn &&
useSettingStore.getState().encryptBackups
)
useSettingStore.getState().toggleEncryptBackups();
const verified =
useSettingStore.getState().encryptBackups ||
(await verifyAccount());
if (verified) await createBackup();
},
variant: "secondary"
}
]

View File

@@ -125,7 +125,6 @@ export const BehaviourSettings: SettingsGroup[] = [
selectedOption: () =>
useSettingStore.getState().trashCleanupInterval.toString(),
options: [
{ value: "1", title: "Daily" },
{ value: "7", title: "Weekly" },
{ value: "30", title: "Monthly" },
{ value: "365", title: "Yearly" },

View File

@@ -1,53 +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 { Button, Text } from "@theme-ui/components";
import { FlexScrollContainer } from "../../../components/scroll-container";
import { useSpellChecker } from "../../../hooks/use-spell-checker";
export function DictionaryWords() {
const words = useSpellChecker((store) => store.words);
const deleteWord = useSpellChecker((store) => store.deleteWord);
return (
<>
<FlexScrollContainer
suppressAutoHide
style={{
maxHeight: 400,
display: "flex",
flexDirection: "column"
}}
>
<Text variant="body" sx={{ my: 1 }}>
You have {words.length} custom dictionary words.
</Text>
{words.map((word) => (
<Button
variant="menuitem"
sx={{ textAlign: "left", p: 1 }}
onClick={() => deleteWord(word)}
>
{word}
</Button>
))}
</FlexScrollContainer>
</>
);
}

View File

@@ -29,7 +29,6 @@ import { useSpellChecker } from "../../hooks/use-spell-checker";
import { SpellCheckerLanguages } from "./components/spell-checker-languages";
import { CustomizeToolbar } from "./components/customize-toolbar";
import { DictionaryWords } from "./components/dictionary-words";
export const EditorSettings: SettingsGroup[] = [
{
@@ -101,7 +100,7 @@ symbols (e.g. 202305261253)`,
key: "double-spacing",
title: "Double spaced paragraphs",
description:
"Use double spacing between paragraphs when you press Enter in the editor.",
"Use double spacing between paragraphs and when you press Enter in the editor.",
onStateChange: (listener) =>
useSettingStore.subscribe((c) => c.doubleSpacedParagraphs, listener),
components: [
@@ -112,27 +111,6 @@ symbols (e.g. 202305261253)`,
useSettingStore.getState().toggleDoubleSpacedParagraphs()
}
]
},
{
key: "markdown-shortcuts",
title: "Markdown shortcuts",
description: `Markdown shortcuts are triggered whenever you input a specific character combination.
For example:
1. Typing '/date' adds the current Date
2. Wrapping something in '**' turns it into bold text
3. Typing '1.' automatically creates a numbered list.
4. etc.`,
onStateChange: (listener) =>
useSettingStore.subscribe((c) => c.markdownShortcuts, listener),
components: [
{
type: "toggle",
isToggled: () => useSettingStore.getState().markdownShortcuts,
toggle: () => useSettingStore.getState().toggleMarkdownShortcuts()
}
]
}
]
},
@@ -171,16 +149,6 @@ For example:
component: SpellCheckerLanguages
}
]
},
{
key: "custom-dictionay-words",
title: "Custom dictionary words",
components: [
{
type: "custom",
component: DictionaryWords
}
]
}
]
},

View File

@@ -36,8 +36,7 @@ import {
Privacy,
Pro,
ShieldLock,
Sync,
Proxy
Sync
} from "../../components/icons";
import { Perform } from "../../common/dialog-controller";
import NavigationItem from "../../components/navigation-menu/navigation-item";
@@ -66,6 +65,7 @@ import {
import { AppearanceSettings } from "./appearance-settings";
import { debounce } from "@notesnook/common";
import { SubscriptionSettings } from "./subscription-settings";
import { alpha } from "@theme-ui/color";
import { ScopedThemeProvider } from "../../components/theme-provider";
type SettingsDialogProps = { onClose: Perform };

View File

@@ -115,7 +115,7 @@ What data is collected & when?`,
key: "custom-cors",
title: "Custom CORS proxy",
description:
"CORS proxy is required to directly download images from within the Notesnook app. It allows Notesnook to bypass browser restrictions by using a proxy. You can set a custom self-hosted proxy URL to increase your privacy.",
"CORS proxy is required to directly download images from within the Notesnook app. It allows Notesnook to bypass browser restrictions by using a proxy. You can set a custom self-hosted proxy URL to increase your privacy",
onStateChange: (listener) =>
useSettingStore.subscribe((s) => s.telemetry, listener),
components: [
@@ -144,30 +144,6 @@ What data is collected & when?`,
variant: "secondary"
}
]
},
{
key: "proxy-config",
title: "Proxy",
description: `Setup an HTTP/HTTPS/SOCKS proxy.
For example:
http://foobar:80
socks4://proxy.example.com
http://username:password@foobar:80
To remove the proxy, simply erase everything in the input.`,
onStateChange: (listener) =>
useSettingStore.subscribe((c) => c.proxyRules, listener),
components: [
{
type: "input",
inputType: "text",
defaultValue: () => useSettingStore.getState().proxyRules || "",
onChange: (value) => {
useSettingStore.getState().setProxyRules(value);
}
}
]
}
]
}

View File

@@ -27,7 +27,6 @@ class SpellCheckerStore extends BaseStore<SpellCheckerStore> {
languages: Language[] = [];
enabled = true;
enabledLanguages: Language[] = [];
words: string[] = [];
toggleSpellChecker = async () => {
const enabled = this.get().enabled;
@@ -45,19 +44,14 @@ class SpellCheckerStore extends BaseStore<SpellCheckerStore> {
};
refresh = async () => {
console.log("SPELL CHECK", await desktop?.spellChecker.isEnabled.query());
this.set({
enabledLanguages:
(await desktop?.spellChecker.enabledLanguages.query()) || [],
languages: (await desktop?.spellChecker.languages.query()) || [],
enabled: await desktop?.spellChecker.isEnabled.query(),
words: (await desktop?.spellChecker.words.query()) || []
enabled: await desktop?.spellChecker.isEnabled.query()
});
};
deleteWord = async (word: string) => {
await desktop?.spellChecker.deleteWord.mutate(word);
await this.get().refresh();
};
}
const [useSpellChecker] = createStore(SpellCheckerStore);

View File

@@ -298,11 +298,7 @@ class AppStore extends BaseStore {
this.updateSyncStatus("syncing");
try {
const result = await db.sync({
type: full ? "full" : "send",
force,
serverLastSynced: lastSynced
});
const result = await db.sync(full, force, lastSynced);
if (!result) return this.updateSyncStatus("failed");
this.updateSyncStatus("completed", true);
@@ -311,6 +307,7 @@ class AppStore extends BaseStore {
if (pendingSync) {
logger.info("Running pending sync", pendingSync);
pendingSync = false;
await this.get().sync(pendingSync.full, false);
}
} catch (err) {

View File

@@ -290,14 +290,12 @@ class EditorStore extends BaseStore {
state: SESSION_STATES.new
};
});
setTimeout(() => {
noteStore.setSelectedNote(0);
this.toggleProperties(false);
if (shouldNavigate)
hashNavigate(`/notes/create`, { replace: true, addNonce: true });
appStore.setIsEditorOpen(false);
setDocumentTitle();
}, 100);
noteStore.setSelectedNote(0);
this.toggleProperties(false);
if (shouldNavigate)
hashNavigate(`/notes/create`, { replace: true, addNonce: true });
setTimeout(() => appStore.setIsEditorOpen(false), 100);
setDocumentTitle();
};
setTitle = (noteId, title) => {
@@ -308,17 +306,8 @@ class EditorStore extends BaseStore {
return this.saveSession(noteId, { [name]: value });
};
/**
*
* @param {*} noteId
* @param {*} sessionId
* @param {boolean} ignoreEdit if this is set to true, we save the content but do not update the dateEdited. Useful for metadata only changes in content.
* @param {*} content
* @returns
*/
saveSessionContent = (noteId, sessionId, ignoreEdit, content) => {
const dateEdited = ignoreEdit ? this.get().session.dateEdited : undefined;
return this.saveSession(noteId, { sessionId, content, dateEdited });
saveSessionContent = (noteId, sessionId, content) => {
return this.saveSession(noteId, { sessionId, content });
};
setTag = (tag) => {

View File

@@ -116,18 +116,17 @@ class NoteStore extends BaseStore {
unlock = async (id) => {
return await Vault.unlockNote(id).then(async (res) => {
if (editorStore.get().session.id === id)
await editorStore.clearSession(true);
await editorStore.openSession(id);
this.refreshItem(id);
return res;
});
};
lock = async (id) => {
if (!(await Vault.lockNote(id))) return false;
await Vault.lockNote(id);
this.refreshItem(id);
if (editorStore.get().session.id === id)
await editorStore.openSession(id, true);
return true;
};
readonly = async (id) => {

View File

@@ -37,13 +37,7 @@ class ReminderStore extends BaseStore {
refresh = (reset = true) => {
const reminders = db.reminders.all;
this.set(
(state) =>
(state.reminders = groupReminders(
reminders,
db.settings.getGroupOptions("reminders")
))
);
this.set((state) => (state.reminders = groupReminders(reminders)));
if (reset) {
resetReminders(reminders);
notestore.refresh();

View File

@@ -38,7 +38,6 @@ class SettingStore extends BaseStore {
PATHS.backupsDirectory
);
doubleSpacedParagraphs = Config.get("doubleSpacedLines", true);
markdownShortcuts = Config.get("markdownShortcuts", true);
notificationsSettings = Config.get("notifications", { reminder: true });
zoomFactor = 1.0;
@@ -59,10 +58,6 @@ class SettingStore extends BaseStore {
desktopIntegrationSettings = undefined;
autoUpdates = true;
isFlatpak = false;
/**
* @type {string|undefined}
*/
proxyRules = undefined;
refresh = async () => {
this.set({
@@ -75,8 +70,7 @@ class SettingStore extends BaseStore {
await desktop?.integration.desktopIntegration.query(),
privacyMode: await desktop?.integration.privacyMode.query(),
zoomFactor: await desktop?.integration.zoomFactor.query(),
autoUpdates: await desktop?.updater.autoUpdates.query(),
proxyRules: await desktop.integration.proxyRules.query()
autoUpdates: await desktop?.updater.autoUpdates.query()
});
};
@@ -105,11 +99,6 @@ class SettingStore extends BaseStore {
this.set({ zoomFactor });
};
setProxyRules = async (proxyRules) => {
await desktop?.integration.setProxyRules.mutate(proxyRules);
this.set({ proxyRules });
};
setEncryptBackups = (encryptBackups) => {
this.set({ encryptBackups });
Config.set("encryptBackups", encryptBackups);
@@ -167,14 +156,6 @@ class SettingStore extends BaseStore {
Config.set("doubleSpacedLines", !doubleSpacedParagraphs);
};
toggleMarkdownShortcuts = (toggleState) => {
this.set((state) => {
state.markdownShortcuts =
toggleState !== undefined ? toggleState : !state.markdownShortcuts;
Config.set("markdownShortcuts", state.markdownShortcuts);
});
};
toggleTelemetry = () => {
const telemetry = this.get().telemetry;
this.set({ telemetry: !telemetry });

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