1
0

auto-git:

[change] index.html
 [change] main.js
 [change] preload.js
 [change] renderer.js
 [change] settings.html
This commit is contained in:
2025-05-24 08:46:46 +02:00
parent d6eacb4541
commit ecd043d94c
5 changed files with 117 additions and 104 deletions

View File

@@ -183,26 +183,24 @@
<!-- Content-Area --> <!-- Content-Area -->
<div class="flex-1 p-4 overflow-y-auto content-area"> <div class="flex-1 p-4 overflow-y-auto content-area">
<!--<h3 id="currentTitle" class="text-xl font-semibold mb-2" <div id="folderTitleDrop"
style="color: var(--accent)"> class="relative flex items-baseline space-x-1 cursor-pointer select-none group pb-2">
No folder selected <svg id="folderTitleArrow"
</h3>--> class="h-4 w-6 flex-none transition-transform"
viewBox="0 0 24 24">
<div id="folderTitleDrop" class="relative flex items-baseline space-x-1 cursor-pointer select-none group"> <path stroke="currentColor" stroke-width="2" fill="none"
<svg id="folderTitleArrow" class="h-6 w-6 flex-none transition-transform" viewBox="0 0 24 24"> stroke-linecap="round" stroke-linejoin="round"
<path stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
d="M9 5l7 7-7 7"/>
</svg> </svg>
<span id="currentTitle" class="font-semibold text-xl truncate"></span> <span id="currentTitle" class="font-semibold text-xl truncate"></span>
</div> </div>
<!-- ASCII-Baum als <pre> (bleibt direkt unterhalb des Titels) --> <!-- ASCII-Baum als <pre> (direkt unter dem Titel, keine Extras) -->
<pre id="folderHierarchyDropdown" <pre id="folderHierarchyDropdown"
class="hidden text-sm font-mono bg-gray-100 border rounded-xl p-4 mb-2 mt-1 max-h-80 overflow-auto select-text"></pre> class="hidden text-sm font-mono select-text leading-snug mb-2 mt-1 max-h-80 overflow-auto"
style="background: none; border: none; color: inherit; padding: 0;"></pre>
<div class="border-t mb-4" style="border-color: var(--border)"></div> <div class="border-t mb-4" style="border-color: var(--border)"></div>
<ul id="contentList" class="space-y-1"></ul> <ul id="contentList" class="space-y-1"></ul>
</div> </div>
<!-- Cat --> <!-- Cat -->

45
main.js
View File

@@ -12,7 +12,8 @@ const store = new Store({
folders: [], folders: [],
selected: null, selected: null,
skymode: true, skymode: true,
skipGitPrompt: true skipGitPrompt: true,
intelligentCommitThreshold: 100
} }
}); });
@@ -178,8 +179,8 @@ function stopMonitoringWatcher(folderPath) {
} }
} }
async function autoCommit(folder, message) { async function autoCommit(folderPath, message) {
const git = simpleGit(folder); const git = simpleGit(folderPath);
const status = await git.status(); const status = await git.status();
if ( if (
status.not_added.length === 0 && status.not_added.length === 0 &&
@@ -242,6 +243,36 @@ async function autoCommit(folder, message) {
} }
} }
// *** Zeilenzählung ***
let diffOutput = await git.diff(['--numstat']);
// Zeilensumme berechnen:
let changedLines = 0;
for (let line of diffOutput.split('\n')) {
// Format: <added> <deleted> <filename>
const match = line.match(/^(\d+|\-)\s+(\d+|\-)\s+(.*)$/);
if (match) {
const added = match[1] === '-' ? 0 : parseInt(match[1], 10);
const deleted = match[2] === '-' ? 0 : parseInt(match[2], 10);
changedLines += added + deleted;
}
}
// Folder-Objekt holen und aktualisieren
let folders = store.get('folders') || [];
let idx = folders.findIndex(f => f.path === folderPath);
if (idx !== -1) {
folders[idx].linesChanged = (folders[idx].linesChanged || 0) + changedLines;
store.set('folders', folders);
// Threshold holen
const threshold = store.get('intelligentCommitThreshold') || 10;
if (folders[idx].linesChanged >= threshold) {
folders[idx].linesChanged = 0;
store.set('folders', folders);
debug('Congratulations! You changed enough lines of code :)');
// Optional: Toast anzeigen, etc.
}
}
await git.add(['-A']); await git.add(['-A']);
debug('[autoCommit] Alle Änderungen gestaged.'); debug('[autoCommit] Alle Änderungen gestaged.');
await git.commit(message || '[auto]'); await git.commit(message || '[auto]');
@@ -312,7 +343,7 @@ app.whenReady().then(() => {
let folders = store.get('folders') || []; let folders = store.get('folders') || [];
let folderObj = folders.find(f => f.path === newFolder); let folderObj = folders.find(f => f.path === newFolder);
if (!folderObj) { if (!folderObj) {
folderObj = { path: newFolder, monitoring: true }; folderObj = { path: newFolder, monitoring: true, linesChanged: 0 };
folders.push(folderObj); folders.push(folderObj);
store.set('folders', folders); store.set('folders', folders);
} }
@@ -619,6 +650,12 @@ app.whenReady().then(() => {
return monitoring; return monitoring;
}); });
ipcMain.handle('get-intelligent-commit-threshold', () => store.get('intelligentCommitThreshold'));
ipcMain.handle('set-intelligent-commit-threshold', (_e, value) => {
store.set('intelligentCommitThreshold', value);
});
// … Ende der IPC-Handler … // … Ende der IPC-Handler …
}); });

View File

@@ -25,6 +25,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
showFolderContextMenu: folderPath => ipcRenderer.send('show-folder-context-menu', folderPath), showFolderContextMenu: folderPath => ipcRenderer.send('show-folder-context-menu', folderPath),
setMonitoring: (folderObj, monitoring) => ipcRenderer.invoke('set-monitoring', folderObj.path, monitoring), setMonitoring: (folderObj, monitoring) => ipcRenderer.invoke('set-monitoring', folderObj.path, monitoring),
getFolderTree: (folderPath) => ipcRenderer.invoke('get-folder-tree', folderPath), getFolderTree: (folderPath) => ipcRenderer.invoke('get-folder-tree', folderPath),
getIntelligentCommitThreshold: () => ipcRenderer.invoke('get-intelligent-commit-threshold'),
setIntelligentCommitThreshold: value => ipcRenderer.invoke('set-intelligent-commit-threshold', value),
}); });
ipcRenderer.on('repo-updated', (_e, folder) => { ipcRenderer.on('repo-updated', (_e, folder) => {

View File

@@ -5,6 +5,7 @@ window.addEventListener('DOMContentLoaded', async () => {
const folderList = document.getElementById('folderList'); const folderList = document.getElementById('folderList');
const addBtn = document.getElementById('addFolderBtn'); const addBtn = document.getElementById('addFolderBtn');
const titleEl = document.getElementById('currentTitle'); const titleEl = document.getElementById('currentTitle');
const treeviewEl = document.getElementById('folderHierarchyDropdown');
const contentList = document.getElementById('contentList'); const contentList = document.getElementById('contentList');
const panel = document.querySelector('.flex-1.p-4.overflow-y-auto'); const panel = document.querySelector('.flex-1.p-4.overflow-y-auto');
@@ -42,8 +43,10 @@ window.addEventListener('DOMContentLoaded', async () => {
const hour = new Date().getHours(); const hour = new Date().getHours();
if (hour >= 18 || hour < 6) { if (hour >= 18 || hour < 6) {
titleEl.style.color = '#fff'; titleEl.style.color = '#fff';
treeviewEl.style.color = '#fff';
} else { } else {
titleEl.style.color = ''; titleEl.style.color = '';
treeviewEl.style.color = '';
} }
} }
updateTitleColor(); updateTitleColor();
@@ -227,7 +230,7 @@ folders.forEach(folderObj => {
folderTitleArrow.classList.remove('open'); folderTitleArrow.classList.remove('open');
isDropdownOpen = false; isDropdownOpen = false;
} }
// ASCII-Baum: wie bei ChatGPT! // ASCII-Baum: wie bei ChatGPT!
function renderFolderTreeAscii(tree, prefix = '', indent = '') { function renderFolderTreeAscii(tree, prefix = '', indent = '') {
if (!Array.isArray(tree)) return ''; if (!Array.isArray(tree)) return '';
@@ -464,72 +467,12 @@ folders.forEach(folderObj => {
commitBtn.textContent = 'Commit'; commitBtn.textContent = 'Commit';
}); });
const inputCommitThreshold = document.getElementById('intelligentCommitThreshold');
window.settingsAPI.getIntelligentCommitThreshold().then(val => inputCommitThreshold.value = val);
inputCommitThreshold.addEventListener('change', e => {
}); const num = Math.max(1, Math.min(1000, Number(inputCommitThreshold.value)));
window.settingsAPI.setIntelligentCommitThreshold(num);
/* inputCommitThreshold.value = num;
const folderTitleDrop = document.getElementById('folderTitleDrop');
const folderTitleArrow = document.getElementById('folderTitleArrow');
const folderHierarchyDropdown = document.getElementById('folderHierarchyDropdown');
const titleEl = document.getElementById('currentTitle');
let isDropdownOpen = false;
folderTitleDrop.addEventListener('click', async () => {
if (isDropdownOpen) {
closeDropdown();
return;
}
const selected = await window.electronAPI.getSelected();
if (!selected || !selected.path) return;
// Lade die aktuelle Ordnerhierarchie
folderHierarchyDropdown.innerHTML = '<div class="text-gray-400 italic">Lade Verzeichnis...</div>';
folderHierarchyDropdown.classList.remove('hidden');
folderHierarchyDropdown.classList.add('open');
folderTitleArrow.classList.add('open');
isDropdownOpen = true;
const tree = await window.electronAPI.getFolderTree(selected.path);
folderHierarchyDropdown.innerHTML = renderFolderTree(tree);
}); });
function closeDropdown() { });
folderHierarchyDropdown.classList.add('hidden');
folderHierarchyDropdown.classList.remove('open');
folderTitleArrow.classList.remove('open');
isDropdownOpen = false;
}
// KEIN automatisches Schließen beim Klick außerhalb!
function renderFolderTree(tree, level = 0) {
if (!Array.isArray(tree)) return '';
return `<ul class="${level === 0 ? 'font-mono text-[0.98rem] space-y-1' : 'pl-4 space-y-1'}">` + tree.map(node => {
if (node.type === 'dir') {
return `
<li>
<span class="inline-flex items-center font-semibold text-blue-700">
<svg class="h-4 w-4 mr-1 inline-block" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 7a2 2 0 012-2h4l2 2h6a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V7"/>
</svg>
${node.name}
</span>
${renderFolderTree(node.children, level + 1)}
</li>
`;
} else {
return `
<li class="flex items-center text-gray-800">
<svg class="h-4 w-4 mr-1 opacity-60" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 17v-6a2 2 0 012-2h6"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M7 17a2 2 0 002 2h6a2 2 0 002-2V7a2 2 0 00-2-2h-6a2 2 0 00-2 2v10z"/>
</svg>
<span>${node.name}</span>
</li>
`;
}
}).join('') + '</ul>';
}
*/

View File

@@ -74,29 +74,57 @@
button:hover { background: #f3f4f6; } button:hover { background: #f3f4f6; }
</style> </style>
<script> <script>
window.addEventListener('DOMContentLoaded', async () => { window.addEventListener('DOMContentLoaded', async () => {
const cbSky = document.getElementById('skymode'); // Elemente holen
const cbSkip = document.getElementById('skipPrompt'); const cbSky = document.getElementById('skymode');
const ok = document.getElementById('okBtn'); const cbSkip = document.getElementById('skipPrompt');
const cancel = document.getElementById('cancelBtn'); const thresholdInput = document.getElementById('intelligentCommitThresholdInput');
const close = document.getElementById('closeBtn'); const ok = document.getElementById('okBtn');
const cancel = document.getElementById('cancelBtn');
const close = document.getElementById('closeBtn');
const [initialSky, initialSkip] = await Promise.all([ // Initialwerte parallel laden
window.settingsAPI.getSkyMode(), const [initialSky, initialSkip, initialThreshold] = await Promise.all([
window.settingsAPI.getSkipPrompt() window.settingsAPI.getSkyMode(),
]); window.settingsAPI.getSkipPrompt(),
window.electronAPI.getIntelligentCommitThreshold()
]);
cbSky.checked = initialSky; // Inputs setzen
cbSkip.checked = initialSkip; cbSky.checked = initialSky;
cbSkip.checked = initialSkip;
thresholdInput.value = initialThreshold;
ok.addEventListener('click', async () => { // **Vorschau**: Sky-Mode sofort übernehmen
await window.settingsAPI.setSkyMode(cbSky.checked); cbSky.addEventListener('change', async () => {
await window.settingsAPI.setSkipPrompt(cbSkip.checked); await window.settingsAPI.setSkyMode(cbSky.checked);
window.close();
});
cancel.addEventListener('click', () => window.close());
close.addEventListener('click', () => window.close());
}); });
// Threshold live speichern + clamp
thresholdInput.addEventListener('blur', async () => {
let v = parseInt(thresholdInput.value, 100);
if (isNaN(v)) v = initialThreshold; // wenn komplett leer gelassen
if (v < 1) v = 1;
if (v > 1000) v = 1000;
thresholdInput.value = v;
await window.settingsAPI.setIntelligentCommitThreshold(v);
});
// OK: alles final speichern
ok.addEventListener('click', async () => {
await window.settingsAPI.setSkipPrompt(cbSkip.checked);
// SkyMode haben wir ja schon beim Change gesetzt
window.close();
});
// Cancel / Close: SkyMode zurücksetzen, dann schließen
const rollback = async () => {
await window.settingsAPI.setSkyMode(initialSky);
window.close();
};
cancel.addEventListener('click', rollback);
close.addEventListener('click', rollback);
});
</script> </script>
</head> </head>
<body> <body>
@@ -110,6 +138,11 @@
<input type="checkbox" id="skymode" /> <input type="checkbox" id="skymode" />
Sky Mode Sky Mode
</label> </label>
<label class="block mb-2 font-semibold">
Intelligent commit after
<input type="number" id="intelligentCommitThresholdInput" min="1" max="1000" value="10" step="1" class="border rounded px-2 py-1 w-20 mx-1">
lines of code changed
</label>
<label class="option"> <label class="option">
<input type="checkbox" id="skipPrompt" /> <input type="checkbox" id="skipPrompt" />
Skip prompt asking to remove .git folder if it has only one commit and no changes Skip prompt asking to remove .git folder if it has only one commit and no changes