mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 02:58:34 +02:00
Compare commits
15 Commits
fix-remind
...
fix-sort-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6d999cf40 | ||
|
|
b8c68b5163 | ||
|
|
2496004896 | ||
|
|
16ec35de25 | ||
|
|
d248b8e2b8 | ||
|
|
641e7484e3 | ||
|
|
a46409ff44 | ||
|
|
5dbabc2706 | ||
|
|
2d4d656dd9 | ||
|
|
ab51b22e4a | ||
|
|
781d09daca | ||
|
|
f21533e5e4 | ||
|
|
2ea9b04863 | ||
|
|
675d517cbc | ||
|
|
c336c57bda |
@@ -32,6 +32,7 @@ import { AssetManager } from "../utils/asset-manager";
|
||||
import { isFlatpak } from "../utils";
|
||||
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
||||
import { rm } from "fs/promises";
|
||||
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
|
||||
|
||||
const t = initTRPC.create();
|
||||
|
||||
@@ -55,6 +56,15 @@ export const osIntegrationRouter = t.router({
|
||||
config.zoomFactor = factor;
|
||||
}),
|
||||
|
||||
customDns: t.procedure.query(() => config.customDns),
|
||||
setCustomDns: t.procedure
|
||||
.input(z.boolean())
|
||||
.mutation(({ input: customDns }) => {
|
||||
if (customDns) enableCustomDns();
|
||||
else disableCustomDns();
|
||||
config.customDns = customDns;
|
||||
}),
|
||||
|
||||
proxyRules: t.procedure.query(() => config.proxyRules),
|
||||
setProxyRules: t.procedure
|
||||
.input(z.string().optional())
|
||||
|
||||
@@ -35,6 +35,7 @@ import path from "path";
|
||||
import { bringToFront } from "./utils/bring-to-front";
|
||||
import { bridge } from "./api/bridge";
|
||||
import { setupDesktopIntegration } from "./utils/desktop-integration";
|
||||
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
|
||||
|
||||
// only run a single instance
|
||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||
@@ -151,14 +152,8 @@ async function createWindow() {
|
||||
app.once("ready", async () => {
|
||||
console.info("App ready. Opening window.");
|
||||
|
||||
app.configureHostResolver({
|
||||
secureDnsServers: [
|
||||
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||
"https://dns.quad9.net/dns-query"
|
||||
],
|
||||
enableBuiltInResolver: true,
|
||||
secureDnsMode: "automatic"
|
||||
});
|
||||
if (config.customDns) enableCustomDns();
|
||||
else disableCustomDns();
|
||||
|
||||
if (!isDevelopment()) registerProtocol();
|
||||
await createWindow();
|
||||
|
||||
@@ -43,6 +43,7 @@ export const config = {
|
||||
theme: nativeTheme.themeSource,
|
||||
automaticUpdates: true,
|
||||
proxyRules: "",
|
||||
customDns: true,
|
||||
|
||||
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
|
||||
windowControlsIconColor:
|
||||
|
||||
37
apps/desktop/src/utils/custom-dns.ts
Normal file
37
apps/desktop/src/utils/custom-dns.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
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 { app } from "electron";
|
||||
|
||||
export function enableCustomDns() {
|
||||
app.configureHostResolver({
|
||||
secureDnsServers: [
|
||||
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||
"https://dns.quad9.net/dns-query"
|
||||
],
|
||||
enableBuiltInResolver: true
|
||||
});
|
||||
}
|
||||
|
||||
export function disableCustomDns() {
|
||||
app.configureHostResolver({
|
||||
secureDnsServers: [],
|
||||
enableBuiltInResolver: true
|
||||
});
|
||||
}
|
||||
@@ -18,12 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useRef, useState } from "react";
|
||||
import {
|
||||
Platform,
|
||||
TextInput,
|
||||
View,
|
||||
ScrollView as RNScrollView
|
||||
} from "react-native";
|
||||
import { Platform, TextInput, View } from "react-native";
|
||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||
import {
|
||||
@@ -369,7 +364,7 @@ export default function ReminderSheet({
|
||||
))}
|
||||
</View>
|
||||
|
||||
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
{recurringMode === RecurringModes.Daily ||
|
||||
recurringMode === RecurringModes.Year
|
||||
? null
|
||||
@@ -430,7 +425,7 @@ export default function ReminderSheet({
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</RNScrollView>
|
||||
</ScrollView>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ const Sort = ({ type, screen }) => {
|
||||
const setOrderBy = async () => {
|
||||
let _groupOptions = {
|
||||
...groupOptions,
|
||||
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
|
||||
sortDirection: groupOptions?.sortDirection === "asc" ? "desc" : "asc"
|
||||
};
|
||||
if (type === "topics") {
|
||||
_groupOptions.groupBy = "none";
|
||||
@@ -85,7 +85,7 @@ const Sort = ({ type, screen }) => {
|
||||
|
||||
<Button
|
||||
title={
|
||||
groupOptions.sortDirection === "asc"
|
||||
groupOptions?.sortDirection === "asc"
|
||||
? groupOptions.groupBy === "abc" ||
|
||||
groupOptions.sortBy === "title"
|
||||
? "A - Z"
|
||||
@@ -100,7 +100,7 @@ const Sort = ({ type, screen }) => {
|
||||
: "New - Old"
|
||||
}
|
||||
icon={
|
||||
groupOptions.sortDirection === "asc"
|
||||
groupOptions?.sortDirection === "asc"
|
||||
? "sort-ascending"
|
||||
: "sort-descending"
|
||||
}
|
||||
@@ -229,7 +229,7 @@ const Sort = ({ type, screen }) => {
|
||||
|
||||
if (item === "abc") {
|
||||
_groupOptions.sortBy = "title";
|
||||
_groupOptions.sortDirection = "asc";
|
||||
_groupOptions.sortDirection = "desc";
|
||||
}
|
||||
|
||||
updateGroupOptions(_groupOptions);
|
||||
|
||||
@@ -142,8 +142,13 @@ function DesktopAppContents({
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocusMode) navPane.current?.collapse();
|
||||
else navPane.current?.expand();
|
||||
if (isFocusMode) {
|
||||
const middlePaneSize = middlePane.current?.getSize() || 20;
|
||||
navPane.current?.collapse();
|
||||
// the middle pane has to be resized because collapsing the nav
|
||||
// pane increases the middle pane's size every time.
|
||||
middlePane.current?.resize(middlePaneSize);
|
||||
} else navPane.current?.expand();
|
||||
}, [isFocusMode]);
|
||||
|
||||
return (
|
||||
@@ -159,9 +164,10 @@ function DesktopAppContents({
|
||||
ref={navPane}
|
||||
className="nav-pane"
|
||||
defaultSize={10}
|
||||
minSize={3}
|
||||
onResize={(size) => setIsNarrow(size <= 3)}
|
||||
minSize={3.5}
|
||||
onResize={(size) => setIsNarrow(size <= 5)}
|
||||
collapsible
|
||||
collapsedSize={3.5}
|
||||
>
|
||||
<NavigationMenu
|
||||
toggleNavigationContainer={(state) => {
|
||||
@@ -192,7 +198,7 @@ function DesktopAppContents({
|
||||
</ScopedThemeProvider>
|
||||
</Panel>
|
||||
<PanelResizeHandle className="panel-resize-handle" />
|
||||
<Panel className="editor-pane">
|
||||
<Panel className="editor-pane" defaultSize={70}>
|
||||
<Flex
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
||||
@@ -77,18 +77,18 @@ export async function introduceFeatures() {
|
||||
|
||||
export const DEFAULT_CONTEXT = { colors: [], tags: [], notebook: {} };
|
||||
|
||||
export async function createBackup() {
|
||||
export async function createBackup(rescueMode = false) {
|
||||
const { isLoggedIn } = useUserStore.getState();
|
||||
const { encryptBackups, toggleEncryptBackups } = useSettingStore.getState();
|
||||
if (!isLoggedIn && encryptBackups) toggleEncryptBackups();
|
||||
|
||||
const verified = encryptBackups || (await verifyAccount());
|
||||
const verified = rescueMode || encryptBackups || (await verifyAccount());
|
||||
if (!verified) {
|
||||
showToast("error", "Could not create a backup: user verification failed.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const encryptedBackups = isLoggedIn && encryptBackups;
|
||||
const encryptedBackups = !rescueMode && isLoggedIn && encryptBackups;
|
||||
|
||||
const filename = sanitizeFilename(
|
||||
`${formatDate(Date.now(), {
|
||||
@@ -139,7 +139,9 @@ export async function createBackup() {
|
||||
console.error(error);
|
||||
} else {
|
||||
showToast("success", `Backup saved at ${filePath}.`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function selectBackupFile() {
|
||||
|
||||
@@ -46,6 +46,7 @@ import { Section } from "../properties";
|
||||
import { scrollIntoViewById } from "@notesnook/editor";
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { useEditorManager } from "./manager";
|
||||
import { TITLE_BAR_HEIGHT } from "../title-bar";
|
||||
|
||||
type TableOfContentsProps = {
|
||||
sessionId: string;
|
||||
@@ -100,7 +101,7 @@ function TableOfContents(props: TableOfContentsProps) {
|
||||
display: "flex",
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
top: TITLE_BAR_HEIGHT,
|
||||
zIndex: 999,
|
||||
height: "100%",
|
||||
width: "300px",
|
||||
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
ZoomIn,
|
||||
ZoomOut
|
||||
} from "../icons";
|
||||
import { getPlatform } from "../../utils/platform";
|
||||
import { TITLE_BAR_HEIGHT } from "../title-bar";
|
||||
|
||||
const DEFAULT_ZOOM_STEP = 0.3;
|
||||
const DEFAULT_LARGE_ZOOM = 4;
|
||||
@@ -337,7 +339,12 @@ export class Lightbox extends React.Component<LightboxProps> {
|
||||
borderRadius: "0px 0px 0px 5px",
|
||||
overflow: "hidden",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end"
|
||||
justifyContent: "flex-end",
|
||||
height: IS_DESKTOP_APP ? TITLE_BAR_HEIGHT : "auto",
|
||||
pr:
|
||||
IS_DESKTOP_APP && getPlatform() !== "darwin"
|
||||
? "calc(100vw - env(titlebar-area-width))"
|
||||
: 0
|
||||
}}
|
||||
>
|
||||
{tools.map((tool) => (
|
||||
@@ -349,6 +356,7 @@ export class Lightbox extends React.Component<LightboxProps> {
|
||||
bg="transparent"
|
||||
title={tool.title}
|
||||
sx={{
|
||||
height: "100%",
|
||||
borderRadius: 0,
|
||||
display: [
|
||||
tool.hideOnMobile ? "none" : "flex",
|
||||
|
||||
@@ -106,7 +106,7 @@ function NavigationItem(
|
||||
<Button
|
||||
data-test-id={`navigation-item`}
|
||||
sx={{
|
||||
px: 2,
|
||||
px: isTablet ? 1 : 2,
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: isTablet ? "center" : "flex-start",
|
||||
@@ -124,7 +124,10 @@ function NavigationItem(
|
||||
}}
|
||||
>
|
||||
{image ? (
|
||||
<Image src={image} sx={{ borderRadius: 50, size: 20 }} />
|
||||
<Image
|
||||
src={image}
|
||||
sx={{ borderRadius: 50, size: 20, minWidth: 20, flexShrink: 0 }}
|
||||
/>
|
||||
) : Icon ? (
|
||||
<Icon
|
||||
size={isTablet ? 16 : 15}
|
||||
|
||||
@@ -61,6 +61,7 @@ import {
|
||||
} from "@notesnook/core";
|
||||
import { VirtualizedTable } from "../virtualized-table";
|
||||
import { TextSlice } from "@notesnook/core/dist/utils/content-block";
|
||||
import { TITLE_BAR_HEIGHT } from "../title-bar";
|
||||
|
||||
const tools = [
|
||||
{ key: "pin", property: "pinned", icon: Pin, label: "Pin" },
|
||||
@@ -129,7 +130,7 @@ function EditorProperties(props: EditorPropertiesProps) {
|
||||
sx={{
|
||||
display: "flex",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
top: TITLE_BAR_HEIGHT,
|
||||
right: 0,
|
||||
zIndex: 999,
|
||||
height: "100%",
|
||||
|
||||
@@ -56,6 +56,7 @@ function Toggle(props: ToggleProps) {
|
||||
<Switch
|
||||
sx={{ m: 0, bg: isOn ? "accent" : "icon-secondary" }}
|
||||
checked={isOn}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "../icons";
|
||||
import { BaseThemeProvider } from "../theme-provider";
|
||||
|
||||
export const TITLE_BAR_HEIGHT = IS_DESKTOP_APP ? 37.8 : 0;
|
||||
export function TitleBar() {
|
||||
const { isMaximized, isFullscreen, hasNativeWindowControls } =
|
||||
useWindowControls();
|
||||
@@ -65,7 +66,7 @@ export function TitleBar() {
|
||||
scope="titleBar"
|
||||
sx={{
|
||||
background: "background",
|
||||
height: 37.8,
|
||||
height: TITLE_BAR_HEIGHT,
|
||||
display: "flex",
|
||||
borderBottom: "1px solid var(--border)",
|
||||
...(!isFullscreen && hasNativeWindowControls
|
||||
|
||||
@@ -44,7 +44,8 @@ export const AppearanceSettings: SettingsGroup[] = [
|
||||
type: "input",
|
||||
inputType: "number",
|
||||
min: 0.5,
|
||||
max: 2.0,
|
||||
max: 3.0,
|
||||
step: 0.1,
|
||||
defaultValue: () => useSettingStore.getState().zoomFactor,
|
||||
onChange: (value) => useSettingStore.getState().setZoomFactor(value)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const AuthenticationSettings: SettingsGroup[] = [
|
||||
title: "Change password",
|
||||
variant: "secondary",
|
||||
action: async () => {
|
||||
await createBackup();
|
||||
if (!(await createBackup())) return;
|
||||
const result = await showPasswordDialog({
|
||||
title: "Change account password",
|
||||
message: `All your data will be re-encrypted and synced with the new password.
|
||||
|
||||
@@ -508,6 +508,7 @@ function SettingItem(props: { item: Setting }) {
|
||||
type={"number"}
|
||||
min={component.min}
|
||||
max={component.max}
|
||||
step={component.step}
|
||||
defaultValue={component.defaultValue()}
|
||||
sx={{ width: 80, mr: 1 }}
|
||||
onChange={debounce((e) => {
|
||||
|
||||
@@ -111,6 +111,26 @@ What data is collected & when?`,
|
||||
section: "privacy",
|
||||
header: "Advanced",
|
||||
settings: [
|
||||
{
|
||||
key: "custom-dns",
|
||||
title: "Use custom DNS",
|
||||
description: `Notesnook uses the following DNS providers:
|
||||
|
||||
1. Cloudflare DNS
|
||||
2. Quad9
|
||||
|
||||
This can sometimes bypass local ISP blockages on Notesnook traffic. Disable this if you want the app to use system's DNS settings.`,
|
||||
onStateChange: (listener) =>
|
||||
useSettingStore.subscribe((s) => s.customDns, listener),
|
||||
isHidden: () => !IS_DESKTOP_APP,
|
||||
components: [
|
||||
{
|
||||
type: "toggle",
|
||||
isToggled: () => useSettingStore.getState().customDns,
|
||||
toggle: () => useSettingStore.getState().toggleCustomDns()
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "custom-cors",
|
||||
title: "Custom CORS proxy",
|
||||
|
||||
@@ -134,6 +134,7 @@ export type NumberInputSettingComponent = BaseSettingComponent<"input"> & {
|
||||
inputType: "number";
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
defaultValue: () => number;
|
||||
onChange: (value: number) => void;
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ async function renderApp() {
|
||||
const { useKeyStore } = await import("./interfaces/key-store");
|
||||
await useKeyStore.getState().init();
|
||||
|
||||
if (serviceWorkerWhitelist.includes(path)) await initializeServiceWorker();
|
||||
// if (serviceWorkerWhitelist.includes(path)) await initializeServiceWorker();
|
||||
|
||||
const { default: Component } = await component();
|
||||
const { default: AppLock } = await import("./views/app-lock");
|
||||
|
||||
@@ -42,6 +42,7 @@ class SettingStore extends BaseStore<SettingStore> {
|
||||
|
||||
zoomFactor = 1.0;
|
||||
privacyMode = false;
|
||||
customDns = true;
|
||||
hideNoteTitle = Config.get("hideNoteTitle", false);
|
||||
telemetry = isTelemetryEnabled();
|
||||
dateFormat = "DD-MM-YYYY";
|
||||
@@ -67,6 +68,7 @@ class SettingStore extends BaseStore<SettingStore> {
|
||||
desktopIntegrationSettings:
|
||||
await desktop?.integration.desktopIntegration.query(),
|
||||
privacyMode: await desktop?.integration.privacyMode.query(),
|
||||
customDns: await desktop?.integration.customDns.query(),
|
||||
zoomFactor: await desktop?.integration.zoomFactor.query(),
|
||||
autoUpdates: await desktop?.updater.autoUpdates.query(),
|
||||
proxyRules: await desktop?.integration.proxyRules.query()
|
||||
@@ -177,6 +179,12 @@ class SettingStore extends BaseStore<SettingStore> {
|
||||
await desktop?.integration.setPrivacyMode.mutate({ enabled: !privacyMode });
|
||||
};
|
||||
|
||||
toggleCustomDns = async () => {
|
||||
const customDns = this.get().customDns;
|
||||
this.set({ customDns: !customDns });
|
||||
await desktop?.integration.setCustomDns.mutate(!customDns);
|
||||
};
|
||||
|
||||
toggleHideTitle = async () => {
|
||||
const { hideNoteTitle } = this.get();
|
||||
this.set({ hideNoteTitle: !hideNoteTitle });
|
||||
|
||||
@@ -460,7 +460,7 @@ function BackupData(props: BaseRecoveryComponentProps<"backup">) {
|
||||
"Please wait while we create a backup file for you to download."
|
||||
}}
|
||||
onSubmit={async () => {
|
||||
await createBackup();
|
||||
await createBackup(true);
|
||||
navigate("new");
|
||||
}}
|
||||
>
|
||||
|
||||
176
packages/core/__tests__/fts-triggers.test.ts
Normal file
176
packages/core/__tests__/fts-triggers.test.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
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 { expect, test } from "vitest";
|
||||
import { TEST_NOTE, databaseTest, noteTest } from "./utils";
|
||||
|
||||
test("updating deleted content should not throw", () =>
|
||||
databaseTest().then(async (db) => {
|
||||
const id = await db.notes.add({
|
||||
title: "New note"
|
||||
});
|
||||
|
||||
const contentId = await db.content.add({
|
||||
data: "helloworld",
|
||||
noteId: id
|
||||
});
|
||||
|
||||
await db.content.remove(contentId!);
|
||||
|
||||
await expect(
|
||||
db.content.collection.update(
|
||||
[contentId!],
|
||||
{ synced: true },
|
||||
{ sendEvent: false }
|
||||
)
|
||||
).resolves.toBeFalsy();
|
||||
}));
|
||||
|
||||
test("updating content should not break full text search", () =>
|
||||
databaseTest().then(async (db) => {
|
||||
const id = await db.notes.add({
|
||||
title: "New note"
|
||||
});
|
||||
|
||||
const contentId = await db.content.add({
|
||||
data: "hello world",
|
||||
noteId: id
|
||||
});
|
||||
|
||||
await db.content.add({
|
||||
id: contentId,
|
||||
data: "i am amazing",
|
||||
noteId: id
|
||||
});
|
||||
|
||||
expect(await db.lookup.notes("amazing").ids()).toContain(id);
|
||||
expect(await db.lookup.notes("hello world").ids()).not.toContain(id);
|
||||
}));
|
||||
|
||||
test("updating note title should not break full text search", () =>
|
||||
databaseTest().then(async (db) => {
|
||||
const id = await db.notes.add({
|
||||
title: "New note"
|
||||
});
|
||||
await db.notes.add({
|
||||
id,
|
||||
title: "What an amazing note!"
|
||||
});
|
||||
expect(await db.lookup.notes("amazing").ids()).toContain(id);
|
||||
expect(await db.lookup.notes("new note").ids()).not.toContain(id);
|
||||
}));
|
||||
|
||||
test("updating deleted note should not throw", () =>
|
||||
databaseTest().then(async (db) => {
|
||||
const id = await db.notes.add({
|
||||
title: "New note"
|
||||
});
|
||||
await db.notes.remove(id);
|
||||
|
||||
await expect(
|
||||
db.notes.collection.update([id], { synced: true }, { sendEvent: false })
|
||||
).resolves.toBeFalsy();
|
||||
}));
|
||||
|
||||
test("overwriting unlocked content with locked content should update search index", () =>
|
||||
noteTest({
|
||||
content: {
|
||||
data: "hello world",
|
||||
type: "tiptap"
|
||||
}
|
||||
}).then(async ({ db, id }) => {
|
||||
await db.content.collection.upsert({
|
||||
id: "something",
|
||||
locked: false,
|
||||
data: "What is this?",
|
||||
noteId: id,
|
||||
dateCreated: Date.now(),
|
||||
dateEdited: Date.now(),
|
||||
synced: false,
|
||||
dateModified: Date.now()
|
||||
});
|
||||
|
||||
await db.content.collection.put([
|
||||
{
|
||||
id: "something",
|
||||
locked: true,
|
||||
data: {
|
||||
alg: "as",
|
||||
cipher: "s",
|
||||
format: "base64",
|
||||
iv: "",
|
||||
length: 20,
|
||||
salt: ""
|
||||
},
|
||||
noteId: id,
|
||||
dateCreated: Date.now(),
|
||||
dateEdited: Date.now(),
|
||||
synced: false,
|
||||
dateModified: Date.now()
|
||||
}
|
||||
]);
|
||||
|
||||
expect(await db.lookup.notes("what is this").ids()).not.toContain(id);
|
||||
}));
|
||||
|
||||
test("overwriting content with deleted content should update search index", () =>
|
||||
noteTest({
|
||||
content: {
|
||||
data: "hello world",
|
||||
type: "tiptap"
|
||||
}
|
||||
}).then(async ({ db, id }) => {
|
||||
await db.content.collection.upsert({
|
||||
id: "something",
|
||||
locked: false,
|
||||
data: "What is this?",
|
||||
noteId: id,
|
||||
dateCreated: Date.now(),
|
||||
dateEdited: Date.now(),
|
||||
synced: false,
|
||||
dateModified: Date.now()
|
||||
});
|
||||
|
||||
await db.content.collection.put([
|
||||
{
|
||||
id: "something",
|
||||
deleted: true,
|
||||
synced: true,
|
||||
dateModified: Date.now()
|
||||
}
|
||||
]);
|
||||
|
||||
expect(await db.lookup.notes("what is this").ids()).not.toContain(id);
|
||||
}));
|
||||
|
||||
test("overwriting note with deleted note should update search index", () =>
|
||||
noteTest({
|
||||
title: "I am title"
|
||||
}).then(async ({ db, id }) => {
|
||||
await db.notes.collection.put([
|
||||
{
|
||||
id,
|
||||
deleted: true,
|
||||
synced: true,
|
||||
dateModified: Date.now()
|
||||
}
|
||||
]);
|
||||
|
||||
expect(await db.lookup.notes("title").ids()).not.toContain(id);
|
||||
}));
|
||||
@@ -44,7 +44,6 @@ import {
|
||||
Kysely,
|
||||
SelectQueryBuilder,
|
||||
SqlBool,
|
||||
Transaction,
|
||||
sql
|
||||
} from "kysely";
|
||||
import { VirtualizedGrouping } from "../utils/virtualized-grouping";
|
||||
@@ -338,8 +337,10 @@ export class SQLCollection<
|
||||
}
|
||||
|
||||
export class FilteredSelector<T extends Item> {
|
||||
private _fields: AnyColumnWithTable<DatabaseSchema, keyof DatabaseSchema>[] =
|
||||
[];
|
||||
private _fields: (
|
||||
| AnyColumn<DatabaseSchema, keyof DatabaseSchema>
|
||||
| AnyColumnWithTable<DatabaseSchema, keyof DatabaseSchema>
|
||||
)[] = [];
|
||||
filter: SelectQueryBuilder<DatabaseSchema, keyof DatabaseSchema, unknown>;
|
||||
private _limit = 0;
|
||||
constructor(
|
||||
@@ -524,6 +525,11 @@ export class FilteredSelector<T extends Item> {
|
||||
|
||||
async *[Symbol.asyncIterator]() {
|
||||
let lastRow: any | null = null;
|
||||
const fields = this._fields.slice();
|
||||
if (!fields.find((f) => f.includes(".dateCreated")))
|
||||
fields.push("dateCreated");
|
||||
if (!fields.find((f) => f.includes(".id"))) fields.push("id");
|
||||
|
||||
while (true) {
|
||||
const rows = await this.filter
|
||||
.orderBy("dateCreated asc")
|
||||
@@ -536,8 +542,8 @@ export class FilteredSelector<T extends Item> {
|
||||
)
|
||||
)
|
||||
.limit(this.batchSize)
|
||||
.$if(this._fields.length === 0, (eb) => eb.selectAll())
|
||||
.$if(this._fields.length > 0, (eb) => eb.select(this._fields))
|
||||
.$if(fields.length === 0, (eb) => eb.selectAll())
|
||||
.$if(fields.length > 0, (eb) => eb.select(fields))
|
||||
.execute();
|
||||
if (rows.length === 0) break;
|
||||
for (const row of rows) {
|
||||
|
||||
@@ -31,8 +31,8 @@ export async function createTriggers(db: Kysely<RawDatabaseSchema>) {
|
||||
.addEvent("insert")
|
||||
.when((eb) =>
|
||||
eb.and([
|
||||
eb.or([eb("new.deleted", "is", null), eb("new.deleted", "==", false)]),
|
||||
eb.or([eb("new.locked", "is", null), eb("new.locked", "==", false)]),
|
||||
eb("new.deleted", "is not", true),
|
||||
eb("new.locked", "is not", true),
|
||||
eb("new.data", "is not", null)
|
||||
])
|
||||
)
|
||||
@@ -71,6 +71,13 @@ export async function createTriggers(db: Kysely<RawDatabaseSchema>) {
|
||||
.onTable("content", "main")
|
||||
.after()
|
||||
.addEvent("update")
|
||||
.when((eb) =>
|
||||
eb.and([
|
||||
eb("old.deleted", "is not", true),
|
||||
eb("old.noteId", "is not", null),
|
||||
eb("old.data", "is not", null)
|
||||
])
|
||||
)
|
||||
.addQuery((c) =>
|
||||
c.insertInto("content_fts").values({
|
||||
content_fts: sql.lit("delete"),
|
||||
@@ -135,7 +142,13 @@ export async function createTriggers(db: Kysely<RawDatabaseSchema>) {
|
||||
.ifNotExists()
|
||||
.onTable("notes", "main")
|
||||
.after()
|
||||
.addEvent("update", ["title"])
|
||||
.addEvent("update")
|
||||
.when((eb) =>
|
||||
eb.and([
|
||||
eb("old.deleted", "is not", true),
|
||||
eb("old.title", "is not", null)
|
||||
])
|
||||
)
|
||||
.addQuery((c) =>
|
||||
c.insertInto("notes_fts").values({
|
||||
notes_fts: sql.lit("delete"),
|
||||
|
||||
Reference in New Issue
Block a user