diff --git a/README.md b/README.md index d346f60..01156f9 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,24 @@ das, was sonst eine "neue Tab"-Seite im Browser wäre, aber als schnelles, eigenes Programm: mit Lesezeichen, Gruppen, Design-Optionen, Uhr/Datum und allem, was eine gute Startseite braucht. Sie basiert auf dem quelloffenen Projekt **nightTab**, komplett ins Deutsche übersetzt und um einige eigene -Funktionen erweitert (Fenster-Bedienung, mehrere Reiter, Cloud-Abgleich). +Funktionen erweitert (Fenster-Bedienung, mehrere Reiter, Abgleich zwischen +mehreren PCs, eigene Bilder und Videos von deinem Rechner). --- ## Installation & erster Start -MyStart ist eine **portable Einzeldatei** (`MyStart.exe`, ca. 48 MB) – kein +MyStart ist eine **portable Einzeldatei** (`MyStart.exe`, ca. 110 MB) – kein Installer, keine Admin-Rechte nötig. Einfach an einen beliebigen Ort legen -und starten. +und starten. Die Datei ist bewusst unkomprimiert und daher größer; sie muss +sich beim Start nicht selbst entpacken. - **Erster Start:** ca. 2 Sekunden, danach öffnet sich MyStart praktisch sofort. -- Die App startet als **normales Fenster** (nicht im Vollbild), zentriert - auf dem Bildschirm. +- Beim allerersten Start öffnet sich die App als **normales Fenster** (nicht + im Vollbild), zentriert auf dem Bildschirm. Danach merkt sie sich Größe, + Position und ob du sie maximiert hattest, und startet dort wieder, wo du + sie zuletzt gelassen hast (siehe unten). - Alle deine Daten (Lesezeichen, Design, Reiter …) werden lokal unter `%LOCALAPPDATA%\MyStart` gespeichert – unabhängig davon, wo die `.exe` liegt oder ob du sie verschiebst. @@ -43,6 +47,11 @@ Zusätzlich: - **Größe ändern:** einfach an einer beliebigen Fensterkante oder -ecke ziehen, genau wie bei einem normalen Windows-Fenster. - Ein maximiertes Fenster deckt nie die Taskleiste ab. +- **Größe und Position merken:** Beim Schließen wird die aktuelle Größe, + Position und ob maximiert gespeichert und beim nächsten Start wieder + angewendet. Passt die gespeicherte Position auf keinen aktuell + angeschlossenen Bildschirm mehr (z. B. zweiter Monitor abgesteckt), startet + MyStart stattdessen wieder zentriert in der Standardgröße. --- @@ -69,12 +78,13 @@ eigenen** Lesezeichen, Gruppen und sein eigenes Design. --- -## Automatischer Abgleich über OneDrive +## Abgleich zwischen mehreren PCs Arbeitest du an **mehreren PCs**, kann MyStart seinen kompletten Datenstand (alle Reiter, Lesezeichen, Design) automatisch zwischen den Rechnern -abgleichen – sofern auf beiden PCs **OneDrive** eingerichtet und angemeldet -ist. +abgleichen – über einen synchronisierten Ordner, standardmäßig dein +**OneDrive**-Ordner (auf allen beteiligten PCs muss er eingerichtet und +angemeldet sein). Der Abgleich ist **optional und standardmäßig aus**. Einschalten unter **Einstellungen → MyStart → „Zwischen PCs abgleichen“** – auf jedem PC @@ -88,30 +98,71 @@ synchronisierten Ordner nehmen (Dropbox, Google Drive, Netzlaufwerk …), „Automatisch (OneDrive)“ stellt es zurück. Auf **allen PCs muss derselbe Ordner** genutzt werden, also dieselbe Cloud-Freigabe. -- MyStart legt dazu im Ordner `OneDrive\MyStart` pro PC eine eigene Datei +- MyStart legt dazu im Sync-Ordner pro PC eine eigene Datei `sync-.json` an (jeder PC schreibt nur seine eigene, damit - OneDrive keine Konfliktkopien erzeugt). Die eigentliche Übertragung - zwischen den PCs übernimmt OneDrive selbst – MyStart schreibt/liest dort - nur diese Dateien. + der Cloud-Dienst keine Konfliktkopien erzeugt). Die eigentliche + Übertragung zwischen den PCs übernimmt der Cloud-Dienst selbst – MyStart + schreibt/liest dort nur diese Dateien. - Abgeglichen wird **alle 30 Sekunden**, außerdem beim Start und beim Schließen der App. -- Regel bei Änderungen: **die neueste Version gewinnt.** Ändern beide PCs - gleichzeitig etwas, ohne dass zwischendurch synchronisiert wurde, setzt - sich schlicht die zuletzt gespeicherte Version durch (kein Zusammenführen - einzelner Änderungen). +- Regel bei Änderungen: **die neueste Version gewinnt.** Ein PC übernimmt den + Stand eines anderen nur, wenn dieser **neuer** ist als sein eigener letzter + Stand. Ändern beide PCs gleichzeitig etwas, ohne dass zwischendurch + synchronisiert wurde, setzt sich schlicht die zuletzt gespeicherte Version + durch (kein Zusammenführen einzelner Änderungen). Dafür sollten die Uhren + beider PCs ungefähr richtig gehen (normale Windows-Zeitsynchronisierung + reicht). +- Ein neuerer Stand vom anderen PC wird erst übernommen, **wenn das + MyStart-Fenster gerade nicht aktiv ist** (zum Beispiel wenn du weggeklickt + oder es minimiert hast). So lädt die Seite nie mitten in deiner Arbeit neu. +- **Bilder und Videos wandern mit:** Verweist ein Reiter auf eine lokale + Bild-/Videodatei (siehe nächster Abschnitt), wird die Datei selbst mit in + den Sync-Ordner kopiert (Unterordner `media`) und beim anderen PC in einen + eigenen lokalen Zwischenspeicher übernommen – das Bild taucht dort also + wirklich auf, nicht nur ein toter Pfad. - Ein kleiner **Punkt** in der Reiter-Leiste zeigt den Status (Maus drüberhalten für Details): - 🟢 **grün** – zuletzt erfolgreich abgeglichen - 🟡 **gelb/orange** – aktuell ein Problem (z. B. Datei kurz durch OneDrive gesperrt; behebt sich meist von selbst beim nächsten Abgleich) -- Ist auf einem PC kein OneDrive eingerichtet, bleibt der Abgleich dort - einfach ohne Fehlermeldung aus – die App funktioniert ganz normal weiter, - nur eben ohne Sync. + - 🟣 **lila** – ein **Konflikt** wurde erkannt: du hattest hier gerade etwas + geändert, während gleichzeitig (ohne zwischenzeitlichen Abgleich) auch + auf einem anderen PC etwas geändert wurde. Dein Stand wird trotzdem + gespeichert, es erscheint zusätzlich kurz ein Hinweisfenster; schau in + dem Fall lieber nochmal nach, ob auf dem anderen PC etwas verloren ging +- Findet MyStart auf einem PC keinen OneDrive-Ordner, ist der Schalter + ausgegraut, bis du mit „Ordner wählen…“ einen Ordner angibst – die App + funktioniert ganz normal weiter, nur eben ohne Sync. - **Wichtig:** Damit der Abgleich funktioniert, muss MyStart auf **jedem** beteiligten PC vorhanden sein und einmal gestartet werden. --- +## Eigene Bilder und Videos von diesem PC + +Für Hintergrundbild, Hintergrundvideo, Lesezeichen-Symbole und +Lesezeichen-Hintergründe kannst du nicht nur Internet-Adressen, sondern auch +**Dateien von deinem PC** nutzen: + +- **„Lokale Datei wählen…“** – neben den Bild-/Video-Feldern in den + Einstellungen; öffnet den Windows-Dateidialog. Beim Hintergrundbild + (Design) kannst du mehrere Bilder auf einmal wählen. +- **Pfad einfügen oder eintippen** geht ebenfalls, zum Beispiel + `C:\Bilder\urlaub.jpg`, ein Pfad in Anführungszeichen (so kopiert ihn der + Explorer mit „Als Pfad kopieren“), `file:///C:/Bilder/urlaub.jpg` oder ein + Netzwerkpfad `\\server\freigabe\bild.png`. Er wird beim Verlassen des + Felds automatisch umgewandelt. +- Erlaubt sind nur Bild- und Videodateien (PNG, JPG, GIF, WebP, SVG, BMP, ICO, + AVIF, MP4, WebM …); andere Dateien werden nie ausgeliefert. + +**Bei mehreren PCs:** Ist der Abgleich (siehe oben) eingeschaltet, wird die +Datei selbst mit synchronisiert – sie erscheint dann auch auf dem anderen PC, +ganz ohne dass dort derselbe Pfad existieren muss. Ist der Abgleich **aus**, +wird nur der Pfad in den Lesezeichen/Design-Daten gespeichert; ohne Sync +bleibt es dann bei der Datei, die lokal unter diesem Pfad liegt. + +--- + ## Autostart Unter **Einstellungen → MyStart** gibt es einen Schalter, um MyStart @@ -145,10 +196,23 @@ Unter **Einstellungen → Daten** kannst du jederzeit: --- +## Hinweis zu Windows-Warnungen und Virenscannern + +MyStart ist **nicht digital signiert** (dafür bräuchte es ein +kostenpflichtiges Zertifikat). Deshalb kann Windows beim ersten Start +„Unbekannter Herausgeber“ melden – über **Weitere Informationen → +Trotzdem ausführen** lässt sich das wegklicken. Manche Virenscanner stufen +kleine, selbst gebaute und unsignierte Programme zusätzlich fälschlich als +verdächtig ein (sogenannter Fehlalarm). Hilft nichts anderes, kannst du für +den Ordner mit der `MyStart.exe` eine Ausnahme in deinem Virenscanner +eintragen oder die Datei beim Hersteller des Scanners als Fehlalarm melden. + +--- + ## Herkunft & Lizenz MyStart basiert auf **nightTab** von zombieFox (https://github.com/zombieFox/nightTab), veröffentlicht unter der **GNU General Public License v3.0**. Die Anpassungen in MyStart (deutsche -Übersetzung, Fenster-Bedienung, Reiter, OneDrive-Sync u. a.) unterliegen -ebenfalls der GPL v3. +Übersetzung, Fenster-Bedienung, Reiter, Sync zwischen PCs, lokale Bilder +u. a.) unterliegen ebenfalls der GPL v3. diff --git a/bundle.js b/bundle.js index 84ff2b9..7c59095 100644 --- a/bundle.js +++ b/bundle.js @@ -39,7 +39,7 @@ const aboutBody = 'const o=y("p");o.innerHTML=`Git: ${a.link().outerHTML}`,' + 'e.appendChild(y("div",[y("div|class:version",[_t.render(),' + 'y("div|class:version-details",[y("h1:MyStart|class:version-app-name"),' + - 'y("p:Version 1.0.3|class:version-number")])]),y("hr"),s,o,y("div|id:mystart-autostart-row,class:mystart-autostart-row")]))'; + 'y("p:Version 1.0.4|class:version-number")])]),y("hr"),s,o,y("div|id:mystart-autostart-row,class:mystart-autostart-row")]))'; mainJs = mainJs.slice(0, i) + aboutBody + mainJs.slice(j + aEndMarker.length); // --- 2) Rename branding (visible text + storage keys) consistently --- @@ -247,13 +247,13 @@ const windowControlsJs = `(function(){ } document.addEventListener('mousemove',function(ev){ if(ev.buttons)return; // an active drag (e.g. scrollbar) is in progress -> leave it alone - var onCtrls=ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles'); + var onCtrls=ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles,#mystart-sync-toast'); var edge=onCtrls?null:edgeAt(ev.clientX,ev.clientY); document.documentElement.style.cursor=edge?CURSOR[edge]:''; },true); document.addEventListener('mousedown',function(ev){ if(ev.button!==0)return; - if(ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles'))return; + if(ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles,#mystart-sync-toast'))return; var edge=edgeAt(ev.clientX,ev.clientY); if(edge){ev.preventDefault();send2('window-resize',{edge:edge});} },true); @@ -426,8 +426,9 @@ const profilesJs = `(function(){ // Sync is opt-in: no dot at all until it's switched on (Einstellungen -> MyStart). if(!lastSync||lastSync.enabled===false){syncDot.style.display='none';return;} syncDot.style.display=''; - syncDot.style.background=lastSync.ok?'#2ea043':'#e8b923'; - syncDot.title=lastSync.ok?('OneDrive-Sync: '+lastSync.message):('OneDrive-Sync-Problem: '+lastSync.message); + syncDot.style.background=lastSync.conflict?'#a371f7':(lastSync.ok?'#2ea043':'#e8b923'); + var prefix=lastSync.conflict?'OneDrive-Sync (Konflikt): ':(lastSync.ok?'OneDrive-Sync: ':'OneDrive-Sync-Problem: '); + syncDot.title=prefix+lastSync.message; } function mkSyncDot(){ var d=document.createElement('div'); @@ -436,12 +437,37 @@ const profilesJs = `(function(){ paintSyncDot(); return d; } + // Short-lived banner for a sync conflict -- a colour-only dot is too easy to + // miss for "you might have lost an edit just now". + var conflictToast=null,conflictToastTimer=null; + function showConflictToast(msg){ + if(!conflictToast){ + conflictToast=document.createElement('div'); + conflictToast.id='mystart-sync-toast'; + conflictToast.style.cssText='position:fixed;top:44px;left:6px;max-width:360px;padding:8px 12px;'+ + 'border-radius:8px;background:rgba(60,30,90,.92);color:#f0e6ff;font-size:12px;line-height:1.4;'+ + 'box-shadow:0 2px 10px rgba(0,0,0,.4);z-index:2147483647;cursor:pointer;'; + conflictToast.title='Zum Ausblenden klicken'; + conflictToast.addEventListener('click',hideConflictToast); + document.body.appendChild(conflictToast); + } + conflictToast.textContent='⚠ '+msg; + conflictToast.style.display=''; + if(conflictToastTimer)clearTimeout(conflictToastTimer); + conflictToastTimer=setTimeout(hideConflictToast,10000); + } + function hideConflictToast(){ + if(conflictToast)conflictToast.style.display='none'; + if(conflictToastTimer){clearTimeout(conflictToastTimer);conflictToastTimer=null;} + } if(window.chrome&&window.chrome.webview){ window.chrome.webview.addEventListener('message',function(ev){ var d=ev.data; if(!d||d.type!=='sync-status')return; + var wasConflict=lastSync&&lastSync.conflict; lastSync=d; paintSyncDot(); + if(d.conflict&&!wasConflict)showConflictToast('Gleichzeitige Änderung erkannt — dein Stand wurde trotzdem gespeichert: '+d.message); }); } var bar; @@ -503,7 +529,11 @@ const profilesJs = `(function(){ new MutationObserver(function(){if(!document.getElementById('mystart-profiles'))add();}).observe(document.documentElement,{childList:true,subtree:true}); })();`; -mainJs = trInfra + autostartJs + windowControlsJs + profilesJs + mainJs; +// Local images/videos from this PC in the image/video URL fields (kept as its +// own file: it is full of backslash regexes that are painful inside a template string). +const localFilesJs = fs.readFileSync(path.join(__dirname, 'inject', 'local-files.js'), 'utf8'); + +mainJs = trInfra + autostartJs + windowControlsJs + profilesJs + localFilesJs + mainJs; const trPatches = [ // panel header (top-level AND sub, both go through element.header) diff --git a/inject/local-files.js b/inject/local-files.js new file mode 100644 index 0000000..cfea953 --- /dev/null +++ b/inject/local-files.js @@ -0,0 +1,116 @@ +// Lokale Bilder/Videos von diesem PC in den Bild-/Video-Feldern von nightTab nutzen. +// +// Die Seite laeuft unter https://mystart.local; der Browser blockt von dort aus +// file://-Adressen. Die App (Program.cs) liefert deshalb lokale Dateien unter +// https://files.mystart.local// (C:\a\b.jpg -> /c/a/b.jpg) +// https://files.mystart.local/unc/// (\\server\share\x.png) +// aus. Dieses Skript wandelt eingetippte/eingefuegte Pfade in solche Adressen um +// und haengt an die Bild-/Video-Felder einen "Lokale Datei waehlen"-Knopf. +(function () { + if (!(window.chrome && window.chrome.webview)) return; + + var HOST = 'https://files.mystart.local/'; + var FIELD = /(image|video)-url$/; // theme-background-image-url, display-visual-image-url, ... + + function encodeSegments(path) { + return path.split(/[\\/]+/).filter(Boolean).map(encodeURIComponent).join('/'); + } + + // Ein Pfad (auch "In Anfuehrungszeichen", wie "Als Pfad kopieren" ihn liefert, + // oder file:///) -> Adresse der App. Alles andere -> null (bleibt unveraendert). + function toUrl(line) { + var s = line.trim(); + if (s.length > 1 && s.charAt(0) === '"' && s.charAt(s.length - 1) === '"') s = s.slice(1, -1).trim(); + if (/^file:\/\/\//i.test(s)) { + s = s.replace(/^file:\/\/\//i, ''); + try { s = decodeURI(s); } catch (e) { /* unveraendert lassen */ } + } + if (/^[A-Za-z]:[\\/]/.test(s)) return HOST + s.charAt(0).toLowerCase() + '/' + encodeSegments(s.slice(3)); + if (/^\\\\[^\\]/.test(s)) return HOST + 'unc/' + encodeSegments(s.slice(2)); + return null; + } + + function isMediaField(el) { + return el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') && FIELD.test(el.id || ''); + } + + function setValue(el, value) { + el.value = value; + el.dispatchEvent(new Event('input', { bubbles: true })); + el.dispatchEvent(new Event('change', { bubbles: true })); + } + + // Pfade in einem Feld (auch mehrzeilig, z. B. mehrere Hintergrundbilder) umschreiben. + function fix(el) { + if (!isMediaField(el) || !el.value) return; + var changed = false; + var out = el.value.split(/\r?\n/).map(function (l) { + var u = toUrl(l); + if (u === null) return l; + changed = true; + return u; + }); + if (changed) setValue(el, out.join('\n')); + } + + // Erst beim Verlassen des Felds/Enter (change) bzw. nach dem Einfuegen umwandeln, + // sonst wuerde schon "C:\" waehrend des Tippens umgeschrieben. + document.addEventListener('change', function (ev) { fix(ev.target); }, true); + document.addEventListener('paste', function (ev) { + var t = ev.target; + setTimeout(function () { fix(t); }, 0); + }, true); + + // --- "Lokale Datei waehlen"-Knopf --- + var targets = {}; + var nextToken = 1; + + window.chrome.webview.addEventListener('message', function (ev) { + var d = ev.data; + if (!d || d.type !== 'file-picked') return; + var el = targets[d.token]; + delete targets[d.token]; + if (!el || !d.paths || !d.paths.length) return; + var urls = d.paths.map(function (p) { return toUrl(p) || p; }); + var multi = /theme-background-image-url$/.test(el.id || ''); + var existing = el.value.trim(); + setValue(el, multi && existing ? existing + '\n' + urls.join('\n') : urls.join('\n')); + }); + + function addButton(el) { + if (el.getAttribute('data-mystart-pick')) return; + el.setAttribute('data-mystart-pick', '1'); + var b = document.createElement('div'); + b.textContent = 'Lokale Datei wählen…'; + b.title = 'Bild/Video von diesem PC auswählen'; + b.style.cssText = 'display:inline-block;margin-top:6px;padding:5px 10px;border-radius:6px;' + + 'background:rgba(128,128,128,.25);cursor:pointer;user-select:none;font-size:12px;'; + b.onmouseenter = function () { b.style.background = 'rgba(128,128,128,.4)'; }; + b.onmouseleave = function () { b.style.background = 'rgba(128,128,128,.25)'; }; + b.addEventListener('click', function () { + var token = nextToken++; + targets[token] = el; + try { + window.chrome.webview.postMessage({ + type: 'pick-file', + token: token, + kind: /video-url$/.test(el.id) ? 'video' : 'image', + multiple: /theme-background-image-url$/.test(el.id) + }); + } catch (e) { /* ignore */ } + }); + el.insertAdjacentElement('afterend', b); + } + + var SELECTOR = '#theme-background-image-url,#theme-background-video-url,#display-visual-image-url,' + + '#background-image-url,#background-video-url'; + var scheduled = false; + function scan() { + scheduled = false; + var list = document.querySelectorAll(SELECTOR); + for (var i = 0; i < list.length; i++) addButton(list[i]); + } + new MutationObserver(function () { + if (!scheduled) { scheduled = true; requestAnimationFrame(scan); } + }).observe(document.documentElement, { childList: true, subtree: true }); +})();