diff --git a/docs/TZ.md b/docs/TZ.md index 09c91e7..f5c3e82 100644 --- a/docs/TZ.md +++ b/docs/TZ.md @@ -4,7 +4,7 @@ | | | |---|---| -| Версия ТЗ | 0.50 | +| Версия ТЗ | 0.51 | | Дата | 2026-09-30 | | Статус | На обсуждении | @@ -541,6 +541,12 @@ - **Базовый URL настраиваемый**: по умолчанию `http://localhost:15134`; произвольный адрес задаётся в настройках попапа и запрашивает host-разрешение (`optional_host_permissions` + `chrome.permissions.request`). +- **Черновик форм сохраняется** (0.51): попап — транзиентное окно и закрывается + при потере фокуса (клик мимо, даже переключение раскладки Win+Space) — API + «не закрывать» у Chromium нет. Поэтому всё заполненное (текст, выбранный + проект, вкладка, настройка адреса/токена) пишется в `chrome.storage.local` и + восстанавливается при следующем открытии; после успешной отправки текст + очищается (как в веб-форме). - Сборка — vite + Vue 3 + **gnexus-ui-kit** (тот же кит, что веб-интерфейс); язык интерфейса — русский. - **Страница-инструкция в меню веб-версии** (`/extension`): скачивание ZIP-сборки diff --git a/extension/src/PopupInner.vue b/extension/src/PopupInner.vue index 41b2109..8b49eb2 100644 --- a/extension/src/PopupInner.vue +++ b/extension/src/PopupInner.vue @@ -3,7 +3,7 @@ // Две вкладки: добавление задачи и задачи «В работе» (только просмотр). // Аутентификация — Bearer MCP-токен (кука сервиса из extension-контекста не ходит). // Максимум готовых компонентов кита: GnInput/GnTextarea/GnSelect/GnTabs/GnBadge. -import { computed, onMounted, ref } from 'vue' +import { computed, onMounted, ref, watch } from 'vue' import { useToast } from 'gnexus-ui-kit/vue' import { createTask, @@ -63,15 +63,60 @@ return !!t.deadline_date && t.deadline_date < today } +// — Черновик формы: переживает закрытие попапа (0.51) — +// Попап — транзиентное окно: клик мимо, переключение раскладки (Win+Space) и т.п. +// убивают DOM. Поэтому всё заполненное пишется в chrome.storage.local и +// восстанавливается при следующем открытии; после отправки текст сбрасывается. +const DRAFT_KEY = 'draft' + +interface Draft { + tab?: 'add' | 'tasks' + text?: string + projectSel?: string | number + settingsOpen?: boolean + baseDraft?: string + tokenDraft?: string +} + +function saveDraft(): void { + const draft: Draft = { + tab: tab.value, + text: text.value, + projectSel: projectSel.value, + settingsOpen: settingsOpen.value, + baseDraft: baseDraft.value, + tokenDraft: tokenDraft.value, + } + void chrome.storage.local.set({ [DRAFT_KEY]: draft }).catch(() => {}) +} + +// Отслеживаемое состояние — все поля форм (watch пишет черновик при каждом вводе) +watch([tab, text, projectSel, settingsOpen, baseDraft, tokenDraft], () => { + if (screen.value === 'ready' || screen.value === 'loggedOut') saveDraft() +}) + async function init(): Promise { screen.value = 'loading' try { - const stored = await chrome.storage.local.get(['baseUrl', 'token']) + const stored = await chrome.storage.local.get(['baseUrl', 'token', DRAFT_KEY]) if (typeof stored.baseUrl === 'string' && stored.baseUrl) base.value = stored.baseUrl if (typeof stored.token === 'string') { tokenDraft.value = stored.token setToken(stored.token) } + // Черновик: восстановление заполненного (если сабмит уже очистил поле — пусто). + // Черновик приоритетнее дефолтов: tokenDraft/baseDraft показываются в форме + // подключения и в настройках. + const draft = stored[DRAFT_KEY] as Draft | undefined + if (draft) { + text.value = draft.text ?? '' + projectSel.value = draft.projectSel ?? '' + tab.value = draft.tab ?? 'add' + settingsOpen.value = draft.settingsOpen ?? false + if (typeof draft.baseDraft === 'string') baseDraft.value = draft.baseDraft + if (typeof draft.tokenDraft === 'string') tokenDraft.value = draft.tokenDraft + if (!baseDraft.value) baseDraft.value = base.value + } } catch { // storage недоступен (не-Chromium окружение) — работаем с дефолтом }