feat: in-place save (File System Access), image paste, print wiring, web capture

The last high-severity web gap plus a small integration bundle.

In-place save: with the File System Access API (Chromium/Edge), Save
now works like desktop. Plain Save on a tab with a retained handle
writes straight through — no picker, no download, no "(1)" copies.
First save or format change opens showSaveFilePicker AT the confirm
gesture (the worker serialization that follows can outlast the
transient-activation window; retained handles need no activation to
write). Picker cancel cancels the save like desktop; picker or write
failure falls back to the legacy download with a note; Firefox/Safari
keep the download flow untouched (feature-detected). Handles are
keyed by tab id in a thread-local registry (Message must be Send —
JsValue cannot ride in messages or App state); closing a tab drops
its handle; Save As re-picks. Dirty clears only on successful
persistence, same race guard as before.

Image paste: Ctrl+V of a screenshot inserts it as an image reference
— the clipboard reader falls back to image/* items when neither the
entity clipboard nor text has anything (precedence preserved), the
bytes enter the image source registry as pasted-<timestamp>.<ext>,
and attach reuses the IMAGE command's model-creation path verbatim.

Browser print: beforeprint snapshots the WebGL canvas into a print-
only <img> (the capture_canvas technique), so the browser's own print
menu produces the drawing instead of a blank page.

Automation capture on web: the capture op no longer calls the
synchronous GPU-map screenshot (dead on wasm); it waits one presented
frame and reads via capture_canvas — the same workaround the save-
thumbnail path uses — delivering the PNG as a download.
This commit is contained in:
Afonsop committed 2026-09-21 11:17:23 +01:00
1 parent 369e2c8b5a
commit 6e89b36d44
34 files changed
+1138 -34

No files matched your search

+4
View File
@@ -161,6 +161,8 @@ modal =
clipboard =
.no-supported-content = Буфер обмена не содержит поддерживаемого текста или объектов чертежа.
.image-unsupported = Не удалось декодировать вставленное изображение "{ $name }".
.pasted-image = Вставлено изображение "{ $name }": { $width }×{ $height } px
.unavailable = Системный буфер обмена недоступен.
.occupied = Системный буфер обмена сейчас используется. Повторите попытку.
.conversion-failed = Не удалось прочитать текст из буфера обмена.
@@ -3583,6 +3585,8 @@ view =
.opening-ocs-discussions-for-help-and-questions = Открытие OCS Дискуссии о помощи и вопросах
.opening-ocs-web = Открытие OCS Веб-сайт
.browser-cannot-open-typed-path = Браузер не может открыть введённый путь; выберите файл.
.save-picker-failed-downloading = Не удалось выбрать место сохранения: __ocs_fmt_0__; загружается копия
.in-place-save-failed-downloaded = Не удалось сохранить в файл (__ocs_fmt_0__); копия загружена
.opening-the-command-reference = Открытие справочника команд...
.already-loaded-different-contents-kept = "__ocs_fmt_0__" уже загружен с другим содержимым; существующий файл сохранён.
.opening-patreon-page = Открытие страницы Patreon...