feat: Musik-Gruppe + Export/Import + launchd auto-start
- Add Musik group (Tiefgang, MDIM, DROII, Soloprojekt) to DEFAULTS + BOARD_META - Ensure Musik group survives localStorage reset via loadGroups() guard - Add Export/Import buttons in sidebar (teal/amber) — full localStorage snapshot as JSON - Add launchd plist (de.robinchoice.kanban-server) for auto-start on login at port 8765 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
81
index.html
81
index.html
@@ -38,6 +38,10 @@ body { background:var(--bg); color:var(--text); font-family:-apple-system,BlinkM
|
|||||||
.nav-badge.blk { background:var(--red-soft); color:var(--red); }
|
.nav-badge.blk { background:var(--red-soft); color:var(--red); }
|
||||||
.s-reset { margin:6px 14px 0; padding:5px 10px; background:var(--surface3); border:1px solid var(--border); border-radius:5px; font-size:10px; color:var(--text-dim); cursor:pointer; text-align:center; }
|
.s-reset { margin:6px 14px 0; padding:5px 10px; background:var(--surface3); border:1px solid var(--border); border-radius:5px; font-size:10px; color:var(--text-dim); cursor:pointer; text-align:center; }
|
||||||
.s-reset:hover { color:var(--red); border-color:var(--red); }
|
.s-reset:hover { color:var(--red); border-color:var(--red); }
|
||||||
|
.s-io { display:flex; gap:6px; margin:6px 14px 0; }
|
||||||
|
.s-io-btn { flex:1; text-align:center; padding:5px 0; background:var(--surface3); border:1px solid var(--border); border-radius:5px; font-size:10px; color:var(--text-dim); cursor:pointer; }
|
||||||
|
.s-io-btn:hover { color:var(--teal); border-color:var(--teal); }
|
||||||
|
.s-io-btn.import:hover { color:var(--amber); border-color:var(--amber); }
|
||||||
.s-actions { display:flex; gap:6px; padding:10px 14px 0; }
|
.s-actions { display:flex; gap:6px; padding:10px 14px 0; }
|
||||||
.s-add-btn { flex:1; text-align:center; padding:5px 0; background:var(--surface2); border:1px solid var(--border2); border-radius:5px; font-size:10.5px; color:var(--text-dim); cursor:pointer; }
|
.s-add-btn { flex:1; text-align:center; padding:5px 0; background:var(--surface2); border:1px solid var(--border2); border-radius:5px; font-size:10.5px; color:var(--text-dim); cursor:pointer; }
|
||||||
.s-add-btn:hover { color:var(--accent); border-color:var(--accent); }
|
.s-add-btn:hover { color:var(--accent); border-color:var(--accent); }
|
||||||
@@ -393,6 +397,11 @@ body { background:var(--bg); color:var(--text); font-family:-apple-system,BlinkM
|
|||||||
<div class="s-add-btn" onclick="showAddGroup()">+ Gruppe</div>
|
<div class="s-add-btn" onclick="showAddGroup()">+ Gruppe</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="s-reset" onclick="resetCurrentBoard()">↺ Reset</div>
|
<div class="s-reset" onclick="resetCurrentBoard()">↺ Reset</div>
|
||||||
|
<div class="s-io">
|
||||||
|
<div class="s-io-btn" onclick="exportState()" title="Kompletten State als JSON exportieren">↓ Export</div>
|
||||||
|
<div class="s-io-btn import" onclick="document.getElementById('import-file').click()" title="State aus JSON-Datei laden">↑ Import</div>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="import-file" accept=".json" style="display:none" onchange="importState(this)">
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="main">
|
<div class="main">
|
||||||
@@ -825,6 +834,42 @@ const DEFAULTS = {
|
|||||||
{id:'done', label:'Done', tasks:[{t:'Schema-Diff: source_type, author, year, isbn',note:'Frontmatter-Schema + Template dokumentiert'}]},
|
{id:'done', label:'Done', tasks:[{t:'Schema-Diff: source_type, author, year, isbn',note:'Frontmatter-Schema + Template dokumentiert'}]},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
tiefgang: {
|
||||||
|
name:'Tiefgang', goal:'',
|
||||||
|
wipLimit:3, throughput:2, sle:{days:14,p:85},
|
||||||
|
cols:[
|
||||||
|
{id:'ready', label:'Ready', tasks:[]},
|
||||||
|
{id:'wip', label:'In Progress', tasks:[]},
|
||||||
|
{id:'done', label:'Done', tasks:[]},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
mdimmusic: {
|
||||||
|
name:'MDIM', goal:'mydrugismusic — Label & Artist-Kollektiv.',
|
||||||
|
wipLimit:3, throughput:2, sle:{days:14,p:85},
|
||||||
|
cols:[
|
||||||
|
{id:'ready', label:'Ready', tasks:[]},
|
||||||
|
{id:'wip', label:'In Progress', tasks:[]},
|
||||||
|
{id:'done', label:'Done', tasks:[]},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
droii: {
|
||||||
|
name:'DROII', goal:'',
|
||||||
|
wipLimit:3, throughput:2, sle:{days:14,p:85},
|
||||||
|
cols:[
|
||||||
|
{id:'ready', label:'Ready', tasks:[]},
|
||||||
|
{id:'wip', label:'In Progress', tasks:[]},
|
||||||
|
{id:'done', label:'Done', tasks:[]},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
soloprojekt: {
|
||||||
|
name:'Soloprojekt', goal:'',
|
||||||
|
wipLimit:3, throughput:2, sle:{days:14,p:85},
|
||||||
|
cols:[
|
||||||
|
{id:'ready', label:'Ready', tasks:[]},
|
||||||
|
{id:'wip', label:'In Progress', tasks:[]},
|
||||||
|
{id:'done', label:'Done', tasks:[]},
|
||||||
|
]
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── BOARD META (group + color + ring for defaults) ────────────────────────
|
// ── BOARD META (group + color + ring for defaults) ────────────────────────
|
||||||
@@ -845,16 +890,21 @@ const BOARD_META = {
|
|||||||
degoogle: {group:'Privat', color:'#60a5fa', ring:'1p'},
|
degoogle: {group:'Privat', color:'#60a5fa', ring:'1p'},
|
||||||
bibliothek:{group:'Privat', color:'#7c6af7', ring:'1p'},
|
bibliothek:{group:'Privat', color:'#7c6af7', ring:'1p'},
|
||||||
aikb: {group:'Privat', color:'#4ade80', ring:'1w'},
|
aikb: {group:'Privat', color:'#4ade80', ring:'1w'},
|
||||||
|
tiefgang: {group:'Musik', color:'#2dd4bf', ring:'1p'},
|
||||||
|
mdimmusic: {group:'Musik', color:'#c084fc', ring:'1p'},
|
||||||
|
droii: {group:'Musik', color:'#f87171', ring:'1p'},
|
||||||
|
soloprojekt:{group:'Musik', color:'#fbbf24', ring:'1p'},
|
||||||
};
|
};
|
||||||
|
|
||||||
const RINGS = ['0','1p','1w','2','3','?'];
|
const RINGS = ['0','1p','1w','2','3','?'];
|
||||||
const RING_LABELS = {'0':'R0','1p':'R1p','1w':'R1w','2':'R2','3':'R3','?':'?'};
|
const RING_LABELS = {'0':'R0','1p':'R1p','1w':'R1w','2':'R2','3':'R3','?':'?'};
|
||||||
const PICK_COLORS = ['#f87171','#fb923c','#fbbf24','#4ade80','#2dd4bf','#60a5fa','#7c6af7','#c084fc'];
|
const PICK_COLORS = ['#f87171','#fb923c','#fbbf24','#4ade80','#2dd4bf','#60a5fa','#7c6af7','#c084fc'];
|
||||||
let GROUPS = ['Meta','Code','Beruflich','Web','Privat'];
|
let GROUPS = ['Meta','Code','Beruflich','Web','Privat','Musik'];
|
||||||
|
|
||||||
function loadGroups() {
|
function loadGroups() {
|
||||||
try { const g = JSON.parse(localStorage.getItem('kanban_groups')); if (Array.isArray(g)) GROUPS = g; } catch {}
|
try { const g = JSON.parse(localStorage.getItem('kanban_groups')); if (Array.isArray(g)) GROUPS = g; } catch {}
|
||||||
if (!GROUPS.includes('Meta')) { GROUPS.unshift('Meta'); saveGroups(); }
|
if (!GROUPS.includes('Meta')) { GROUPS.unshift('Meta'); saveGroups(); }
|
||||||
|
if (!GROUPS.includes('Musik')) { GROUPS.push('Musik'); saveGroups(); }
|
||||||
}
|
}
|
||||||
function saveGroups() { localStorage.setItem('kanban_groups', JSON.stringify(GROUPS)); }
|
function saveGroups() { localStorage.setItem('kanban_groups', JSON.stringify(GROUPS)); }
|
||||||
|
|
||||||
@@ -2326,6 +2376,33 @@ loadIdeas();
|
|||||||
renderIdeas();
|
renderIdeas();
|
||||||
show('doener');
|
show('doener');
|
||||||
setView('overview');
|
setView('overview');
|
||||||
|
|
||||||
|
// ── EXPORT / IMPORT ──────────────────────────────────────────────────────────
|
||||||
|
function exportState() {
|
||||||
|
const keys = ['kanban_v2','kanban_groups','kanban_board_order','kanban_ideas','kanban_ideas_seeded'];
|
||||||
|
const snap = {};
|
||||||
|
keys.forEach(k => { const v = localStorage.getItem(k); if (v !== null) snap[k] = v; });
|
||||||
|
const blob = new Blob([JSON.stringify(snap, null, 2)], {type:'application/json'});
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = `kanban-backup-${new Date().toISOString().slice(0,10)}.json`;
|
||||||
|
a.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function importState(input) {
|
||||||
|
const file = input.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => {
|
||||||
|
try {
|
||||||
|
const snap = JSON.parse(e.target.result);
|
||||||
|
Object.entries(snap).forEach(([k, v]) => localStorage.setItem(k, v));
|
||||||
|
input.value = '';
|
||||||
|
location.reload();
|
||||||
|
} catch { alert('Ungültige Backup-Datei.'); }
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user