Upload via GUI (298 Dateien)

This commit is contained in:
Git Manager GUI
2026-08-07 08:53:16 +02:00
parent 12dfec7330
commit 1958a47b3a
8 changed files with 9832 additions and 8 deletions

241
bundle.js
View File

@@ -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.1|class:version-number")])]),y("hr"),s,o,y("div|id:mystart-autostart-row,class:mystart-autostart-row")]))';
'y("p:Version 1.0.2|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 ---
@@ -188,13 +188,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');
var onCtrls=ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles');
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'))return;
if(ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles'))return;
var edge=edgeAt(ev.clientX,ev.clientY);
if(edge){ev.preventDefault();send2('window-resize',{edge:edge});}
},true);
@@ -202,7 +202,240 @@ const windowControlsJs = `(function(){
function send2(t,extra){try{window.chrome.webview.postMessage(Object.assign({type:t},extra));}catch(e){}}
})();`;
mainJs = trInfra + autostartJs + windowControlsJs + mainJs;
// Optional, self-named data "Reiter" (tabs) in the top-left corner -- e.g.
// one for the office, one for a second workplace. Opt-in: with none created,
// only a small unobtrusive "+" shows -- nothing changes for anyone who
// doesn't want this. nightTab keeps its ENTIRE state (bookmarks, groups,
// theme, layout, ...) in a single localStorage key (the app name, here
// "MyStart"); switching swaps that key's content with a saved per-tab slot
// and reloads. Purely local to this install, no server/sync involved.
const profilesJs = `(function(){
var KEY='${APPNAME}';
var ACTIVE=KEY+'-slot-active';
var LIST=KEY+'-profiles';
function slotKey(id){return KEY+'-slot-'+id;}
function getProfiles(){try{var a=JSON.parse(localStorage.getItem(LIST));return Array.isArray(a)?a:[];}catch(e){return[];}}
function saveProfiles(a){localStorage.setItem(LIST,JSON.stringify(a));}
function getActive(){return localStorage.getItem(ACTIVE);}
function newId(){return 'p'+Date.now().toString(36)+Math.random().toString(36).slice(2,6);}
// Users of the earlier fixed "Büro"/"Extern" build had an old-format
// marker (ACTIVE='a' or 'b', no profiles list). Turn that into the new
// list format once, in place, without moving any data around.
function migrateLegacy(){
if(localStorage.getItem(LIST)!=null)return;
var legacyActive=localStorage.getItem(ACTIVE);
if(legacyActive==null)return;
var profiles=[];
['a','b'].forEach(function(s){
if(localStorage.getItem(slotKey(s))!=null||legacyActive===s){
var label=localStorage.getItem(KEY+'-slot-'+s+'-label')||(s==='a'?'Büro':'Extern');
profiles.push({id:s,label:label});
}
});
if(profiles.length)saveProfiles(profiles);else localStorage.removeItem(ACTIVE);
}
function loadDataInto(json){
if(json!=null){
localStorage.setItem(KEY,json);
// Pre-set the flash-prevention theme flag so reload doesn't briefly
// flash the previous tab's background colour.
try{
var style=JSON.parse(json).state.theme.style;
if(style==='dark'||style==='light')localStorage.setItem(KEY+'Style',style);
else localStorage.removeItem(KEY+'Style');
}catch(e){}
}else{
localStorage.removeItem(KEY);
localStorage.removeItem(KEY+'Style');
}
}
function switchTo(id){
var active=getActive();
if(id===active)return;
if(active!=null){
var live=localStorage.getItem(KEY);
if(live!=null)localStorage.setItem(slotKey(active),live);else localStorage.removeItem(slotKey(active));
}
loadDataInto(localStorage.getItem(slotKey(id)));
localStorage.setItem(ACTIVE,id);
location.reload();
}
function addProfile(){
var name=window.prompt('Name für den neuen Reiter:','');
if(!name||!name.trim())return;
name=name.trim();
var profiles=getProfiles();
var id=newId();
if(profiles.length===0&&getActive()==null){
// First tab ever: adopt whatever is currently loaded, nothing lost, no reload needed.
var current=localStorage.getItem(KEY);
if(current!=null)localStorage.setItem(slotKey(id),current);
localStorage.setItem(ACTIVE,id);
profiles.push({id:id,label:name});
saveProfiles(profiles);
render();
}else{
// Additional tab: stash the current one, start this one blank.
var active=getActive();
if(active!=null){
var live=localStorage.getItem(KEY);
if(live!=null)localStorage.setItem(slotKey(active),live);else localStorage.removeItem(slotKey(active));
}
loadDataInto(null);
profiles.push({id:id,label:name});
saveProfiles(profiles);
localStorage.setItem(ACTIVE,id);
location.reload();
}
}
function renameProfile(id){
var profiles=getProfiles();
var p=profiles.find(function(x){return x.id===id;});
if(!p)return;
var name=window.prompt('Name für diesen Reiter:',p.label);
if(name&&name.trim()){p.label=name.trim();saveProfiles(profiles);render();}
}
function deleteProfile(id){
var profiles=getProfiles();
var p=profiles.find(function(x){return x.id===id;});
if(!p)return;
if(!window.confirm('"'+p.label+'" wirklich entfernen? Die darin gespeicherten Daten dieses Reiters gehen dabei verloren.'))return;
profiles=profiles.filter(function(x){return x.id!==id;});
localStorage.removeItem(slotKey(id));
var wasActive=getActive()===id;
saveProfiles(profiles);
if(wasActive){
if(profiles.length){
var next=profiles[0].id;
loadDataInto(localStorage.getItem(slotKey(next)));
localStorage.setItem(ACTIVE,next);
location.reload();
}else{
// No tabs left -> back to a single, un-tabbed dataset (whatever was loaded stays, nothing wiped).
localStorage.removeItem(ACTIVE);
render();
}
}else{
render();
}
}
// --- Cloud sync hooks, called from the native host (Program.cs), which
// polls an OneDrive-synced JSON file. The host treats the exported blob as
// opaque; only this page understands/produces/consumes its structure. ---
window.__mystartSyncExport=function(){
var profiles=getProfiles();
var active=getActive();
var slots={};
profiles.forEach(function(p){
var raw=(p.id===active)?localStorage.getItem(KEY):localStorage.getItem(slotKey(p.id));
if(raw!=null){try{slots[p.id]=JSON.parse(raw);}catch(e){}}
});
var live=null;
if(active==null){
var liveRaw=localStorage.getItem(KEY);
if(liveRaw!=null){try{live=JSON.parse(liveRaw);}catch(e){}}
}
var data={profiles:profiles,active:active,slots:slots,live:live};
return JSON.stringify({data:data,savedAt:new Date().toISOString()});
};
window.__mystartSyncImport=function(json){
try{
var payload=JSON.parse(json);
var data=payload.data;
if(data.active==null){
if(data.live!=null)localStorage.setItem(KEY,JSON.stringify(data.live));else localStorage.removeItem(KEY);
localStorage.removeItem(LIST);
localStorage.removeItem(ACTIVE);
}else{
saveProfiles(data.profiles||[]);
localStorage.setItem(ACTIVE,data.active);
(data.profiles||[]).forEach(function(p){
var val=data.slots?data.slots[p.id]:null;
if(val!=null)localStorage.setItem(slotKey(p.id),JSON.stringify(val));
});
var activeVal=data.slots?data.slots[data.active]:null;
loadDataInto(activeVal!=null?JSON.stringify(activeVal):null);
}
location.reload();
return true;
}catch(e){return false;}
};
var syncDot;
function mkSyncDot(){
var d=document.createElement('div');
d.style.cssText='width:8px;height:8px;border-radius:50%;background:#565d68;margin-left:6px;flex:0 0 auto;';
d.title='OneDrive-Sync: noch kein Status';
syncDot=d;
return d;
}
if(window.chrome&&window.chrome.webview){
window.chrome.webview.addEventListener('message',function(ev){
var d=ev.data;
if(!d||d.type!=='sync-status'||!syncDot)return;
syncDot.style.background=d.ok?'#2ea043':'#e8b923';
syncDot.title=d.ok?('OneDrive-Sync: '+d.message):('OneDrive-Sync-Problem: '+d.message);
});
}
var bar;
var TXT_SHADOW='text-shadow:0 1px 3px rgba(0,0,0,.85),0 0 1px rgba(0,0,0,.6);';
function mkPlus(){
var b=document.createElement('div');
b.textContent='+';
b.title='Neuen Reiter anlegen';
b.style.cssText='width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-family:"Segoe UI",system-ui,sans-serif;font-size:15px;font-weight:600;color:#c7cbd3;cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:6px;transition:background .12s,color .12s;'+TXT_SHADOW;
b.onmouseenter=function(){b.style.background='rgba(255,255,255,0.16)';b.style.color='#fff';};
b.onmouseleave=function(){b.style.background='transparent';b.style.color='#c7cbd3';};
b.addEventListener('click',function(){addProfile();});
return b;
}
function mkTab(p,isActive){
var wrap=document.createElement('div');
wrap.style.cssText='position:relative;display:flex;align-items:center;';
var b=document.createElement('div');
b.textContent=p.label;
b.title='Zu diesem Reiter wechseln — Doppelklick zum Umbenennen';
b.style.cssText='padding:6px 22px 6px 12px;font-family:"Segoe UI",system-ui,sans-serif;font-size:12.5px;cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:6px;transition:background .12s,color .12s;'+TXT_SHADOW+
(isActive
? 'color:#ffffff;font-weight:700;background:rgba(255,255,255,0.22);box-shadow:inset 0 0 0 1px rgba(255,255,255,0.28);'
: 'color:#d7dae0;font-weight:500;background:transparent;');
b.addEventListener('click',function(){switchTo(p.id);});
b.addEventListener('dblclick',function(ev){ev.preventDefault();renameProfile(p.id);});
var x=document.createElement('div');
x.textContent='\\u00d7';
x.title='Reiter entfernen';
x.style.cssText='position:absolute;right:2px;top:50%;transform:translateY(-50%);width:16px;height:16px;display:flex;align-items:center;justify-content:center;font-size:12px;color:#c7cbd3;cursor:pointer;opacity:0;transition:opacity .12s,color .12s,background .12s;border-radius:4px;';
x.addEventListener('click',function(ev){ev.stopPropagation();deleteProfile(p.id);});
// Toggle on the wrapper (not the label itself) so moving the cursor
// from the label onto the x doesn't count as "leaving" and hide it again mid-click.
wrap.addEventListener('mouseenter',function(){if(!isActive)b.style.background='rgba(255,255,255,0.14)';x.style.opacity='1';});
wrap.addEventListener('mouseleave',function(){if(!isActive)b.style.background='transparent';x.style.opacity='0';});
x.onmouseenter=function(){x.style.color='#fff';x.style.background='#e81123';};
x.onmouseleave=function(){x.style.color='#c7cbd3';x.style.background='transparent';};
wrap.appendChild(b);wrap.appendChild(x);
return wrap;
}
function render(){
if(!bar)return;
bar.innerHTML='';
var active=getActive();
getProfiles().forEach(function(p){bar.appendChild(mkTab(p,p.id===active));});
bar.appendChild(mkPlus());
bar.appendChild(mkSyncDot());
}
function add(){
if(!document.body||document.getElementById('mystart-profiles'))return;
migrateLegacy();
bar=document.createElement('div');
bar.id='mystart-profiles';
bar.style.cssText='position:fixed;top:6px;left:6px;display:flex;align-items:center;gap:2px;padding:4px;z-index:2147483647;background:rgba(22,24,29,0.68);border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,0.4),inset 0 0 0 1px rgba(255,255,255,0.06);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);';
document.body.appendChild(bar);
render();
}
if(document.body)add();else document.addEventListener('DOMContentLoaded',add);
new MutationObserver(function(){if(!document.getElementById('mystart-profiles'))add();}).observe(document.documentElement,{childList:true,subtree:true});
})();`;
mainJs = trInfra + autostartJs + windowControlsJs + profilesJs + mainJs;
const trPatches = [
// panel header (top-level AND sub, both go through element.header)