umbau 1.3

This commit is contained in:
2026-09-03 08:01:27 +02:00
parent 13542163d8
commit 96d9f331b5
10 changed files with 654 additions and 44 deletions

View File

@ -220,6 +220,19 @@ pre.log-view {
color: var(--text); font-weight: 400; font-size: 0.85rem; border-radius: 4px;
}
.term-paste-menu button:hover { background: var(--border); filter: none; }
/* Ruecksprung fuer das Kopieren, wenn der Browser die Clipboard-API
verweigert (terminal.js::_copyViaExecCommand). document.execCommand("copy")
kopiert die aktuelle Auswahl -- dafuer muss das <textarea> TATSAECHLICH
gerendert sein: mit display:none oder .hidden laesst es sich nicht
selektieren und es wuerde leerer Text kopiert. Deshalb sichtbar im
Layout, aber vollstaendig transparent und ausserhalb der Wahrnehmung.
position:fixed statt absolute, damit .select() die Seite nicht scrollt. */
.clipboard-helper {
position: fixed; top: 0; left: 0; width: 1px; height: 1px;
padding: 0; margin: 0; border: none; outline: none; box-shadow: none;
background: transparent; color: transparent; opacity: 0; pointer-events: none;
}
.session-toolbar .status.warning { color: #f5b942; }
/* F1 (Umsetzungsauftrag_Sonnet5.md Teil F.3.1): vormals #terminal/#rdp-display
(feste, dokumentweit eindeutige IDs -- eine Seite hatte immer genau eine

View File

@ -13,27 +13,37 @@
*
* Vollbild: native Browser-Fullscreen-API auf dem Instanz-Wurzelelement
* (Konzept 4.3).
* Copy & Paste: xterm.js liefert Markieren-zum-Kopieren + Strg+Umschalt+V
* bereits nativ. Aufgabe #11 (vom Nutzer nachtraeglich hinzugefuegt, siehe
* FORTSETZUNG_Aufgabe11.md) ergaenzt zwei konkret gewuenschte Wege:
* `copyOnSelect: true` versucht, eine Markierung sofort in die System-
* Zwischenablage zu kopieren (kein Strg+C/Cmd+C mehr noetig -- Strg+C bleibt
* ohne Markierung weiterhin SIGINT, xterm.js' Standardverhalten). Das ist
* aber eine Automatik OHNE direkte Benutzergeste (sie laeuft aus xterm.js'
* eigenem mouseup-Handler heraus) und schlaegt daher in der Praxis manchmal
* fehl, ohne dass die App das mitbekommt (Live-Test 2.9.: Markierung+Ziehen
* hat sichtbar funktioniert, in der Zwischenablage kam aber nichts an).
* Deshalb zusaetzlich ein Rechtsklick-Kontextmenue, das PuTTYs bekannte
* Konvention nachbildet: Rechtsklick MIT aktiver Markierung kopiert (direkter
* Versuch ueber die Clipboard-API, da ein Rechtsklick selbst eine
* "transiente Aktivierung"/Benutzergeste ist -- das ist deutlich
* zuverlaessiger als copyOnSelects impliziter Aufruf); schlaegt das fehl
* oder ist keine Markierung aktiv, faellt es auf "Einfuegen" zurueck --
* ebenfalls zuerst direkt ueber die Clipboard-API versucht, sonst ein
* winziges Kontextmenue mit "Kopieren"- bzw. "Einfuegen"-Eintrag, dessen
* Klick selbst wieder eine frische Benutzergeste ist (siehe _wireClipboard()
* unten). Keine serverseitige Sonderbehandlung noetig, im Gegensatz zu RDP
* (siehe rdp.js).
* Copy & Paste (Aufgabe #11, KORRIGIERT nach Live-Test 3.9.):
*
* FEHLERURSACHE der bisherigen Fassung: sie setzte `copyOnSelect: true` in
* den Terminal-Optionen. DIESE OPTION EXISTIERT IN XTERM.JS NICHT -- sie ist
* eine VS-Code-Einstellung ("terminal.integrated.copyOnSelection"). xterm.js'
* OptionsService uebernimmt aus dem Konstruktor-Objekt ausschliesslich
* Schluessel, die in seinen Default-Optionen vorkommen, und verwirft alle
* uebrigen KOMMENTARLOS -- kein Fehler, keine Warnung, keine Wirkung.
* Gegenprobe: `grep -c copyOnSelect static/js/vendor/xterm.js` liefert 0.
* "Markieren kopiert" hat damit nie funktioniert; das im Live-Test am 2.9.
* beobachtete "Markierung sichtbar, Zwischenablage bleibt leer" war genau
* das und NICHT das dort vermutete Benutzergesten-Problem.
*
* Jetzt drei Wege, keiner davon auf eine Berechtigungsabfrage angewiesen:
* 1. Markieren kopiert -- selbst implementiert ueber term.onSelectionChange().
* 2. Strg+Einfg kopiert, Umschalt+Einfg fuegt ein (PuTTY-Konvention, per
* attachCustomKeyEventHandler). BEWUSST NICHT Strg+Umschalt+C/V: die
* sind in Chrome/Edge fest fuer die Entwicklertools belegt und erreichen
* die Seite gar nicht -- die alten Fehlertexte ("Bitte Strg+Umschalt+C
* verwenden") liefen dort also ins Leere. Strg+V und Umschalt+Einfg
* erzeugen ein natives 'paste'-Ereignis auf xterm.js' verstecktem
* <textarea>, das xterm.js selbst verarbeitet -- ganz ohne Clipboard-API.
* 3. Rechtsklick: mit Markierung kopieren, ohne Markierung einfuegen
* (Ruecksicherung ueber ein Mini-Kontextmenue, dessen Klick eine frische
* Benutzergeste ist).
* Das Kopieren faellt bei blockierter Clipboard-API auf
* document.execCommand("copy") zurueck (siehe _copyViaExecCommand): veraltet,
* aber ohne Berechtigung nutzbar und auch dann funktionsfaehig, wenn der
* Browser die Clipboard-API verweigert -- etwa auf einer Seite mit
* selbstsigniertem Zertifikat. Keine serverseitige Sonderbehandlung noetig,
* im Gegensatz zu RDP (siehe rdp.js).
* Markieren mit gedrueckter Umschalttaste: xterm.js reicht Mausklicks an ein
* Vollbild-Zielprogramm mit eigener Maussteuerung (tmux, vim, htop, mc, ...)
* weiter, sobald dieses per ANSI-Escape-Sequenz Maus-Tracking anfordert --
@ -298,11 +308,9 @@
fontFamily: "Menlo, Consolas, monospace",
fontSize: 14,
theme: TERMINAL_THEMES[this._themeName].theme,
// Aufgabe #11: eine Markierung wird sofort in die System-
// Zwischenablage kopiert -- kein zusaetzliches Strg+C/Cmd+C noetig.
// Strg+C OHNE Markierung bleibt unveraendert SIGINT (xterm.js'
// Standardverhalten, wird von copyOnSelect nicht beruehrt).
copyOnSelect: true,
// KEIN copyOnSelect hier -- die Option gibt es in xterm.js nicht
// (siehe Kopfkommentar). Kopieren-beim-Markieren macht jetzt
// _wireClipboard() selbst ueber term.onSelectionChange().
});
this.fitAddon = new FitAddon.FitAddon();
this.term.loadAddon(this.fitAddon);
@ -493,6 +501,37 @@
// ist und dadurch bessere Chancen auf die Berechtigung hat).
_wireClipboard() {
// (1) Markieren kopiert -- SELBST implementiert, weil xterm.js keine
// copyOnSelect-Option kennt (siehe Kopfkommentar). onSelectionChange
// feuert aus xterm.js' mouseup-/Tastatur-Handler heraus, also noch
// innerhalb der transienten Benutzeraktivierung des Browsers --
// writeText() ist dort erlaubt. Schlaegt es dennoch fehl, greift der
// execCommand-Ruecksprung in _copyToClipboard().
this.term.onSelectionChange(() => {
const text = this.term.getSelection();
if (!text) return;
this._copyToClipboard(text);
});
// (2) Tastatur nach PuTTY-Konvention. BEWUSST Einfg-Kombinationen und
// nicht Strg+Umschalt+C/V -- letztere sind in Chrome/Edge fest fuer
// die Entwicklertools reserviert und erreichen die Seite nie.
this.term.attachCustomKeyEventHandler((ev) => {
if (ev.type !== "keydown") return true;
// Strg+Einfg: Markierung kopieren, nicht an die Sitzung schicken.
if (ev.ctrlKey && !ev.shiftKey && ev.key === "Insert") {
const text = this.term.getSelection();
if (text) this._copyToClipboard(text);
return false;
}
// Umschalt+Einfg und Strg+V: NICHT abfangen. Der Browser erzeugt
// daraus selbst ein natives 'paste'-Ereignis auf xterm.js'
// verstecktem <textarea>, das xterm.js bereits verarbeitet -- ganz
// ohne Clipboard-API und damit ohne Berechtigungsabfrage.
return true;
});
// (3) Rechtsklick: mit Markierung kopieren, ohne Markierung einfuegen.
this.terminalContainer.addEventListener("contextmenu", (ev) => {
ev.preventDefault();
if (this.term.hasSelection()) {
@ -503,17 +542,54 @@
});
}
/** Kopiert Text in die Zwischenablage. Zuerst ueber die Clipboard-API,
* bei Fehlschlag ueber document.execCommand("copy"). Letzteres gilt als
* veraltet, ist aber weiterhin in allen aktuellen Browsern implementiert,
* braucht KEINE Berechtigung und funktioniert auch dann, wenn der Browser
* die Clipboard-API verweigert (haeufigster Fall in diesem Projekt: eine
* Seite mit selbstsigniertem Zertifikat). Liefert true bei Erfolg. */
async _copyToClipboard(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (_) {
// Weiter mit dem Ruecksprung unten.
}
}
return this._copyViaExecCommand(text);
}
_copyViaExecCommand(text) {
const ta = document.createElement("textarea");
ta.value = text;
// Positionierung ueber eine CSS-Klasse, NICHT ueber element.style --
// die CSP dieses Projekts verbietet Inline-Styles (style-src 'self',
// kein 'unsafe-inline'), siehe app/main.py. Und bewusst nicht
// display:none/hidden: ein nicht gerendertes Element laesst sich nicht
// selektieren, execCommand("copy") kopierte dann einen leeren Text.
ta.className = "clipboard-helper";
document.body.appendChild(ta);
let ok = false;
try {
ta.focus();
ta.select();
ta.setSelectionRange(0, ta.value.length);
ok = document.execCommand("copy");
} catch (err) {
console.error("Kopieren ueber execCommand fehlgeschlagen:", err);
ok = false;
}
ta.remove();
this.term.focus();
return ok;
}
async _attemptDirectCopy(x, y) {
const text = this.term.getSelection();
if (!navigator.clipboard || !navigator.clipboard.writeText) {
this._showCopyMenu(x, y, text);
return;
}
try {
await navigator.clipboard.writeText(text);
} catch (_) {
// Wie bei _attemptDirectPaste(): Ruecksicherung unten, deren Klick
// selbst wieder eine frische Benutzergeste ist.
if (!(await this._copyToClipboard(text))) {
// Beide Wege verweigert -- Ruecksicherung, deren Klick selbst wieder
// eine frische Benutzergeste ist.
this._showCopyMenu(x, y, text);
}
}
@ -615,7 +691,7 @@
} catch (err) {
console.error("Einfuegen aus der Zwischenablage nicht moeglich:", err);
this._setStatus(
"Einfuegen nicht moeglich -- Browser verweigert Zwischenablage-Zugriff. Bitte Strg+Umschalt+V verwenden.",
"Einfuegen nicht moeglich -- der Browser verweigert den Lesezugriff auf die Zwischenablage. Bitte stattdessen Strg+V oder Umschalt+Einfg druecken (das geht ohne Berechtigung, das Terminal muss nur den Fokus haben).",
true
);
}
@ -625,11 +701,11 @@
_showCopyMenu(x, y, text) {
this._showClipboardMenu(x, y, "Kopieren", async () => {
try {
await navigator.clipboard.writeText(text);
if (!(await this._copyToClipboard(text))) throw new Error("execCommand(copy) lieferte false");
} catch (err) {
console.error("Kopieren in die Zwischenablage nicht moeglich:", err);
this._setStatus(
"Kopieren nicht moeglich -- Browser verweigert Zwischenablage-Zugriff. Bitte Strg+Umschalt+C verwenden.",
"Kopieren nicht moeglich -- der Browser verweigert den Schreibzugriff auf die Zwischenablage. Bitte den markierten Text mit Strg+Einfg kopieren.",
true
);
}