Compare commits
13 Commits
8207b3626e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fe42d42500 | |||
| 087d22f1fd | |||
| a74fd244b0 | |||
| 7fbb789fdd | |||
| 8d6a20ff05 | |||
| 271780f48a | |||
| 264441f83b | |||
| c218162edf | |||
| 886a2f7372 | |||
| 68e11c57e1 | |||
| 50ef40d2e0 | |||
| b3aa1f2992 | |||
| e1484b1723 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,7 +1,8 @@
|
|||||||
# Build output
|
# Build output (main.js is tracked for easy installation)
|
||||||
*.js.map
|
*.js.map
|
||||||
release/
|
release/
|
||||||
*.zip
|
*.zip
|
||||||
|
immerse-*.zip
|
||||||
|
|
||||||
# npm
|
# npm
|
||||||
node_modules/
|
node_modules/
|
||||||
@@ -22,6 +23,7 @@ data.json
|
|||||||
# Development/Documentation (not for distribution)
|
# Development/Documentation (not for distribution)
|
||||||
RELEASE-GUIDE.md
|
RELEASE-GUIDE.md
|
||||||
ROADMAP.md
|
ROADMAP.md
|
||||||
|
ROADMAP_*.md
|
||||||
deploy-test.bat
|
deploy-test.bat
|
||||||
.claude/
|
.claude/
|
||||||
RELEASE_NOTES_v1.1.0.md
|
RELEASE_NOTES_*.md
|
||||||
|
|||||||
260
README.md
260
README.md
@@ -4,79 +4,35 @@ A powerful task management and focus timer plugin for [Obsidian](https://obsidia
|
|||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
## 🎯 Overview
|
## 🎯 Overview
|
||||||
|
|
||||||
Immerse brings the power of time-boxed task management directly into your Obsidian vault. Plan your day, track time with the Pomodoro technique, and crush your tasks with satisfying checkoffs - all without leaving your notes.
|
Task management and focus timer for Obsidian. Plan your day, track time with Pomodoro technique, and manage tasks without leaving your vault.
|
||||||
|
|
||||||
### Why Immerse?
|
## ✨ Key Features
|
||||||
|
|
||||||
- **Stay in Flow**: No need to switch between apps - manage tasks where you take notes
|
**📋 Task Management**
|
||||||
- **Time Awareness**: Know exactly how long tasks take vs. your estimates
|
- Create tasks with time estimates and organize into customizable lists
|
||||||
- **Pomodoro Built-in**: Work in focused sprints with automatic break reminders
|
- Filter by list, status, or date
|
||||||
- **Satisfying Feedback**: Celebratory messages and sounds when you complete tasks
|
- Schedule tasks with reminders (5/10/15/30/60 min before due)
|
||||||
- **Visual Progress**: See your daily progress with stats and streaks
|
- Daily note integration with automatic task logging
|
||||||
|
|
||||||
## ✨ Features
|
**⏱️ Dual Timer Modes**
|
||||||
|
- **Pomodoro**: Configurable work sessions with automatic breaks
|
||||||
|
- **Stopwatch**: Free-form time tracking with estimate alerts
|
||||||
|
|
||||||
### 📋 Task Management
|
**📊 Analytics & Reports**
|
||||||
- Create tasks with time estimates
|
- View productivity metrics (tasks/day, hours/day, streaks)
|
||||||
- Organize tasks into customizable lists (Work, Personal, Learning, etc.)
|
- Pie charts and bar graphs showing activity breakdown
|
||||||
- Add notes and details to tasks
|
- Time tracking by list category
|
||||||
- Filter tasks by list, today's tasks, or completed items
|
- Insights on most productive hours, days, and months
|
||||||
- Drag-and-drop task reordering
|
|
||||||
|
|
||||||
### ⏱️ Dual Timer Modes
|
**🎨 Polish**
|
||||||
|
- Status bar timer, celebration messages, sound alerts
|
||||||
#### Pomodoro Timer
|
- Keyboard shortcuts, dark mode support
|
||||||
- Configurable work sessions (default: 25 minutes)
|
- Overdue task detection with visual indicators
|
||||||
- Short breaks (default: 5 minutes)
|
- Works on desktop and mobile
|
||||||
- Long breaks after a set number of pomodoros (default: 15 minutes every 4 pomodoros)
|
|
||||||
- Auto-start break option
|
|
||||||
- Visual countdown with progress bar
|
|
||||||
|
|
||||||
#### Stopwatch Mode
|
|
||||||
- Free-form time tracking
|
|
||||||
- Alerts when you exceed your estimate
|
|
||||||
- Track actual time vs. estimated time
|
|
||||||
|
|
||||||
### 📊 Progress Tracking
|
|
||||||
- **Daily Stats**: See tasks completed, focus time, and more
|
|
||||||
- **Streak Counter**: Build momentum with consecutive productive days
|
|
||||||
- **Time Comparison**: Compare estimated vs. actual time to improve planning
|
|
||||||
- **Pomodoro Count**: Track total pomodoros completed
|
|
||||||
- **Daily Note Logging**: Automatically log completed tasks to your daily notes with timestamps and performance metrics
|
|
||||||
|
|
||||||
### 📈 Reporting & Analytics (New in v1.1.0!)
|
|
||||||
- **Comprehensive Reports**: View detailed productivity reports with date range filtering
|
|
||||||
- **Key Metrics Dashboard**: Track tasks done, tasks per day, hours per day, minutes per task, and day streaks
|
|
||||||
- **Visual Analytics**: Pie charts showing task distribution and time allocation
|
|
||||||
- **Time by List**: See how much time you spend on different task categories
|
|
||||||
- **Productivity Insights**: Discover your most productive hour, day, and month
|
|
||||||
- **Daily Breakdown**: Visual bar charts showing last 10 days of activity
|
|
||||||
- **Quick Filters**: Today, Last 7/30/90 days for easy report generation
|
|
||||||
|
|
||||||
### 🗓️ Task Scheduling & Reminders (New in v1.1.0!)
|
|
||||||
- **Schedule Tasks**: Set specific date and time for tasks
|
|
||||||
- **Smart Reminders**: Get notifications before task is due (5/10/15/30/60 minute options)
|
|
||||||
- **Overdue Detection**: Visual indicators (⚠️ red badge) for past-due tasks
|
|
||||||
- **Startup Checks**: Alerts when opening Obsidian if tasks are overdue
|
|
||||||
- **Background Monitoring**: Automatic 30-second checks for due tasks
|
|
||||||
- **Sound Alerts**: Optional audio notifications for reminders
|
|
||||||
|
|
||||||
### 📱 Mobile Optimized (New in v1.1.0!)
|
|
||||||
- **Responsive Design**: Fully optimized for mobile screens (tablets and phones)
|
|
||||||
- **Touch-Friendly**: Larger buttons and tap targets (44px minimum)
|
|
||||||
- **Adaptive Layouts**: Charts and visualizations scale appropriately
|
|
||||||
- **Mobile Testing**: Works great on Obsidian mobile app
|
|
||||||
|
|
||||||
### 🎨 User Experience
|
|
||||||
- **Status Bar Timer**: Timer that stays visible while you work
|
|
||||||
- **Celebration Messages**: Fun, randomized messages when you complete tasks
|
|
||||||
- **Sound Notifications**: Audio alerts for timer completion and task completion
|
|
||||||
- **Keyboard Shortcuts**: Quick access to common actions
|
|
||||||
- **Responsive Design**: Works great in any panel size
|
|
||||||
|
|
||||||
## 🚀 Installation
|
## 🚀 Installation
|
||||||
|
|
||||||
@@ -111,183 +67,43 @@ npm run build
|
|||||||
cp main.js manifest.json styles.css /path/to/your/vault/.obsidian/plugins/immerse/
|
cp main.js manifest.json styles.css /path/to/your/vault/.obsidian/plugins/immerse/
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📖 Usage
|
## 📖 Quick Start
|
||||||
|
|
||||||
### Getting Started
|
1. Click ⚡ icon in ribbon or use command palette (`Ctrl/Cmd + P` → "Open Immerse Panel")
|
||||||
|
2. Add a task with "+ Add Task" (description, time estimate, list)
|
||||||
|
3. Start timer: ▶ for Pomodoro or ⏱ for stopwatch
|
||||||
|
4. Complete and enjoy the celebration!
|
||||||
|
|
||||||
1. **Open Immerse**: Click the ⚡ icon in the ribbon or use the command palette (`Ctrl/Cmd + P` → "Open Immerse Panel")
|
## ⚙️ Configuration
|
||||||
|
|
||||||
2. **Add a Task**: Click "+ Add Task" and fill in:
|
All settings available in Settings → Immerse:
|
||||||
- Task description
|
- Pomodoro durations (work: 25min, short break: 5min, long break: 15min)
|
||||||
- Time estimate
|
- Default time estimates, sounds, celebrations
|
||||||
- List category
|
- Daily note integration (auto-log completed tasks)
|
||||||
|
- Custom lists with names, emojis, and colors
|
||||||
3. **Start Focusing**: Click ▶ on any task to start a Pomodoro session, or ⏱ for stopwatch mode
|
|
||||||
|
|
||||||
4. **Complete Tasks**: Check off tasks when done and enjoy the celebration!
|
|
||||||
|
|
||||||
### Keyboard Shortcuts
|
|
||||||
|
|
||||||
| Action | Command |
|
|
||||||
|--------|---------|
|
|
||||||
| Open Panel | `Ctrl/Cmd + P` → "Open Immerse Panel" |
|
|
||||||
| Quick Add Task | `Ctrl/Cmd + P` → "Quick Add Task" |
|
|
||||||
| Toggle Timer | `Ctrl/Cmd + P` → "Toggle Timer" |
|
|
||||||
| Complete Task | `Ctrl/Cmd + P` → "Complete Current Task" |
|
|
||||||
| Start Focus | `Ctrl/Cmd + P` → "Start Focus Mode on Next Task" |
|
|
||||||
|
|
||||||
### Timer Modes Explained
|
|
||||||
|
|
||||||
#### 🍅 Pomodoro Mode (▶ button)
|
|
||||||
Best for: Maintaining focus on challenging tasks
|
|
||||||
|
|
||||||
1. Timer counts down from your configured work duration
|
|
||||||
2. When time's up, you'll get a notification
|
|
||||||
3. Take a break (short or long, based on your settings)
|
|
||||||
4. Repeat until the task is complete
|
|
||||||
|
|
||||||
#### ⏱️ Stopwatch Mode (⏱ button)
|
|
||||||
Best for: Tracking time on open-ended tasks
|
|
||||||
|
|
||||||
1. Timer counts up from zero
|
|
||||||
2. Get alerted when you exceed your estimate
|
|
||||||
3. Stop whenever the task is complete
|
|
||||||
4. See exactly how long the task took
|
|
||||||
|
|
||||||
## ⚙️ Settings
|
|
||||||
|
|
||||||
### Pomodoro Timer
|
|
||||||
| Setting | Description | Default |
|
|
||||||
|---------|-------------|---------|
|
|
||||||
| Work Duration | Length of each work session | 25 min |
|
|
||||||
| Short Break | Length of short breaks | 5 min |
|
|
||||||
| Long Break | Length of long breaks | 15 min |
|
|
||||||
| Long Break Interval | Pomodoros before a long break | 4 |
|
|
||||||
| Auto-start Breaks | Automatically start break timer | On |
|
|
||||||
|
|
||||||
### General
|
|
||||||
| Setting | Description | Default |
|
|
||||||
|---------|-------------|---------|
|
|
||||||
| Default Time Estimate | Default estimate for new tasks | 30 min |
|
|
||||||
| Enable Sounds | Play completion/alert sounds | On |
|
|
||||||
| Enable Celebrations | Show celebration messages | On |
|
|
||||||
| Show Floating Timer | Display draggable timer widget | On |
|
|
||||||
|
|
||||||
### Daily Note Integration
|
|
||||||
|
|
||||||
| Setting | Description | Default |
|
|
||||||
|---------|-------------|---------|
|
|
||||||
| Log to Daily Note | Automatically log completed tasks to your daily note | Off |
|
|
||||||
|
|
||||||
When enabled, completed tasks are automatically appended to your daily note with:
|
|
||||||
|
|
||||||
- Task name and list category
|
|
||||||
- Time spent vs. estimated time
|
|
||||||
- Completion timestamp
|
|
||||||
- Performance indicator (under/over/on target)
|
|
||||||
|
|
||||||
**Example entry:**
|
|
||||||
|
|
||||||
```
|
|
||||||
- [x] Write project proposal | 💼 Work | ⏱️ 45min / 30min (15min over estimate) | ✅ 14:30
|
|
||||||
```
|
|
||||||
|
|
||||||
**Requirements:** The core "Daily Notes" plugin must be enabled in Obsidian settings. Immerse respects your Daily Notes configuration (folder, date format, and template).
|
|
||||||
|
|
||||||
### Lists
|
|
||||||
Customize your task lists with:
|
|
||||||
- Custom names
|
|
||||||
- Emoji icons
|
|
||||||
- Color coding
|
|
||||||
|
|
||||||
Default lists: Work 💼, Personal 🏠, Learning 📚
|
|
||||||
|
|
||||||
## 🎨 Customization
|
|
||||||
|
|
||||||
### Adding Custom Lists
|
|
||||||
1. Go to Settings → Immerse → Lists
|
|
||||||
2. Click "+ Add List"
|
|
||||||
3. Set the name, emoji, and color
|
|
||||||
4. Click Save
|
|
||||||
|
|
||||||
### Theming
|
|
||||||
Immerse respects your Obsidian theme and adapts to both light and dark modes automatically.
|
|
||||||
|
|
||||||
## 📁 Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
immerse/
|
|
||||||
├── src/
|
|
||||||
│ ├── main.ts # Main plugin class
|
|
||||||
│ ├── types.ts # TypeScript interfaces
|
|
||||||
│ ├── view.ts # Main UI view
|
|
||||||
│ └── modals.ts # Task modals
|
|
||||||
├── styles.css # Plugin styles
|
|
||||||
├── manifest.json # Obsidian plugin manifest
|
|
||||||
├── package.json # npm configuration
|
|
||||||
├── tsconfig.json # TypeScript config
|
|
||||||
├── esbuild.config.mjs # Build configuration
|
|
||||||
└── README.md # This file
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🙏 Inspiration & Credits
|
|
||||||
|
|
||||||
This plugin is **heavily inspired by [Blitzit](https://www.blitzit.app/)**, a fantastic standalone productivity app that combines task management with focused time tracking.
|
|
||||||
|
|
||||||
Blitzit's approach to productivity resonated with me:
|
|
||||||
- Simple, focused interface
|
|
||||||
- Time estimation and tracking
|
|
||||||
- Pomodoro technique integration
|
|
||||||
- Satisfying task completion experience
|
|
||||||
- Progress insights
|
|
||||||
|
|
||||||
I wanted to bring this experience directly into Obsidian, where I already manage my notes and knowledge. Immerse is my attempt to capture the essence of what makes Blitzit great while leveraging Obsidian's powerful ecosystem.
|
|
||||||
|
|
||||||
**If you're looking for a dedicated productivity app, I highly recommend checking out [Blitzit](https://www.blitzit.app/)!**
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
## 🤝 Contributing
|
||||||
|
|
||||||
Contributions are welcome! Feel free to:
|
Contributions welcome! Fork, create a feature branch, and open a PR.
|
||||||
|
|
||||||
1. Fork the repository
|
|
||||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
||||||
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
||||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
||||||
5. Open a Pull Request
|
|
||||||
|
|
||||||
### Development Setup
|
|
||||||
|
|
||||||
|
**Dev Setup:**
|
||||||
```bash
|
```bash
|
||||||
# Clone the repo
|
|
||||||
git clone https://git.cribdev.com/crib/immerse.git
|
git clone https://git.cribdev.com/crib/immerse.git
|
||||||
cd immerse
|
cd immerse
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Start development build (watches for changes)
|
|
||||||
npm run dev
|
npm run dev
|
||||||
|
|
||||||
# Create symlink to your test vault
|
|
||||||
ln -s $(pwd) /path/to/vault/.obsidian/plugins/immerse
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📜 License
|
## 📜 License
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
MIT License - see [LICENSE](LICENSE) file.
|
||||||
|
|
||||||
## 🔗 Links
|
|
||||||
|
|
||||||
- **Repository**: [https://git.cribdev.com/crib/immerse](https://git.cribdev.com/crib/immerse)
|
|
||||||
- **Issues**: [https://git.cribdev.com/crib/immerse/issues](https://git.cribdev.com/crib/immerse/issues)
|
|
||||||
- **Inspiration**: [Blitzit App](https://www.blitzit.app/)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Links:** [Repository](https://git.cribdev.com/crib/immerse) • [Issues](https://git.cribdev.com/crib/immerse/issues) • [Blitzit](https://www.blitzit.app/)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Made with ❤️ for the Obsidian community
|
Made with ❤️ for the Obsidian community
|
||||||
<br>
|
|
||||||
Inspired by <a href="https://www.blitzit.app/">Blitzit</a> ⚡
|
|
||||||
<br><br>
|
<br><br>
|
||||||
<em>✨ Vibe coded with assistance from <a href="https://claude.ai">Claude.ai</a></em>
|
<em>✨ Coded with assistance from <a href="https://claude.ai">Claude.ai</a></em>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
# Immerse v1.1.0 Release Notes
|
|
||||||
|
|
||||||
Release Date: November 24, 2024
|
|
||||||
|
|
||||||
## 🎉 What's New
|
|
||||||
|
|
||||||
### 📈 Reporting & Analytics System
|
|
||||||
A complete analytics dashboard to track your productivity over time!
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- **Key Metrics Dashboard**: View tasks completed, tasks per day, hours per day, minutes per task, and current day streak
|
|
||||||
- **Visual Charts**: Beautiful pie chart showing distribution of tasks, hours, and pomodoros
|
|
||||||
- **Time by List Analysis**: See exactly how much time you spend on different task categories
|
|
||||||
- **Productivity Insights**: Discover your most productive hour, day, and month
|
|
||||||
- **Daily Breakdown**: Visual bar charts showing the last 10 days of activity with tasks, hours, and pomodoros
|
|
||||||
- **Flexible Filtering**: Quick filters (Today, Last 7/30/90 days) plus custom date range selection
|
|
||||||
- **Report View**: Dedicated view accessible via "📊 Reports" button
|
|
||||||
|
|
||||||
### 🗓️ Task Scheduling & Reminders
|
|
||||||
Never miss a deadline with the new scheduling system!
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- **Schedule Tasks**: Set specific date and time when tasks are due
|
|
||||||
- **Smart Reminders**: Choose notification timing (5, 10, 15, 30, or 60 minutes before due time)
|
|
||||||
- **Visual Indicators**:
|
|
||||||
- Blue 📅 badge shows scheduled date/time
|
|
||||||
- Red ⚠️ pulsing "OVERDUE" badge for past-due tasks
|
|
||||||
- Red left border highlight on overdue tasks
|
|
||||||
- **Background Monitoring**: Automatic checks every 30 seconds for upcoming/overdue tasks
|
|
||||||
- **Startup Alerts**: Get notified when opening Obsidian if tasks are overdue
|
|
||||||
- **Sound Notifications**: Optional audio alerts for reminders (respects sound settings)
|
|
||||||
- **Duplicate Prevention**: Smart tracking ensures you don't get reminded multiple times
|
|
||||||
|
|
||||||
### 📱 Mobile Optimization
|
|
||||||
Full responsive design for excellent mobile experience!
|
|
||||||
|
|
||||||
**Improvements:**
|
|
||||||
- **Responsive Layouts**: All UI elements adapt to mobile screen sizes
|
|
||||||
- **Touch-Friendly**:
|
|
||||||
- Minimum 44px tap targets (Apple's recommended size)
|
|
||||||
- Larger checkboxes (28px on mobile)
|
|
||||||
- Larger action buttons
|
|
||||||
- **Optimized Views**:
|
|
||||||
- Pie charts scale down appropriately (220px on tablets, 180px on phones)
|
|
||||||
- Stats grid adapts (2 columns on tablets, 1 column on small phones)
|
|
||||||
- Daily breakdown bars stack vertically on mobile
|
|
||||||
- Modal buttons become full-width and stack
|
|
||||||
- **Always Visible Actions**: Task action buttons always visible on mobile (no hover needed)
|
|
||||||
- **Adaptive Typography**: Font sizes scale appropriately for readability
|
|
||||||
|
|
||||||
## 🔧 Technical Improvements
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
- Efficient report generation with on-demand calculation
|
|
||||||
- Optimized reminder checks with 30-second intervals
|
|
||||||
- Smart caching to prevent duplicate notifications
|
|
||||||
|
|
||||||
### Code Structure
|
|
||||||
- New `reportView.ts` file for analytics view
|
|
||||||
- Enhanced type definitions for scheduling and reporting
|
|
||||||
- Improved data tracking for historical statistics
|
|
||||||
- Better separation of concerns
|
|
||||||
|
|
||||||
### Compatibility
|
|
||||||
- Fully compatible with Obsidian desktop and mobile
|
|
||||||
- Works with both light and dark themes
|
|
||||||
- Respects user's sound and notification preferences
|
|
||||||
|
|
||||||
## 📊 Statistics Tracked
|
|
||||||
|
|
||||||
The plugin now tracks comprehensive statistics including:
|
|
||||||
- Total tasks completed
|
|
||||||
- Total time spent (all-time and daily)
|
|
||||||
- Total pomodoros completed
|
|
||||||
- Tasks per day average
|
|
||||||
- Hours per day average
|
|
||||||
- Minutes per task average
|
|
||||||
- Current day streak
|
|
||||||
- Most productive hour/day/month
|
|
||||||
- Time breakdown by task list
|
|
||||||
|
|
||||||
## 🎨 UI/UX Enhancements
|
|
||||||
|
|
||||||
- New "📊 Reports" button in main view header
|
|
||||||
- Modern, clean report interface with gradient accents
|
|
||||||
- Color-coded progress bars using list colors
|
|
||||||
- Hover effects and animations for better interactivity
|
|
||||||
- Responsive filter controls
|
|
||||||
- Improved date picker styling and alignment
|
|
||||||
|
|
||||||
## 🐛 Bug Fixes
|
|
||||||
|
|
||||||
- Fixed pie chart rendering issues (now uses proper color values)
|
|
||||||
- Improved gradient calculation for proper segment display
|
|
||||||
- Better normalization of metrics (using time-based calculations)
|
|
||||||
- Fixed date input alignment in report filters
|
|
||||||
|
|
||||||
## ⚙️ Settings Updates
|
|
||||||
|
|
||||||
### New Settings:
|
|
||||||
- **Enable Reminders**: Toggle reminder notifications on/off
|
|
||||||
- **Default Reminder Minutes**: Set default reminder time for new scheduled tasks (default: 30 minutes)
|
|
||||||
|
|
||||||
### Updated Settings:
|
|
||||||
All existing settings remain compatible with no migration required.
|
|
||||||
|
|
||||||
## 📱 Mobile Testing Recommendations
|
|
||||||
|
|
||||||
To ensure the best experience on mobile:
|
|
||||||
|
|
||||||
1. **Test on actual device**: Install Obsidian mobile and test the plugin
|
|
||||||
2. **Browser DevTools**: Use Chrome/Edge DevTools device emulation
|
|
||||||
3. **Responsive breakpoints**:
|
|
||||||
- Desktop: >768px
|
|
||||||
- Tablet: ≤768px
|
|
||||||
- Phone: ≤480px
|
|
||||||
|
|
||||||
## 🔄 Upgrade Instructions
|
|
||||||
|
|
||||||
### From v1.0.x:
|
|
||||||
|
|
||||||
1. **Backup your data** (optional but recommended):
|
|
||||||
- Your tasks and settings are in `.obsidian/plugins/immerse/data.json`
|
|
||||||
- Make a copy before updating
|
|
||||||
|
|
||||||
2. **Update files**:
|
|
||||||
- Copy `main.js`, `manifest.json`, and `styles.css` to your vault
|
|
||||||
- **DO NOT replace `data.json`** - this contains your tasks!
|
|
||||||
|
|
||||||
3. **Reload Obsidian**:
|
|
||||||
- Press Ctrl/Cmd+R to reload
|
|
||||||
- Or restart Obsidian
|
|
||||||
|
|
||||||
4. **Verify**:
|
|
||||||
- Check that your tasks are still there
|
|
||||||
- Click "📊 Reports" to see your new analytics
|
|
||||||
- Try scheduling a task with a reminder
|
|
||||||
|
|
||||||
## 🚀 What's Next (v1.2.0)
|
|
||||||
|
|
||||||
Future enhancements we're considering:
|
|
||||||
- Calendar view integration
|
|
||||||
- Export reports (PDF/CSV)
|
|
||||||
- More chart types and visualizations
|
|
||||||
- Task templates
|
|
||||||
- Recurring tasks
|
|
||||||
- Advanced filtering options
|
|
||||||
|
|
||||||
## 💡 Feedback
|
|
||||||
|
|
||||||
Found a bug or have a feature request?
|
|
||||||
- Open an issue: [https://git.cribdev.com/crib/immerse/issues](https://git.cribdev.com/crib/immerse/issues)
|
|
||||||
- Join the discussion in the repository
|
|
||||||
|
|
||||||
## 🙏 Credits
|
|
||||||
|
|
||||||
Special thanks to:
|
|
||||||
- [Blitzit](https://www.blitzit.app/) for continued inspiration
|
|
||||||
- The Obsidian community for feedback and support
|
|
||||||
- Claude.ai for development assistance
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Enjoy v1.1.0!** ⚡
|
|
||||||
|
|
||||||
Made with ❤️ for the Obsidian community
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"id": "immerse",
|
"id": "immerse",
|
||||||
"name": "Immerse",
|
"name": "Immerse",
|
||||||
"version": "1.1.0",
|
"version": "1.1.5",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "A Blitzit-inspired task management and focus timer plugin. Plan your day, track time with Pomodoro technique, and crush your tasks with satisfying checkoffs.",
|
"description": "A Blitzit-inspired task management and focus timer plugin. Plan your day, track time with Pomodoro technique, and crush your tasks with satisfying checkoffs.",
|
||||||
"author": "Crib",
|
"author": "Crib",
|
||||||
"authorUrl": "https://git.cribdev.com/crib",
|
"authorUrl": "https://git.cribdev.com/crib",
|
||||||
"fundingUrl": "",
|
"fundingUrl": "",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immerse",
|
"name": "immerse",
|
||||||
"version": "1.1.0",
|
"version": "1.1.5",
|
||||||
"description": "A Blitzit-inspired task management and focus timer plugin for Obsidian",
|
"description": "A Blitzit-inspired task management and focus timer plugin for Obsidian",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
97
src/main.ts
97
src/main.ts
@@ -22,7 +22,7 @@ import {
|
|||||||
|
|
||||||
import { ImmerseView } from './view';
|
import { ImmerseView } from './view';
|
||||||
import { ReportView, VIEW_TYPE_REPORT } from './reportView';
|
import { ReportView, VIEW_TYPE_REPORT } from './reportView';
|
||||||
import { QuickAddTaskModal } from './modals';
|
import { QuickAddTaskModal, EmojiPickerModal } from './modals';
|
||||||
|
|
||||||
// ============ Main Plugin Class ============
|
// ============ Main Plugin Class ============
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
currentTimerSeconds: number = 0;
|
currentTimerSeconds: number = 0;
|
||||||
isTimerRunning: boolean = false;
|
isTimerRunning: boolean = false;
|
||||||
isBreakMode: boolean = false;
|
isBreakMode: boolean = false;
|
||||||
|
isStopwatchMode: boolean = false;
|
||||||
activeTaskId: string | null = null;
|
activeTaskId: string | null = null;
|
||||||
pomodoroCount: number = 0;
|
pomodoroCount: number = 0;
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
// Focus time tracking (in seconds for accuracy)
|
// Focus time tracking (in seconds for accuracy)
|
||||||
private focusSecondsToday: number = 0;
|
private focusSecondsToday: number = 0;
|
||||||
private secondsWorkedOnCurrentTask: number = 0;
|
private secondsWorkedOnCurrentTask: number = 0;
|
||||||
|
private sessionStartSeconds: number = 0; // Track seconds at start of current session
|
||||||
|
|
||||||
// Status bar element
|
// Status bar element
|
||||||
statusBarEl: HTMLElement | null = null;
|
statusBarEl: HTMLElement | null = null;
|
||||||
@@ -53,16 +55,28 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
private reminderCheckInterval: number | null = null;
|
private reminderCheckInterval: number | null = null;
|
||||||
private notifiedReminders: Set<string> = new Set(); // Track which reminders have been shown
|
private notifiedReminders: Set<string> = new Set(); // Track which reminders have been shown
|
||||||
|
|
||||||
|
// Daily reset check interval
|
||||||
|
private dailyResetCheckInterval: number | null = null;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
await this.loadAllData();
|
await this.loadAllData();
|
||||||
|
|
||||||
// Check and reset daily stats
|
// Check and reset daily stats
|
||||||
this.checkDailyReset();
|
this.checkDailyReset();
|
||||||
|
|
||||||
|
// Start daily reset check interval (check every 60 seconds)
|
||||||
|
this.dailyResetCheckInterval = window.setInterval(() => {
|
||||||
|
this.checkDailyReset();
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
// Handle visibility changes to sync timer when app comes back to foreground
|
// Handle visibility changes to sync timer when app comes back to foreground
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (!document.hidden && this.isTimerRunning) {
|
if (!document.hidden) {
|
||||||
this.syncTimerFromTimestamp();
|
// Check for day change when app becomes visible
|
||||||
|
this.checkDailyReset();
|
||||||
|
if (this.isTimerRunning) {
|
||||||
|
this.syncTimerFromTimestamp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,6 +148,12 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
onunload() {
|
onunload() {
|
||||||
this.stopTimer();
|
this.stopTimer();
|
||||||
this.stopReminderSystem();
|
this.stopReminderSystem();
|
||||||
|
|
||||||
|
// Stop daily reset check interval
|
||||||
|
if (this.dailyResetCheckInterval) {
|
||||||
|
window.clearInterval(this.dailyResetCheckInterval);
|
||||||
|
this.dailyResetCheckInterval = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAllData() {
|
async loadAllData() {
|
||||||
@@ -278,6 +298,12 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
this.data.completedToday++;
|
this.data.completedToday++;
|
||||||
this.data.lastActiveDate = new Date().toDateString();
|
this.data.lastActiveDate = new Date().toDateString();
|
||||||
|
|
||||||
|
// Add focus time from this session (only on completion)
|
||||||
|
if (this.activeTaskId === taskId && this.sessionStartSeconds !== undefined) {
|
||||||
|
const sessionTime = this.secondsWorkedOnCurrentTask - this.sessionStartSeconds;
|
||||||
|
this.focusSecondsToday += sessionTime;
|
||||||
|
}
|
||||||
|
|
||||||
// Archive task for historical reporting
|
// Archive task for historical reporting
|
||||||
this.archiveCompletedTask(task);
|
this.archiveCompletedTask(task);
|
||||||
|
|
||||||
@@ -544,13 +570,14 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
const task = this.data.tasks.find(t => t.id === taskId);
|
const task = this.data.tasks.find(t => t.id === taskId);
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
|
|
||||||
// Stop any existing timer
|
// Stop any existing timer, preserving actual time
|
||||||
this.stopTimer();
|
this.stopTimer(true);
|
||||||
|
|
||||||
// Set active task
|
// Set active task
|
||||||
this.activeTaskId = taskId;
|
this.activeTaskId = taskId;
|
||||||
task.isActive = true;
|
task.isActive = true;
|
||||||
this.isBreakMode = false;
|
this.isBreakMode = false;
|
||||||
|
this.isStopwatchMode = true;
|
||||||
this.currentTimerSeconds = 0;
|
this.currentTimerSeconds = 0;
|
||||||
this.isTimerRunning = true;
|
this.isTimerRunning = true;
|
||||||
this.secondsWorkedOnCurrentTask = task.actualMinutes * 60;
|
this.secondsWorkedOnCurrentTask = task.actualMinutes * 60;
|
||||||
@@ -561,6 +588,7 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
|
|
||||||
// Store initial values
|
// Store initial values
|
||||||
const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
|
const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
|
||||||
|
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; // Track session start for focus time
|
||||||
let alertShown = false;
|
let alertShown = false;
|
||||||
|
|
||||||
// Full refresh to show the active task card
|
// Full refresh to show the active task card
|
||||||
@@ -581,10 +609,6 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
|
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
|
||||||
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
|
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
|
||||||
|
|
||||||
// Update focus time
|
|
||||||
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
|
|
||||||
this.focusSecondsToday = newFocusSeconds;
|
|
||||||
|
|
||||||
// Light update - only timer display, no full refresh
|
// Light update - only timer display, no full refresh
|
||||||
this.updateStatusBar();
|
this.updateStatusBar();
|
||||||
this.updateTimerDisplay();
|
this.updateTimerDisplay();
|
||||||
@@ -606,10 +630,11 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
const task = this.data.tasks.find(t => t.id === taskId);
|
const task = this.data.tasks.find(t => t.id === taskId);
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
|
|
||||||
this.stopTimer();
|
this.stopTimer(true);
|
||||||
this.activeTaskId = taskId;
|
this.activeTaskId = taskId;
|
||||||
task.isActive = true;
|
task.isActive = true;
|
||||||
this.isBreakMode = false;
|
this.isBreakMode = false;
|
||||||
|
this.isStopwatchMode = false;
|
||||||
this.currentTimerSeconds = this.settings.pomodoroWorkMinutes * 60;
|
this.currentTimerSeconds = this.settings.pomodoroWorkMinutes * 60;
|
||||||
this.isTimerRunning = true;
|
this.isTimerRunning = true;
|
||||||
|
|
||||||
@@ -623,6 +648,7 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
|
|
||||||
// Store the initial seconds worked to calculate delta
|
// Store the initial seconds worked to calculate delta
|
||||||
const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
|
const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
|
||||||
|
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; // Track session start for focus time
|
||||||
|
|
||||||
// Full refresh to show the active task card
|
// Full refresh to show the active task card
|
||||||
this.refreshView();
|
this.refreshView();
|
||||||
@@ -645,10 +671,6 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
if (task.actualMinutes !== actualMinutes) {
|
if (task.actualMinutes !== actualMinutes) {
|
||||||
task.actualMinutes = actualMinutes;
|
task.actualMinutes = actualMinutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update focus time based on elapsed seconds
|
|
||||||
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
|
|
||||||
this.focusSecondsToday = newFocusSeconds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Light update - only timer display, no full refresh
|
// Light update - only timer display, no full refresh
|
||||||
@@ -766,24 +788,26 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
const elapsedMs = now - this.timerStartTimestamp;
|
const elapsedMs = now - this.timerStartTimestamp;
|
||||||
const elapsedSeconds = Math.floor(elapsedMs / 1000);
|
const elapsedSeconds = Math.floor(elapsedMs / 1000);
|
||||||
|
|
||||||
// Update timer (countdown from paused position)
|
// Update timer based on mode
|
||||||
this.currentTimerSeconds = Math.max(0, this.pausedTimeRemaining - elapsedSeconds);
|
if (this.pausedTimeRemaining === 0 || this.isStopwatchMode) {
|
||||||
|
// Stopwatch mode - count up from paused position
|
||||||
|
this.currentTimerSeconds = this.pausedTimeRemaining + elapsedSeconds;
|
||||||
|
} else {
|
||||||
|
// Countdown mode (pomodoro/break) - count down from paused position
|
||||||
|
this.currentTimerSeconds = Math.max(0, this.pausedTimeRemaining - elapsedSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
if (task && !this.isBreakMode) {
|
if (task && !this.isBreakMode) {
|
||||||
// Update actual time worked
|
// Update actual time worked
|
||||||
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
|
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
|
||||||
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
|
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
|
||||||
|
|
||||||
// Update focus time
|
|
||||||
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
|
|
||||||
this.focusSecondsToday = newFocusSeconds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Light update - only timer display
|
// Light update - only timer display
|
||||||
this.updateStatusBar();
|
this.updateStatusBar();
|
||||||
this.updateTimerDisplay();
|
this.updateTimerDisplay();
|
||||||
|
|
||||||
if (this.currentTimerSeconds <= 0) {
|
if (this.currentTimerSeconds <= 0 && !this.isStopwatchMode) {
|
||||||
this.handlePomodoroEnd();
|
this.handlePomodoroEnd();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -796,7 +820,7 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
this.refreshView();
|
this.refreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
stopTimer() {
|
stopTimer(preserveActualTime: boolean = false) {
|
||||||
if (this.timerInterval) {
|
if (this.timerInterval) {
|
||||||
window.clearInterval(this.timerInterval);
|
window.clearInterval(this.timerInterval);
|
||||||
this.timerInterval = null;
|
this.timerInterval = null;
|
||||||
@@ -806,15 +830,18 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
const task = this.data.tasks.find(t => t.id === this.activeTaskId);
|
const task = this.data.tasks.find(t => t.id === this.activeTaskId);
|
||||||
if (task) {
|
if (task) {
|
||||||
task.isActive = false;
|
task.isActive = false;
|
||||||
// Reset actual time when manually stopping (not after a break)
|
// Only reset actual time when manually stopping (not when resuming after break)
|
||||||
// This allows starting fresh next time
|
if (!preserveActualTime) {
|
||||||
task.actualMinutes = 0;
|
task.actualMinutes = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isTimerRunning = false;
|
this.isTimerRunning = false;
|
||||||
|
this.isStopwatchMode = false;
|
||||||
this.activeTaskId = null;
|
this.activeTaskId = null;
|
||||||
this.secondsWorkedOnCurrentTask = 0;
|
this.secondsWorkedOnCurrentTask = 0;
|
||||||
|
this.sessionStartSeconds = 0;
|
||||||
this.timerStartTimestamp = 0;
|
this.timerStartTimestamp = 0;
|
||||||
this.pausedTimeRemaining = 0;
|
this.pausedTimeRemaining = 0;
|
||||||
this.updateStatusBar();
|
this.updateStatusBar();
|
||||||
@@ -1384,12 +1411,20 @@ class ImmerseSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.settings.lists[index].name = value;
|
this.plugin.settings.lists[index].name = value;
|
||||||
await this.plugin.saveAllData();
|
await this.plugin.saveAllData();
|
||||||
}))
|
}))
|
||||||
.addText(text => text
|
.addButton(btn => btn
|
||||||
.setValue(list.icon)
|
.setButtonText(list.icon || '📁')
|
||||||
.setPlaceholder('Emoji')
|
.setTooltip('Choose emoji')
|
||||||
.onChange(async value => {
|
.onClick(() => {
|
||||||
this.plugin.settings.lists[index].icon = value;
|
const modal = new EmojiPickerModal(
|
||||||
await this.plugin.saveAllData();
|
this.app,
|
||||||
|
list.icon,
|
||||||
|
async (emoji) => {
|
||||||
|
this.plugin.settings.lists[index].icon = emoji;
|
||||||
|
await this.plugin.saveAllData();
|
||||||
|
this.display();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
modal.open();
|
||||||
}))
|
}))
|
||||||
.addColorPicker(picker => picker
|
.addColorPicker(picker => picker
|
||||||
.setValue(list.color)
|
.setValue(list.color)
|
||||||
|
|||||||
398
src/modals.ts
398
src/modals.ts
@@ -8,6 +8,293 @@ import {
|
|||||||
import { ImmerseTask } from './types';
|
import { ImmerseTask } from './types';
|
||||||
import ImmersePlugin from './main';
|
import ImmersePlugin from './main';
|
||||||
|
|
||||||
|
// Emoji search keywords mapping
|
||||||
|
const EMOJI_KEYWORDS: { [key: string]: string } = {
|
||||||
|
'💼': 'briefcase work business office job',
|
||||||
|
'🏠': 'home house',
|
||||||
|
'📚': 'books study read library',
|
||||||
|
'🎯': 'target goal aim dart',
|
||||||
|
'✅': 'check mark done complete checkbox tick',
|
||||||
|
'📝': 'memo note write pencil',
|
||||||
|
'💡': 'light bulb idea',
|
||||||
|
'🔥': 'fire hot flame',
|
||||||
|
'⚡': 'lightning bolt electric zap',
|
||||||
|
'🎨': 'art paint palette',
|
||||||
|
'🏆': 'trophy award win',
|
||||||
|
'💪': 'muscle strong flex',
|
||||||
|
'🚀': 'rocket ship launch',
|
||||||
|
'📊': 'chart graph data',
|
||||||
|
'⏰': 'clock time alarm',
|
||||||
|
'💰': 'money bag cash dollar',
|
||||||
|
'😀': 'smile happy face grin',
|
||||||
|
'😃': 'smile happy grin',
|
||||||
|
'😄': 'smile happy laugh',
|
||||||
|
'😁': 'grin smile happy',
|
||||||
|
'😆': 'laugh smile happy',
|
||||||
|
'😅': 'sweat smile nervous',
|
||||||
|
'🤣': 'laugh rolling floor',
|
||||||
|
'😂': 'tears joy laugh cry',
|
||||||
|
'🙂': 'smile happy slight',
|
||||||
|
'🙃': 'upside down smile',
|
||||||
|
'😉': 'wink smile flirt',
|
||||||
|
'😊': 'blush smile happy',
|
||||||
|
'😇': 'angel halo smile',
|
||||||
|
'🥰': 'love hearts smile',
|
||||||
|
'😍': 'love heart eyes smile',
|
||||||
|
'🤩': 'star eyes excited',
|
||||||
|
'😘': 'kiss love heart',
|
||||||
|
'😗': 'kiss love',
|
||||||
|
'😚': 'kiss love',
|
||||||
|
'😙': 'kiss love smile',
|
||||||
|
'🥲': 'smile tear cry happy',
|
||||||
|
'😋': 'yum delicious smile',
|
||||||
|
'😛': 'tongue playful',
|
||||||
|
'😜': 'wink tongue playful',
|
||||||
|
'🤪': 'crazy wild eyes',
|
||||||
|
'😝': 'tongue eyes squint',
|
||||||
|
'🤑': 'money dollar rich',
|
||||||
|
'🤗': 'hug smile',
|
||||||
|
'🤭': 'hand over mouth giggle',
|
||||||
|
'🤫': 'shush quiet secret',
|
||||||
|
'🤔': 'think hmm wonder',
|
||||||
|
'🤐': 'zipper mouth secret',
|
||||||
|
'🤨': 'eyebrow raised skeptical',
|
||||||
|
'😐': 'neutral meh',
|
||||||
|
'😑': 'expressionless blank',
|
||||||
|
'😶': 'no mouth silent',
|
||||||
|
'😏': 'smirk confident',
|
||||||
|
'😒': 'unamused annoyed',
|
||||||
|
'🙄': 'eye roll annoyed',
|
||||||
|
'😬': 'grimace awkward',
|
||||||
|
'🤥': 'liar lying pinocchio',
|
||||||
|
'😌': 'relieved content',
|
||||||
|
'😔': 'sad pensive',
|
||||||
|
'😪': 'sleepy tired',
|
||||||
|
'🤤': 'drool sleep',
|
||||||
|
'😴': 'sleep zzz',
|
||||||
|
'😷': 'mask sick medical',
|
||||||
|
'🤒': 'sick thermometer',
|
||||||
|
'🤕': 'injured bandage',
|
||||||
|
'🤢': 'nausea sick',
|
||||||
|
'🤮': 'vomit sick',
|
||||||
|
'🤧': 'sneeze sick tissue',
|
||||||
|
'🥵': 'hot sweat',
|
||||||
|
'🥶': 'cold freeze',
|
||||||
|
'😎': 'cool sunglasses',
|
||||||
|
'🤓': 'nerd glasses',
|
||||||
|
'🧐': 'monocle fancy',
|
||||||
|
'😕': 'confused uncertain',
|
||||||
|
'😟': 'worried concerned',
|
||||||
|
'🙁': 'frown sad',
|
||||||
|
'☹️': 'frown sad',
|
||||||
|
'😮': 'wow surprised',
|
||||||
|
'😯': 'surprised shocked',
|
||||||
|
'😲': 'shocked astonished',
|
||||||
|
'😳': 'flushed embarrassed',
|
||||||
|
'🥺': 'pleading puppy eyes',
|
||||||
|
'😦': 'frown worried',
|
||||||
|
'😧': 'anguished worried',
|
||||||
|
'😨': 'fearful scared',
|
||||||
|
'😰': 'anxious sweat',
|
||||||
|
'😥': 'sad sweat',
|
||||||
|
'😢': 'cry tear sad',
|
||||||
|
'😭': 'cry tears sob',
|
||||||
|
'😱': 'scream fear',
|
||||||
|
'😖': 'confounded',
|
||||||
|
'😣': 'persevere struggle',
|
||||||
|
'😞': 'disappointed sad',
|
||||||
|
'😓': 'downcast sweat',
|
||||||
|
'😩': 'weary tired',
|
||||||
|
'😫': 'tired exhausted',
|
||||||
|
'🥱': 'yawn tired',
|
||||||
|
'😤': 'triumph proud',
|
||||||
|
'😡': 'angry mad rage',
|
||||||
|
'😠': 'angry mad',
|
||||||
|
'🤬': 'cursing swearing angry',
|
||||||
|
'😈': 'devil smiling evil',
|
||||||
|
'👿': 'devil angry evil',
|
||||||
|
'💀': 'skull death',
|
||||||
|
'☠️': 'skull crossbones death',
|
||||||
|
'💩': 'poop poo',
|
||||||
|
'🤡': 'clown funny',
|
||||||
|
'👹': 'ogre monster',
|
||||||
|
'👺': 'goblin monster',
|
||||||
|
'👻': 'ghost boo',
|
||||||
|
'👽': 'alien extraterrestrial',
|
||||||
|
'👾': 'alien monster game',
|
||||||
|
'🤖': 'robot bot',
|
||||||
|
'❤️': 'red heart love',
|
||||||
|
'🧡': 'orange heart love',
|
||||||
|
'💛': 'yellow heart love',
|
||||||
|
'💚': 'green heart love',
|
||||||
|
'💙': 'blue heart love',
|
||||||
|
'💜': 'purple heart love',
|
||||||
|
'🤎': 'brown heart love',
|
||||||
|
'🖤': 'black heart love',
|
||||||
|
'🤍': 'white heart love',
|
||||||
|
'💔': 'broken heart sad',
|
||||||
|
'❣️': 'heart exclamation love',
|
||||||
|
'💕': 'two hearts love',
|
||||||
|
'💞': 'revolving hearts love',
|
||||||
|
'💓': 'beating heart love',
|
||||||
|
'💗': 'growing heart love',
|
||||||
|
'💖': 'sparkling heart love',
|
||||||
|
'💘': 'arrow heart love cupid',
|
||||||
|
'💝': 'heart box gift love',
|
||||||
|
'💟': 'heart decoration love',
|
||||||
|
'❤️🔥': 'heart fire love passion',
|
||||||
|
'❤️🩹': 'heart bandage healing',
|
||||||
|
'💌': 'love letter heart',
|
||||||
|
'💋': 'kiss lips',
|
||||||
|
'💑': 'couple love kiss',
|
||||||
|
'💏': 'kiss couple love',
|
||||||
|
'👋': 'wave hand hello goodbye',
|
||||||
|
'🤚': 'raised hand back',
|
||||||
|
'🖐️': 'hand fingers spread',
|
||||||
|
'✋': 'raised hand stop',
|
||||||
|
'🖖': 'vulcan salute spock',
|
||||||
|
'👌': 'ok okay hand',
|
||||||
|
'🤌': 'pinched fingers italian',
|
||||||
|
'🤏': 'pinching hand small',
|
||||||
|
'✌️': 'peace victory hand',
|
||||||
|
'🤞': 'crossed fingers luck',
|
||||||
|
'🤟': 'love you hand',
|
||||||
|
'🤘': 'rock on horns',
|
||||||
|
'🤙': 'call me hang loose',
|
||||||
|
'👈': 'left point finger',
|
||||||
|
'👉': 'right point finger',
|
||||||
|
'👆': 'up point finger',
|
||||||
|
'🖕': 'middle finger rude',
|
||||||
|
'👇': 'down point finger',
|
||||||
|
'☝️': 'up point finger',
|
||||||
|
'👍': 'thumbs up yes good',
|
||||||
|
'👎': 'thumbs down no bad',
|
||||||
|
'✊': 'fist hand',
|
||||||
|
'👊': 'fist bump punch',
|
||||||
|
'🤛': 'left fist bump',
|
||||||
|
'🤜': 'right fist bump',
|
||||||
|
'👏': 'clap applause',
|
||||||
|
'🙌': 'raising hands celebration',
|
||||||
|
'👐': 'open hands',
|
||||||
|
'🤲': 'palms together pray',
|
||||||
|
'🤝': 'handshake deal',
|
||||||
|
'🙏': 'pray please thank',
|
||||||
|
'✍️': 'writing hand',
|
||||||
|
'💅': 'nail polish manicure',
|
||||||
|
'🤳': 'selfie camera phone',
|
||||||
|
'🐶': 'dog puppy pet',
|
||||||
|
'🐱': 'cat kitty pet',
|
||||||
|
'🐭': 'mouse rat',
|
||||||
|
'🐹': 'hamster pet',
|
||||||
|
'🐰': 'rabbit bunny',
|
||||||
|
'🦊': 'fox',
|
||||||
|
'🐻': 'bear',
|
||||||
|
'🐼': 'panda bear',
|
||||||
|
'🐨': 'koala bear',
|
||||||
|
'🐯': 'tiger face',
|
||||||
|
'🦁': 'lion face',
|
||||||
|
'🐮': 'cow face',
|
||||||
|
'🐷': 'pig face',
|
||||||
|
'🐸': 'frog face',
|
||||||
|
'🐵': 'monkey face',
|
||||||
|
'🍎': 'apple red fruit',
|
||||||
|
'🍊': 'orange fruit',
|
||||||
|
'🍋': 'lemon fruit',
|
||||||
|
'🍌': 'banana fruit',
|
||||||
|
'🍉': 'watermelon fruit',
|
||||||
|
'🍇': 'grapes fruit',
|
||||||
|
'🍓': 'strawberry fruit',
|
||||||
|
'🍒': 'cherry fruit',
|
||||||
|
'🍑': 'peach fruit',
|
||||||
|
'🥭': 'mango fruit',
|
||||||
|
'🍍': 'pineapple fruit',
|
||||||
|
'🥥': 'coconut fruit',
|
||||||
|
'🥝': 'kiwi fruit',
|
||||||
|
'🍅': 'tomato vegetable',
|
||||||
|
'🥑': 'avocado fruit',
|
||||||
|
'🍞': 'bread food',
|
||||||
|
'⚽': 'soccer ball football',
|
||||||
|
'🏀': 'basketball ball',
|
||||||
|
'🏈': 'american football',
|
||||||
|
'⚾': 'baseball ball',
|
||||||
|
'🎾': 'tennis ball',
|
||||||
|
'🏐': 'volleyball ball',
|
||||||
|
'🚗': 'car auto vehicle',
|
||||||
|
'🚕': 'taxi car',
|
||||||
|
'🚙': 'suv car vehicle',
|
||||||
|
'🚌': 'bus vehicle',
|
||||||
|
'🚎': 'trolleybus bus',
|
||||||
|
'🏎️': 'racing car fast',
|
||||||
|
'🚓': 'police car cop',
|
||||||
|
'🚑': 'ambulance emergency',
|
||||||
|
'🚒': 'fire truck engine',
|
||||||
|
'🚲': 'bicycle bike',
|
||||||
|
'✈️': 'airplane plane flight',
|
||||||
|
'💻': 'laptop computer',
|
||||||
|
'⌨️': 'keyboard computer',
|
||||||
|
'🖱️': 'mouse computer',
|
||||||
|
'🖥️': 'desktop computer',
|
||||||
|
'🖨️': 'printer',
|
||||||
|
'📱': 'phone mobile iphone',
|
||||||
|
'📞': 'phone telephone',
|
||||||
|
'☎️': 'telephone phone',
|
||||||
|
'📺': 'tv television',
|
||||||
|
'📻': 'radio',
|
||||||
|
'📁': 'folder file',
|
||||||
|
'📂': 'open folder file',
|
||||||
|
'📅': 'calendar date',
|
||||||
|
'📆': 'calendar date',
|
||||||
|
'📈': 'chart up graph',
|
||||||
|
'📉': 'chart down graph',
|
||||||
|
'📌': 'pushpin pin',
|
||||||
|
'📍': 'pin location map',
|
||||||
|
'📎': 'paperclip clip',
|
||||||
|
'🎵': 'music note',
|
||||||
|
'🎶': 'music notes',
|
||||||
|
'🎼': 'musical score',
|
||||||
|
'🎹': 'piano keyboard music',
|
||||||
|
'🎸': 'guitar music',
|
||||||
|
'🎺': 'trumpet music',
|
||||||
|
'🎷': 'saxophone music',
|
||||||
|
'🥁': 'drum music',
|
||||||
|
'🎤': 'microphone mic sing',
|
||||||
|
'🎧': 'headphones music',
|
||||||
|
'🔊': 'speaker loud volume',
|
||||||
|
'❌': 'cross x no cancel',
|
||||||
|
'⚠️': 'warning caution alert',
|
||||||
|
'🔴': 'red circle',
|
||||||
|
'🟢': 'green circle',
|
||||||
|
'🔵': 'blue circle',
|
||||||
|
'🟡': 'yellow circle',
|
||||||
|
'🟣': 'purple circle',
|
||||||
|
'⚫': 'black circle',
|
||||||
|
'⚪': 'white circle',
|
||||||
|
'🟤': 'brown circle',
|
||||||
|
'🔺': 'triangle red up',
|
||||||
|
'🔻': 'triangle red down',
|
||||||
|
'🔸': 'diamond orange small',
|
||||||
|
'🔹': 'diamond blue small',
|
||||||
|
'🔶': 'diamond orange large',
|
||||||
|
'🔷': 'diamond blue large',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emoji categories for picker
|
||||||
|
const EMOJI_CATEGORIES = {
|
||||||
|
'⭐ Frequently Used': ['💼', '🏠', '📚', '🎯', '✅', '📝', '💡', '🔥', '⚡', '🎨', '🏆', '💪', '🚀', '📊', '⏰', '💰'],
|
||||||
|
'😀 Smileys & Emotion': ['😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂', '🙂', '🙃', '😉', '😊', '😇', '🥰', '😍', '🤩', '😘', '😗', '😚', '😙', '🥲', '😋', '😛', '😜', '🤪', '😝', '🤑', '🤗', '🤭', '🤫', '🤔', '🤐', '🤨', '😐', '😑', '😶', '😏', '😒', '🙄', '😬', '🤥', '😌', '😔', '😪', '🤤', '😴', '😷', '🤒', '🤕', '🤢', '🤮', '🤧', '🥵', '🥶', '😎', '🤓', '🧐', '😕', '😟', '🙁', '☹️', '😮', '😯', '😲', '😳', '🥺', '😦', '😧', '😨', '😰', '😥', '😢', '😭', '😱', '😖', '😣', '😞', '😓', '😩', '😫', '🥱', '😤', '😡', '😠', '🤬', '😈', '👿', '💀', '☠️', '💩', '🤡', '👹', '👺', '👻', '👽', '👾', '🤖'],
|
||||||
|
'❤️ Hearts & Love': ['❤️', '🧡', '💛', '💚', '💙', '💜', '🤎', '🖤', '🤍', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟', '❤️🔥', '❤️🩹', '💌', '💋', '💑', '💏', '👩❤️👨', '👨❤️👨', '👩❤️👩'],
|
||||||
|
'👤 People & Body': ['👋', '🤚', '🖐️', '✋', '🖖', '👌', '🤌', '🤏', '✌️', '🤞', '🤟', '🤘', '🤙', '👈', '👉', '👆', '🖕', '👇', '☝️', '👍', '👎', '✊', '👊', '🤛', '🤜', '👏', '🙌', '👐', '🤲', '🤝', '🙏', '✍️', '💅', '🤳', '💪', '🦾', '🦿', '🦵', '🦶', '👂', '🦻', '👃', '🧠', '🫀', '🫁', '🦷', '🦴', '👀', '👁️', '👅', '👄', '👶', '🧒', '👦', '👧', '🧑', '👨', '👩', '🧔', '🧑🦰', '👨🦰', '👩🦰', '🧑🦱', '👨🦱', '👩🦱', '🧑🦳', '👨🦳', '👩🦳', '🧑🦲', '👨🦲', '👩🦲', '👱', '👱♂️', '👱♀️', '🧓', '👴', '👵', '🙍', '🙍♂️', '🙍♀️', '🙎', '🙎♂️', '🙎♀️', '🙅', '🙅♂️', '🙅♀️', '🙆', '🙆♂️', '🙆♀️', '💁', '💁♂️', '💁♀️', '🙋', '🙋♂️', '🙋♀️', '🧏', '🧏♂️', '🧏♀️', '🙇', '🙇♂️', '🙇♀️', '🤦', '🤦♂️', '🤦♀️', '🤷', '🤷♂️', '🤷♀️'],
|
||||||
|
'🐶 Animals & Nature': ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🐯', '🦁', '🐮', '🐷', '🐽', '🐸', '🐵', '🙈', '🙉', '🙊', '🐒', '🐔', '🐧', '🐦', '🐤', '🐣', '🐥', '🦆', '🦅', '🦉', '🦇', '🐺', '🐗', '🐴', '🦄', '🐝', '🐛', '🦋', '🐌', '🐞', '🐜', '🦟', '🦗', '🕷️', '🕸️', '🦂', '🐢', '🐍', '🦎', '🦖', '🦕', '🐙', '🦑', '🦐', '🦞', '🦀', '🐡', '🐠', '🐟', '🐬', '🐳', '🐋', '🦈', '🐊', '🐅', '🐆', '🦓', '🦍', '🦧', '🐘', '🦛', '🦏', '🐪', '🐫', '🦒', '🦘', '🐃', '🐂', '🐄', '🐎', '🐖', '🐏', '🐑', '🦙', '🐐', '🦌', '🐕', '🐩', '🦮', '🐕🦺', '🐈', '🐈⬛', '🐓', '🦃', '🦚', '🦜', '🦢', '🦩', '🕊️', '🐇', '🦝', '🦨', '🦡', '🦦', '🦥', '🐁', '🐀', '🐿️', '🦔', '🌲', '🌳', '🌴', '🌱', '🌿', '☘️', '🍀', '🎍', '🎋', '🍃', '🍂', '🍁', '🍄', '🌾', '💐', '🌷', '🌹', '🥀', '🌺', '🌸', '🌼', '🌻', '🌞', '🌝', '🌛', '🌜', '🌚', '🌕', '🌖', '🌗', '🌘', '🌑', '🌒', '🌓', '🌔', '🌙', '🌎', '🌍', '🌏', '🪐', '💫', '⭐', '🌟', '✨', '⚡', '☄️', '💥', '🔥', '🌪️', '🌈', '☀️', '🌤️', '⛅', '🌥️', '☁️', '🌦️', '🌧️', '⛈️', '🌩️', '🌨️', '❄️', '☃️', '⛄', '🌬️', '💨', '💧', '💦', '☔', '☂️', '🌊', '🌫️'],
|
||||||
|
'🍎 Food & Drink': ['🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🥭', '🍎', '🍏', '🍐', '🍑', '🍒', '🍓', '🫐', '🥝', '🍅', '🫒', '🥥', '🥑', '🍆', '🥔', '🥕', '🌽', '🌶️', '🫑', '🥒', '🥬', '🥦', '🧄', '🧅', '🍄', '🥜', '🌰', '🍞', '🥐', '🥖', '🫓', '🥨', '🥯', '🥞', '🧇', '🧀', '🍖', '🍗', '🥩', '🥓', '🍔', '🍟', '🍕', '🌭', '🥪', '🌮', '🌯', '🫔', '🥙', '🧆', '🥚', '🍳', '🥘', '🍲', '🫕', '🥣', '🥗', '🍿', '🧈', '🧂', '🥫', '🍱', '🍘', '🍙', '🍚', '🍛', '🍜', '🍝', '🍠', '🍢', '🍣', '🍤', '🍥', '🥮', '🍡', '🥟', '🥠', '🥡', '🦀', '🦞', '🦐', '🦑', '🦪', '🍦', '🍧', '🍨', '🍩', '🍪', '🎂', '🍰', '🧁', '🥧', '🍫', '🍬', '🍭', '🍮', '🍯', '🍼', '🥛', '☕', '🫖', '🍵', '🍶', '🍾', '🍷', '🍸', '🍹', '🍺', '🍻', '🥂', '🥃', '🥤', '🧋', '🧃', '🧉', '🧊'],
|
||||||
|
'⚽ Activities & Sports': ['⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱', '🪀', '🏓', '🏸', '🏒', '🏑', '🥍', '🏏', '🪃', '🥅', '⛳', '🪁', '🏹', '🎣', '🤿', '🥊', '🥋', '🎽', '🛹', '🛼', '🛷', '⛸️', '🥌', '🎿', '⛷️', '🏂', '🪂', '🏋️', '🏋️♂️', '🏋️♀️', '🤼', '🤼♂️', '🤼♀️', '🤸', '🤸♂️', '🤸♀️', '⛹️', '⛹️♂️', '⛹️♀️', '🤺', '🤾', '🤾♂️', '🤾♀️', '🏌️', '🏌️♂️', '🏌️♀️', '🏇', '🧘', '🧘♂️', '🧘♀️', '🏄', '🏄♂️', '🏄♀️', '🏊', '🏊♂️', '🏊♀️', '🤽', '🤽♂️', '🤽♀️', '🚣', '🚣♂️', '🚣♀️', '🧗', '🧗♂️', '🧗♀️', '🚵', '🚵♂️', '🚵♀️', '🚴', '🚴♂️', '🚴♀️', '🏆', '🥇', '🥈', '🥉', '🏅', '🎖️', '🏵️', '🎗️', '🎫', '🎟️', '🎪', '🤹', '🤹♂️', '🤹♀️', '🎭', '🩰', '🎨', '🎬', '🎤', '🎧', '🎼', '🎹', '🥁', '🪘', '🎷', '🎺', '🪗', '🎸', '🪕', '🎻', '🎲', '♟️', '🎯', '🎳', '🎮', '🎰', '🧩'],
|
||||||
|
'🚗 Travel & Places': ['🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐', '🛻', '🚚', '🚛', '🚜', '🦯', '🦽', '🦼', '🛴', '🚲', '🛵', '🏍️', '🛺', '🚨', '🚔', '🚍', '🚘', '🚖', '🚡', '🚠', '🚟', '🚃', '🚋', '🚞', '🚝', '🚄', '🚅', '🚈', '🚂', '🚆', '🚇', '🚊', '🚉', '✈️', '🛫', '🛬', '🛩️', '💺', '🛰️', '🚀', '🛸', '🚁', '🛶', '⛵', '🚤', '🛥️', '🛳️', '⛴️', '🚢', '⚓', '⛽', '🚧', '🚦', '🚥', '🚏', '🗺️', '🗿', '🗽', '🗼', '🏰', '🏯', '🏟️', '🎡', '🎢', '🎠', '⛲', '⛱️', '🏖️', '🏝️', '🏜️', '🌋', '⛰️', '🏔️', '🗻', '🏕️', '⛺', '🛖', '🏠', '🏡', '🏘️', '🏚️', '🏗️', '🏭', '🏢', '🏬', '🏣', '🏤', '🏥', '🏦', '🏨', '🏪', '🏫', '🏩', '💒', '🏛️', '⛪', '🕌', '🕍', '🛕', '🕋', '⛩️', '🛤️', '🛣️', '🗾', '🎑', '🏞️', '🌅', '🌄', '🌠', '🎇', '🎆', '🌇', '🌆', '🏙️', '🌃', '🌌', '🌉', '🌁'],
|
||||||
|
'💻 Objects & Technology': ['⌚', '📱', '📲', '💻', '⌨️', '🖥️', '🖨️', '🖱️', '🖲️', '🕹️', '🗜️', '💾', '💿', '📀', '📼', '📷', '📸', '📹', '🎥', '📽️', '🎞️', '📞', '☎️', '📟', '📠', '📺', '📻', '🎙️', '🎚️', '🎛️', '🧭', '⏱️', '⏲️', '⏰', '🕰️', '⌛', '⏳', '📡', '🔋', '🔌', '💡', '🔦', '🕯️', '🪔', '🧯', '🛢️', '💸', '💵', '💴', '💶', '💷', '🪙', '💰', '💳', '💎', '⚖️', '🪜', '🧰', '🪛', '🔧', '🔨', '⚒️', '🛠️', '⛏️', '🪚', '🔩', '⚙️', '🪤', '🧱', '⛓️', '🧲', '🔫', '💣', '🧨', '🪓', '🔪', '🗡️', '⚔️', '🛡️', '🚬', '⚰️', '🪦', '⚱️', '🏺', '🔮', '📿', '🧿', '💈', '⚗️', '🔭', '🔬', '🕳️', '🩹', '🩺', '💊', '💉', '🩸', '🧬', '🦷', '🧪', '🌡️', '🧹', '🪠', '🧺', '🧻', '🚽', '🚰', '🚿', '🛁', '🛀', '🧼', '🪥', '🪒', '🧽', '🪣', '🧴', '🛎️', '🔑', '🗝️', '🚪', '🪑', '🛋️', '🛏️', '🛌', '🧸', '🪆', '🖼️', '🪞', '🪟', '🛍️', '🛒', '🎁', '🎈', '🎏', '🎀', '🪄', '🪅', '🎊', '🎉', '🎎', '🏮', '🎐', '🧧'],
|
||||||
|
'📋 Office & Writing': ['✉️', '📧', '📨', '📩', '📤', '📥', '📦', '📫', '📪', '📬', '📭', '📮', '🗳️', '✏️', '✒️', '🖋️', '🖊️', '🖌️', '🖍️', '📝', '💼', '📁', '📂', '🗂️', '📅', '📆', '🗒️', '🗓️', '📇', '📈', '📉', '📊', '📋', '📌', '📍', '📎', '🖇️', '📏', '📐', '✂️', '🗃️', '🗄️', '🗑️', '🔒', '🔓', '🔏', '🔐', '🔑', '🗝️', '🔨', '🪓', '⛏️', '⚒️', '🛠️', '🗡️', '⚔️', '💣', '🪃', '🏹', '🛡️', '🪚', '🔧', '🪛', '🔩', '⚙️', '🗜️', '⚖️'],
|
||||||
|
'🎵 Music & Sound': ['🎵', '🎶', '🎼', '🎹', '🎸', '🎺', '🎷', '🥁', '🪘', '🎤', '🎧', '📻', '🎙️', '🔊', '🔉', '🔈', '🔇', '📢', '📣', '📯', '🔔', '🔕', '🎚️', '🎛️', '🎖️', '🏆', '🥇', '🥈', '🥉', '⚡', '🔥', '💥'],
|
||||||
|
'⚡ Symbols & Signs': ['❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟', '☮️', '✝️', '☪️', '🕉️', '☸️', '✡️', '🔯', '🕎', '☯️', '☦️', '🛐', '⛎', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '🆔', '⚛️', '🉑', '☢️', '☣️', '📴', '📳', '🈶', '🈚', '🈸', '🈺', '🈷️', '✴️', '🆚', '💮', '🉐', '㊙️', '㊗️', '🈴', '🈵', '🈹', '🈲', '🅰️', '🅱️', '🆎', '🆑', '🅾️', '🆘', '❌', '⭕', '🛑', '⛔', '📛', '🚫', '💯', '💢', '♨️', '🚷', '🚯', '🚳', '🚱', '🔞', '📵', '🚭', '❗', '❕', '❓', '❔', '‼️', '⁉️', '🔅', '🔆', '〽️', '⚠️', '🚸', '🔱', '⚜️', '🔰', '♻️', '✅', '🈯', '💹', '❇️', '✳️', '❎', '🌐', '💠', 'Ⓜ️', '🌀', '💤', '🏧', '🚾', '♿', '🅿️', '🛗', '🈳', '🈂️', '🛂', '🛃', '🛄', '🛅', '🚹', '🚺', '🚼', '⚧️', '🚻', '🚮', '🎦', '📶', '🈁', '🔣', 'ℹ️', '🔤', '🔡', '🔠', '🆖', '🆗', '🆙', '🆒', '🆕', '🆓', '0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟', '🔢', '#️⃣', '*️⃣', '⏏️', '▶️', '⏸️', '⏯️', '⏹️', '⏺️', '⏭️', '⏮️', '⏩', '⏪', '⏫', '⏬', '◀️', '🔼', '🔽', '➡️', '⬅️', '⬆️', '⬇️', '↗️', '↘️', '↙️', '↖️', '↕️', '↔️', '↪️', '↩️', '⤴️', '⤵️', '🔀', '🔁', '🔂', '🔄', '🔃', '🎵', '🎶', '➕', '➖', '➗', '✖️', '♾️', '💲', '💱', '™️', '©️', '®️', '〰️', '➰', '➿', '🔚', '🔙', '🔛', '🔝', '🔜', '✔️', '☑️', '🔘', '🔴', '🟠', '🟡', '🟢', '🔵', '🟣', '⚫', '⚪', '🟤', '🔺', '🔻', '🔸', '🔹', '🔶', '🔷', '🔳', '🔲', '▪️', '▫️', '◾', '◽', '◼️', '◻️', '🟥', '🟧', '🟨', '🟩', '🟦', '🟪', '⬛', '⬜', '🟫', '🔈', '🔇', '🔉', '🔊', '🔔', '🔕', '📣', '📢', '💬', '💭', '🗯️', '♠️', '♣️', '♥️', '♦️', '🃏', '🎴', '🀄', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕛', '🕜', '🕝', '🕞', '🕟', '🕠', '🕡', '🕢', '🕣', '🕤', '🕥', '🕦', '🕧'],
|
||||||
|
'🏁 Flags': ['🏁', '🚩', '🎌', '🏴', '🏳️', '🏳️🌈', '🏳️⚧️', '🏴☠️', '🇦🇨', '🇦🇩', '🇦🇪', '🇦🇫', '🇦🇬', '🇦🇮', '🇦🇱', '🇦🇲', '🇦🇴', '🇦🇶', '🇦🇷', '🇦🇸', '🇦🇹', '🇦🇺', '🇦🇼', '🇦🇽', '🇦🇿', '🇧🇦', '🇧🇧', '🇧🇩', '🇧🇪', '🇧🇫', '🇧🇬', '🇧🇭', '🇧🇮', '🇧🇯', '🇧🇱', '🇧🇲', '🇧🇳', '🇧🇴', '🇧🇶', '🇧🇷', '🇧🇸', '🇧🇹', '🇧🇻', '🇧🇼', '🇧🇾', '🇧🇿', '🇨🇦', '🇨🇨', '🇨🇩', '🇨🇫', '🇨🇬', '🇨🇭', '🇨🇮', '🇨🇰', '🇨🇱', '🇨🇲', '🇨🇳', '🇨🇴', '🇨🇵', '🇨🇷', '🇨🇺', '🇨🇻', '🇨🇼', '🇨🇽', '🇨🇾', '🇨🇿', '🇩🇪', '🇩🇬', '🇩🇯', '🇩🇰', '🇩🇲', '🇩🇴', '🇩🇿', '🇪🇦', '🇪🇨', '🇪🇪', '🇪🇬', '🇪🇭', '🇪🇷', '🇪🇸', '🇪🇹', '🇪🇺', '🇫🇮', '🇫🇯', '🇫🇰', '🇫🇲', '🇫🇴', '🇫🇷', '🇬🇦', '🇬🇧', '🇬🇩', '🇬🇪', '🇬🇫', '🇬🇬', '🇬🇭', '🇬🇮', '🇬🇱', '🇬🇲', '🇬🇳', '🇬🇵', '🇬🇶', '🇬🇷', '🇬🇸', '🇬🇹', '🇬🇺', '🇬🇼', '🇬🇾', '🇭🇰', '🇭🇲', '🇭🇳', '🇭🇷', '🇭🇹', '🇭🇺', '🇮🇨', '🇮🇩', '🇮🇪', '🇮🇱', '🇮🇲', '🇮🇳', '🇮🇴', '🇮🇶', '🇮🇷', '🇮🇸', '🇮🇹', '🇯🇪', '🇯🇲', '🇯🇴', '🇯🇵', '🇰🇪', '🇰🇬', '🇰🇭', '🇰🇮', '🇰🇲', '🇰🇳', '🇰🇵', '🇰🇷', '🇰🇼', '🇰🇾', '🇰🇿', '🇱🇦', '🇱🇧', '🇱🇨', '🇱🇮', '🇱🇰', '🇱🇷', '🇱🇸', '🇱🇹', '🇱🇺', '🇱🇻', '🇱🇾', '🇲🇦', '🇲🇨', '🇲🇩', '🇲🇪', '🇲🇫', '🇲🇬', '🇲🇭', '🇲🇰', '🇲🇱', '🇲🇲', '🇲🇳', '🇲🇴', '🇲🇵', '🇲🇶', '🇲🇷', '🇲🇸', '🇲🇹', '🇲🇺', '🇲🇻', '🇲🇼', '🇲🇽', '🇲🇾', '🇲🇿', '🇳🇦', '🇳🇨', '🇳🇪', '🇳🇫', '🇳🇬', '🇳🇮', '🇳🇱', '🇳🇴', '🇳🇵', '🇳🇷', '🇳🇺', '🇳🇿', '🇴🇲', '🇵🇦', '🇵🇪', '🇵🇫', '🇵🇬', '🇵🇭', '🇵🇰', '🇵🇱', '🇵🇲', '🇵🇳', '🇵🇷', '🇵🇸', '🇵🇹', '🇵🇼', '🇵🇾', '🇶🇦', '🇷🇪', '🇷🇴', '🇷🇸', '🇷🇺', '🇷🇼', '🇸🇦', '🇸🇧', '🇸🇨', '🇸🇩', '🇸🇪', '🇸🇬', '🇸🇭', '🇸🇮', '🇸🇯', '🇸🇰', '🇸🇱', '🇸🇲', '🇸🇳', '🇸🇴', '🇸🇷', '🇸🇸', '🇸🇹', '🇸🇻', '🇸🇽', '🇸🇾', '🇸🇿', '🇹🇦', '🇹🇨', '🇹🇩', '🇹🇫', '🇹🇬', '🇹🇭', '🇹🇯', '🇹🇰', '🇹🇱', '🇹🇲', '🇹🇳', '🇹🇴', '🇹🇷', '🇹🇹', '🇹🇻', '🇹🇼', '🇹🇿', '🇺🇦', '🇺🇬', '🇺🇲', '🇺🇳', '🇺🇸', '🇺🇾', '🇺🇿', '🇻🇦', '🇻🇨', '🇻🇪', '🇻🇬', '🇻🇮', '🇻🇳', '🇻🇺', '🇼🇫', '🇼🇸', '🇽🇰', '🇾🇪', '🇾🇹', '🇿🇦', '🇿🇲', '🇿🇼', '🏴', '🏴', '🏴'],
|
||||||
|
};
|
||||||
|
|
||||||
// ============ Quick Add Task Modal ============
|
// ============ Quick Add Task Modal ============
|
||||||
|
|
||||||
export class QuickAddTaskModal extends Modal {
|
export class QuickAddTaskModal extends Modal {
|
||||||
@@ -548,3 +835,114 @@ export class ReportModal extends Modal {
|
|||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============ Emoji Picker Modal ============
|
||||||
|
|
||||||
|
export class EmojiPickerModal extends Modal {
|
||||||
|
onSelect: (emoji: string) => void;
|
||||||
|
currentEmoji: string;
|
||||||
|
|
||||||
|
constructor(app: App, currentEmoji: string, onSelect: (emoji: string) => void) {
|
||||||
|
super(app);
|
||||||
|
this.currentEmoji = currentEmoji;
|
||||||
|
this.onSelect = onSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
const { contentEl } = this;
|
||||||
|
contentEl.addClass('immerse-emoji-picker');
|
||||||
|
|
||||||
|
contentEl.createEl('h2', { text: 'Select Emoji' });
|
||||||
|
|
||||||
|
// Current selection
|
||||||
|
if (this.currentEmoji) {
|
||||||
|
const currentDiv = contentEl.createDiv({ cls: 'immerse-emoji-current' });
|
||||||
|
currentDiv.createEl('span', { text: 'Current: ' });
|
||||||
|
currentDiv.createEl('span', { text: this.currentEmoji, cls: 'immerse-emoji-current-icon' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search box
|
||||||
|
const searchContainer = contentEl.createDiv({ cls: 'immerse-emoji-search' });
|
||||||
|
const searchInput = searchContainer.createEl('input', {
|
||||||
|
type: 'text',
|
||||||
|
placeholder: 'Search emojis...',
|
||||||
|
cls: 'immerse-emoji-search-input'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emoji grid container
|
||||||
|
const gridContainer = contentEl.createDiv({ cls: 'immerse-emoji-categories' });
|
||||||
|
|
||||||
|
// Render all categories
|
||||||
|
const renderCategories = (filter: string = '') => {
|
||||||
|
gridContainer.empty();
|
||||||
|
|
||||||
|
Object.entries(EMOJI_CATEGORIES).forEach(([category, emojis]) => {
|
||||||
|
const filteredEmojis = filter
|
||||||
|
? emojis.filter(emoji => {
|
||||||
|
const keywords = EMOJI_KEYWORDS[emoji] || '';
|
||||||
|
return keywords.toLowerCase().includes(filter) || emoji.includes(filter);
|
||||||
|
})
|
||||||
|
: emojis;
|
||||||
|
|
||||||
|
if (filteredEmojis.length === 0) return;
|
||||||
|
|
||||||
|
const categoryDiv = gridContainer.createDiv({ cls: 'immerse-emoji-category' });
|
||||||
|
categoryDiv.createEl('h3', { text: category, cls: 'immerse-emoji-category-title' });
|
||||||
|
|
||||||
|
const grid = categoryDiv.createDiv({ cls: 'immerse-emoji-grid' });
|
||||||
|
|
||||||
|
filteredEmojis.forEach(emoji => {
|
||||||
|
const button = grid.createEl('button', {
|
||||||
|
text: emoji,
|
||||||
|
cls: 'immerse-emoji-button'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (emoji === this.currentEmoji) {
|
||||||
|
button.addClass('immerse-emoji-selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
this.onSelect(emoji);
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
renderCategories();
|
||||||
|
|
||||||
|
// Search functionality
|
||||||
|
searchInput.addEventListener('input', (e) => {
|
||||||
|
const filter = (e.target as HTMLInputElement).value.toLowerCase();
|
||||||
|
renderCategories(filter);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Custom emoji input
|
||||||
|
const customDiv = contentEl.createDiv({ cls: 'immerse-emoji-custom' });
|
||||||
|
customDiv.createEl('span', { text: 'Or enter custom emoji: ' });
|
||||||
|
const customInput = customDiv.createEl('input', {
|
||||||
|
type: 'text',
|
||||||
|
placeholder: 'Paste emoji',
|
||||||
|
cls: 'immerse-emoji-custom-input',
|
||||||
|
});
|
||||||
|
|
||||||
|
const customBtn = customDiv.createEl('button', {
|
||||||
|
text: 'Use Custom',
|
||||||
|
cls: 'immerse-btn immerse-btn-primary'
|
||||||
|
});
|
||||||
|
|
||||||
|
customBtn.addEventListener('click', () => {
|
||||||
|
const customEmoji = customInput.value.trim();
|
||||||
|
if (customEmoji) {
|
||||||
|
this.onSelect(customEmoji);
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
const { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export class ReportView extends ItemView {
|
|||||||
startDate: string;
|
startDate: string;
|
||||||
endDate: string;
|
endDate: string;
|
||||||
selectedListIds: string[] = [];
|
selectedListIds: string[] = [];
|
||||||
|
activeQuickFilter: string | null = 'Last 7 days'; // Track active filter
|
||||||
|
startDateInput: HTMLInputElement | null = null;
|
||||||
|
endDateInput: HTMLInputElement | null = null;
|
||||||
|
|
||||||
constructor(leaf: WorkspaceLeaf, plugin: ImmersePlugin) {
|
constructor(leaf: WorkspaceLeaf, plugin: ImmersePlugin) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
@@ -83,16 +86,24 @@ export class ReportView extends ItemView {
|
|||||||
.setName('Start Date')
|
.setName('Start Date')
|
||||||
.addText(text => {
|
.addText(text => {
|
||||||
text.setValue(this.startDate)
|
text.setValue(this.startDate)
|
||||||
.onChange(value => this.startDate = value);
|
.onChange(value => {
|
||||||
|
this.startDate = value;
|
||||||
|
this.activeQuickFilter = null; // Clear active filter when manually changing dates
|
||||||
|
});
|
||||||
text.inputEl.type = 'date';
|
text.inputEl.type = 'date';
|
||||||
|
this.startDateInput = text.inputEl; // Store reference
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(dateRow)
|
new Setting(dateRow)
|
||||||
.setName('End Date')
|
.setName('End Date')
|
||||||
.addText(text => {
|
.addText(text => {
|
||||||
text.setValue(this.endDate)
|
text.setValue(this.endDate)
|
||||||
.onChange(value => this.endDate = value);
|
.onChange(value => {
|
||||||
|
this.endDate = value;
|
||||||
|
this.activeQuickFilter = null; // Clear active filter when manually changing dates
|
||||||
|
});
|
||||||
text.inputEl.type = 'date';
|
text.inputEl.type = 'date';
|
||||||
|
this.endDateInput = text.inputEl; // Store reference
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quick filters
|
// Quick filters
|
||||||
@@ -111,12 +122,33 @@ export class ReportView extends ItemView {
|
|||||||
text: filter.label,
|
text: filter.label,
|
||||||
cls: 'immerse-quick-filter-btn'
|
cls: 'immerse-quick-filter-btn'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set active class if this is the default active filter
|
||||||
|
if (filter.label === this.activeQuickFilter) {
|
||||||
|
btn.addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
|
// Update dates
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
this.endDate = today.toISOString().split('T')[0];
|
this.endDate = today.toISOString().split('T')[0];
|
||||||
const startDate = new Date(today);
|
const startDate = new Date(today);
|
||||||
startDate.setDate(startDate.getDate() - filter.days);
|
startDate.setDate(startDate.getDate() - filter.days);
|
||||||
this.startDate = startDate.toISOString().split('T')[0];
|
this.startDate = startDate.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// Update date inputs
|
||||||
|
if (this.startDateInput) this.startDateInput.value = this.startDate;
|
||||||
|
if (this.endDateInput) this.endDateInput.value = this.endDate;
|
||||||
|
|
||||||
|
// Update active filter
|
||||||
|
this.activeQuickFilter = filter.label;
|
||||||
|
|
||||||
|
// Update button states
|
||||||
|
quickFilters.querySelectorAll('.immerse-quick-filter-btn').forEach(b => {
|
||||||
|
b.removeClass('active');
|
||||||
|
});
|
||||||
|
btn.addClass('active');
|
||||||
|
|
||||||
this.renderReport(container);
|
this.renderReport(container);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
46
src/view.ts
46
src/view.ts
@@ -154,7 +154,13 @@ export class ImmerseView extends ItemView {
|
|||||||
const breakLabel = this.plugin.currentTimerSeconds > 0 ? '☕ BREAK TIME' : '✨ BREAK COMPLETE';
|
const breakLabel = this.plugin.currentTimerSeconds > 0 ? '☕ BREAK TIME' : '✨ BREAK COMPLETE';
|
||||||
activeCard.createEl('div', { cls: 'immerse-active-label', text: breakLabel });
|
activeCard.createEl('div', { cls: 'immerse-active-label', text: breakLabel });
|
||||||
} else {
|
} else {
|
||||||
const workLabel = this.plugin.currentTimerSeconds > 0 ? '🎯 FOCUSING ON' : '🍅 POMODORO COMPLETE';
|
// Determine label based on whether timer is active and mode (stopwatch vs pomodoro)
|
||||||
|
let workLabel: string;
|
||||||
|
if (this.plugin.currentTimerSeconds > 0 || this.plugin.isStopwatchMode) {
|
||||||
|
workLabel = '🎯 FOCUSING ON';
|
||||||
|
} else {
|
||||||
|
workLabel = '🍅 POMODORO COMPLETE';
|
||||||
|
}
|
||||||
activeCard.createEl('div', { cls: 'immerse-active-label', text: workLabel });
|
activeCard.createEl('div', { cls: 'immerse-active-label', text: workLabel });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,24 +173,26 @@ export class ImmerseView extends ItemView {
|
|||||||
text: this.plugin.formatTime(this.plugin.currentTimerSeconds)
|
text: this.plugin.formatTime(this.plugin.currentTimerSeconds)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Progress bar - store reference for updates
|
// Progress bar - only show in pomodoro/break mode, not stopwatch
|
||||||
const progressWrap = activeCard.createEl('div', { cls: 'immerse-progress-wrap' });
|
if (!this.plugin.isStopwatchMode) {
|
||||||
this.progressBarEl = progressWrap.createEl('div', { cls: 'immerse-progress-bar' });
|
const progressWrap = activeCard.createEl('div', { cls: 'immerse-progress-wrap' });
|
||||||
|
this.progressBarEl = progressWrap.createEl('div', { cls: 'immerse-progress-bar' });
|
||||||
|
|
||||||
let progressPercent = 0;
|
let progressPercent = 0;
|
||||||
if (this.plugin.isBreakMode) {
|
if (this.plugin.isBreakMode) {
|
||||||
const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0
|
const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0
|
||||||
? this.plugin.settings.longBreakMinutes * 60
|
? this.plugin.settings.longBreakMinutes * 60
|
||||||
: this.plugin.settings.pomodoroBreakMinutes * 60;
|
: this.plugin.settings.pomodoroBreakMinutes * 60;
|
||||||
progressPercent = ((breakDuration - this.plugin.currentTimerSeconds) / breakDuration) * 100;
|
progressPercent = ((breakDuration - this.plugin.currentTimerSeconds) / breakDuration) * 100;
|
||||||
} else {
|
} else {
|
||||||
const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60;
|
const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60;
|
||||||
progressPercent = ((workDuration - this.plugin.currentTimerSeconds) / workDuration) * 100;
|
progressPercent = ((workDuration - this.plugin.currentTimerSeconds) / workDuration) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`;
|
||||||
|
if (progressPercent >= 100) this.progressBarEl.addClass('immerse-overtime');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`;
|
|
||||||
if (progressPercent >= 100) this.progressBarEl.addClass('immerse-overtime');
|
|
||||||
|
|
||||||
// Time info - store reference for actual time updates
|
// Time info - store reference for actual time updates
|
||||||
if (!this.plugin.isBreakMode) {
|
if (!this.plugin.isBreakMode) {
|
||||||
const timeInfo = activeCard.createEl('div', { cls: 'immerse-time-info' });
|
const timeInfo = activeCard.createEl('div', { cls: 'immerse-time-info' });
|
||||||
@@ -234,8 +242,8 @@ export class ImmerseView extends ItemView {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Work mode controls
|
// Work mode controls
|
||||||
if (this.plugin.currentTimerSeconds > 0) {
|
if (this.plugin.currentTimerSeconds > 0 || this.plugin.isStopwatchMode) {
|
||||||
// Work session still running
|
// Work session still running (or stopwatch mode active)
|
||||||
this.pauseBtnEl = controls.createEl('button', { cls: 'immerse-btn immerse-btn-secondary' });
|
this.pauseBtnEl = controls.createEl('button', { cls: 'immerse-btn immerse-btn-secondary' });
|
||||||
this.pauseBtnEl.innerHTML = this.plugin.isTimerRunning ? '⏸ Pause' : '▶ Resume';
|
this.pauseBtnEl.innerHTML = this.plugin.isTimerRunning ? '⏸ Pause' : '▶ Resume';
|
||||||
this.pauseBtnEl.addEventListener('click', () => this.plugin.toggleTimer());
|
this.pauseBtnEl.addEventListener('click', () => this.plugin.toggleTimer());
|
||||||
@@ -248,7 +256,7 @@ export class ImmerseView extends ItemView {
|
|||||||
stopBtn.innerHTML = '✕ Stop';
|
stopBtn.innerHTML = '✕ Stop';
|
||||||
stopBtn.addEventListener('click', () => this.plugin.stopTimer());
|
stopBtn.addEventListener('click', () => this.plugin.stopTimer());
|
||||||
} else {
|
} else {
|
||||||
// Work session finished - show break and completion options
|
// Pomodoro session finished - show break and completion options
|
||||||
const startBreakBtn = controls.createEl('button', { cls: 'immerse-btn immerse-btn-secondary' });
|
const startBreakBtn = controls.createEl('button', { cls: 'immerse-btn immerse-btn-secondary' });
|
||||||
startBreakBtn.innerHTML = '☕ Start Break';
|
startBreakBtn.innerHTML = '☕ Start Break';
|
||||||
startBreakBtn.addEventListener('click', () => this.plugin.startBreak());
|
startBreakBtn.addEventListener('click', () => this.plugin.startBreak());
|
||||||
|
|||||||
159
styles.css
159
styles.css
@@ -288,11 +288,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.immerse-active-controls .immerse-btn {
|
.immerse-active-controls .immerse-btn {
|
||||||
flex: 1;
|
flex: 1 1 auto;
|
||||||
min-width: 80px;
|
min-width: 100px;
|
||||||
|
max-width: 100%;
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
|
white-space: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
line-height: 1.3;
|
||||||
|
padding: 8px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.immerse-active-controls .immerse-btn:hover {
|
.immerse-active-controls .immerse-btn:hover {
|
||||||
@@ -752,15 +757,38 @@
|
|||||||
border: 1px solid var(--ft-border);
|
border: 1px solid var(--ft-border);
|
||||||
color: var(--ft-text);
|
color: var(--ft-text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.3s ease;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.immerse-quick-filter-btn:hover {
|
.immerse-quick-filter-btn:hover {
|
||||||
background: var(--ft-primary);
|
background: var(--ft-primary);
|
||||||
color: white;
|
color: white;
|
||||||
border-color: var(--ft-primary);
|
border-color: var(--ft-primary);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-quick-filter-btn.active {
|
||||||
|
background: var(--ft-primary);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--ft-primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2),
|
||||||
|
0 0 20px rgba(99, 102, 241, 0.4);
|
||||||
|
animation: pulse-glow 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-glow {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2),
|
||||||
|
0 0 20px rgba(99, 102, 241, 0.4);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.3),
|
||||||
|
0 0 25px rgba(99, 102, 241, 0.6);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.immerse-report-generate-btn {
|
.immerse-report-generate-btn {
|
||||||
@@ -1443,3 +1471,128 @@
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============ Emoji Picker ============ */
|
||||||
|
.immerse-emoji-picker {
|
||||||
|
width: 85vw;
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-picker .modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-current {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--ft-bg-secondary);
|
||||||
|
border-radius: var(--ft-radius-sm);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-current-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-search {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: var(--ft-radius-sm);
|
||||||
|
border: 1px solid var(--ft-border);
|
||||||
|
background: var(--ft-bg);
|
||||||
|
color: var(--ft-text);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-categories {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-category {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-category-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ft-text-muted);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));
|
||||||
|
gap: 4px;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-button {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid var(--ft-border);
|
||||||
|
background: var(--ft-bg);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-button:hover {
|
||||||
|
background: var(--ft-bg-tertiary);
|
||||||
|
transform: scale(1.1);
|
||||||
|
border-color: var(--ft-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-button.immerse-emoji-selected {
|
||||||
|
background: var(--ft-primary);
|
||||||
|
border-color: var(--ft-primary);
|
||||||
|
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-custom {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid var(--ft-border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-emoji-custom-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: var(--ft-radius-sm);
|
||||||
|
border: 1px solid var(--ft-border);
|
||||||
|
background: var(--ft-bg);
|
||||||
|
color: var(--ft-text);
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"1.0.4": "0.15.0",
|
"1.0.4": "0.15.0",
|
||||||
"1.0.5": "0.15.0",
|
"1.0.5": "0.15.0",
|
||||||
"1.0.6": "0.15.0",
|
"1.0.6": "0.15.0",
|
||||||
"1.0.7": "0.15.0",
|
"1.0.7": "0.15.0",
|
||||||
"1.0.8": "0.15.0",
|
"1.0.8": "0.15.0",
|
||||||
"1.0.9": "0.15.0"
|
"1.0.9": "0.15.0",
|
||||||
|
"1.1.3": "0.15.0",
|
||||||
|
"1.1.4": "0.15.0"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user