Compare commits

...

23 Commits

Author SHA1 Message Date
Ammar Ahmed
22497199c5 mobile: catch errors 2025-12-25 11:40:05 +05:00
Ammar Ahmed
873b180dab mobile: enable feature for pro users 2025-12-25 11:37:27 +05:00
Ammar Ahmed
ca8e505cd0 mobile: add support for color shortcuts on homescreen 2025-12-24 14:17:52 +05:00
Ammar Ahmed
465e1af838 mobile: keep shortcuts synced with the app 2025-12-24 13:11:08 +05:00
Ammar Ahmed
b59a3fb5de mobile: improved navigation for deep links 2025-12-23 10:54:46 +05:00
Ammar Ahmed
0a7f8631b6 mobile: add home shortcuts
Add ability to create shortcuts on launcher homescreen for notes, notebooks and tags.
2025-12-22 18:59:36 +05:00
Ammar Ahmed
50d0827972 Merge pull request #9071 from streetwriters/mobile-release/3.3.11
mobile: release 3.3.11
2025-12-19 10:47:24 +05:00
Abdullah Atta
a0d4be2379 Merge pull request #9069 from streetwriters/common/command-palette-web-keybinding
common: add ctrl+shift+: web keybinding for command palette
2025-12-18 11:56:06 +05:00
Ammar Ahmed
e4eb5d1f0f mobile: release 3.3.11 2025-12-18 11:46:32 +05:00
Ammar Ahmed
9ae4d3df55 Merge pull request #9067 from streetwriters/fix-wrapped-background-mobile
Fixed wrapped background in screenshots on mobile
2025-12-18 11:35:31 +05:00
Abdullah Atta
95b6b45cc4 Merge pull request #9068 from streetwriters/editor/fix-scroll-mobile-table
editor: fix scroll for tables in mobile
2025-12-18 11:34:29 +05:00
01zulfi
9280d0ef7e common: add ctrl+shift+: web keybinding for command palette
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-18 10:34:39 +05:00
01zulfi
5171b2e5e2 editor: fix scroll for tables in mobile
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-18 10:20:10 +05:00
Ammar Ahmed
8455ff876a mobile: fixed wrapped background in screenshots 2025-12-18 09:48:54 +05:00
Ammar Ahmed
0503af6819 Merge pull request #9066 from streetwriters/mobile-release/3.3.10
Mobile release 3.3.10
2025-12-18 09:46:43 +05:00
Abdullah Atta
fbaba8ebe8 Merge pull request #9064 from streetwriters/release/3.3.6
Release v3.3.6
2025-12-18 00:48:11 +05:00
Abdullah Atta
64c70f51c9 web: bump version to 3.3.6 2025-12-17 23:09:20 +05:00
Abdullah Atta
946da6e274 desktop: update sqlite3-fts5-html to 0.0.6
this should fix the `The specified module could not be found` error on Windows
2025-12-17 23:02:46 +05:00
Abdullah Atta
9450849b9d web: remove htmlparser2 2025-12-17 16:25:07 +05:00
Abdullah Atta
461c4acfb7 ci: use macos-15-intel instead of macos-13 2025-12-17 14:49:46 +05:00
Abdullah Atta
ccb0840771 web: update @playwright/test to 1.57.0 2025-12-17 14:49:22 +05:00
Abdullah Atta
b5a706a6a9 editor: fix TouchEvent is not defined when resizing columns 2025-12-17 14:40:25 +05:00
Ammar Ahmed
4fbb39efd0 Merge pull request #9041 from streetwriters/fix-subscription-type
Fix subscription type
2025-12-12 11:05:45 +05:00
44 changed files with 2783 additions and 501 deletions

View File

@@ -45,7 +45,7 @@ jobs:
test-macos-x64:
name: Test macOS x64
needs: build
runs-on: macos-13
runs-on: macos-15-intel
steps:
- name: Check out Git repository

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.6-beta.3",
"version": "3.3.6",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",
@@ -37,7 +37,7 @@
"electron-updater": "^6.6.2",
"icojs": "^0.19.5",
"sqlite-better-trigram": "0.0.5",
"sqlite3-fts5-html": "^0.0.4",
"sqlite3-fts5-html": "^0.0.6",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"zod": "3.24.3"

View File

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

View File

@@ -6,7 +6,14 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
@@ -29,6 +36,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class RCTNNativeModule extends ReactContextBaseJavaModule {
@@ -237,6 +245,221 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
}
}
@ReactMethod
public void addShortcut(final String id, final String type, final String title, final String description, final String color, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
String uri = "https://app.notesnook.com/open_" + type + "?id=" + id;
Intent intent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(uri));
intent.setPackage(mContext.getPackageName());
Icon icon = createLetterIcon(type, title, color);
ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, id)
.setShortLabel(title)
.setLongLabel(description != null && !description.isEmpty() ? description : title)
.setIcon(icon)
.setCategories(Set.of(type))
.setIntent(intent)
.build();
shortcutManager.requestPinShortcut(shortcut, null);
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void removeShortcut(final String id, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
shortcutManager.disableShortcuts(java.util.Collections.singletonList(id));
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void updateShortcut(final String id, final String type, final String title, final String description,final String color, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
// Get existing shortcut to preserve icon and intent
List<ShortcutInfo> shortcuts = shortcutManager.getPinnedShortcuts();
ShortcutInfo existingShortcut = null;
for (ShortcutInfo s : shortcuts) {
if (s.getId().equals(id)) {
existingShortcut = s;
break;
}
}
if (existingShortcut == null) {
return;
}
Icon icon = createLetterIcon(type, title, color);
ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(mContext, id)
.setShortLabel(title)
.setIcon(icon)
.setLongLabel(description != null && !description.isEmpty() ? description : title)
.setIntent(existingShortcut.getIntent())
.build();
shortcutManager.updateShortcuts(java.util.Collections.singletonList(updatedShortcut));
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void removeAllShortcuts(Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
return;
}
try {
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
if (shortcutManager == null) {
promise.reject("ERROR", "ShortcutManager not available");
return;
}
List<ShortcutInfo> pinnedShortcuts = shortcutManager.getPinnedShortcuts();
if (!pinnedShortcuts.isEmpty()) {
List<String> ids = new ArrayList<>();
for (ShortcutInfo shortcut : pinnedShortcuts) {
ids.add(shortcut.getId());
}
shortcutManager.disableShortcuts(ids);
}
promise.resolve(true);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void getAllShortcuts(Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
WritableArray shortcuts = Arguments.createArray();
List<ShortcutInfo> infos = shortcutManager.getPinnedShortcuts();
for (ShortcutInfo info: infos) {
WritableMap data = Arguments.createMap();
data.putString("id", info.getId());
if (info.getShortLabel() != null) {
data.putString("title", info.getShortLabel().toString());
}
if (info.getLongLabel() != null) {
data.putString("description", info.getLongLabel().toString());
}
if (!Objects.requireNonNull(info.getCategories()).isEmpty()) {
if (info.getCategories().contains("note")) {
data.putString("type", "note");
} else if (info.getCategories().contains("notebook")) {
data.putString("type", "notebook");
} else if (info.getCategories().contains("tag")) {
data.putString("type", "tag");
} else if (info.getCategories().contains("color")) {
data.putString("type", "color");
}
}
shortcuts.pushMap(data);
}
promise.resolve(shortcuts);
}
private Icon createLetterIcon(String type, String title, String colorCode) {
String letter = type.contains("tag") ? "#" : title != null && !title.isEmpty()
? title.substring(0, 1).toUpperCase()
: "?";
int color = type.equals("color") ? Color.parseColor(colorCode) : getColorForLetter(letter);
if (type.equals("notebook")) return Icon.createWithResource(mContext, R.drawable.ic_notebook);
// Use a larger canvas and fill it completely to avoid white borders from launcher masking.
int iconSize = 256;
Bitmap bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
if (type.equals("color")) {
Paint backgroundPaint = new Paint();
backgroundPaint.setColor(color);
backgroundPaint.setAntiAlias(true);
canvas.drawCircle(iconSize / 2, iconSize / 2, iconSize/2, backgroundPaint);
} else {
Paint textPaint = new Paint();
textPaint.setColor(color);
textPaint.setTextSize(130);
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTypeface(android.graphics.Typeface.create(android.graphics.Typeface.DEFAULT, android.graphics.Typeface.BOLD));
float x = iconSize / 2f;
float y = (iconSize / 2f) - ((textPaint.descent() + textPaint.ascent()) / 2f);
canvas.drawText(letter, x, y, textPaint);
}
return Icon.createWithBitmap(bitmap);
}
private int getColorForLetter(String letter) {
int[] colors = {
0xFF1976D2, // Blue
0xFFD32F2F, // Red
0xFF388E3C, // Green
0xFFF57C00, // Orange
0xFF7B1FA2, // Purple
0xFF0097A7, // Cyan
0xFFC2185B, // Pink
0xFF455A64, // Blue Grey
0xFF6A1B9A, // Deep Purple
0xFF00796B, // Teal
0xFF512DA8, // Indigo
0xFF1565C0 // Dark Blue
};
int hash = Math.abs(letter.hashCode());
return colors[hash % colors.length];
}
}

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="#008837"
android:alpha="1">
<path
android:fillColor="@android:color/white"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L720,80Q753,80 776.5,103.5Q800,127 800,160L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,160Q720,160 720,160Q720,160 720,160L640,160L640,440L540,380L440,440L440,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM440,440L540,380L640,440L640,440L540,380L440,440Z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

View File

@@ -24,7 +24,7 @@ import {
useThemeEngineStore
} from "@notesnook/theme";
import React, { useEffect } from "react";
import { Appearance, I18nManager, StatusBar } from "react-native";
import { Appearance, I18nManager, Linking, StatusBar } from "react-native";
import "react-native-gesture-handler";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
@@ -43,7 +43,7 @@ import { changeSystemBarColors, useThemeStore } from "./stores/use-theme-store";
import { useUserStore } from "./stores/use-user-store";
import RNBootSplash from "react-native-bootsplash";
import AppLocked from "./components/app-lock";
import { useSettingStore } from "./stores/use-setting-store";
I18nManager.allowRTL(false);
I18nManager.forceRTL(false);
I18nManager.swapLeftAndRightInRTL(false);
@@ -54,13 +54,18 @@ if (appLockEnabled || appLockMode !== "none") {
RNBootSplash.hide({
fade: true
});
Linking.getInitialURL().then((url) => {
useSettingStore.setState({
initialUrl: url
});
});
const App = (props: { configureMode: "note-preview" }) => {
useAppEvents();
//@ts-ignore
globalThis["IS_MAIN_APP_RUNNING"] = true;
useEffect(() => {
changeSystemBarColors();
SettingsService.onFirstLaunch();
changeSystemBarColors();
setTimeout(async () => {
await Notifications.get();
if (SettingsService.get().notifNotes) {

View File

@@ -50,7 +50,7 @@ export const Header = ({
onLeftMenuButtonPress?: () => void;
renderedInRoute?: RouteName;
id?: string;
title: string;
title?: string;
canGoBack?: boolean;
onPressDefaultRightButton?: () => void;
hasSearch?: boolean;
@@ -114,7 +114,16 @@ export const Header = ({
onLeftButtonPress={onLeftMenuButtonPress}
/>
{hasSearch ? (
{!title ? (
<View
style={{
width: 100,
backgroundColor: colors.primary.hover,
height: 10,
borderRadius: 100
}}
/>
) : hasSearch ? (
<Paragraph>
{selectionMode
? `${selectedItemsList.length} selected`

View File

@@ -57,6 +57,7 @@ const BOTTOM_BAR_ITEMS: ActionId[] = [
"export",
"copy-link",
"duplicate",
"launcher-shortcut",
"trash"
];
@@ -76,6 +77,7 @@ const COLUMN_BAR_ITEMS: ActionId[] = [
"reorder",
"rename-color",
"rename-tag",
"launcher-shortcut",
"restore",
"trash",
"delete"
@@ -170,15 +172,15 @@ export const Items = ({
DDS.isTab
? AppFontSize.xxl
: shouldShrink
? AppFontSize.xxl
: AppFontSize.lg
? AppFontSize.xxl
: AppFontSize.lg
}
color={
item.checked
? item.activeColor || colors.primary.accent
: item.id.match(/(delete|trash)/g)
? colors.error.icon
: colors.secondary.icon
? colors.error.icon
: colors.secondary.icon
}
/>
</Pressable>
@@ -209,8 +211,8 @@ export const Items = ({
text: item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.paragraph
: colors.primary.paragraph
? colors.error.paragraph
: colors.primary.paragraph
}}
testID={"icon-" + item.id}
onPress={item.onPress}
@@ -274,8 +276,8 @@ export const Items = ({
item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.icon
: colors.secondary.icon
? colors.error.icon
: colors.secondary.icon
}
/>

View File

@@ -19,10 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-inner-declarations */
import { useAreFeaturesAvailable } from "@notesnook/common";
import {
Color,
createInternalLink,
Item,
ItemReference,
Note,
Notebook,
VAULT_ERRORS
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
@@ -70,6 +72,7 @@ import { deleteItems } from "../utils/functions";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
import { resetStoredState } from "./use-stored-state";
import { NotesnookModule } from "../utils/notesnook-module";
export type ActionId =
| "select"
@@ -114,7 +117,8 @@ export type ActionId =
| "remove-from-notebook"
| "trash"
| "default-homepage"
| "default-tag";
| "default-tag"
| "launcher-shortcut";
export type Action = {
id: ActionId;
@@ -158,7 +162,8 @@ export const useActions = ({
"shortcuts",
"notebooks",
"customizableSidebar",
"customHomepage"
"customHomepage",
"androidLauncherShortcuts"
]);
const [item, setItem] = useState(propItem);
const { colors } = useThemeColors();
@@ -297,7 +302,6 @@ export const useActions = ({
id: item.id,
title: value
});
eSendEvent(Navigation.routeNames.TaggedNotes);
InteractionManager.runAfterInteractions(() => {
useTagStore.getState().refresh();
@@ -706,26 +710,6 @@ export const useActions = ({
type: item.type
}
);
resetStoredState(
"app-home-navigtion-key",
isHomepage
? undefined
: {
name:
item.type === "notebook"
? "Notebook"
: item.type === "tag"
? "TaggedNotes"
: item.type === "color"
? "ColorNotes"
: undefined,
params: {
item: item,
id: item.id
}
}
);
}
});
}
@@ -1207,5 +1191,44 @@ export const useActions = ({
});
}
if (
Platform.OS === "android" &&
(item.type === "tag" ||
item.type === "note" ||
item.type === "notebook" ||
item.type === "color")
) {
actions.push({
id: "launcher-shortcut",
title: strings.addToHome(),
icon: "cellphone-arrow-down",
locked: !features?.androidLauncherShortcuts.isAllowed,
onPress: async () => {
if (features && !features?.androidLauncherShortcuts.isAllowed) {
ToastManager.show({
message: features?.androidLauncherShortcuts.error,
type: "info",
actionText: strings.upgrade(),
context: "local",
func: () => {
PaywallSheet.present(features?.androidLauncherShortcuts);
}
});
return;
}
try {
await NotesnookModule.addShortcut(
item.id,
item.type,
item.title,
(item as Note).headline || (item as Notebook).description || "",
(item as Color).colorCode
);
} catch (e) {}
}
});
}
return actions;
};

View File

@@ -85,7 +85,7 @@ import Notifications from "../services/notifications";
import PremiumService from "../services/premium";
import SettingsService from "../services/settings";
import Sync from "../services/sync";
import { clearAllStores, initAfterSync, initialize } from "../stores";
import { clearAllStores, initAfterSync } from "../stores";
import { refreshAllStores } from "../stores/create-db-collection-store";
import { useAttachmentStore } from "../stores/use-attachment-store";
import { useMessageStore } from "../stores/use-message-store";
@@ -159,7 +159,10 @@ const onUserSessionExpired = async () => {
eSendEvent(eLoginSessionExpired);
};
const onAppOpenedFromURL = async (event: { url: string }) => {
const onAppOpenedFromURL = async (event: {
url: string;
isInitialUrl?: boolean;
}) => {
const url = event.url;
try {
@@ -171,7 +174,7 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
eSendEvent(eOnLoadNote, { newNote: true });
fluidTabsRef.current?.goToPage("editor", false);
return;
} else if (url.startsWith("https://app.notesnook.com/open_note")) {
} else if (url.startsWith("https://app.notesnook.com/open_note?")) {
const id = new URL(url).searchParams.get("id");
if (id) {
const note = await db.notes.note(id);
@@ -182,6 +185,53 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
fluidTabsRef.current?.goToPage("editor", false);
}
}
} else if (
url.startsWith("https://app.notesnook.com/open_notebook?") &&
!event.isInitialUrl
) {
const id = new URL(url).searchParams.get("id");
if (id) {
const notebook = await db.notebooks.notebook(id);
if (notebook) {
Navigation.navigate("Notebook", {
id: notebook.id,
canGoBack: true,
item: notebook
});
}
}
} else if (
url.startsWith("https://app.notesnook.com/open_tag?") &&
!event.isInitialUrl
) {
const id = new URL(url).searchParams.get("id");
if (id) {
const tag = await db.tags.tag(id);
if (tag) {
Navigation.navigate("TaggedNotes", {
type: "tag",
id: tag.id,
item: tag,
canGoBack: true
});
}
}
} else if (
url.startsWith("https://app.notesnook.com/open_color?") &&
!event.isInitialUrl
) {
const id = new URL(url).searchParams.get("id");
if (id) {
const color = await db.colors.color(id);
if (color) {
Navigation.navigate("ColoredNotes", {
type: "color",
id: color.id,
item: color,
canGoBack: true
});
}
}
} else if (url.startsWith("https://app.notesnook.com/open_reminder")) {
const id = new URL(url).searchParams.get("id");
if (id) {
@@ -464,7 +514,10 @@ const initializeDatabase = async (password?: string) => {
};
export const useAppEvents = () => {
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const [isAppLoading, initialUrl] = useSettingStore((state) => [
state.isAppLoading,
state.initialUrl
]);
const [setLastSynced, setUser, appLocked, syncing] = useUserStore((state) => [
state.setLastSynced,
state.setUser,
@@ -509,6 +562,15 @@ export const useAppEvents = () => {
};
}, [isAppLoading, onSyncComplete]);
useEffect(() => {
if (initialUrl) {
onAppOpenedFromURL({
url: initialUrl!,
isInitialUrl: true
});
}
}, [initialUrl]);
const subscribeToPurchaseListeners = useCallback(async () => {
if (Platform.OS === "android") {
try {
@@ -774,23 +836,10 @@ export const useAppEvents = () => {
}
};
if (!refValues.current.initialUrl) {
Linking.getInitialURL().then((url) => {
if (url) {
refValues.current.initialUrl = url;
}
});
}
let sub: NativeEventSubscription;
if (!isAppLoading && !appLocked) {
setTimeout(() => {
sub = AppState.addEventListener("change", onAppStateChanged);
if (refValues.current.initialUrl) {
onAppOpenedFromURL({
url: refValues.current.initialUrl!
});
refValues.current.initialUrl = undefined;
}
}, 1000);
refValues.current.removeInternetStateListener = NetInfo.addEventListener(

View File

@@ -31,8 +31,6 @@ import { useSettingStore } from "../stores/use-setting-store";
import { rootNavigatorRef } from "../utils/global-refs";
import Navigation from "../services/navigation";
import { isFeatureAvailable } from "@notesnook/common";
import { useStoredValue } from "../hooks/use-stored-state";
import { db } from "../common/database";
const RootStack = createNativeStackNavigator();
const AppStack = createNativeStackNavigator();
@@ -58,105 +56,114 @@ const AppNavigation = React.memo(
() => {
const { colors } = useThemeColors();
const homepageV2 = useSettingStore((state) => state.settings.homepageV2);
const home = useStoredValue(
"app-home-navigtion-key",
!homepageV2 || homepageV2.id === DEFAULT_HOME.name
? DEFAULT_HOME
: undefined
);
const loading = useSettingStore((state) => state.isAppLoading);
const [home, setHome] = React.useState<
{ name: string; params: any } | undefined
>(undefined);
if (!home.value && !homepageV2) {
home.value = DEFAULT_HOME;
}
React.useEffect(() => {
if (useSettingStore.getState().initialUrl) {
const url = useSettingStore.getState().initialUrl;
if (url?.startsWith("https://app.notesnook.com/open_notebook?")) {
const id = new URL(url).searchParams.get("id");
if (id) {
setHome({
name: "Notebook",
params: {
id: id
}
});
return;
}
} else if (url?.startsWith("https://app.notesnook.com/open_tag?")) {
const id = new URL(url).searchParams.get("id");
if (id) {
setHome({
name: "TaggedNotes",
params: {
type: "tag",
id: id
}
});
return;
}
} else if (url?.startsWith("https://app.notesnook.com/open_color?")) {
const id = new URL(url).searchParams.get("id");
if (id) {
setHome({
name: "ColoredNotes",
params: {
type: "color",
id: id
}
});
return;
}
}
}
if (homepageV2) {
switch (homepageV2.type) {
case "notebook": {
setHome({
name: "Notebook",
params: {
id: homepageV2.id
}
});
return;
}
case "color": {
setHome({
name: "ColoredNotes",
params: {
type: "color",
id: homepageV2.id
}
});
return;
}
case "tag": {
setHome({
name: "TaggedNotes",
params: {
type: "tag",
id: homepageV2.id
}
});
return;
}
case "default":
setHome(DEFAULT_HOME);
return;
}
} else {
setHome(DEFAULT_HOME);
}
}, []);
React.useEffect(() => {
if (!homepageV2 || loading) return;
(async () => {
isFeatureAvailable("customHomepage").then((value) => {
if (!value.isAllowed) {
home.value = DEFAULT_HOME;
SettingsService.setProperty("homepageV2", undefined);
}
});
if (!home.value) {
switch (homepageV2.type) {
case "notebook":
{
const notebook = await db.notebooks.notebook(homepageV2.id);
if (notebook) {
home.value = {
name: "Notebook",
params: {
item: notebook,
id: notebook.id,
title: notebook.title
}
};
return;
}
}
break;
case "color": {
const color = await db.colors.color(homepageV2.id);
if (color) {
home.value = {
name: "ColoredNotes",
params: {
item: color,
id: color.id,
title: color.title
}
};
return;
}
break;
}
case "tag": {
const tag = await db.tags.tag(homepageV2.id);
if (tag) {
home.value = {
name: "TaggedNotes",
params: {
item: tag,
id: tag.id,
title: tag.title
}
};
return;
}
break;
}
case "default":
{
home.value = DEFAULT_HOME;
}
return;
}
home.value = undefined;
setTimeout(() => {
home.value = DEFAULT_HOME;
});
}
})();
}, [homepageV2, loading, home.value]);
}, [homepageV2, loading]);
React.useEffect(() => {
if (!home) return;
useNavigationStore.getState().update(home?.name as keyof RouteParams);
useNavigationStore
.getState()
.update(home.value?.name as keyof RouteParams);
useNavigationStore
.getState()
.setFocusedRouteId(home.value?.params?.id || home.value?.name);
.setFocusedRouteId(home?.params?.id || home?.name);
}, [home]);
return !home.value ? null : (
return !home ? null : (
<AppStack.Navigator
initialRouteName={home.value?.name}
initialRouteName={home?.name}
screenOptions={{
headerShown: false,
animation: "none",
@@ -197,7 +204,7 @@ const AppNavigation = React.memo(
return TaggedNotes;
}}
initialParams={
home.value?.name === "TaggedNotes" ? home.value?.params : undefined
home?.name === "TaggedNotes" ? home?.params : undefined
}
/>
@@ -209,7 +216,7 @@ const AppNavigation = React.memo(
return ColoredNotes;
}}
initialParams={
home.value?.name === "ColoredNotes" ? home.value?.params : undefined
home?.name === "ColoredNotes" ? home?.params : undefined
}
/>
@@ -244,9 +251,7 @@ const AppNavigation = React.memo(
Notebook = Notebook || require("../screens/notebook").default;
return Notebook;
}}
initialParams={
home.value?.name === "Notebook" ? home.value?.params : undefined
}
initialParams={home?.name === "Notebook" ? home?.params : undefined}
/>
<AppStack.Screen

View File

@@ -1088,7 +1088,10 @@ export const useEditor = (
if (!state.current?.initialLoadCalled) {
const url = await Linking.getInitialURL();
let noteId = url && new URL(url).searchParams.get("id");
let noteId =
url &&
url.startsWith("https://app.notesnook.com/open_note?") &&
new URL(url).searchParams.get("id");
if (noteId) {
const note = await db.notes?.note(noteId);
fluidTabsRef.current?.goToPage("editor");

View File

@@ -38,7 +38,6 @@ import { eUpdateNotebookRoute } from "../../utils/events";
import { findRootNotebookId } from "../../utils/notebooks";
import { openEditor, setOnFirstSave } from "../notes/common";
import { View } from "react-native";
import { DefaultAppStyles } from "../../utils/styles";
import { Notebooks } from "../../components/sheets/notebooks";
import { useSettingStore } from "../../stores/use-setting-store";
import { rootNavigatorRef } from "../../utils/global-refs";
@@ -47,6 +46,9 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const params = useRef<NotebookScreenParams>(route?.params);
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const [notebook, setNotebook] = useState<Notebook | undefined>(
params.current.item
);
const [loading, setLoading] = useState(true);
const updateOnFocus = useRef(false);
const [breadcrumbs, setBreadcrumbs] = useState<
@@ -75,10 +77,10 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
});
const syncWithNavigation = React.useCallback(() => {
useNavigationStore.getState().setFocusedRouteId(params?.current?.item?.id);
useNavigationStore.getState().setFocusedRouteId(params?.current?.id);
setOnFirstSave({
type: "notebook",
id: params.current.item.id
id: params.current.id
});
}, []);
@@ -86,23 +88,20 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
async (data?: NotebookScreenParams) => {
if (useSettingStore.getState().isAppLoading) return;
if (
useNavigationStore.getState().focusedRouteId !==
params.current.item.id &&
useNavigationStore.getState().focusedRouteId !== params.current.id &&
!data
) {
updateOnFocus.current = true;
return;
}
if (data?.item?.id && params.current.item?.id !== data?.item?.id) {
const nextRootNotebookId = await findRootNotebookId(data?.item?.id);
const currentNotebookRoot = await findRootNotebookId(
params.current.item.id
);
if (data?.id && params.current?.id !== data?.id) {
const nextRootNotebookId = await findRootNotebookId(data?.id);
const currentNotebookRoot = await findRootNotebookId(params.current.id);
if (
nextRootNotebookId !== currentNotebookRoot ||
nextRootNotebookId === params.current?.item?.id
nextRootNotebookId === params.current?.id
) {
// Never update notebook in route if root is different or if the root is current notebook.
return;
@@ -112,14 +111,13 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
if (data) params.current = data;
try {
const notebook = await db.notebooks?.notebook(
params?.current?.item?.id
);
const notebook = await db.notebooks?.notebook(params?.current?.id);
setNotebook(notebook);
params.current.item = notebook;
if (notebook) {
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.length - 1));
params.current.item = notebook;
params.current.id = notebook.id;
const notes = await db.relations
.from(notebook, "note")
.selector.grouped(db.settings.getGroupOptions("notes"));
@@ -160,29 +158,27 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
<>
<Header
renderedInRoute={route.name}
title={params.current.item?.title}
title={notebook?.title}
canGoBack={params?.current?.canGoBack}
rightButton={{
name: "dots-vertical",
onPress: () => {
Properties.present(params.current.item);
Properties.present(notebook);
}
}}
hasSearch={true}
onSearch={() => {
const selector = db.relations.from(
params.current.item,
"note"
).selector;
if (!notebook) return;
const selector = db.relations.from(notebook, "note").selector;
Navigation.push("Search", {
placeholder: strings.searchInRoute(params.current.item?.title),
placeholder: strings.searchInRoute(notebook?.title),
type: "note",
title: params.current.item?.title,
title: notebook?.title,
route: route.name,
items: selector
});
}}
id={params.current.item?.id}
id={notebook?.id}
/>
<DelayLayout wait={loading}>
@@ -192,19 +188,19 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
onRefresh={() => {
onRequestUpdate();
}}
id={params.current.item?.id}
id={params.current?.id}
renderedInRoute="Notebook"
headerTitle={params.current.item.title}
headerTitle={notebook?.title}
loading={loading}
CustomLisHeader={
<NotebookHeader
breadcrumbs={breadcrumbs}
notebook={params.current.item}
notebook={notebook!}
totalNotes={notes?.placeholders.length || 0}
/>
}
placeholder={{
title: params.current.item?.title,
title: notebook?.title!,
paragraph: strings.notesEmpty(),
button: strings.addFirstNote(),
action: openEditor,
@@ -225,7 +221,8 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
testID="notebookTreeSheet"
size="small"
onPress={() => {
Notebooks.present(params.current.item);
if (!notebook) return;
Notebooks.present(notebook);
}}
style={{
position: "relative",
@@ -246,7 +243,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
/>
</View>
<SelectionHeader
id={route.params?.item?.id}
id={route.params?.id}
items={notes}
type="note"
renderedInRoute="Notebook"
@@ -260,6 +257,7 @@ NotebookScreen.navigate = async (item: Notebook, canGoBack?: boolean) => {
const { currentRoute, focusedRouteId } = useNavigationStore.getState();
if (currentRoute === "Notebooks") {
Navigation.push("Notebook", {
id: item.id,
item: item,
canGoBack
});
@@ -276,22 +274,24 @@ NotebookScreen.navigate = async (item: Notebook, canGoBack?: boolean) => {
// Update the route in place instead
eSendEvent(eUpdateNotebookRoute, {
item: item,
title: item.title,
canGoBack: canGoBack
id: item.id,
canGoBack: canGoBack,
item: item
});
} else {
// Push a new route
Navigation.push("Notebook", {
item: item,
canGoBack
id: item.id,
canGoBack,
item: item
});
}
} else {
// Push a new route anyways
Navigation.push("Notebook", {
item: item,
canGoBack
id: item.id,
canGoBack,
item: item
});
}
};

View File

@@ -25,7 +25,7 @@ import Navigation, { NavigationProps } from "../../services/navigation";
import useNavigationStore, {
NotesScreenParams
} from "../../stores/use-navigation-store";
import { PLACEHOLDER_DATA, openEditor, toCamelCase } from "./common";
import { PLACEHOLDER_DATA, openEditor } from "./common";
export const ColoredNotes = ({
navigation,
route
@@ -45,11 +45,13 @@ export const ColoredNotes = ({
ColoredNotes.get = async (params: NotesScreenParams, grouped = true) => {
if (!grouped) {
return await db.relations.from(params.item, "note").resolve();
return await db.relations
.from({ id: params.id, type: "color" }, "note")
.resolve();
}
return await db.relations
.from(params.item, "note")
.from({ id: params.id, type: "color" }, "note")
.selector.grouped(db.settings.getGroupOptions("notes"));
};
@@ -63,8 +65,10 @@ ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
}
Navigation.push<"ColoredNotes">("ColoredNotes", {
item: item,
canGoBack
type: "color",
id: item.id,
canGoBack,
item: item
});
};

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { resolveItems } from "@notesnook/common";
import { VirtualizedGrouping } from "@notesnook/core";
import { Tag, VirtualizedGrouping } from "@notesnook/core";
import { Color, Note } from "@notesnook/core";
import React, { useEffect, useRef, useState } from "react";
import { db } from "../../common/database";
@@ -76,14 +76,12 @@ const NotesPage = ({
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const [loadingNotes, setLoadingNotes] = useState(true);
const isMonograph = route.name === "Monographs";
const title =
params.current?.item.type === "tag"
? "#" + params.current?.item.title
: params.current?.item.title;
const [item, setItem] = useState<Tag | Color | undefined>(
params.current.item
);
const title = item?.type === "tag" ? "#" + item?.title : item?.title;
const accentColor =
route.name === "ColoredNotes"
? (params.current?.item as Color)?.colorCode
: undefined;
route.name === "ColoredNotes" ? (item as Color)?.colorCode : undefined;
const updateOnFocus = useRef(false);
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const isFocused = useNavigationFocus(navigation, {
@@ -107,31 +105,30 @@ const NotesPage = ({
});
const syncWithNavigation = React.useCallback(() => {
const { item } = params.current;
useNavigationStore
.getState()
.setFocusedRouteId(params?.current?.item?.id || route.name);
const { id } = params.current;
useNavigationStore.getState().setFocusedRouteId(id || route.name);
!isMonograph &&
setOnFirstSave({
type: getItemType(route.name),
id: item.id
id: id
});
}, [isMonograph, route.name]);
const onRequestUpdate = React.useCallback(
async (data?: NotesScreenParams) => {
if (useSettingStore.getState().isAppLoading) return;
if (
params.current.item.id &&
useNavigationStore.getState().focusedRouteId !==
params.current.item.id &&
params.current.id &&
useNavigationStore.getState().focusedRouteId !== params.current.id &&
!data
) {
updateOnFocus.current = false;
return;
}
const isNew = data && data?.item?.id !== params.current?.item?.id;
const isNew = data && data?.id !== params.current?.id;
if (data) params.current = data;
try {
@@ -142,9 +139,9 @@ const NotesPage = ({
)) as VirtualizedGrouping<Note>;
if (route.name === "TaggedNotes" || route.name === "ColoredNotes") {
const item = await (db as any)[params.current.item.type + "s"][
params.current.item.type
](params.current.item.id);
const item = await (db as any)[params.current.type + "s"][
params.current.type
](params.current.id);
if (!item) {
if (rootNavigatorRef.canGoBack()) {
@@ -154,7 +151,7 @@ const NotesPage = ({
}
return;
}
setItem(item);
params.current.item = item;
}
@@ -173,15 +170,7 @@ const NotesPage = ({
useEffect(() => {
if (isAppLoading) return;
if (loadingNotes) {
get(params.current, true)
.then(async (items) => {
setNotes(items as VirtualizedGrouping<Note>);
await (items as VirtualizedGrouping<Note>).item(0, resolveItems);
setLoadingNotes(false);
})
.catch((e) => {
setLoadingNotes(false);
});
onRequestUpdate(params.current);
}
}, [loadingNotes, get, isAppLoading]);
@@ -202,19 +191,18 @@ const NotesPage = ({
}
canGoBack={params?.current?.canGoBack}
hasSearch={true}
id={
route.name === "Monographs" ? "Monographs" : params?.current.item?.id
}
id={route.name === "Monographs" ? "Monographs" : params?.current?.id}
onSearch={() => {
if (!item) return;
const selector =
route.name === "Monographs"
? db.monographs.all
: db.relations.from(params.current.item, "note").selector;
: db.relations.from(item, "note").selector;
Navigation.push("Search", {
placeholder: strings.searchInRoute(title || route.name),
type: "note",
title: title,
title: title!,
route: route.name,
items: selector
});
@@ -229,7 +217,7 @@ const NotesPage = ({
onRefresh={onRequestUpdate}
loading={false}
renderedInRoute={route.name}
id={params.current.item?.id}
id={params.current?.id}
headerTitle={title || "Monographs"}
customAccentColor={accentColor}
placeholder={placeholder}
@@ -246,7 +234,7 @@ const NotesPage = ({
) : null}
</DelayLayout>
<SelectionHeader
id={route.params?.item?.id || route.name}
id={route.params?.id || route.name}
items={notes}
type="note"
renderedInRoute={route.name}

View File

@@ -62,7 +62,8 @@ Monographs.get = async (params?: NotesScreenParams, grouped = true) => {
Monographs.navigate = (canGoBack?: boolean) => {
Navigation.navigate<"Monographs">("Monographs", {
item: { type: "monograph" } as any,
type: "monograph",
id: "monograph",
canGoBack: canGoBack as boolean
});
};

View File

@@ -46,11 +46,13 @@ export const TaggedNotes = ({
TaggedNotes.get = async (params: NotesScreenParams, grouped = true) => {
if (!grouped) {
return await db.relations.from(params.item, "note").resolve();
return await db.relations
.from({ id: params.id, type: "tag" }, "note")
.resolve();
}
return await db.relations
.from(params.item, "note")
.from({ id: params.id, type: "tag" }, "note")
.selector.grouped(db.settings.getGroupOptions("notes"));
};
@@ -64,8 +66,10 @@ TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
}
Navigation.push<"TaggedNotes">("TaggedNotes", {
item: item,
canGoBack
id: item.id,
type: "tag",
canGoBack,
item: item
});
};

View File

@@ -24,9 +24,7 @@ import dayjs from "dayjs";
import React, { useEffect, useRef, useState } from "react";
import {
ActivityIndicator,
Dimensions,
ScrollView,
StyleSheet,
useWindowDimensions,
View
} from "react-native";
@@ -44,8 +42,6 @@ import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { useSettingStore } from "../../stores/use-setting-store";
import { useStoredRef } from "../../hooks/use-stored-ref";
function formatNumber(num: number) {
if (num >= 1000000) {
@@ -635,7 +631,8 @@ function SummarySlide({
>
<View
style={{
padding: DefaultAppStyles.GAP
padding: DefaultAppStyles.GAP,
backgroundColor: colors.primary.background
}}
>
<Heading

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 { db } from "../common/database";
import { DatabaseLogger, db } from "../common/database";
import { eSendEvent } from "../services/event-manager";
import Navigation from "../services/navigation";
import { NotePreviewWidget } from "../services/note-preview-widget";
import Notifications from "../services/notifications";
import { eAfterSync } from "../utils/events";
import { NotesnookModule, ShortcutInfo } from "../utils/notesnook-module";
import { useFavoriteStore } from "./use-favorite-store";
import { useMenuStore } from "./use-menu-store";
import { useMonographStore } from "./use-monograph-store";
@@ -34,6 +35,81 @@ import { useTagStore } from "./use-tag-store";
import { useTrashStore } from "./use-trash-store";
import { useUserStore } from "./use-user-store";
async function syncShortcuts(result: ShortcutInfo[]) {
try {
for (let shortcut of result) {
switch (shortcut.type) {
case "note":
{
const note = await db.notes.note(shortcut.id);
if (!note) {
NotesnookModule.removeShortcut(shortcut.id);
} else if (note.title !== shortcut.title) {
NotesnookModule.updateShortcut(
shortcut.id,
"note",
note.title,
note.headline
);
}
}
break;
case "notebook":
{
const notebook = await db.notebooks.notebook(shortcut.id);
if (!notebook) {
NotesnookModule.removeShortcut(shortcut.id);
} else if (notebook.title !== shortcut.title) {
NotesnookModule.updateShortcut(
shortcut.id,
"notebook",
notebook.title,
notebook.description
);
}
}
break;
case "tag":
{
const tag = await db.tags.tag(shortcut.id);
if (!tag) {
NotesnookModule.removeShortcut(shortcut.id);
} else if (tag.title !== shortcut.title) {
NotesnookModule.updateShortcut(
shortcut.id,
"tag",
tag.title,
tag.title
);
}
}
break;
case "color":
{
const color = await db.colors.color(shortcut.id);
if (!color) {
NotesnookModule.removeShortcut(shortcut.id);
} else if (color.title !== shortcut.title) {
NotesnookModule.updateShortcut(
shortcut.id,
"color",
color.title,
color.title,
color.colorCode
);
}
}
break;
}
}
} catch (e) {
DatabaseLogger.error(
e as Error,
"Error while syncing homescreen shortcuts"
);
}
}
export function initAfterSync(type: "full" | "send" = "send") {
if (type === "full") {
Navigation.queueRoutesForUpdate();
@@ -46,9 +122,16 @@ export function initAfterSync(type: "full" | "send" = "send") {
profile: db.settings.getProfile()
});
}
Notifications.setupReminders(true);
NotePreviewWidget.updateNotes();
eSendEvent(eAfterSync);
NotesnookModule.getAllShortcuts()
.then(syncShortcuts)
.catch((e) => {
DatabaseLogger.log(e);
});
}
export async function initialize() {}

View File

@@ -25,8 +25,7 @@ import {
Note,
Notebook,
Reminder,
Tag,
TrashItem
Tag
} from "@notesnook/core";
import { ParamListBase } from "@react-navigation/core";
import { create } from "zustand";
@@ -36,12 +35,15 @@ export type GenericRouteParam = {
};
export type NotebookScreenParams = {
item: Notebook;
id: string;
item?: Notebook;
canGoBack?: boolean;
};
export type NotesScreenParams = {
item: Note | Notebook | Tag | Color | TrashItem | Reminder;
type: "tag" | "color" | "monograph";
id: string;
item?: Tag | Color;
canGoBack?: boolean;
};

View File

@@ -136,6 +136,7 @@ export interface SettingStore {
dateFormat: string;
dbPassword?: string;
isOldAppLock: () => boolean;
initialUrl: string | null;
}
const { width, height } = Dimensions.get("window");
@@ -236,5 +237,6 @@ export const useSettingStore = create<SettingStore>((set, get) => ({
},
insets: initialWindowMetrics?.insets
? initialWindowMetrics.insets
: { top: 0, right: 0, left: 0, bottom: 0 }
: { top: 0, right: 0, left: 0, bottom: 0 },
initialUrl: null
}));

View File

@@ -19,6 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { NativeModules, Platform } from "react-native";
export type ShortcutInfo = {
id: string;
title: string;
description: string;
type: "note" | "notebook" | "tag" | "color";
};
interface NotesnookModuleInterface {
getActivityName: () => Promise<string>;
setBackgroundColor: (color: string) => void;
@@ -41,6 +48,23 @@ interface NotesnookModuleInterface {
updateWidgetNote: (noteId: string, data: string) => void;
updateReminderWidget: () => void;
isGestureNavigationEnabled: () => boolean;
addShortcut: (
id: string,
type: "note" | "notebook" | "tag" | "color",
title: string,
description?: string,
color?: string
) => Promise<boolean>;
removeShortcut: (id: string) => Promise<boolean>;
updateShortcut: (
id: string,
type: "note" | "notebook" | "tag" | "color",
title: string,
description?: string,
color?: string
) => Promise<boolean>;
removeAllShortcuts: () => Promise<boolean>;
getAllShortcuts: () => Promise<ShortcutInfo[]>;
}
export const NotesnookModule: NotesnookModuleInterface = Platform.select({
@@ -61,7 +85,12 @@ export const NotesnookModule: NotesnookModuleInterface = Platform.select({
hasWidgetNote: () => {},
updateWidgetNote: () => {},
updateReminderWidget: () => {},
isGestureNavigationEnabled: () => true
isGestureNavigationEnabled: () => true,
addShortcut: () => Promise.resolve(false),
removeShortcut: () => Promise.resolve(false),
updateShortcut: () => Promise.resolve(false),
removeAllShortcuts: () => Promise.resolve(false),
getAllShortcuts: () => Promise.resolve([])
},
android: NativeModules.NNativeModule
});

View File

@@ -1029,7 +1029,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2166;
CURRENT_PROJECT_VERSION = 2167;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1104,7 +1104,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.10;
MARKETING_VERSION = 3.3.11;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1135,7 +1135,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2166;
CURRENT_PROJECT_VERSION = 2167;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1210,7 +1210,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.10;
MARKETING_VERSION = 3.3.11;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1367,7 +1367,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2166;
CURRENT_PROJECT_VERSION = 2167;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1379,7 +1379,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.10;
MARKETING_VERSION = 3.3.11;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1410,7 +1410,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2166;
CURRENT_PROJECT_VERSION = 2167;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1423,7 +1423,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.10;
MARKETING_VERSION = 3.3.11;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1453,7 +1453,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2166;
CURRENT_PROJECT_VERSION = 2167;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1534,7 +1534,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.10;
MARKETING_VERSION = 3.3.11;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1565,7 +1565,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2166;
CURRENT_PROJECT_VERSION = 2167;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1647,7 +1647,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.10;
MARKETING_VERSION = 3.3.11;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.3.10",
"version": "3.3.11",
"private": true,
"license": "GPL-3.0-or-later",
"scripts": {
@@ -208,4 +208,4 @@
"engines": {
"node": ">=20"
}
}
}

View File

@@ -115,7 +115,8 @@ const EXTRA_ICON_NAMES = [
"update",
"notebook-minus",
"calendar-blank",
"email-newsletter"
"email-newsletter",
"cellphone-arrow-down"
];
const __filename = fileURLToPath(import.meta.url);

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "3.3.6-beta.3",
"version": "3.3.6",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",
@@ -57,7 +57,6 @@
"file-saver": "^2.0.5",
"hash-wasm": "4.12.0",
"hotkeys-js": "^3.8.3",
"htmlparser2": "^10.0.0",
"katex": "0.16.11",
"mac-scrollbar": "0.13.6",
"mutative": "^1.1.0",
@@ -87,7 +86,7 @@
"devDependencies": {
"@babel/core": "7.22.5",
"@cloudflare/workers-types": "^4.20250224.0",
"@playwright/test": "1.48.2",
"@playwright/test": "1.57.0",
"@swc/plugin-react-remove-properties": "^6.0.2",
"@trpc/server": "10.45.2",
"@types/babel__core": "^7.20.1",

View File

@@ -91,7 +91,27 @@ const features: Record<FeatureKeys, Feature> = {
)
}
]
: [],
: [
{
title: "Wrapped 2025",
subtitle:
"Notesnook Wrapped 2025 is your personal recap of the year you spent with Notesnook. Available throughout December and accessible from the sidebar, it highlights your activity, stats and how you used Notesnook this year."
},
{
title: "Monograph analytics",
subtitle:
"Pro users can now see view count analytics for monographs."
},
{
title: "Twitter/X embed support",
subtitle: "Simply paste the post URL in the Insert Embed dialog."
},
{
title: "New keyboard shortcuts",
subtitle:
"Use Ctrl/Cmd + Alt/Option + F to open search/replace in the editor. We have also added Ctrl/Cmd + Alt/Option + C for toggling text color."
}
],
cta: {
title: strings.gotIt(),
icon: Checkmark,

View File

@@ -22,7 +22,7 @@ The following keyboard shortcuts will help you navigate Notesnook faster.
| --- | --- | --- | --- |
| Next tab | Ctrl Alt → / Ctrl Alt ⇧ → | Ctrl tab | ⌘ tab |
| Previous tab | Ctrl Alt ← / Ctrl Alt ⇧ ← | Ctrl ⇧ tab | ⌘ ⇧ tab |
| Command palette | Ctrl ⇧ P | Ctrl ⇧ P | ⌘ ⇧ P |
| Command palette | Ctrl ⇧ P / Ctrl ⇧ : | Ctrl ⇧ P | ⌘ ⇧ P |
| Quick open | Ctrl P | Ctrl P | ⌘ P |
| New tab | - | Ctrl T | ⌘ T |
| Close active tab | - | Ctrl W | ⌘ W |

View File

@@ -0,0 +1,5 @@
- Get an overview of your year with Notesnook with Wrapped 2025
- Cell selection and column resizing in tables
- Bug fixes and minor improvements
Thank you for using Notesnook!

View File

@@ -469,6 +469,17 @@ const features = {
believer: createLimit(true),
legacyPro: createLimit(true)
}
}),
androidLauncherShortcuts: createFeature({
id: "androidLauncherShortcuts",
title: "Android Launcher Shortcuts",
availability: {
free: createLimit(false),
essential: createLimit(false),
pro: createLimit(true),
believer: createLimit(true),
legacyPro: createLimit(true)
}
})
};

View File

@@ -96,7 +96,10 @@ export const hotkeys = {
type: "hotkeys"
},
openCommandPalette: {
keys: normalizeKeys(["ctrl+shift+p"]),
keys: normalizeKeys({
desktop: ["ctrl+shift+p"],
web: ["ctrl+shift+p", "ctrl+shift+:"]
}),
description: "Command palette",
category: "Navigation",
type: "hotkeys"

View File

@@ -119,7 +119,15 @@ export function TableComponent(
</SimpleBar>
</DesktopOnly>
<MobileOnly>
<div dir={textDirection}>
<div
dir={textDirection}
style={{
overflowY: "hidden",
overflowX: "auto",
WebkitOverflowScrolling: "touch",
maxWidth: "100%"
}}
>
<table
ref={(ref) => {
forwardRef?.(ref);

View File

@@ -1,3 +1,22 @@
/*
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 { Attrs, Node as ProsemirrorNode } from "prosemirror-model";
import { EditorState, Plugin, PluginKey, Transaction } from "prosemirror-state";
import {
@@ -9,7 +28,7 @@ import {
import { tableNodeTypes } from "./schema.js";
import { TableMap } from "./tablemap.js";
import { TableView, updateColumnsOnResize } from "./tableview.js";
import { cellAround, CellAttrs, getClientX } from "./util.js";
import { cellAround, CellAttrs, getClientX, isTouchEvent } from "./util.js";
/**
* @public
@@ -192,8 +211,7 @@ function handleMouseDown(
const pluginState = columnResizingPluginKey.getState(view.state);
if (pluginState?.dragging) {
if (event instanceof TouchEvent)
(view as any).domObserver.connectSelection();
if (isTouchEvent(event)) (view as any).domObserver.connectSelection();
updateColumnWidth(
view,
activeHandle,
@@ -224,8 +242,7 @@ function handleMouseDown(
win.addEventListener("touchcancel", finish);
win.addEventListener("touchmove", move);
event.preventDefault();
if (event instanceof TouchEvent)
(view as any).domObserver.disconnectSelection();
if (isTouchEvent(event)) (view as any).domObserver.disconnectSelection();
return true;
}

View File

@@ -1,5 +1,21 @@
// This file defines a number of helpers for wiring up user input to
// table-related functionality.
/*
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 { keydownHandler } from "prosemirror-keymap";
import { Fragment, ResolvedPos, Slice } from "prosemirror-model";
@@ -23,6 +39,7 @@ import {
getClientY,
inSameTable,
isInTable,
isTouchEvent,
nextCell,
selectionCell,
tableEditingKey
@@ -313,7 +330,3 @@ function cellUnderMouse(
if (!mousePos) return null;
return mousePos ? cellAround(view.state.doc.resolve(mousePos.pos)) : null;
}
function isTouchEvent(event: Event): event is TouchEvent {
return "TouchEvent" in window && event instanceof TouchEvent;
}

View File

@@ -1,4 +1,21 @@
// Various helper function for working with tables
/*
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 { EditorState, NodeSelection, PluginKey } from "prosemirror-state";
@@ -216,3 +233,7 @@ export function getClientY(event: MouseEvent | TouchEvent): number | null {
? event.changedTouches[0].clientY
: null;
}
export function isTouchEvent(event: Event): event is TouchEvent {
return "TouchEvent" in window && event instanceof TouchEvent;
}

View File

@@ -735,6 +735,10 @@ msgstr "Add tags to multiple notes at once"
msgid "Add to dictionary"
msgstr "Add to dictionary"
#: src/strings.ts:2613
msgid "Add to home"
msgstr "Add to home"
#: src/strings.ts:2476
msgid "Add to notebook"
msgstr "Add to notebook"

View File

@@ -735,6 +735,10 @@ msgstr ""
msgid "Add to dictionary"
msgstr ""
#: src/strings.ts:2613
msgid "Add to home"
msgstr ""
#: src/strings.ts:2476
msgid "Add to notebook"
msgstr ""

View File

@@ -2609,5 +2609,6 @@ Use this if changes from other devices are not appearing on this device. This wi
views: () => t`Views`,
clickToUpdate: () => t`Click to update`,
noPassword: () => t`No password`,
publishToTheWeb: () => t`Publish to the web`
publishToTheWeb: () => t`Publish to the web`,
addToHome: () => t`Add to home`
};