feat(editor, settings): refactor state handling and enhance validation
Refactored editor.page.html to use a Vue-style `data()` function for reactive state, adding a new `content` property and cleaning up redundant inline styles. Updated profile-form.html to replace single `error` handling with field-specific `errors.<field>` bindings, improving form validation clarity and user feedback.
This commit is contained in:
parent
f2624aef94
commit
02eaac783f
21 changed files with 391 additions and 365 deletions
|
|
@ -186,16 +186,22 @@ import './style.css';
|
|||
|
||||
export default {
|
||||
// Reactive state
|
||||
fileName: 'Document 1',
|
||||
fontSize: '12',
|
||||
fontFamily: 'Calibri',
|
||||
textColor: '#000000',
|
||||
highlightColor: '#ffff00',
|
||||
activeTab: 'home',
|
||||
zoom: 100,
|
||||
pages: [1],
|
||||
editor: null,
|
||||
fileInputRef: null,
|
||||
data() {
|
||||
return {
|
||||
fileName: 'Document 1',
|
||||
fontSize: '12',
|
||||
fontFamily: 'Calibri',
|
||||
textColor: '#000000',
|
||||
highlightColor: '#ffff00',
|
||||
activeTab: 'home',
|
||||
zoom: 100,
|
||||
pages: [1],
|
||||
editor: null,
|
||||
fileInputRef: null,
|
||||
content: '',
|
||||
activeTab: 'home'
|
||||
}
|
||||
},
|
||||
|
||||
// Lifecycle
|
||||
async mounted() {
|
||||
|
|
@ -334,7 +340,3 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Original styles moved to global CSS */
|
||||
</style>
|
||||
|
|
|
|||
39
web/app/player/player.page.html
Normal file
39
web/app/player/player.page.html
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<!-- Basic player page -->
|
||||
<template>
|
||||
<div class="player-container">
|
||||
<h1>Player</h1>
|
||||
<div class="player-content">
|
||||
<p>Player functionality will be implemented here</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import './style.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentTrack: null,
|
||||
isPlaying: false,
|
||||
volume: 80,
|
||||
currentTime: 0,
|
||||
duration: 0,
|
||||
playlist: [],
|
||||
shuffle: false,
|
||||
repeat: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
play() {
|
||||
this.isPlaying = true;
|
||||
},
|
||||
pause() {
|
||||
this.isPlaying = false;
|
||||
},
|
||||
setVolume(vol) {
|
||||
this.volume = Math.max(0, Math.min(100, vol));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -4,10 +4,10 @@
|
|||
<controller name="username">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-1">Username</label>
|
||||
<input class="w-full p-2 border rounded {error ? 'border-red-500' : 'border-gray-300'}"
|
||||
<input class="w-full p-2 border rounded {errors.username ? 'border-red-500' : 'border-gray-300'}"
|
||||
bind="{username}"
|
||||
placeholder="Enter username" />
|
||||
{error && <p class="text-red-500 text-xs mt-1">{error.message}</p>}
|
||||
{errors.username && <p class="text-red-500 text-xs mt-1">{errors.username.message}</p>}
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
This is your public display name. It can be your real name or a pseudonym. You can only change this once every 30 days.
|
||||
</p>
|
||||
|
|
@ -18,10 +18,10 @@
|
|||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-1">Email</label>
|
||||
<input type="email"
|
||||
class="w-full p-2 border rounded {error ? 'border-red-500' : 'border-gray-300'}"
|
||||
class="w-full p-2 border rounded {errors.email ? 'border-red-500' : 'border-gray-300'}"
|
||||
bind="{email}"
|
||||
placeholder="Enter email" />
|
||||
{error && <p class="text-red-500 text-xs mt-1">{error.message}</p>}
|
||||
{errors.email && <p class="text-red-500 text-xs mt-1">{errors.email.message}</p>}
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
You can manage verified email addresses in your email settings.
|
||||
</p>
|
||||
|
|
@ -31,11 +31,11 @@
|
|||
<controller name="bio">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-1">Bio</label>
|
||||
<textarea class="w-full p-2 border rounded {error ? 'border-red-500' : 'border-gray-300'}"
|
||||
<textarea class="w-full p-2 border rounded {errors.bio ? 'border-red-500' : 'border-gray-300'}"
|
||||
bind="{bio}"
|
||||
rows="4"
|
||||
placeholder="Tell us a little bit about yourself"></textarea>
|
||||
{error && <p class="text-red-500 text-xs mt-1">{error.message}</p>}
|
||||
{errors.bio && <p class="text-red-500 text-xs mt-1">{errors.bio.message}</p>}
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
You can @mention other users and organizations to link to them.
|
||||
</p>
|
||||
|
|
@ -58,10 +58,18 @@ import './style.css';
|
|||
|
||||
export default {
|
||||
// Reactive state
|
||||
username: '',
|
||||
email: '',
|
||||
bio: '',
|
||||
error: null,
|
||||
data() {
|
||||
return {
|
||||
username: '',
|
||||
email: '',
|
||||
bio: '',
|
||||
errors: {
|
||||
username: null,
|
||||
email: null,
|
||||
bio: null
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Validation schema
|
||||
schema: z.object({
|
||||
|
|
@ -77,10 +85,20 @@ export default {
|
|||
email: this.email,
|
||||
bio: this.bio
|
||||
});
|
||||
|
||||
// Clear previous errors
|
||||
this.errors = {
|
||||
username: null,
|
||||
email: null,
|
||||
bio: null
|
||||
};
|
||||
|
||||
if (result.success) {
|
||||
callback(result.data);
|
||||
} else {
|
||||
this.error = result.error.errors[0];
|
||||
result.error.errors.forEach(err => {
|
||||
this.errors[err.path[0]] = err;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
18
web/app/settings/style.css
Normal file
18
web/app/settings/style.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* Settings page styles */
|
||||
.settings-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
|
@ -71,3 +71,7 @@
|
|||
.download-btn:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.dashboard-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dashboard</title>
|
||||
<link rel="stylesheet" href="../css/global.css">
|
||||
<link rel="stylesheet" href="dashboard.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<main class="container p-4 space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold text-foreground">Dashboard</h1>
|
||||
<h1 class="text-2xl font-bold text-foreground">1Dashboard</h1>
|
||||
<div class="date-range-picker"></div>
|
||||
<button class="download-btn">Download</button>
|
||||
</div>
|
||||
|
|
@ -36,6 +28,4 @@
|
|||
<script src="dashboard.js"></script>
|
||||
<script src="components/date-range-picker.js"></script>
|
||||
<script src="components/overview.js"></script>
|
||||
<script src="components/recent-sales.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<script src="components/recent-sales.js"></script>
|
||||
|
|
@ -21,10 +21,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
]
|
||||
};
|
||||
|
||||
// Initialize dashboard
|
||||
// Initialize dashboard safely
|
||||
function init() {
|
||||
renderCards();
|
||||
document.querySelector('.download-btn').addEventListener('click', handleDownload);
|
||||
const ensure = setInterval(() => {
|
||||
const main = document.querySelector('#main-content');
|
||||
const section = main && main.querySelector('.cards-grid');
|
||||
const btn = main && main.querySelector('.download-btn');
|
||||
if (section && btn) {
|
||||
clearInterval(ensure);
|
||||
renderCards();
|
||||
btn.addEventListener('click', handleDownload);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Render dashboard cards
|
||||
|
|
@ -54,5 +62,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
}
|
||||
|
||||
// Initialize dashboard
|
||||
init();
|
||||
document.addEventListener('DOMContentLoaded',()=>{init();});
|
||||
});
|
||||
|
|
|
|||
1
web/desktop/drive/drive.css
Normal file
1
web/desktop/drive/drive.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
|
|
@ -1,89 +1,73 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Editor</title>
|
||||
<link rel="stylesheet" href="../css/global.css">
|
||||
<link rel="stylesheet" href="editor.css">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
</head>
|
||||
<body x-data="editor" x-init="init()">
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<!-- Quick Access Toolbar -->
|
||||
<div class="quick-access">
|
||||
<button class="quick-access-btn" @click="document.execCommand('undo', false)">
|
||||
<svg><!-- Undo icon --></svg>
|
||||
<div id="main-content" x-data="editor">
|
||||
<div class="content-section active">
|
||||
<!-- Quick Access Toolbar -->
|
||||
<div class="quick-access">
|
||||
<button class="quick-access-btn" @click="document.execCommand('undo', false)">
|
||||
<svg><!-- Undo icon --></svg>
|
||||
</button>
|
||||
<button class="quick-access-btn" @click="document.execCommand('redo', false)">
|
||||
<svg><!-- Redo icon --></svg>
|
||||
</button>
|
||||
<div class="title-controls">
|
||||
<input type="text" class="title-input" x-model="fileName" placeholder="Document name">
|
||||
<button class="quick-access-btn" @click="saveDocument">
|
||||
<svg><!-- Save icon --></svg>
|
||||
</button>
|
||||
<button class="quick-access-btn" @click="document.execCommand('redo', false)">
|
||||
<svg><!-- Redo icon --></svg>
|
||||
</button>
|
||||
<div class="title-controls">
|
||||
<input type="text" class="title-input" x-model="fileName" placeholder="Document name">
|
||||
<button class="quick-access-btn" @click="saveDocument">
|
||||
<svg><!-- Save icon --></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ribbon -->
|
||||
<div class="ribbon">
|
||||
<div class="ribbon-tabs">
|
||||
<button class="ribbon-tab-button" :class="{ 'active': activeTab === 'home' }"
|
||||
@click="setActiveTab('home')">Home</button>
|
||||
<button class="ribbon-tab-button" :class="{ 'active': activeTab === 'insert' }"
|
||||
@click="setActiveTab('insert')">Insert</button>
|
||||
<button class="ribbon-tab-button" :class="{ 'active': activeTab === 'view' }"
|
||||
@click="setActiveTab('view')">View</button>
|
||||
</div>
|
||||
|
||||
<!-- Ribbon -->
|
||||
<div class="ribbon">
|
||||
<div class="ribbon-tabs">
|
||||
<button class="ribbon-tab-button"
|
||||
:class="{ 'active': activeTab === 'home' }"
|
||||
@click="setActiveTab('home')">Home</button>
|
||||
<button class="ribbon-tab-button"
|
||||
:class="{ 'active': activeTab === 'insert' }"
|
||||
@click="setActiveTab('insert')">Insert</button>
|
||||
<button class="ribbon-tab-button"
|
||||
:class="{ 'active': activeTab === 'view' }"
|
||||
@click="setActiveTab('view')">View</button>
|
||||
</div>
|
||||
|
||||
<div class="ribbon-content" x-show="activeTab === 'home'">
|
||||
<div class="ribbon-group">
|
||||
<div class="ribbon-group-title">Format</div>
|
||||
<div class="ribbon-group-content">
|
||||
<button class="ribbon-button" @click="formatBold">B</button>
|
||||
<button class="ribbon-button" @click="formatItalic">I</button>
|
||||
<button class="ribbon-button" @click="formatUnderline">U</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ribbon-group">
|
||||
<div class="ribbon-group-title">Alignment</div>
|
||||
<div class="ribbon-group-content">
|
||||
<button class="ribbon-button" @click="alignLeft">Left</button>
|
||||
<button class="ribbon-button" @click="alignCenter">Center</button>
|
||||
<button class="ribbon-button" @click="alignRight">Right</button>
|
||||
<button class="ribbon-button" @click="alignJustify">Justify</button>
|
||||
</div>
|
||||
<div class="ribbon-content" x-show="activeTab === 'home'">
|
||||
<div class="ribbon-group">
|
||||
<div class="ribbon-group-title">Format</div>
|
||||
<div class="ribbon-group-content">
|
||||
<button class="ribbon-button" @click="formatBold">B</button>
|
||||
<button class="ribbon-button" @click="formatItalic">I</button>
|
||||
<button class="ribbon-button" @click="formatUnderline">U</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Editor Area -->
|
||||
<div class="editor-container">
|
||||
<div class="editor-main">
|
||||
<div class="pages-container">
|
||||
<div class="page">
|
||||
<div class="page-number">Page 1</div>
|
||||
<div class="page-content" id="editor-content" contenteditable="true" x-html="content"></div>
|
||||
</div>
|
||||
<div class="ribbon-group">
|
||||
<div class="ribbon-group-title">Alignment</div>
|
||||
<div class="ribbon-group-content">
|
||||
<button class="ribbon-button" @click="alignLeft">Left</button>
|
||||
<button class="ribbon-button" @click="alignCenter">Center</button>
|
||||
<button class="ribbon-button" @click="alignRight">Right</button>
|
||||
<button class="ribbon-button" @click="alignJustify">Justify</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<div class="status-bar">
|
||||
<div>Page <span x-text="pages.length"></span> of <span x-text="pages.length"></span></div>
|
||||
<div class="zoom-controls">
|
||||
<button @click="zoomOut">-</button>
|
||||
<span x-text="zoom + '%'"></span>
|
||||
<button @click="zoomIn">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="editor.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!-- Editor Area -->
|
||||
<div class="editor-container">
|
||||
<div class="editor-main">
|
||||
<div class="pages-container">
|
||||
<div class="page">
|
||||
<div class="page-number">Page 1</div>
|
||||
<div class="page-content" id="editor-content" contenteditable="true" x-html="content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<div class="status-bar">
|
||||
<div>Page <span x-text="pages.length"></span> of <span x-text="pages.length"></span></div>
|
||||
<div class="zoom-controls">
|
||||
<button @click="zoomOut">-</button>
|
||||
<span x-text="zoom + '%'"></span>
|
||||
<button @click="zoomIn">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -41,5 +41,13 @@
|
|||
<script src="drive/drive.js"></script>
|
||||
<script src="tasks/tasks.js"></script>
|
||||
<script src="mail/mail.js"></script>
|
||||
<script src="dashboard/dashboard.js"></script>
|
||||
<script src="editor/editor.js"></script>
|
||||
<script src="player/player.js"></script>
|
||||
<script src="paper/paper.js"></script>
|
||||
<script src="settings/settings.js"></script>
|
||||
<script src="tables/tables.js"></script>
|
||||
<script src="news/news.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const sections = {
|
|||
player: 'player/player.html',
|
||||
paper: 'paper/paper.html',
|
||||
settings: 'settings/settings.html',
|
||||
tables: 'tablesv2/tables.html',
|
||||
tables: 'tables/tables.html',
|
||||
news: 'news/news.html'
|
||||
};
|
||||
|
||||
|
|
@ -21,8 +21,34 @@ async function switchSection(section) {
|
|||
const mainContent = document.getElementById('main-content');
|
||||
|
||||
try {
|
||||
const html = await loadSectionHTML(sections[section]);
|
||||
const htmlPath = sections[section];
|
||||
console.log('Loading section:', section, 'from', htmlPath);
|
||||
const cssPath =
|
||||
htmlPath.replace('.html', '.css');
|
||||
|
||||
// Remove any existing section CSS
|
||||
document.querySelectorAll('link[data-section-css]').forEach(link => link.remove());
|
||||
|
||||
// Load CSS first
|
||||
const cssLink = document.createElement('link');
|
||||
cssLink.rel = 'stylesheet';
|
||||
cssLink.href = cssPath;
|
||||
cssLink.setAttribute('data-section-css', 'true');
|
||||
document.head.appendChild(cssLink);
|
||||
|
||||
// First load HTML
|
||||
const html = await loadSectionHTML(htmlPath);
|
||||
mainContent.innerHTML = html;
|
||||
|
||||
// Then load JS after HTML is inserted
|
||||
const jsPath = htmlPath.replace('.html', '.js');
|
||||
const existingScript = document.querySelector(`script[src="${jsPath}"]`);
|
||||
if (!existingScript) {
|
||||
const script = document.createElement('script');
|
||||
script.src = jsPath;
|
||||
script.defer = true;
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
window.history.pushState({}, '', `#${section}`);
|
||||
Alpine.initTree(mainContent);
|
||||
} catch (err) {
|
||||
|
|
|
|||
1
web/desktop/mail/mail.css
Normal file
1
web/desktop/mail/mail.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
|
|
@ -1,11 +1,3 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>News</title>
|
||||
<link rel="stylesheet" href="../css/global.css">
|
||||
<link rel="stylesheet" href="news.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<div class="panel">
|
||||
|
|
@ -14,6 +6,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="news.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,77 +1,55 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Paper</title>
|
||||
<link rel="stylesheet" href="../css/global.css">
|
||||
<link rel="stylesheet" href="paper.css">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
</head>
|
||||
<body x-data="paper">
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<!-- Paper Shadow Effect -->
|
||||
<div class="mx-4 my-8 bg-card rounded-lg shadow-2xl shadow-black/20 border border-border">
|
||||
<div class="editor-content"
|
||||
x-ref="editor"
|
||||
contenteditable="true"
|
||||
x-init="initEditor()"
|
||||
class="min-h-[calc(100vh-12rem)] p-8 prose max-w-none focus:outline-none">
|
||||
Start writing your thoughts here...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating Toolbar -->
|
||||
<div class="floating-toolbar" x-show="showToolbar" x-transition>
|
||||
<div class="flex items-center bg-card border border-border rounded-lg shadow-lg p-1">
|
||||
<!-- Text Formatting -->
|
||||
<button @click="formatText('bold')"
|
||||
class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isActive('bold')}">
|
||||
B
|
||||
</button>
|
||||
<button @click="formatText('italic')"
|
||||
class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isActive('italic')}">
|
||||
I
|
||||
</button>
|
||||
<button @click="formatText('underline')"
|
||||
class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isActive('underline')}">
|
||||
U
|
||||
</button>
|
||||
|
||||
<div class="w-px h-6 bg-border mx-1"></div>
|
||||
|
||||
<!-- Text Alignment -->
|
||||
<button @click="alignText('left')"
|
||||
class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isAligned('left')}">
|
||||
Left
|
||||
</button>
|
||||
<button @click="alignText('center')"
|
||||
class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isAligned('center')}">
|
||||
Center
|
||||
</button>
|
||||
<button @click="alignText('right')"
|
||||
class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isAligned('right')}">
|
||||
Right
|
||||
</button>
|
||||
|
||||
<div class="w-px h-6 bg-border mx-1"></div>
|
||||
|
||||
<!-- Link -->
|
||||
<button @click="addLink"
|
||||
class="p-2 rounded hover:bg-accent transition-colors">
|
||||
Link
|
||||
</button>
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<!-- Paper Shadow Effect -->
|
||||
<div class="mx-4 my-8 bg-card rounded-lg shadow-2xl shadow-black/20 border border-border">
|
||||
<div class="editor-content" x-ref="editor" contenteditable="true" x-init="initEditor()"
|
||||
class="min-h-[calc(100vh-12rem)] p-8 prose max-w-none focus:outline-none">
|
||||
Start writing your thoughts here...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating Toolbar -->
|
||||
<div class="floating-toolbar" x-show="showToolbar" x-transition>
|
||||
<div class="flex items-center bg-card border border-border rounded-lg shadow-lg p-1">
|
||||
<!-- Text Formatting -->
|
||||
<button @click="formatText('bold')" class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isActive('bold')}">
|
||||
B
|
||||
</button>
|
||||
<button @click="formatText('italic')" class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isActive('italic')}">
|
||||
I
|
||||
</button>
|
||||
<button @click="formatText('underline')" class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isActive('underline')}">
|
||||
U
|
||||
</button>
|
||||
|
||||
<div class="w-px h-6 bg-border mx-1"></div>
|
||||
|
||||
<!-- Text Alignment -->
|
||||
<button @click="alignText('left')" class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isAligned('left')}">
|
||||
Left
|
||||
</button>
|
||||
<button @click="alignText('center')" class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isAligned('center')}">
|
||||
Center
|
||||
</button>
|
||||
<button @click="alignText('right')" class="p-2 rounded hover:bg-accent transition-colors"
|
||||
:class="{'bg-primary text-primary-foreground': isAligned('right')}">
|
||||
Right
|
||||
</button>
|
||||
|
||||
<div class="w-px h-6 bg-border mx-1"></div>
|
||||
|
||||
<!-- Link -->
|
||||
<button @click="addLink" class="p-2 rounded hover:bg-accent transition-colors">
|
||||
Link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="paper.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
|
|
@ -1,26 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Player</title>
|
||||
<link rel="stylesheet" href="../css/global.css">
|
||||
<link rel="stylesheet" href="player.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<div class="player-container">
|
||||
<div class="video-container">
|
||||
<!-- Video element will be inserted here -->
|
||||
</div>
|
||||
<div class="player-controls">
|
||||
<button class="play-btn">Play</button>
|
||||
<input type="range" class="slider progress-slider" min="0" max="100" value="0">
|
||||
<span class="time-display">0:00 / 0:00</span>
|
||||
<input type="range" class="slider volume-slider" min="0" max="100" value="80">
|
||||
</div>
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<div class="player-container">
|
||||
<div class="video-container">
|
||||
<!-- Video element will be inserted here -->
|
||||
</div>
|
||||
<div class="player-controls">
|
||||
<button class="play-btn">Play</button>
|
||||
<input type="range" class="slider progress-slider" min="0" max="100" value="0">
|
||||
<span class="time-display">0:00 / 0:00</span>
|
||||
<input type="range" class="slider volume-slider" min="0" max="100" value="80">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="player.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
|
|
@ -1,61 +1,63 @@
|
|||
// Player module JavaScript
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const playerContainer = document.querySelector('.player-container');
|
||||
const videoContainer = document.querySelector('.video-container');
|
||||
const playBtn = document.querySelector('.play-btn');
|
||||
const progressSlider = document.querySelector('.progress-slider');
|
||||
const volumeSlider = document.querySelector('.volume-slider');
|
||||
const timeDisplay = document.querySelector('.time-display');
|
||||
|
||||
// Create video element
|
||||
const video = document.createElement('video');
|
||||
video.src = ''; // Will be set when loading media
|
||||
video.controls = false;
|
||||
videoContainer.appendChild(video);
|
||||
|
||||
// Play/Pause toggle
|
||||
playBtn.addEventListener('click', () => {
|
||||
if (video.paused) {
|
||||
video.play();
|
||||
playBtn.textContent = 'Pause';
|
||||
} else {
|
||||
video.pause();
|
||||
playBtn.textContent = 'Play';
|
||||
const ready = setInterval(() => {
|
||||
const container = document.querySelector('.player-container');
|
||||
if (!container) return;
|
||||
clearInterval(ready);
|
||||
const videoContainer = document.querySelector('.video-container');
|
||||
const playBtn = document.querySelector('.play-btn');
|
||||
const progressSlider = document.querySelector('.progress-slider');
|
||||
const volumeSlider = document.querySelector('.volume-slider');
|
||||
const timeDisplay = document.querySelector('.time-display');
|
||||
const ensure = setInterval(() => {
|
||||
const container = document.querySelector('.video-container');
|
||||
if (container) {
|
||||
clearInterval(ensure);
|
||||
const video = document.createElement('video');
|
||||
video.src = '';
|
||||
video.controls = false;
|
||||
container.appendChild(video);
|
||||
setupControls(video);
|
||||
}
|
||||
}, 50);
|
||||
function setupControls(video) {
|
||||
const playBtn = document.querySelector('.play-btn');
|
||||
const progressSlider = document.querySelector('.progress-slider');
|
||||
const volumeSlider = document.querySelector('.volume-slider');
|
||||
const timeDisplay = document.querySelector('.time-display');
|
||||
playBtn.addEventListener('click', () => {
|
||||
if (video.paused) {
|
||||
video.play();
|
||||
playBtn.textContent = 'Pause';
|
||||
} else {
|
||||
video.pause();
|
||||
playBtn.textContent = 'Play';
|
||||
}
|
||||
});
|
||||
video.addEventListener('timeupdate', () => {
|
||||
const progress = (video.currentTime / video.duration) * 100;
|
||||
progressSlider.value = progress || 0;
|
||||
updateTimeDisplay(video, timeDisplay);
|
||||
});
|
||||
progressSlider.addEventListener('input', () => {
|
||||
const seekTime = (progressSlider.value / 100) * video.duration;
|
||||
video.currentTime = seekTime;
|
||||
});
|
||||
volumeSlider.addEventListener('input', () => {
|
||||
video.volume = volumeSlider.value / 100;
|
||||
});
|
||||
window.loadMedia = (src) => {
|
||||
video.src = src;
|
||||
video.load();
|
||||
};
|
||||
}
|
||||
function updateTimeDisplay(video, display) {
|
||||
const formatTime = (seconds) => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
||||
};
|
||||
display.textContent = `${formatTime(video.currentTime)} / ${formatTime(video.duration)}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Update progress slider
|
||||
video.addEventListener('timeupdate', () => {
|
||||
const progress = (video.currentTime / video.duration) * 100;
|
||||
progressSlider.value = progress || 0;
|
||||
updateTimeDisplay();
|
||||
});
|
||||
|
||||
// Seek video
|
||||
progressSlider.addEventListener('input', () => {
|
||||
const seekTime = (progressSlider.value / 100) * video.duration;
|
||||
video.currentTime = seekTime;
|
||||
});
|
||||
|
||||
// Volume control
|
||||
volumeSlider.addEventListener('input', () => {
|
||||
video.volume = volumeSlider.value / 100;
|
||||
});
|
||||
|
||||
// Format time display
|
||||
function updateTimeDisplay() {
|
||||
const formatTime = (seconds) => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
||||
};
|
||||
|
||||
timeDisplay.textContent = `${formatTime(video.currentTime)} / ${formatTime(video.duration)}`;
|
||||
}
|
||||
|
||||
// Load media (to be called externally)
|
||||
window.loadMedia = (src) => {
|
||||
video.src = src;
|
||||
video.load();
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,74 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Settings</title>
|
||||
<link rel="stylesheet" href="../css/global.css">
|
||||
<link rel="stylesheet" href="settings.css">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
</head>
|
||||
<body x-data="settings">
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<div class="space-y-6 p-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-medium">Profile</h2>
|
||||
<p class="text-sm text-gray-500"></p>
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<div class="space-y-6 p-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-medium">Profile</h2>
|
||||
<p class="text-sm text-gray-500"></p>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 my-4"></div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Username Field -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-1">Username</label>
|
||||
<input class="w-full p-2 border rounded" x-model="username" placeholder="Enter username" />
|
||||
<template x-if="errors.username">
|
||||
<p class="text-red-500 text-xs mt-1" x-text="errors.username"></p>
|
||||
</template>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
This is your public display name. It can be your real name or a pseudonym.
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 my-4"></div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Username Field -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-1">Username</label>
|
||||
<input class="w-full p-2 border rounded"
|
||||
x-model="username"
|
||||
placeholder="Enter username" />
|
||||
<template x-if="errors.username">
|
||||
<p class="text-red-500 text-xs mt-1" x-text="errors.username"></p>
|
||||
</template>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
This is your public display name. It can be your real name or a pseudonym.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Email Field -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-1">Email</label>
|
||||
<input type="email"
|
||||
class="w-full p-2 border rounded"
|
||||
x-model="email"
|
||||
placeholder="Enter email" />
|
||||
<template x-if="errors.email">
|
||||
<p class="text-red-500 text-xs mt-1" x-text="errors.email"></p>
|
||||
</template>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
You can manage verified email addresses in your email settings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Bio Field -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-1">Bio</label>
|
||||
<textarea class="w-full p-2 border rounded"
|
||||
x-model="bio"
|
||||
rows="4"
|
||||
placeholder="Tell us a little bit about yourself"></textarea>
|
||||
<template x-if="errors.bio">
|
||||
<p class="text-red-500 text-xs mt-1" x-text="errors.bio"></p>
|
||||
</template>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
You can @mention other users and organizations to link to them.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
||||
@click="submitForm">
|
||||
Update profile
|
||||
</button>
|
||||
<!-- Email Field -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-1">Email</label>
|
||||
<input type="email" class="w-full p-2 border rounded" x-model="email" placeholder="Enter email" />
|
||||
<template x-if="errors.email">
|
||||
<p class="text-red-500 text-xs mt-1" x-text="errors.email"></p>
|
||||
</template>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
You can manage verified email addresses in your email settings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Bio Field -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-1">Bio</label>
|
||||
<textarea class="w-full p-2 border rounded" x-model="bio" rows="4"
|
||||
placeholder="Tell us a little bit about yourself"></textarea>
|
||||
<template x-if="errors.bio">
|
||||
<p class="text-red-500 text-xs mt-1" x-text="errors.bio"></p>
|
||||
</template>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
You can @mention other users and organizations to link to them.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" @click="submitForm">
|
||||
Update profile
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="settings.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
|
|
@ -1,12 +1,3 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tables</title>
|
||||
<link rel="stylesheet" href="../css/global.css">
|
||||
<link rel="stylesheet" href="tables.css">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
</head>
|
||||
<body x-data="tablesApp()" x-init="init()">
|
||||
<div id="main-content">
|
||||
<div class="content-section active">
|
||||
<div class="app-container">
|
||||
|
|
@ -62,6 +53,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="tables.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1
web/desktop/tasks/tasks.css
Normal file
1
web/desktop/tasks/tasks.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
Loading…
Add table
Reference in a new issue