simplifying the chat interface.
This commit is contained in:
parent
564ad32417
commit
37a15ea9e0
9 changed files with 1283 additions and 195 deletions
|
|
@ -1 +1,246 @@
|
||||||
a {}
|
/* Drive Layout */
|
||||||
|
.drive-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 250px 1fr 300px;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .drive-layout {
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drive-sidebar,
|
||||||
|
.drive-main,
|
||||||
|
.drive-details {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .drive-sidebar,
|
||||||
|
[data-theme="dark"] .drive-main,
|
||||||
|
[data-theme="dark"] .drive-details {
|
||||||
|
background: #202124;
|
||||||
|
border-color: #3c4043;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drive-sidebar {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drive-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drive-details {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation Items */
|
||||||
|
.nav-item {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin: 0.25rem 0.5rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
color: #5f6368;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .nav-item {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background: rgba(26, 115, 232, 0.08);
|
||||||
|
color: #1a73e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .nav-item:hover {
|
||||||
|
background: rgba(138, 180, 248, 0.08);
|
||||||
|
color: #8ab4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active {
|
||||||
|
background: #e8f0fe;
|
||||||
|
color: #1a73e8;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .nav-item.active {
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #8ab4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* File List */
|
||||||
|
.file-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: all 0.2s;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item:hover {
|
||||||
|
background: rgba(26, 115, 232, 0.08);
|
||||||
|
border-color: rgba(26, 115, 232, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .file-item:hover {
|
||||||
|
background: rgba(138, 180, 248, 0.08);
|
||||||
|
border-color: rgba(138, 180, 248, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.selected {
|
||||||
|
background: #e8f0fe;
|
||||||
|
border-color: #1a73e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .file-item.selected {
|
||||||
|
background: #1e3a5f;
|
||||||
|
border-color: #8ab4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headers */
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text Styles */
|
||||||
|
.text-xs {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray {
|
||||||
|
color: #5f6368;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .text-gray {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inputs */
|
||||||
|
input[type="text"] {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1a73e8 !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] input[type="text"]:focus {
|
||||||
|
border-color: #8ab4f8 !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(138, 180, 248, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
button {
|
||||||
|
font-family: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar Styles */
|
||||||
|
.drive-sidebar::-webkit-scrollbar,
|
||||||
|
.file-list::-webkit-scrollbar,
|
||||||
|
.drive-details::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drive-sidebar::-webkit-scrollbar-track,
|
||||||
|
.file-list::-webkit-scrollbar-track,
|
||||||
|
.drive-details::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drive-sidebar::-webkit-scrollbar-thumb,
|
||||||
|
.file-list::-webkit-scrollbar-thumb,
|
||||||
|
.drive-details::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(128, 128, 128, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drive-sidebar::-webkit-scrollbar-thumb:hover,
|
||||||
|
.file-list::-webkit-scrollbar-thumb:hover,
|
||||||
|
.drive-details::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(128, 128, 128, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .drive-sidebar::-webkit-scrollbar-thumb,
|
||||||
|
[data-theme="dark"] .file-list::-webkit-scrollbar-thumb,
|
||||||
|
[data-theme="dark"] .drive-details::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .drive-sidebar::-webkit-scrollbar-thumb:hover,
|
||||||
|
[data-theme="dark"] .file-list::-webkit-scrollbar-thumb:hover,
|
||||||
|
[data-theme="dark"] .drive-details::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.drive-layout {
|
||||||
|
grid-template-columns: 200px 1fr 250px;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.drive-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drive-details {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alpine.js cloak */
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,57 @@
|
||||||
<div class="drive-layout" x-data="driveApp()" x-cloak>
|
<div class="drive-layout" x-data="driveApp()" x-cloak>
|
||||||
<div class="panel drive-sidebar">
|
<div class="drive-sidebar">
|
||||||
<div style="padding: 1rem; border-bottom: 1px solid #334155;">
|
<div
|
||||||
|
style="
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border, #e0e0e0);
|
||||||
|
"
|
||||||
|
>
|
||||||
<h3>General Bots Drive</h3>
|
<h3>General Bots Drive</h3>
|
||||||
</div>
|
</div>
|
||||||
<template x-for="item in navItems" :key="item.name">
|
<template x-for="item in navItems" :key="item.name">
|
||||||
<div class="nav-item"
|
<div
|
||||||
|
class="nav-item"
|
||||||
:class="{ active: current === item.name }"
|
:class="{ active: current === item.name }"
|
||||||
@click="current = item.name">
|
@click="current = item.name"
|
||||||
|
>
|
||||||
<span x-text="item.icon"></span>
|
<span x-text="item.icon"></span>
|
||||||
<span x-text="item.name"></span>
|
<span x-text="item.name"></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel drive-main">
|
<div class="drive-main">
|
||||||
<div style="padding: 1rem; border-bottom: 1px solid #334155;">
|
<div
|
||||||
|
style="
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border, #e0e0e0);
|
||||||
|
"
|
||||||
|
>
|
||||||
<h2 x-text="current"></h2>
|
<h2 x-text="current"></h2>
|
||||||
<input type="text" x-model="search" placeholder="Search files..."
|
<input
|
||||||
style="width: 100%; margin-top: 0.5rem; padding: 0.5rem; background: #0f172a; border: 1px solid #334155; border-radius: 0.375rem; color: #e2e8f0;">
|
type="text"
|
||||||
|
x-model="search"
|
||||||
|
placeholder="Search files..."
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: inherit;
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-list">
|
<div class="file-list">
|
||||||
<template x-for="file in filteredFiles" :key="file.id">
|
<template x-for="file in filteredFiles" :key="file.id">
|
||||||
<div class="file-item"
|
<div
|
||||||
|
class="file-item"
|
||||||
:class="{ selected: selectedFile?.id === file.id }"
|
:class="{ selected: selectedFile?.id === file.id }"
|
||||||
@click="selectedFile = file">
|
@click="selectedFile = file"
|
||||||
|
>
|
||||||
<span class="file-icon" x-text="file.icon"></span>
|
<span class="file-icon" x-text="file.icon"></span>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1">
|
||||||
<div style="font-weight: 600;" x-text="file.name"></div>
|
<div style="font-weight: 600" x-text="file.name"></div>
|
||||||
<div class="text-xs text-gray" x-text="file.date"></div>
|
<div class="text-xs text-gray" x-text="file.date"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray" x-text="file.size"></div>
|
<div class="text-sm text-gray" x-text="file.size"></div>
|
||||||
|
|
@ -35,31 +60,64 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel drive-details">
|
<div class="drive-details">
|
||||||
<template x-if="selectedFile">
|
<template x-if="selectedFile">
|
||||||
<div style="padding: 2rem;">
|
<div style="padding: 2rem">
|
||||||
<div style="text-align: center; margin-bottom: 2rem;">
|
<div style="text-align: center; margin-bottom: 2rem">
|
||||||
<div style="font-size: 4rem; margin-bottom: 1rem;" x-text="selectedFile.icon"></div>
|
<div
|
||||||
|
style="font-size: 4rem; margin-bottom: 1rem"
|
||||||
|
x-text="selectedFile.icon"
|
||||||
|
></div>
|
||||||
<h3 x-text="selectedFile.name"></h3>
|
<h3 x-text="selectedFile.name"></h3>
|
||||||
<p class="text-sm text-gray" x-text="selectedFile.type"></p>
|
<p class="text-sm text-gray" x-text="selectedFile.type"></p>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-bottom: 1rem;">
|
<div style="margin-bottom: 1rem">
|
||||||
<div class="text-sm" style="margin-bottom: 0.5rem;">Size</div>
|
<div class="text-sm" style="margin-bottom: 0.5rem">
|
||||||
|
Size
|
||||||
|
</div>
|
||||||
<div class="text-gray" x-text="selectedFile.size"></div>
|
<div class="text-gray" x-text="selectedFile.size"></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-bottom: 1rem;">
|
<div style="margin-bottom: 1rem">
|
||||||
<div class="text-sm" style="margin-bottom: 0.5rem;">Modified</div>
|
<div class="text-sm" style="margin-bottom: 0.5rem">
|
||||||
|
Modified
|
||||||
|
</div>
|
||||||
<div class="text-gray" x-text="selectedFile.date"></div>
|
<div class="text-gray" x-text="selectedFile.date"></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; gap: 0.5rem; margin-top: 2rem;">
|
<div style="display: flex; gap: 0.5rem; margin-top: 2rem">
|
||||||
<button style="flex: 1; padding: 0.75rem; background: #3b82f6; color: white; border: none; border-radius: 0.375rem; cursor: pointer;">Download</button>
|
<button
|
||||||
<button style="flex: 1; padding: 0.75rem; background: #10b981; color: white; border: none; border-radius: 0.375rem; cursor: pointer;">Share</button>
|
style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: #1a73e8;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: #34a853;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="!selectedFile">
|
<template x-if="!selectedFile">
|
||||||
<div style="padding: 2rem; text-align: center; color: #64748b;">
|
<div style="padding: 2rem; text-align: center; color: #5f6368">
|
||||||
<div style="font-size: 4rem; margin-bottom: 1rem;">📄</div>
|
<div style="font-size: 4rem; margin-bottom: 1rem">📄</div>
|
||||||
<p>Select a file to view details</p>
|
<p>Select a file to view details</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,85 @@
|
||||||
function driveApp() {
|
window.driveApp = function driveApp() {
|
||||||
return {
|
return {
|
||||||
current: 'All Files',
|
current: "All Files",
|
||||||
search: '',
|
search: "",
|
||||||
selectedFile: null,
|
selectedFile: null,
|
||||||
navItems: [
|
navItems: [
|
||||||
{ name: 'All Files', icon: '📁' },
|
{ name: "All Files", icon: "📁" },
|
||||||
{ name: 'Recent', icon: '🕐' },
|
{ name: "Recent", icon: "🕐" },
|
||||||
{ name: 'Starred', icon: '⭐' },
|
{ name: "Starred", icon: "⭐" },
|
||||||
{ name: 'Shared', icon: '👥' },
|
{ name: "Shared", icon: "👥" },
|
||||||
{ name: 'Trash', icon: '🗑' }
|
{ name: "Trash", icon: "🗑" },
|
||||||
],
|
],
|
||||||
files: [
|
files: [
|
||||||
{ id: 1, name: 'Project Proposal.pdf', type: 'PDF', icon: '📄', size: '2.4 MB', date: 'Nov 10, 2025' },
|
{
|
||||||
{ id: 2, name: 'Design Assets', type: 'Folder', icon: '📁', size: '—', date: 'Nov 12, 2025' },
|
id: 1,
|
||||||
{ id: 3, name: 'Meeting Notes.docx', type: 'Document', icon: '📝', size: '156 KB', date: 'Nov 14, 2025' },
|
name: "Project Proposal.pdf",
|
||||||
{ id: 4, name: 'Budget 2025.xlsx', type: 'Spreadsheet', icon: '📊', size: '892 KB', date: 'Nov 13, 2025' },
|
type: "PDF",
|
||||||
{ id: 5, name: 'Presentation.pptx', type: 'Presentation', icon: '📽', size: '5.2 MB', date: 'Nov 11, 2025' },
|
icon: "📄",
|
||||||
{ id: 6, name: 'team-photo.jpg', type: 'Image', icon: '🖼', size: '3.1 MB', date: 'Nov 9, 2025' },
|
size: "2.4 MB",
|
||||||
{ id: 7, name: 'source-code.zip', type: 'Archive', icon: '📦', size: '12.8 MB', date: 'Nov 8, 2025' },
|
date: "Nov 10, 2025",
|
||||||
{ id: 8, name: 'video-demo.mp4', type: 'Video', icon: '🎬', size: '45.2 MB', date: 'Nov 7, 2025' }
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Design Assets",
|
||||||
|
type: "Folder",
|
||||||
|
icon: "📁",
|
||||||
|
size: "—",
|
||||||
|
date: "Nov 12, 2025",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Meeting Notes.docx",
|
||||||
|
type: "Document",
|
||||||
|
icon: "📝",
|
||||||
|
size: "156 KB",
|
||||||
|
date: "Nov 14, 2025",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Budget 2025.xlsx",
|
||||||
|
type: "Spreadsheet",
|
||||||
|
icon: "📊",
|
||||||
|
size: "892 KB",
|
||||||
|
date: "Nov 13, 2025",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Presentation.pptx",
|
||||||
|
type: "Presentation",
|
||||||
|
icon: "📽",
|
||||||
|
size: "5.2 MB",
|
||||||
|
date: "Nov 11, 2025",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "team-photo.jpg",
|
||||||
|
type: "Image",
|
||||||
|
icon: "🖼",
|
||||||
|
size: "3.1 MB",
|
||||||
|
date: "Nov 9, 2025",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "source-code.zip",
|
||||||
|
type: "Archive",
|
||||||
|
icon: "📦",
|
||||||
|
size: "12.8 MB",
|
||||||
|
date: "Nov 8, 2025",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "video-demo.mp4",
|
||||||
|
type: "Video",
|
||||||
|
icon: "🎬",
|
||||||
|
size: "45.2 MB",
|
||||||
|
date: "Nov 7, 2025",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
get filteredFiles() {
|
get filteredFiles() {
|
||||||
return this.files.filter(file =>
|
return this.files.filter((file) =>
|
||||||
file.name.toLowerCase().includes(this.search.toLowerCase())
|
file.name.toLowerCase().includes(this.search.toLowerCase()),
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,10 @@
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
<script
|
||||||
|
defer
|
||||||
|
src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"
|
||||||
|
></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,29 @@ async function loadSectionHTML(path) {
|
||||||
return await response.text();
|
return await response.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadScript(jsPath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const existingScript = document.querySelector(`script[src="${jsPath}"]`);
|
||||||
|
if (existingScript) {
|
||||||
|
console.log(`Script already loaded: ${jsPath}`);
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = jsPath;
|
||||||
|
script.onload = () => {
|
||||||
|
console.log(`✓ Script loaded: ${jsPath}`);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
script.onerror = (err) => {
|
||||||
|
console.error(`✗ Script failed to load: ${jsPath}`, err);
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function switchSection(section) {
|
async function switchSection(section) {
|
||||||
const mainContent = document.getElementById("main-content");
|
const mainContent = document.getElementById("main-content");
|
||||||
|
|
||||||
|
|
@ -51,8 +74,8 @@ async function switchSection(section) {
|
||||||
try {
|
try {
|
||||||
const htmlPath = sections[section];
|
const htmlPath = sections[section];
|
||||||
console.log("Loading section:", section, "from", htmlPath);
|
console.log("Loading section:", section, "from", htmlPath);
|
||||||
// Resolve CSS path relative to the base directory.
|
|
||||||
const cssPath = getBasePath() + htmlPath.replace(".html", ".css");
|
const cssPath = getBasePath() + htmlPath.replace(".html", ".css");
|
||||||
|
const jsPath = getBasePath() + htmlPath.replace(".html", ".js");
|
||||||
|
|
||||||
// Preload chat CSS if the target is chat
|
// Preload chat CSS if the target is chat
|
||||||
if (section === "chat") {
|
if (section === "chat") {
|
||||||
|
|
@ -103,12 +126,44 @@ async function switchSection(section) {
|
||||||
loadingDiv.textContent = "Loading…";
|
loadingDiv.textContent = "Loading…";
|
||||||
container.appendChild(loadingDiv);
|
container.appendChild(loadingDiv);
|
||||||
|
|
||||||
|
// For Alpine sections, load JavaScript FIRST before HTML
|
||||||
|
const isAlpineSection = ["drive", "tasks", "mail"].includes(section);
|
||||||
|
|
||||||
|
if (isAlpineSection) {
|
||||||
|
console.log(`Loading JS before HTML for Alpine section: ${section}`);
|
||||||
|
await loadScript(jsPath);
|
||||||
|
|
||||||
|
// Wait for the component function to be registered
|
||||||
|
const appFunctionName = section + "App";
|
||||||
|
let retries = 0;
|
||||||
|
while (typeof window[appFunctionName] !== "function" && retries < 50) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window[appFunctionName] !== "function") {
|
||||||
|
console.error(`${appFunctionName} function not found after waiting!`);
|
||||||
|
throw new Error(
|
||||||
|
`Component function ${appFunctionName} not available`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✓ Component function registered: ${appFunctionName}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Load HTML
|
// Load HTML
|
||||||
const html = await loadSectionHTML(htmlPath);
|
const html = await loadSectionHTML(htmlPath);
|
||||||
|
|
||||||
// Create wrapper for the new section
|
// Create wrapper for the new section
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.id = `section-${section}`;
|
wrapper.id = `section-${section}`;
|
||||||
wrapper.className = "section";
|
wrapper.className = "section";
|
||||||
|
|
||||||
|
// For Alpine sections, mark for manual initialization
|
||||||
|
if (isAlpineSection) {
|
||||||
|
wrapper.setAttribute("x-ignore", "");
|
||||||
|
}
|
||||||
|
|
||||||
wrapper.innerHTML = html;
|
wrapper.innerHTML = html;
|
||||||
|
|
||||||
// Hide any existing sections
|
// Hide any existing sections
|
||||||
|
|
@ -127,6 +182,28 @@ async function switchSection(section) {
|
||||||
container.appendChild(wrapper);
|
container.appendChild(wrapper);
|
||||||
sectionCache[section] = wrapper;
|
sectionCache[section] = wrapper;
|
||||||
|
|
||||||
|
// For Alpine sections, initialize after DOM insertion
|
||||||
|
if (isAlpineSection && window.Alpine) {
|
||||||
|
console.log(`Initializing Alpine for section: ${section}`);
|
||||||
|
|
||||||
|
// Remove x-ignore to allow Alpine to process
|
||||||
|
wrapper.removeAttribute("x-ignore");
|
||||||
|
|
||||||
|
// Small delay to ensure DOM is ready
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.Alpine.initTree(wrapper);
|
||||||
|
console.log(`✓ Alpine initialized for ${section}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error initializing Alpine for ${section}:`, err);
|
||||||
|
}
|
||||||
|
} else if (!isAlpineSection) {
|
||||||
|
// For non-Alpine sections (like chat), load JS after HTML
|
||||||
|
await loadScript(jsPath);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch a custom event to notify the section it's being shown
|
// Dispatch a custom event to notify the section it's being shown
|
||||||
wrapper.dispatchEvent(new CustomEvent("section-shown"));
|
wrapper.dispatchEvent(new CustomEvent("section-shown"));
|
||||||
|
|
||||||
|
|
@ -138,35 +215,8 @@ async function switchSection(section) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then load JS after HTML is inserted (skip if already loaded)
|
|
||||||
// Resolve JS path relative to the base directory.
|
|
||||||
const jsPath = getBasePath() + htmlPath.replace(".html", ".js");
|
|
||||||
const existingScript = document.querySelector(`script[src="${jsPath}"]`);
|
|
||||||
|
|
||||||
if (!existingScript) {
|
|
||||||
// Create script and wait for it to load before initializing Alpine
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.src = jsPath;
|
|
||||||
script.defer = true;
|
|
||||||
|
|
||||||
// Wait for script to load before initializing Alpine
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
script.onload = resolve;
|
|
||||||
script.onerror = reject;
|
|
||||||
document.body.appendChild(script);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.history.pushState({}, "", `#${section}`);
|
window.history.pushState({}, "", `#${section}`);
|
||||||
|
|
||||||
// Start Alpine on first load, then just init the tree for new sections
|
|
||||||
if (typeof window.startAlpine === "function") {
|
|
||||||
window.startAlpine();
|
|
||||||
delete window.startAlpine;
|
|
||||||
} else if (window.Alpine) {
|
|
||||||
window.Alpine.initTree(mainContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputEl = document.getElementById("messageInput");
|
const inputEl = document.getElementById("messageInput");
|
||||||
if (inputEl) {
|
if (inputEl) {
|
||||||
inputEl.focus();
|
inputEl.focus();
|
||||||
|
|
@ -201,18 +251,49 @@ function getInitialSection() {
|
||||||
// Default to chat if nothing matches
|
// Default to chat if nothing matches
|
||||||
return section || "chat";
|
return section || "chat";
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
// Small delay to ensure all resources are loaded
|
console.log("DOM Content Loaded");
|
||||||
setTimeout(() => {
|
|
||||||
|
const initApp = () => {
|
||||||
const section = getInitialSection();
|
const section = getInitialSection();
|
||||||
|
console.log(`Initializing app with section: ${section}`);
|
||||||
|
|
||||||
// Ensure valid section
|
// Ensure valid section
|
||||||
if (!sections[section]) {
|
if (!sections[section]) {
|
||||||
|
console.warn(`Invalid section: ${section}, defaulting to chat`);
|
||||||
window.location.hash = "#chat";
|
window.location.hash = "#chat";
|
||||||
switchSection("chat");
|
switchSection("chat");
|
||||||
} else {
|
} else {
|
||||||
switchSection(section);
|
switchSection(section);
|
||||||
}
|
}
|
||||||
}, 50);
|
};
|
||||||
|
|
||||||
|
// Check if Alpine sections might be needed and wait for Alpine
|
||||||
|
const hash = window.location.hash.substring(1);
|
||||||
|
if (["drive", "tasks", "mail"].includes(hash)) {
|
||||||
|
console.log(`Waiting for Alpine to load for section: ${hash}`);
|
||||||
|
|
||||||
|
const waitForAlpine = () => {
|
||||||
|
if (window.Alpine) {
|
||||||
|
console.log("Alpine is ready");
|
||||||
|
setTimeout(initApp, 100);
|
||||||
|
} else {
|
||||||
|
console.log("Waiting for Alpine...");
|
||||||
|
setTimeout(waitForAlpine, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Also listen for alpine:init event
|
||||||
|
document.addEventListener("alpine:init", () => {
|
||||||
|
console.log("Alpine initialized via event");
|
||||||
|
});
|
||||||
|
|
||||||
|
waitForAlpine();
|
||||||
|
} else {
|
||||||
|
// For chat, don't need to wait for Alpine
|
||||||
|
setTimeout(initApp, 100);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle browser back/forward navigation
|
// Handle browser back/forward navigation
|
||||||
|
|
|
||||||
|
|
@ -1 +1,357 @@
|
||||||
a {}
|
/* Mail Layout */
|
||||||
|
.mail-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 250px 350px 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-layout {
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-sidebar,
|
||||||
|
.mail-list,
|
||||||
|
.mail-content {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-sidebar,
|
||||||
|
[data-theme="dark"] .mail-list,
|
||||||
|
[data-theme="dark"] .mail-content {
|
||||||
|
background: #202124;
|
||||||
|
border-color: #3c4043;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-sidebar {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Folder Navigation */
|
||||||
|
.nav-item {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin: 0.25rem 0.5rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
color: #5f6368;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .nav-item {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background: rgba(26, 115, 232, 0.08);
|
||||||
|
color: #1a73e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .nav-item:hover {
|
||||||
|
background: rgba(138, 180, 248, 0.08);
|
||||||
|
color: #8ab4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active {
|
||||||
|
background: #e8f0fe;
|
||||||
|
color: #1a73e8;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .nav-item.active {
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #8ab4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item .count {
|
||||||
|
margin-left: auto;
|
||||||
|
background: #1a73e8;
|
||||||
|
color: white;
|
||||||
|
padding: 0.125rem 0.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .nav-item .count {
|
||||||
|
background: #8ab4f8;
|
||||||
|
color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mail Items */
|
||||||
|
.mail-item {
|
||||||
|
padding: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
transition: all 0.2s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-item {
|
||||||
|
border-bottom-color: #3c4043;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-item:hover {
|
||||||
|
background: rgba(26, 115, 232, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-item:hover {
|
||||||
|
background: rgba(138, 180, 248, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-item.unread {
|
||||||
|
background: #f8f9fa;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-item.unread {
|
||||||
|
background: #292a2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-item.selected {
|
||||||
|
background: #e8f0fe;
|
||||||
|
border-left: 3px solid #1a73e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-item.selected {
|
||||||
|
background: #1e3a5f;
|
||||||
|
border-left-color: #8ab4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-item-from {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-item-from {
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-item.unread .mail-item-from {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-item-subject {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-item-subject {
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-item-preview {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #5f6368;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-item-preview {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-item-time {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #5f6368;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-item-time {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mail Content View */
|
||||||
|
.mail-content-view {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-content-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
color: #5f6368;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-content-empty {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-content-empty .icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mail Header */
|
||||||
|
.mail-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-header {
|
||||||
|
border-bottom-color: #3c4043;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-subject {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-subject {
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #5f6368;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-meta {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-from {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-from {
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-to {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-date {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mail Body */
|
||||||
|
.mail-body {
|
||||||
|
line-height: 1.7;
|
||||||
|
color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-body {
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-body p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-body p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headers */
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar Styles */
|
||||||
|
.mail-sidebar::-webkit-scrollbar,
|
||||||
|
.mail-list::-webkit-scrollbar,
|
||||||
|
.mail-content::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-sidebar::-webkit-scrollbar-track,
|
||||||
|
.mail-list::-webkit-scrollbar-track,
|
||||||
|
.mail-content::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-sidebar::-webkit-scrollbar-thumb,
|
||||||
|
.mail-list::-webkit-scrollbar-thumb,
|
||||||
|
.mail-content::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(128, 128, 128, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-sidebar::-webkit-scrollbar-thumb:hover,
|
||||||
|
.mail-list::-webkit-scrollbar-thumb:hover,
|
||||||
|
.mail-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(128, 128, 128, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-sidebar::-webkit-scrollbar-thumb,
|
||||||
|
[data-theme="dark"] .mail-list::-webkit-scrollbar-thumb,
|
||||||
|
[data-theme="dark"] .mail-content::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mail-sidebar::-webkit-scrollbar-thumb:hover,
|
||||||
|
[data-theme="dark"] .mail-list::-webkit-scrollbar-thumb:hover,
|
||||||
|
[data-theme="dark"] .mail-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alpine.js cloak */
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.mail-layout {
|
||||||
|
grid-template-columns: 200px 300px 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.mail-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-sidebar {
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-item.selected + .mail-content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,62 @@
|
||||||
function mailApp() {
|
window.mailApp = function mailApp() {
|
||||||
return {
|
return {
|
||||||
currentFolder: 'Inbox',
|
currentFolder: "Inbox",
|
||||||
selectedMail: null,
|
selectedMail: null,
|
||||||
|
|
||||||
folders: [
|
folders: [
|
||||||
{ name: 'Inbox', icon: '📥', count: 4 },
|
{ name: "Inbox", icon: "📥", count: 4 },
|
||||||
{ name: 'Sent', icon: '📤', count: 0 },
|
{ name: "Sent", icon: "📤", count: 0 },
|
||||||
{ name: 'Drafts', icon: '📝', count: 2 },
|
{ name: "Drafts", icon: "📝", count: 2 },
|
||||||
{ name: 'Starred', icon: '⭐', count: 0 },
|
{ name: "Starred", icon: "⭐", count: 0 },
|
||||||
{ name: 'Trash', icon: '🗑', count: 0 }
|
{ name: "Trash", icon: "🗑", count: 0 },
|
||||||
],
|
],
|
||||||
|
|
||||||
mails: [
|
mails: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
from: 'Sarah Johnson',
|
from: "Sarah Johnson",
|
||||||
to: 'me@example.com',
|
to: "me@example.com",
|
||||||
subject: 'Q4 Project Update',
|
subject: "Q4 Project Update",
|
||||||
preview: 'Hi team, I wanted to share the latest updates on our Q4 projects...',
|
preview:
|
||||||
body: '<p>Hi team,</p><p>I wanted to share the latest updates on our Q4 projects. We\'ve made significant progress on the main deliverables and are on track to meet our goals.</p><p>Please review the attached documents and let me know if you have any questions.</p><p>Best regards,<br>Sarah</p>',
|
"Hi team, I wanted to share the latest updates on our Q4 projects...",
|
||||||
time: '10:30 AM',
|
body: "<p>Hi team,</p><p>I wanted to share the latest updates on our Q4 projects. We've made significant progress on the main deliverables and are on track to meet our goals.</p><p>Please review the attached documents and let me know if you have any questions.</p><p>Best regards,<br>Sarah</p>",
|
||||||
date: 'Nov 15, 2025',
|
time: "10:30 AM",
|
||||||
read: false
|
date: "Nov 15, 2025",
|
||||||
|
read: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
from: 'Mike Chen',
|
from: "Mike Chen",
|
||||||
to: 'me@example.com',
|
to: "me@example.com",
|
||||||
subject: 'Meeting Tomorrow',
|
subject: "Meeting Tomorrow",
|
||||||
preview: 'Don\'t forget about our meeting tomorrow at 2 PM...',
|
preview: "Don't forget about our meeting tomorrow at 2 PM...",
|
||||||
body: '<p>Hi,</p><p>Don\'t forget about our meeting tomorrow at 2 PM to discuss the new features.</p><p>See you then!<br>Mike</p>',
|
body: "<p>Hi,</p><p>Don't forget about our meeting tomorrow at 2 PM to discuss the new features.</p><p>See you then!<br>Mike</p>",
|
||||||
time: '9:15 AM',
|
time: "9:15 AM",
|
||||||
date: 'Nov 15, 2025',
|
date: "Nov 15, 2025",
|
||||||
read: false
|
read: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
from: 'Emma Wilson',
|
from: "Emma Wilson",
|
||||||
to: 'me@example.com',
|
to: "me@example.com",
|
||||||
subject: 'Design Review Complete',
|
subject: "Design Review Complete",
|
||||||
preview: 'The design review for the new dashboard is complete...',
|
preview: "The design review for the new dashboard is complete...",
|
||||||
body: '<p>Hi,</p><p>The design review for the new dashboard is complete. Overall, the team is happy with the direction.</p><p>I\'ve made the requested changes and updated the Figma file.</p><p>Thanks,<br>Emma</p>',
|
body: "<p>Hi,</p><p>The design review for the new dashboard is complete. Overall, the team is happy with the direction.</p><p>I've made the requested changes and updated the Figma file.</p><p>Thanks,<br>Emma</p>",
|
||||||
time: 'Yesterday',
|
time: "Yesterday",
|
||||||
date: 'Nov 14, 2025',
|
date: "Nov 14, 2025",
|
||||||
read: true
|
read: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
from: 'David Lee',
|
from: "David Lee",
|
||||||
to: 'me@example.com',
|
to: "me@example.com",
|
||||||
subject: 'Budget Approval Needed',
|
subject: "Budget Approval Needed",
|
||||||
preview: 'Could you please review and approve the Q1 budget?',
|
preview: "Could you please review and approve the Q1 budget?",
|
||||||
body: '<p>Hi,</p><p>Could you please review and approve the Q1 budget when you get a chance?</p><p>It\'s attached to this email.</p><p>Thanks,<br>David</p>',
|
body: "<p>Hi,</p><p>Could you please review and approve the Q1 budget when you get a chance?</p><p>It's attached to this email.</p><p>Thanks,<br>David</p>",
|
||||||
time: 'Yesterday',
|
time: "Yesterday",
|
||||||
date: 'Nov 14, 2025',
|
date: "Nov 14, 2025",
|
||||||
read: false
|
read: false,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
get filteredMails() {
|
get filteredMails() {
|
||||||
|
|
@ -69,10 +70,10 @@ function mailApp() {
|
||||||
},
|
},
|
||||||
|
|
||||||
updateFolderCounts() {
|
updateFolderCounts() {
|
||||||
const inbox = this.folders.find(f => f.name === 'Inbox');
|
const inbox = this.folders.find((f) => f.name === "Inbox");
|
||||||
if (inbox) {
|
if (inbox) {
|
||||||
inbox.count = this.mails.filter(m => !m.read).length;
|
inbox.count = this.mails.filter((m) => !m.read).length;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1 +1,288 @@
|
||||||
a {}
|
/* Tasks Container */
|
||||||
|
.tasks-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .tasks-container {
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Task Input */
|
||||||
|
.task-input {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-input input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.875rem 1rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #202124;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-input input {
|
||||||
|
background: #202124;
|
||||||
|
border-color: #3c4043;
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-input input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1a73e8;
|
||||||
|
box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-input input:focus {
|
||||||
|
border-color: #8ab4f8;
|
||||||
|
box-shadow: 0 0 0 2px rgba(138, 180, 248, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-input input::placeholder {
|
||||||
|
color: #5f6368;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-input input::placeholder {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-input button {
|
||||||
|
padding: 0.875rem 1.5rem;
|
||||||
|
background: #1a73e8;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-input button:hover {
|
||||||
|
background: #1557b0;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-input button:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Task List */
|
||||||
|
.task-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item {
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-item {
|
||||||
|
background: #202124;
|
||||||
|
border-color: #3c4043;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item:hover {
|
||||||
|
border-color: #1a73e8;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-item:hover {
|
||||||
|
border-color: #8ab4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item.completed {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item.completed span {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item input[type="checkbox"] {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: #1a73e8;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item span {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item button {
|
||||||
|
background: #ea4335;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item button:hover {
|
||||||
|
background: #c5221f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Task Filters */
|
||||||
|
.task-filters {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-filters {
|
||||||
|
border-top-color: #3c4043;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-filters button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #5f6368;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-filters button {
|
||||||
|
background: #202124;
|
||||||
|
color: #9aa0a6;
|
||||||
|
border-color: #3c4043;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-filters button:hover {
|
||||||
|
background: #e8f0fe;
|
||||||
|
color: #1a73e8;
|
||||||
|
border-color: #1a73e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-filters button:hover {
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #8ab4f8;
|
||||||
|
border-color: #8ab4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-filters button.active {
|
||||||
|
background: #1a73e8;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a73e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-filters button.active {
|
||||||
|
background: #8ab4f8;
|
||||||
|
color: #202124;
|
||||||
|
border-color: #8ab4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-filters button:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats */
|
||||||
|
.task-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #5f6368;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .task-stats {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-stats span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar */
|
||||||
|
.tasks-container::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks-container::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks-container::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(128, 128, 128, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(128, 128, 128, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .tasks-container::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .tasks-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headers */
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 1.5rem 0;
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alpine.js cloak */
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tasks-container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-input {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-input button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,37 @@
|
||||||
function tasksApp() {
|
window.tasksApp = function tasksApp() {
|
||||||
return {
|
return {
|
||||||
newTask: '',
|
newTask: "",
|
||||||
filter: 'all',
|
filter: "all",
|
||||||
tasks: [],
|
tasks: [],
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const saved = localStorage.getItem('tasks');
|
const saved = localStorage.getItem("tasks");
|
||||||
if (saved) {
|
if (saved) {
|
||||||
try {
|
try {
|
||||||
this.tasks = JSON.parse(saved);
|
this.tasks = JSON.parse(saved);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load tasks:', e);
|
console.error("Failed to load tasks:", e);
|
||||||
this.tasks = [];
|
this.tasks = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addTask() {
|
addTask() {
|
||||||
if (this.newTask.trim() === '') return;
|
if (this.newTask.trim() === "") return;
|
||||||
|
|
||||||
this.tasks.push({
|
this.tasks.push({
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
text: this.newTask.trim(),
|
text: this.newTask.trim(),
|
||||||
completed: false,
|
completed: false,
|
||||||
createdAt: new Date().toISOString()
|
createdAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.newTask = '';
|
this.newTask = "";
|
||||||
this.save();
|
this.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleTask(id) {
|
toggleTask(id) {
|
||||||
const task = this.tasks.find(t => t.id === id);
|
const task = this.tasks.find((t) => t.id === id);
|
||||||
if (task) {
|
if (task) {
|
||||||
task.completed = !task.completed;
|
task.completed = !task.completed;
|
||||||
this.save();
|
this.save();
|
||||||
|
|
@ -39,39 +39,39 @@ function tasksApp() {
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTask(id) {
|
deleteTask(id) {
|
||||||
this.tasks = this.tasks.filter(t => t.id !== id);
|
this.tasks = this.tasks.filter((t) => t.id !== id);
|
||||||
this.save();
|
this.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
clearCompleted() {
|
clearCompleted() {
|
||||||
this.tasks = this.tasks.filter(t => !t.completed);
|
this.tasks = this.tasks.filter((t) => !t.completed);
|
||||||
this.save();
|
this.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('tasks', JSON.stringify(this.tasks));
|
localStorage.setItem("tasks", JSON.stringify(this.tasks));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to save tasks:', e);
|
console.error("Failed to save tasks:", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
get filteredTasks() {
|
get filteredTasks() {
|
||||||
if (this.filter === 'active') {
|
if (this.filter === "active") {
|
||||||
return this.tasks.filter(t => !t.completed);
|
return this.tasks.filter((t) => !t.completed);
|
||||||
}
|
}
|
||||||
if (this.filter === 'completed') {
|
if (this.filter === "completed") {
|
||||||
return this.tasks.filter(t => t.completed);
|
return this.tasks.filter((t) => t.completed);
|
||||||
}
|
}
|
||||||
return this.tasks;
|
return this.tasks;
|
||||||
},
|
},
|
||||||
|
|
||||||
get activeTasks() {
|
get activeTasks() {
|
||||||
return this.tasks.filter(t => !t.completed).length;
|
return this.tasks.filter((t) => !t.completed).length;
|
||||||
},
|
},
|
||||||
|
|
||||||
get completedTasks() {
|
get completedTasks() {
|
||||||
return this.tasks.filter(t => t.completed).length;
|
return this.tasks.filter((t) => t.completed).length;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue