web: allow saving note from status icon click & tab menu (#8316)

* web: allow saving note from status icon click & tab menu
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: directly save content instead of using events

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
This commit is contained in:
01zulfi
2025-12-30 13:27:10 +05:00
committed by GitHub
parent 2a92ea4efd
commit b0c18a8ece
9 changed files with 99 additions and 5 deletions

View File

@@ -415,6 +415,44 @@ test("when autosave is disabled, closing the note should save it", async ({
expect(await notes.editor.getContent("text")).toBe(content.trim());
});
test("when autosave is disabled, clicking the not saved icon should save the note", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const content = "a ".repeat(100);
await notes.createNote({
title: NOTE.title,
content
});
await notes.editor.notSavedIcon.waitFor({ state: "visible" });
await notes.editor.notSavedIcon.click();
await expect(notes.editor.savedIcon).toBeVisible();
expect(await notes.editor.getContent("text")).toBe(content.trim());
});
test("when autosave is disabled, clicking save from tab menu should save the note", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const content = "a ".repeat(100);
await notes.createNote({
title: NOTE.title,
content
});
const tab = (await notes.editor.getTabs())[0];
await tab.contextMenu.save();
await expect(notes.editor.savedIcon).toBeVisible();
expect(await notes.editor.getContent("text")).toBe(content.trim());
});
test("control + alt + right arrow should go to next note", async ({ page }) => {
const app = new AppModel(page);
await app.goto();

View File

@@ -74,4 +74,9 @@ class TabContextMenuModel {
await this.open();
return this.menu.getItem("reveal-in-list");
}
async save() {
await this.open();
return this.menu.clickOnItem("save");
}
}

View File

@@ -83,6 +83,7 @@ import useTablet from "../../hooks/use-tablet";
import { isMac } from "../../utils/platform";
import { CREATE_BUTTON_MAP } from "../../common";
import { getDragData } from "../../utils/data-transfer";
import { saveContent } from "./index";
type ToolButton = {
title: string;
@@ -406,6 +407,13 @@ const TabStrip = React.memo(function TabStrip() {
isLocked={isLockedSession(session)}
isRevealInListDisabled={isFocusMode}
type={session.type}
onSave={() => {
const { activeEditorId, getEditor } =
useEditorManager.getState();
const editor = getEditor(activeEditorId || "")?.editor;
if (!editor) return;
saveContent(session.id, false, editor.getContent());
}}
onFocus={() => {
if (tab.id !== currentTab) {
useEditorStore.getState().activateSession(tab.sessionId);
@@ -486,6 +494,7 @@ type TabProps = {
onCloseToTheLeft: () => void;
onCloseAll: () => void;
onPin: () => void;
onSave: () => void;
onRevealInList?: () => void;
};
function Tab(props: TabProps) {
@@ -505,7 +514,8 @@ function Tab(props: TabProps) {
onCloseToTheRight,
onCloseToTheLeft,
onRevealInList,
onPin
onPin,
onSave
} = props;
const Icon = isLocked
? type === "locked"
@@ -580,6 +590,14 @@ function Tab(props: TabProps) {
onContextMenu={(e) => {
e.preventDefault();
Menu.openMenu([
{
type: "button",
title: strings.save(),
key: "save",
onClick: onSave,
isHidden: !isUnsaved
},
{ type: "separator", key: "sep0", isHidden: !isUnsaved },
{
type: "button",
title: strings.close(),
@@ -610,7 +628,7 @@ function Tab(props: TabProps) {
key: "close-all",
onClick: onCloseAll
},
{ type: "separator", key: "sep" },
{ type: "separator", key: "sep1" },
{
type: "button",
title: strings.revealInList(),

View File

@@ -34,7 +34,11 @@ import {
NormalMode,
Cross
} from "../icons";
import { useEditorConfig, useNoteStatistics } from "./manager";
import {
useEditorConfig,
useEditorManager,
useNoteStatistics
} from "./manager";
import { getFormattedDate } from "@notesnook/common";
import { MAX_AUTO_SAVEABLE_WORDS, NoteStatistics } from "./types";
import { strings } from "@notesnook/intl";
@@ -43,6 +47,7 @@ import { useWindowControls } from "../../hooks/use-window-controls";
import { exitFullscreen } from "../../utils/fullscreen";
import { useRef, useState } from "react";
import { PopupPresenter } from "@notesnook/ui";
import { saveContent } from "./index";
const SAVE_STATE_ICON_MAP = {
"-1": NotSaved,
@@ -276,6 +281,24 @@ function EditorFooter() {
? "icon-error"
: "paragraph"
}
title={
saveState === SaveState.NotSaved ? strings.clickToSave() : undefined
}
sx={{
height: "100%",
cursor: saveState === SaveState.NotSaved ? "pointer" : "default",
":hover": {
bg: saveState === SaveState.NotSaved ? "hover" : "initial"
}
}}
onClick={() => {
if (saveState === SaveState.NotSaved) {
const { activeEditorId, getEditor } = useEditorManager.getState();
const editor = getEditor(activeEditorId || "")?.editor;
if (!editor) return;
saveContent(session.id, false, editor.getContent());
}
}}
/>
)}
</Flex>

View File

@@ -84,7 +84,7 @@ const PDFPreview = React.lazy(() => import("../pdf-preview"));
const autoSaveToast = { show: true, hide: () => {} };
async function saveContent(
export async function saveContent(
noteId: string,
ignoreEdit: boolean,
content: string

View File

@@ -68,6 +68,7 @@ import { showFeatureNotAllowedToast } from "../../common/toasts";
import { UpgradeDialog } from "../../dialogs/buy-dialog/upgrade-dialog";
import { ConfirmDialog } from "../../dialogs/confirm";
import { strings } from "@notesnook/intl";
import { AppEventManager, AppEvents } from "../../common/app-events";
export type OnChangeHandler = (
content: () => string,

View File

@@ -1604,6 +1604,10 @@ msgstr "Click to remove"
msgid "Click to reset {title}"
msgstr "Click to reset {title}"
#: src/strings.ts:2614
msgid "Click to save"
msgstr "Click to save"
#: src/strings.ts:2610
msgid "Click to update"
msgstr "Click to update"

View File

@@ -1593,6 +1593,10 @@ msgstr ""
msgid "Click to reset {title}"
msgstr ""
#: src/strings.ts:2614
msgid "Click to save"
msgstr ""
#: src/strings.ts:2610
msgid "Click to update"
msgstr ""

View File

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