Compare commits

...

23 Commits

Author SHA1 Message Date
Abdullah Atta
8f97e54afa web: chore bump version to v3.1.1 2025-05-22 11:27:38 +05:00
Abdullah Atta
8205aafe65 web: fix flicker when programatically updating title 2025-05-22 11:25:24 +05:00
Abdullah Atta
8e4e9c9e4c core: do not allow empty titles 2025-05-22 11:25:24 +05:00
SB
a3c2d50d64 docs: add missing '-' in build-steps for MacOS and Linux (AppImage) 2025-05-22 11:25:24 +05:00
01zulfi
41a6cfaa29 web: fix list pane not expanding (#7963)
* web: fix list pane not expanding
* fix list pane not expanding to saved width on reload
* fix list pane not expanding to saved width on collapse

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: collapse pane and set size to 0 when applicable
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

---------

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-05-22 11:25:24 +05:00
01zulfi
96ca8598ae core: segregate logic for note headline and note headline title generation (#7968)
* core: segregate logic for note headline and note headline title generation
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* core: minor refactor note headline title
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-05-22 11:25:24 +05:00
Ammar Ahmed
c9cd7d1fad ci: fix build ios
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-05-22 11:25:24 +05:00
Ammar Ahmed
0c2781ee32 ci: fix build ios
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-05-22 11:25:24 +05:00
Ammar Ahmed
97c5fd881f ci: fix build ios
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-05-22 11:25:24 +05:00
Ammar Ahmed
5a636f94df mobile: fix crash when going from trash -> search 2025-05-22 11:25:24 +05:00
Ammar Ahmed
edfdf84e46 mobile: release v3.1.1 2025-05-22 11:25:24 +05:00
Ammar Ahmed
5bc16a8e3a mobile: fix crash on delete item 2025-05-22 11:25:24 +05:00
Ammar Ahmed
2a6db1b06f mobile: fix crash in list 2025-05-22 11:25:24 +05:00
Ammar Ahmed
f3193ba3bc mobile: fix cursor bug on ios 2025-05-22 11:25:24 +05:00
Ammar Ahmed
7f883da94c mobile: fix typing in editor title input 2025-05-22 11:25:24 +05:00
Ammar Ahmed
e6385a09ae mobile: fix default font setting not followed 2025-05-22 11:25:24 +05:00
01zulfi
46bf058bfc web: make nav menu header draggable (#7985)
* web: make part of nav menu header draggable
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* Update apps/web/src/app.css

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>

* Update apps/web/src/components/navigation-menu/index.tsx

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>

---------

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2025-05-22 11:25:24 +05:00
Ammar Ahmed
74691f299f mobile: remove READ_MEDIA_IMAGES permission 2025-05-22 11:25:24 +05:00
Abdullah Atta
a653f28b44 web: validate release track on app launch 2025-05-22 11:25:24 +05:00
Abdullah Atta
37d364d893 web: use separate service worker cache for each app version 2025-05-22 11:25:24 +05:00
Abdullah Atta
69aea1a909 web: improve update installation 2025-05-22 11:25:24 +05:00
Ammar Ahmed
10044162e0 mobile: fix swipe active offsets 2025-05-22 11:25:24 +05:00
Ammar Ahmed
2c858a1310 ci: fix release workflow
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-05-22 11:25:24 +05:00
37 changed files with 220 additions and 150 deletions

View File

@@ -58,7 +58,7 @@ jobs:
ccache -p
- name: Cache gradle
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/.gradle/caches

View File

@@ -4,7 +4,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: macos-latest
runs-on: macos-14
timeout-minutes: 60
steps:
@@ -14,6 +14,11 @@ jobs:
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.1"
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit

View File

@@ -58,10 +58,10 @@ This will compile and run the app in production mode but it won't generate any p
npm run release -- --rebuild
# For macOS
npx electron-builder --config=electron-builder.config.js --mac dmg --arm64 --x64 -publish never
npx electron-builder --config=electron-builder.config.js --mac dmg --arm64 --x64 --publish never
# For Linux (AppImage)
npx electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -publish never
npx electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 --publish never
# For Windows
npx electron-builder --config=electron-builder.config.js --win --publish never

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.1.0",
"version": "3.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.1.0",
"version": "3.1.1",
"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": "3.1.0",
"version": "3.1.1",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",

View File

@@ -302,7 +302,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
const gesture = Gesture.Pan()
.maxPointers(1)
.enabled(enabled && !disabled)
.activeOffsetX([-10, 10])
.activeOffsetX([-5, 5])
.failOffsetY([-10, 10])
.onBegin((event) => {
locked.value = false;

View File

@@ -73,7 +73,7 @@ export default function List(props: ListProps) {
? "home"
: props.renderedInRoute === "Favorites"
? "favorites"
: props.renderedInRoute === "Trash"
: props.renderedInRoute === "Trash" || props.dataType === "trash"
? "trash"
: `${props.dataType}s`;

View File

@@ -58,7 +58,6 @@ const Line = ({ top = 6, bottom = 6 }) => {
export const Properties = ({ close = () => {}, item, buttons = [] }) => {
const { colors } = useThemeColors();
const isColor = !!ColorValues[item.title];
if (!item || !item.id) {
return (
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>

View File

@@ -56,7 +56,7 @@ const Sort = ({
db.settings.getGroupOptions(
screen === "Notes"
? "home"
: screen === "Trash"
: screen === "Trash" || type === "trash"
? "trash"
: ((type + "s") as GroupingKey)
)
@@ -65,10 +65,11 @@ const Sort = ({
const groupType =
screen === "Notes"
? "home"
: screen === "Trash"
: screen === "Trash" || type === "trash"
? "trash"
: screen === "Favorites"
? "favorites"
: ((type + "s") as GroupingKey);
console.log("updateGroupOptions for group", groupType, "in", screen);
await db.settings.setGroupOptions(groupType, _groupOptions);
setGroupOptions(_groupOptions);
setTimeout(() => {

View File

@@ -19,13 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [
{
title: "Notebook Tree View",
body: "Notebooks are now displayed in a tree view for better organization and user experience."
},
{
title: "New SideBar",
body: "Sidebar now contains tabs for Notebooks and Tags for easy navigation."
}
];
export const features: FeatureType[] = [];

View File

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

View File

@@ -177,7 +177,7 @@ export const useEditorEvents = (
useEffect(() => {
const handleKeyboardDidShow: KeyboardEventListener = () => {
editor.commands.keyboardShown(true);
//editor.postMessage(NativeEvents.keyboardShown, undefined);
editor.postMessage(NativeEvents.keyboardShown, undefined);
};
const handleKeyboardDidHide: KeyboardEventListener = () => {
editor.commands.keyboardShown(false);

View File

@@ -352,7 +352,7 @@ export const useEditor = (
DatabaseLogger.log(`Note saved: ${id}...`);
clearTimeout(saveTimer);
const oldNote = currentNotes.current[id];
if (id) {
currentNotes.current[id] = await db.notes?.note(id);
}
@@ -376,11 +376,16 @@ export const useEditor = (
}
}
postMessage(
NativeEvents.title,
currentNotes.current[id]?.title,
tabId
);
if (
oldNote?.title !== currentNotes.current[id]?.title &&
!noteData.title
) {
postMessage(
NativeEvents.title,
currentNotes.current[id]?.title,
tabId
);
}
if (Notifications.isNotePinned(id as string)) {
Notifications.pinNote(id as string);

View File

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

View File

@@ -15,7 +15,6 @@
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="remove"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" tools:node="remove" />

View File

@@ -1,8 +1,3 @@
- v3.1.0 brings new features and improvements to enhance your Notesnook experience.
- Notebooks and Tags are not located in the Sidebar
- Tree view for notebooks
- Improved list view UI for better user experience
- Improved notebook related actions by integrating tree view in most places
- You can set any notebook, tag or colors as home screen
- Bug fixes
Thank you for using Notesnook!

View File

@@ -1063,7 +1063,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2132;
CURRENT_PROJECT_VERSION = 2133;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1137,7 +1137,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.1.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1168,7 +1168,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2132;
CURRENT_PROJECT_VERSION = 2133;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1242,7 +1242,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.1.1;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1401,7 +1401,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2132;
CURRENT_PROJECT_VERSION = 2133;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1413,7 +1413,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.1.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1444,7 +1444,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2132;
CURRENT_PROJECT_VERSION = 2133;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1457,7 +1457,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.1.1;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1487,7 +1487,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2132;
CURRENT_PROJECT_VERSION = 2133;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1561,7 +1561,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.1.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1592,7 +1592,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2132;
CURRENT_PROJECT_VERSION = 2133;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1667,7 +1667,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.1.1;
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.1.0",
"version": "3.1.1",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -56,4 +56,4 @@
"react": "18.2.0",
"react-native": "0.74.5"
}
}
}

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "3.1.0",
"version": "3.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "3.1.0",
"version": "3.1.1",
"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": "3.1.0",
"version": "3.1.1",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -1,6 +1,7 @@
.tabsScroll,
.titlebarLogo,
.theme-scope-titleBar,
.navigation-menu-header,
.route-container-header {
-webkit-app-region: drag;
}

View File

@@ -164,8 +164,11 @@ function DesktopAppContents() {
useEffect(() => {
if (isListPaneVisible) {
navPane.current?.expand(1);
navPane.current?.reset(1);
if (navPane.current?.hasExpandedSize(1)) {
navPane.current?.expand(1);
} else {
navPane.current?.reset(1);
}
} else {
navPane.current?.collapse(1);
}

View File

@@ -62,7 +62,7 @@ function TitleBox(props: TitleBoxProps) {
withSelectionPersist(inputRef.current, (input) => {
input.value = title || "";
requestAnimationFrame(() => resizeTextarea(input));
setTimeout(() => resizeTextarea(input));
});
}, [sessionType, id, sessionTitle]);

View File

@@ -272,7 +272,9 @@ function NavigationMenu({ onExpand }: { onExpand?: () => void }) {
}}
>
<Flex
className="navigation-menu-header"
sx={{
flex: 1,
flexDirection: "row",
alignItems: "center",
gap: 1

View File

@@ -62,6 +62,7 @@ export type SplitPaneImperativeHandle = {
expand: (index: number) => void;
reset: (index: number) => void;
isCollapsed: (index: number) => boolean;
hasExpandedSize: (index: number) => boolean;
};
export const SplitPane = React.forwardRef<
SplitPaneImperativeHandle,
@@ -281,8 +282,9 @@ export const SplitPane = React.forwardRef<
const currentPane = paneSizes.current[index];
if (!currentPane || !currentPane.collapsed) return;
currentPane.collapsed = false;
currentPane.size =
currentPane.expandedSize ?? currentPane.initialSize;
currentPane.size = !!currentPane.expandedSize
? currentPane.expandedSize
: currentPane.initialSize;
currentPane.expandedSize = undefined;
setSizes(paneSizes.current, wrapSize.current);
},
@@ -296,6 +298,9 @@ export const SplitPane = React.forwardRef<
},
isCollapsed: (index: number) => {
return paneSizes.current[index].collapsed;
},
hasExpandedSize: (index: number) => {
return paneSizes.current[index].expandedSize !== undefined;
}
};
},
@@ -375,6 +380,19 @@ export const SplitPane = React.forwardRef<
nextPane.nextSize = undefined;
currentPane.nextSize = undefined;
[currentPane, nextPane].forEach((pane) => {
if (pane.size === 0) {
// if pane size is 0, collapsed should be true
pane.collapsed = true;
}
if (pane.min === 0 && pane.size < pane.snap) {
// if pane min is 0, and size is less than snap, collapsed should be true
pane.collapsed = true;
// and size should be set to 0
pane.size = 0;
}
});
setSizes(paneSizes.current, wrapSize.current);
},
[paneSizes, setSizes, splitAxis]

View File

@@ -39,6 +39,26 @@
<title>Notesnook</title>
<style id="theme-colors"></style>
<script>
(async function validateServiceWorkerTrack() {
const cookie = document.cookie.match(/release\-track=([^;]+)/);
const releaseTrack = cookie ? cookie[1] : "stable";
const expectedPrefix =
releaseTrack === "beta" ? "notesnook-beta" : "notesnook";
const cacheKeys = await caches.keys();
const mismatched = cacheKeys.some(
(key) => !key.startsWith(expectedPrefix)
);
if (mismatched) {
const regs = await navigator.serviceWorker.getRegistrations();
await Promise.all(regs.map((r) => r.unregister()));
await Promise.all(cacheKeys.map((key) => caches.delete(key)));
window.location.reload();
}
})();
</script>
<script type="module" src="./index.ts"></script>
<style>
html {

View File

@@ -19,18 +19,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-var */
/// <reference lib="webworker" />
import { ExpirationPlugin } from "workbox-expiration";
import {
precacheAndRoute,
createHandlerBoundToURL,
cleanupOutdatedCaches
} from "workbox-precaching";
import { setCacheNameDetails } from "workbox-core";
import { registerRoute } from "workbox-routing";
import { StaleWhileRevalidate } from "workbox-strategies";
import "./service-worker.dev.js";
declare var self: ServiceWorkerGlobalScope & typeof globalThis;
setCacheNameDetails({
prefix: IS_BETA ? "notesnook-beta" : "notesnook",
suffix: `${self.registration.scope}-${APP_VERSION}-${GIT_HASH}`,
precache: "precache",
runtime: "runtime"
});
cleanupOutdatedCaches();
precacheAndRoute(self.__WB_MANIFEST);
@@ -59,19 +65,3 @@ registerRoute(
},
createHandlerBoundToURL(PUBLIC_URL + "/index.html")
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) =>
url.origin === self.location.origin && url.pathname.endsWith(".png"), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: "images",
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 })
]
})
);

View File

@@ -73,13 +73,19 @@ export async function installUpdate() {
else {
const registrations =
(await navigator.serviceWorker?.getRegistrations()) || [];
let reload = false;
for (const registration of registrations) {
if (registration.waiting) {
registration.waiting.addEventListener("statechange", () => {
const worker =
registration.active ||
registration.waiting ||
registration.installing;
if (worker?.state === "activated") {
window.location.reload();
}
});
registration.waiting.postMessage({ type: "SKIP_WAITING" });
reload = true;
}
}
if (reload) window.location.reload();
}
}

View File

@@ -0,0 +1,3 @@
- Bug fixes
Thank you for using Notesnook!

View File

@@ -134,7 +134,7 @@ test("changing content shouldn't reset the note title ", () =>
expect(note?.title).toBe("I am a note");
}));
test("note title with headline format should keep generating headline until title is edited", () =>
test("note title with headline format should keep generating headline title until title is edited", () =>
noteTest().then(async ({ db }) => {
await db.settings.setTitleFormat("$headline$");
const id = await db.notes.add({
@@ -191,19 +191,44 @@ test("note title with headline format should keep generating headline until titl
"headlineLorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam rutrum ex ac eros egestas, ut rhoncus felis faucibus. Mauris tempor orci nisl,"
]
].forEach(([testCase, content, expectedHeadline]) => {
test(`note should generate headline up to HEADLINE_CHARACTER_LIMIT characters - ${testCase}`, () =>
noteTest({
...TEST_NOTE,
content: {
type: TEST_NOTE.content.type,
data: content
}
}).then(async ({ db, id }) => {
test(`note title with headline format should generate headline title up to 150 characters - ${testCase}`, () =>
noteTest().then(async ({ db }) => {
await db.settings.setTitleFormat("$headline$");
const id = await db.notes.add({
content: {
type: TEST_NOTE.content.type,
data: content
}
});
const note = await db.notes.note(id);
expect(note?.headline).toBe(expectedHeadline);
expect(note?.title).toBe(expectedHeadline);
}));
});
test("note should get headline from first paragraph in content", () =>
noteTest({
...TEST_NOTE,
content: {
type: TEST_NOTE.content.type,
data: "<p>This is a very colorful existence.</p>"
}
}).then(async ({ db, id }) => {
const note = await db.notes.note(id);
expect(note?.headline).toBe("This is a very colorful existence.");
}));
test("note should not get headline if there is no p tag", () =>
noteTest({
...TEST_NOTE,
content: {
type: TEST_NOTE.content.type,
data: `<ol style="list-style-type: decimal;" data-mce-style="list-style-type: decimal;"><li>Hello I won't be a headline :(</li><li>Me too.</li><li>Gold.</li></ol>`
}
}).then(async ({ db, id }) => {
const note = await db.notes.note(id);
expect(note?.headline).toBe("");
}));
test("note title should allow trailing space", () =>
noteTest({ title: "Hello ", content: TEST_NOTE.content }).then(
async ({ db, id }) => {

View File

@@ -87,6 +87,7 @@ export class Notes implements ICollection {
let contentId = item.contentId;
let dateEdited = item.dateEdited;
let headline = item.headline;
let headlineTitle = "";
if (item.content && item.content.data && item.content.type) {
logger.debug("saving content", { id });
@@ -95,7 +96,8 @@ export class Notes implements ICollection {
const content = await getContentFromData(type, data);
if (!content) throw new Error("Invalid content type.");
headline = getNoteHeadline(content);
headline = content.toHeadline();
headlineTitle = content.toTitle();
dateEdited = Date.now();
contentId = await this.db.content.add({
noteId: id,
@@ -125,7 +127,7 @@ export class Notes implements ICollection {
this.db.settings.getTitleFormat(),
this.db.settings.getDateFormat(),
this.db.settings.getTimeFormat(),
headline ? headlineToTitle(headline) : "",
headlineTitle,
this.totalNotes
);
item.isGeneratedTitle = true;
@@ -134,8 +136,7 @@ export class Notes implements ICollection {
const currentNoteTitleFields = await this.db
.sql()
.selectFrom("notes")
.select("isGeneratedTitle")
.select("title")
.select(["isGeneratedTitle", "title"])
.where("id", "=", id)
.executeTakeFirst();
if (isUpdating) {
@@ -147,14 +148,12 @@ export class Notes implements ICollection {
if (
item.isGeneratedTitle &&
HEADLINE_REGEX.test(titleFormat) &&
headline &&
currentNoteTitleFields?.title !== headlineToTitle(headline)
headlineTitle &&
currentNoteTitleFields?.title !== headlineTitle
) {
item.title = titleFormat.replace(
HEADLINE_REGEX,
headlineToTitle(headline)
);
}
item.title = titleFormat.replace(HEADLINE_REGEX, headlineTitle);
} else if (item.title === "")
item.title = currentNoteTitleFields?.title || "Untitled";
}
if (isUpdating) {
@@ -488,11 +487,3 @@ export class Notes implements ICollection {
).internalLinks;
}
}
function getNoteHeadline(content: Tiptap) {
return content.toHeadline();
}
function headlineToTitle(headline: string) {
return headline.split(" ").splice(0, 10).join(" ");
}

View File

@@ -31,6 +31,7 @@ import dataurl from "../utils/dataurl.js";
import {
HTMLParser,
extractHeadline,
extractTitle,
getDummyDocument
} from "../utils/html-parser.js";
import { HTMLRewriter } from "../utils/html-rewriter.js";
@@ -89,7 +90,11 @@ export class Tiptap {
}
toHeadline() {
return extractHeadline(this.data, 150);
return extractHeadline(this.data);
}
toTitle() {
return extractTitle(this.data, 150);
}
// isEmpty() {

View File

@@ -44,14 +44,42 @@ function wrapIntoHTMLDocument(input: string) {
return `<!doctype html><html lang="en"><head><title>Document Fragment</title></head><body>${input}</body></html>`;
}
export function extractHeadline(html: string, headlineCharacterLimit: number) {
export function extractHeadline(html: string) {
let text = "";
let start = false;
const parser = new Parser(
{
onopentag: (name) => {
if (name === "p") start = true;
},
onclosetag: (name) => {
if (name === "p") {
start = false;
parser.pause();
parser.end();
}
},
ontext: (data) => {
if (start) text += data;
}
},
{
lowerCaseTags: false,
decodeEntities: true
}
);
parser.end(html);
return text;
}
export function extractTitle(html: string, characterLimit: number) {
let text = "";
const parser = new Parser(
{
ontext: (data) => {
text += data;
if (text.length > headlineCharacterLimit) {
text = text.slice(0, headlineCharacterLimit);
if (text.length > characterLimit) {
text = text.slice(0, characterLimit);
parser.pause();
parser.end();
}

View File

@@ -15,10 +15,10 @@
font-family: "Inter";
}
p {
font-family: "Inter";
/* p { */
/* font-family: "Inter"; */
/* color: var(--nn_primary_paragraph) ## TODO: use fixed color */
}
/* } */
::selection {
color: white;
@@ -37,6 +37,10 @@
height: 100%;
display: flex;
}
#statusbar p {
font-family: "Inter";
}
</style>
<meta name="description" content="Notesnook editor for mobile" />
<!--

View File

@@ -43,7 +43,11 @@ export default function TiptapEditorWrapper(props: {
sx={{
display: props.settings.noToolbar ? "none" : "flex",
overflowY: "hidden",
minHeight: "50px"
minHeight: "50px",
position: "absolute",
bottom: 0,
left: 0,
right: 0
}}
editor={editor}
location="bottom"

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Editor, scrollIntoViewById } from "@notesnook/editor";
import { keepLastLineInView } from "@notesnook/editor/extensions/keep-in-view/keep-in-view.js";
import { strings } from "@notesnook/intl";
import {
ThemeDefinition,
@@ -66,38 +67,12 @@ type Timers = {
scroll: NodeJS.Timeout | null;
};
function isInViewport(element: any) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
function scrollIntoView(editor: Editor) {
if (__PLATFORM__ == "android") return;
setTimeout(() => {
try {
const node = editor?.state.selection.$from;
const dom = node ? editor?.view?.domAtPos?.(node.pos) : null;
let domNode = dom?.node;
if (domNode) {
if (domNode.nodeType === Node.TEXT_NODE && domNode.parentNode) {
domNode = domNode.parentNode;
}
if (isInViewport(domNode)) return;
(domNode as HTMLElement).scrollIntoView({
behavior: "smooth",
block: "end"
});
}
} catch (e) {
/* empty */
}
}, 100);
if (!editor.isFocused) return;
keepLastLineInView(editor);
}, 1);
}
export type EditorController = {

View File

@@ -71,7 +71,7 @@ export const KeepInView = Extension.create<
export function keepLastLineInView(
editor: Editor,
THRESHOLD = 80,
THRESHOLD = 90,
SCROLL_THRESHOLD = 100
) {
if (