From 626576e156013086833bfd2d9dd1bf6f2d31572b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D1=83=D1=80=D0=BD=D0=B0=D1=82=20=D0=90=D0=BD=D0=B4?= =?UTF-8?q?=D1=80=D0=B5=D0=B9?= Date: Thu, 4 Jun 2026 21:00:40 +0300 Subject: [PATCH] Preserve QWord caret while typing --- src/App.test.ts | 35 ++++++++++++++++++ src/App.tsx | 25 ++++++++++++- src/components/AppShell.test.tsx | 10 ++++++ src/components/AppShell.tsx | 6 ++-- src/features/qword/QWord.test.tsx | 60 +++++++++++++++++++++++++++++++ src/features/qword/QWord.tsx | 10 +++++- 6 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 src/App.test.ts diff --git a/src/App.test.ts b/src/App.test.ts new file mode 100644 index 0000000..0285702 --- /dev/null +++ b/src/App.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; +import { initialState } from "./data/sampleData"; +import type { QOfficeState } from "./types"; +import { qofficeWindowTitle } from "./App"; + +describe("qofficeWindowTitle", () => { + it("uses the current artifact name for standalone application title bars", () => { + expect(qofficeWindowTitle(initialState, "word")).toBe(`${initialState.word.title} - QWord`); + expect(qofficeWindowTitle(initialState, "sheets")).toBe(`${initialState.workbook.title} - QExcell`); + expect(qofficeWindowTitle(initialState, "slides")).toBe(`${initialState.deck.title} - QPowerPoint`); + + const activeMessage = initialState.mail.messages.find((message) => message.id === initialState.mail.activeMessageId); + expect(qofficeWindowTitle(initialState, "mail")).toBe(`${activeMessage?.subject} - QOutlook`); + }); + + it("falls back to default standalone titles when names are blank", () => { + const state: QOfficeState = { + ...initialState, + word: { ...initialState.word, title: " " }, + workbook: { ...initialState.workbook, title: "" }, + deck: { ...initialState.deck, title: "\n\t" }, + mail: { + ...initialState.mail, + messages: initialState.mail.messages.map((message) => + message.id === initialState.mail.activeMessageId ? { ...message, subject: "" } : message + ) + } + }; + + expect(qofficeWindowTitle(state, "word")).toBe("Документ1 - QWord"); + expect(qofficeWindowTitle(state, "sheets")).toBe("Книга1 - QExcell"); + expect(qofficeWindowTitle(state, "slides")).toBe("Презентация1 - QPowerPoint"); + expect(qofficeWindowTitle(state, "mail")).toBe("Почта - QOutlook"); + }); +}); diff --git a/src/App.tsx b/src/App.tsx index f011e0d..9894863 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,28 @@ function clampedWordZoom(value: number) { return Math.min(200, Math.max(50, value)); } +function normalizedWindowTitlePart(value: string | undefined, fallback: string) { + const normalized = value?.replace(/\s+/g, " ").trim(); + return normalized || fallback; +} + +export function qofficeWindowTitle(state: QOfficeState, activeApp: QOfficeState["activeApp"]) { + if (activeApp === "word") { + return `${normalizedWindowTitlePart(state.word.title, "Документ1")} - QWord`; + } + + if (activeApp === "sheets") { + return `${normalizedWindowTitlePart(state.workbook.title, "Книга1")} - QExcell`; + } + + if (activeApp === "slides") { + return `${normalizedWindowTitlePart(state.deck.title, "Презентация1")} - QPowerPoint`; + } + + const activeMessage = state.mail.messages.find((message) => message.id === state.mail.activeMessageId); + return `${normalizedWindowTitlePart(activeMessage?.subject, "Почта")} - QOutlook`; +} + function migrateQOfficeState(state: QOfficeState): QOfficeState { const launchChecklistAttachments = initialState.mail.messages.find((message) => message.id === "mail-1")?.attachments; const sampleSheet = initialState.workbook.sheets.find((sheet) => sheet.id === "sheet-1"); @@ -101,6 +123,7 @@ export function App() { const [wordStatus, setWordStatus] = useState(defaultQWordStatus); const [wordZoom, setWordZoom] = useState(100); const activeApp = activeAppFromLocation(window.location); + const windowTitle = qofficeWindowTitle(state, activeApp); const updateWordStatus = useCallback((nextStatus: QWordStatus) => { setWordStatus((current) => @@ -227,7 +250,7 @@ export function App() { ); return ( - + {activeSurface} ); diff --git a/src/components/AppShell.test.tsx b/src/components/AppShell.test.tsx index b7ee66c..a71ef73 100644 --- a/src/components/AppShell.test.tsx +++ b/src/components/AppShell.test.tsx @@ -42,6 +42,16 @@ describe("AppShell", () => { expect(screen.getByText("125%")).toBeInTheDocument(); }); + it("показывает заголовок текущего файла активного приложения", () => { + render( + +
Рабочая область QWord
+
+ ); + + expect(screen.getByText("Отчет.docx - QWord")).toBeInTheDocument(); + }); + it("отправляет команды панели быстрого доступа активному приложению", () => { const commands: string[] = []; function onCommand(event: WindowEventMap["qoffice-command"]) { diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index 4a32e7e..e980031 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -12,6 +12,7 @@ import type { AppId } from "../types"; interface AppShellProps { activeApp: AppId; + windowTitle?: string; status: { savedLabel: string; updatedAt?: string; @@ -37,8 +38,9 @@ function keepActiveEditorFocus(event: MouseEvent) { event.preventDefault(); } -export function AppShell({ activeApp, status, children }: AppShellProps) { +export function AppShell({ activeApp, windowTitle, status, children }: AppShellProps) { const statusClassName = ["statusbar", status.className].filter(Boolean).join(" "); + const displayWindowTitle = windowTitle?.trim() || appTitles[activeApp]; return (
@@ -58,7 +60,7 @@ export function AppShell({ activeApp, status, children }: AppShellProps) {
- {appTitles[activeApp]} + {displayWindowTitle}