Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions app/static/css/v2/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -1892,3 +1892,90 @@ tr:hover .row-actions {
.md-content th { background: var(--bg-tertiary); font-weight: 600; color: var(--text-primary); }
.md-content a { color: var(--accent); text-decoration: none; }
.md-content a:hover { text-decoration: underline; }

/* ==========================================================================
INBOX PAGE - Two-Pane Layout & Inspector
========================================================================== */
/* Icon buttons for Inspector action bar */
.btn-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
font-size: 16px;
background: var(--bg-elevated);
border: 1px solid var(--border-default);
border-radius: 6px;
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
flex-shrink: 0;
}

.btn-icon:hover:not(:disabled) {
background: var(--bg-overlay);
border-color: var(--accent);
}

.btn-icon:disabled {
cursor: not-allowed;
}

/* Content switcher (Summary vs Transcript) */
.content-switcher-btn {
padding: var(--space-2) var(--space-4);
font-size: 0.8125rem;
font-weight: 500;
color: var(--text-muted);
cursor: pointer;
border: none;
background: none;
border-bottom: 2px solid transparent;
transition: all 0.15s;
}

.content-switcher-btn:hover {
color: var(--text-secondary);
}

.content-switcher-btn.active {
color: var(--accent);
border-bottom-color: var(--accent);
}

/* List item selected state with accent border */
.list-item.selected {
position: relative;
}

.list-item.selected::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--accent);
}

/* Dropdown menu */
.dropdown-menu {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: 6px;
padding: var(--space-2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.dropdown-item {
padding: var(--space-2);
cursor: pointer;
border-radius: 4px;
font-size: 0.8125rem;
transition: background var(--duration-fast) var(--ease-out);
}

.dropdown-item:hover {
background: var(--bg-elevated);
}
121 changes: 120 additions & 1 deletion app/static/js/v2/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,26 @@ document.addEventListener('alpine:init', function() {
/**
* Note list component
*/
Alpine.data('noteList', function(initialNotes, initialTagFilter) {
Alpine.data('inboxView', function(initialNotes, initialTagFilter) {
return {
notes: initialNotes || [],
selectedId: null,
filter: 'all',
searchQuery: '',
tagFilter: initialTagFilter || '',
loading: false,
knownProjects: [],

init: function() {
// Load known projects
var self = this;
fetch('/v2/api/projects')
.then(function(res) { return res.json(); })
.then(function(data) {
self.knownProjects = (data.projects || []).map(function(p) { return p.name; });
})
.catch(function(err) { console.warn('Failed to load projects:', err); });
},

get filteredNotes() {
var filtered = this.notes;
Expand Down Expand Up @@ -225,6 +237,113 @@ document.addEventListener('alpine:init', function() {
.finally(function() { self.loading = false; });
},

// Inspector Actions
openAudio: function() {
var note = this.filteredNotes.find(function(n) { return n.id === this.selectedId; }.bind(this));
if (note && note.audio_url) {
window.open(note.audio_url, '_blank');
}
},

openNote: function() {
var note = this.filteredNotes.find(function(n) { return n.id === this.selectedId; }.bind(this));
if (note) {
window.location.href = note.link || ('/v2/note/' + note.id);
}
},

copyTranscript: function() {
var note = this.filteredNotes.find(function(n) { return n.id === this.selectedId; }.bind(this));
if (note && note.transcript) {
navigator.clipboard.writeText(note.transcript)
.then(function() {
console.log('Transcript copied to clipboard');
// TODO: Show toast notification
})
.catch(function(err) {
console.error('Failed to copy transcript:', err);
});
}
},

downloadTranscript: function() {
var note = this.filteredNotes.find(function(n) { return n.id === this.selectedId; }.bind(this));
if (note && note.source === 'registry' && note.transcript) {
window.open('/v2/api/registry/' + note.id + '/download/transcript', '_blank');
}
},

copyBreakdown: function() {
var note = this.filteredNotes.find(function(n) { return n.id === this.selectedId; }.bind(this));
if (note && note.content) {
navigator.clipboard.writeText(note.content)
.then(function() {
console.log('Breakdown copied to clipboard');
// TODO: Show toast notification
})
.catch(function(err) {
console.error('Failed to copy breakdown:', err);
});
}
},

downloadBreakdown: function() {
var note = this.filteredNotes.find(function(n) { return n.id === this.selectedId; }.bind(this));
if (note && note.source === 'registry' && note.content) {
window.open('/v2/api/registry/' + note.id + '/download/note', '_blank');
}
},

// Project assignment
assignProject: function(projectName) {
var note = this.filteredNotes.find(function(n) { return n.id === this.selectedId; }.bind(this));
if (!note || note.source !== 'registry') return;

var self = this;
var projects = projectName ? [projectName] : [];

// Optimistic update
note.projects = projects;

fetch('/v2/api/registry/' + note.id + '/projects', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ projects: projects })
})
.then(function(res) { return res.json(); })
.then(function(data) {
self.toast('Project updated');
})
.catch(function(err) {
self.toast('Failed to update project. Please try again.');
console.error('Project update error:', err);
});
},

createAndAssignProject: function(projectName) {
var note = this.filteredNotes.find(function(n) { return n.id === this.selectedId; }.bind(this));
if (!note || note.source !== 'registry') return;

projectName = (projectName || '').trim();
if (!projectName) return;

var self = this;

// Add to known projects
if (!this.knownProjects.includes(projectName)) {
this.knownProjects.push(projectName);
}

// Assign the project
this.assignProject(projectName);
},

toast: function(msg) {
// Simple toast notification - TODO: Implement proper toast UI
// For now, just log to console as a placeholder
console.log('Toast:', msg);
},

// Format helpers for templates
formatDate: function(d) { return window.formatDate ? window.formatDate(d) : (d || ''); },
formatDuration: function(s) { return window.formatDuration ? window.formatDuration(s) : (s || ''); },
Expand Down
Loading