mirror of
https://github.com/Lifeforge-app/lifeforge.git
synced 2026-03-03 02:27:00 +00:00
25w30
Former-commit-id: 35a9466bf1c6ab76a3661572d462629db63627ef [formerly a5a76a5a0d32707cbc64da97aaf0bb47c38d38b7] [formerly 1367e1ce733eb5de793bf57c0593a421f5037d61 [formerly 3e422f73ac87b543a0aaaea2e3de35fd3ab07d0d]] Former-commit-id: 640847dab31327c4fe256276e5266378b7580ade [formerly ab6dfc75784b5ab327135db06b8f171bfb66ac48] Former-commit-id: 9dccac672972db4e1d9b8b95364727d7de5fbaa1
This commit is contained in:
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,5 +1,3 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"ITMDB"
|
||||
]
|
||||
}
|
||||
"cSpell.words": ["ITMDB"]
|
||||
}
|
||||
|
||||
210
CHANGELOG.md
210
CHANGELOG.md
@@ -3,44 +3,53 @@
|
||||
## 📌 **dev 25w30 (7/21/2025 - 7/28/2025)**
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
- **Enhancements**:
|
||||
|
||||
- **Enhancements**:
|
||||
- `LocationInput` will now display a loading state when fetching the API key availability
|
||||
|
||||
## 📌 **dev 25w29 (7/14/2025 - 7/20/2025)**
|
||||
|
||||
### 🎸 **Guitar Tabs**
|
||||
|
||||
- Migrated file uploading mechanism to use the task pool system for better performance
|
||||
- Enhanced web scraping logic with improved edge case handling
|
||||
- Added missing internationalization support
|
||||
|
||||
### 💡 **Idea Box**
|
||||
|
||||
- Fixed frontend synchronization issue where idea entries weren't properly updated after user actions
|
||||
|
||||
### 📚 **Books Library**
|
||||
|
||||
- Resolved book update functionality due to frontend and backend schema mismatches
|
||||
|
||||
### 📅 **Calendar**
|
||||
|
||||
- Added automatic location data retrieval from Google Places API when parsing event details from images
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- Introduced new "Spending Heatmap" subpage for visualizing transaction patterns
|
||||
|
||||
### 🌐 **Localization Manager**
|
||||
|
||||
- Fixed malformed entry creation in the root layer
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Migration**: Fully migrated to the new `lifeforge-ui` package under monorepo structure
|
||||
- Deprecated legacy codebase on GitHub
|
||||
- Removed `@lifeforge/ui` package from npm registry
|
||||
- **Enhancement & Fixes**:
|
||||
- **Enhancement & Fixes**:
|
||||
- Corrected toast progress bar color application
|
||||
- Improved the callback logic for `DeleteConfirmationModal`
|
||||
- **Type Safety**: Enhanced `FormModal` component with automatic field type determination based on field value types
|
||||
- **New Components**:
|
||||
- **New Components**:
|
||||
- Added `NumberInput` component
|
||||
- Added `FormNumberInput` component for form number input fields
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Monorepo Migration**: Complete transition to monorepo structure using `bun` as package manager
|
||||
- **Type Sharing**: Generated TypeScript interfaces now reside in `shared` package for frontend/backend consistency
|
||||
- **Shared Resources**:
|
||||
@@ -53,6 +62,7 @@
|
||||
- Enhanced ESLint rules for better code quality
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **Migration**: Moved API codebase to monorepo structure and deprecated legacy repository
|
||||
- **Documentation**: Added comprehensive JSDoc documentation for `forgeController`
|
||||
- **Type Safety**: Enhanced `existenceCheck` function with schema-based parameter validation
|
||||
@@ -61,6 +71,7 @@
|
||||
## 📌 **dev 25w28 (7/7/2025 - 7/14/2025)**
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **File Organization**: Migrated all `<module>_interface.ts` files to `schema.ts` for better structure
|
||||
- **Schema Generation**: Automated schema generation from database instead of hardcoded definitions
|
||||
- **Package Scripts**: Added `schema:generate` command to `package.json`
|
||||
@@ -69,6 +80,7 @@
|
||||
## 📌 **dev 25w27 (6/30/2025 - 7/7/2025)**
|
||||
|
||||
### 📚 **Books Library**
|
||||
|
||||
- **Error Handling**: Fixed API logic where book addition failures weren't properly handled
|
||||
- **Terminology**: Renamed `category` to `collection` for better clarity
|
||||
- **UI Synchronization**: Resolved sidebar content updates when items are deleted
|
||||
@@ -77,9 +89,11 @@
|
||||
- **Reading Status**: Users can now mark books as read or unread
|
||||
|
||||
### 🎸 **Guitar Tabs**
|
||||
|
||||
- **Task Pool Integration**: Migrated download process to task pool mechanism
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- **Transaction Details**: Added detailed transaction view accessible by clicking entries
|
||||
- **Location Features**:
|
||||
- Transaction coordinates now recorded and displayed in a heatmap
|
||||
@@ -89,13 +103,16 @@
|
||||
- Replaced receipt thumbnails with modal-opening buttons
|
||||
|
||||
### 💾 **Backups**
|
||||
|
||||
- **New Module**: Complete backup management system within the application
|
||||
- **Independence**: No longer requires PocketBase admin UI for backup operations
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **Task Pool**: Implemented SocketIO-based task pool for long-running operations (e.g., Libgen downloads)
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Preloader**: Added inline styling for Tailwind-independent loading states
|
||||
- **Modal System**: Complete `ModalStore` overhaul with direct component passing and enhanced type safety
|
||||
- **Input Focus**: Fixed ComboBox input focus behavior
|
||||
@@ -105,25 +122,31 @@
|
||||
## 📌 **dev 25w26 (6/23/2025 - 6/30/2025)**
|
||||
|
||||
### 🎬 **Movies**
|
||||
|
||||
- **Organization**: Added tab selector to separate watched and unwatched movies
|
||||
- **Data Sync**: Fixed query data updates when adding movies to library
|
||||
- **Sorting**: Enhanced movie sorting algorithms
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- **Visualization**: Added assets balance chart modal for timeline analysis
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **Architecture**: Major refactoring to implement chained controller architecture
|
||||
|
||||
## 📌 **dev 25w25 (6/16/2025 - 6/23/2025)**
|
||||
|
||||
### 📅 **Calendar**
|
||||
|
||||
- **UI Enhancement**: Added missing calendar sidebar divider
|
||||
|
||||
### 🎬 **Movies**
|
||||
|
||||
- **Bug Fix**: Resolved date picker crashes in ticket entry modification
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **File Input**: Added file icons to input components and modals
|
||||
- **Color Palette**: Fixed scrolling issues in TailwindCSS color palette
|
||||
- **Improvements**: Minor enhancements to file input components
|
||||
@@ -131,94 +154,117 @@
|
||||
## 📌 **dev 25w24 (6/9/2025 - 6/16/2025)**
|
||||
|
||||
### 🛍️ **Wishlist**
|
||||
|
||||
- **Search**: Added search bar functionality
|
||||
|
||||
### 🎨 **Personalization**
|
||||
|
||||
- **Dynamic Favicon**: Favicon now generates based on theme color
|
||||
- **Performance**: Added `derivedTheme` attribute to prevent recalculation overhead
|
||||
|
||||
### 🔐 **Authentication**
|
||||
|
||||
- **2FA Fix**: Resolved resend OTP button issue on page refresh
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Route Control**: Added `forceDisable` attribute to `Route` interface
|
||||
- **Stylesheet Organization**: Created centralized `style/index.css`
|
||||
- **Performance**: Reduced `useDebounce` timeout from 500ms to 300ms
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **FormModal**: Fixed default API logic firing with custom `onSubmit`
|
||||
|
||||
### 🌐 **Localization**
|
||||
|
||||
- **Sync**: Theme and language config synchronized with main system
|
||||
- **UI Migration**: Updated modals to use new `lifeforge-ui` package
|
||||
- **Loading**: Removed authentication loading screen
|
||||
|
||||
### 🔍 **API Explorer**
|
||||
|
||||
- **Complete Rewrite**: New separate app with SSO access
|
||||
- **Sync**: Theme and language synchronized with main system
|
||||
|
||||
## 📌 **dev 25w23 (6/2/2025 - 6/9/2025)**
|
||||
|
||||
### 📅 **Calendar**
|
||||
|
||||
- **Event Organization**: Added calendar grouping (Work, Personal, etc.) in sidebar
|
||||
- **Mobile Support**: Introduced sidebar functionality for mobile view
|
||||
- **Visual Polish**: Made calendar borders more subtle
|
||||
- **UI Improvements**: Various minor enhancements
|
||||
|
||||
### 💡 **Idea Box**
|
||||
|
||||
- **Performance**: Container entry counts now calculated dynamically using PocketBase views
|
||||
|
||||
### ⏱️ **Codetime**
|
||||
|
||||
- **VS Code Integration**: Fixed time display issues in VS Code status bar
|
||||
|
||||
### 🎸 **Guitar Tabs**
|
||||
|
||||
- **UI Cleanup**: Removed non-functional "Download All" button post-S3 migration
|
||||
|
||||
### 🎵 **Music**
|
||||
|
||||
- **Feature Removal**: Temporarily removed NAS import feature (replacement planned)
|
||||
|
||||
### 🎬 **Movies**
|
||||
|
||||
- **UI Fix**: Corrected movie ticket modal layout
|
||||
|
||||
### ✅ **Todo List**
|
||||
|
||||
- **Feature Removal**: Removed subtask feature (better implementation planned)
|
||||
|
||||
### 📊 **Server Status**
|
||||
|
||||
- **Module Deprecation**: Removed due to system-specific nature (replacement planned)
|
||||
|
||||
### 🎨 **Personalization**
|
||||
|
||||
- **Font Support**: Fixed font family application for names containing numbers
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **Validation**: Major refactoring introducing Zod validation library
|
||||
- **Middleware**: Replaced inconsistent middlewares with Zod validation
|
||||
- **Endpoints**:
|
||||
- **Endpoints**:
|
||||
- Removed `/quotes` endpoint
|
||||
- Added `/cors-anywhere` for cross-origin requests
|
||||
|
||||
### 📊 **Dashboard**
|
||||
|
||||
- **Quote Widget**: Now uses `/cors-anywhere` endpoint for external quote fetching
|
||||
|
||||
### 🏗️ **Code Quality**
|
||||
|
||||
- **Bug Fixes**: Various minor fixes throughout the codebase
|
||||
|
||||
## 📌 **dev 25w22 (5/26/2025 - 6/2/2025)**
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- **Calculations**: Fixed percentage calculation rounding issues in statements
|
||||
- **Performance**: Utilized PocketBase views with SQL aggregation for asset balance derivation
|
||||
|
||||
## 📌 **dev 25w21 (5/19/2025 - 5/26/2025)**
|
||||
|
||||
### ⚙️ **Account Settings**
|
||||
|
||||
- **Naming**: Renamed folder from `Account` to `Account Settings` for clarity
|
||||
- **UI Fix**: Resolved input box overflow in modification modal
|
||||
|
||||
### ✅ **Todo List**
|
||||
|
||||
- **Dashboard**: Fixed task tag display issues in dashboard widget
|
||||
|
||||
### 💡 **Idea Box**
|
||||
- **UI Polish**:
|
||||
|
||||
- **UI Polish**:
|
||||
- Fixed sidebar blur when create idea FAB is clicked
|
||||
- Added entry type display with improved hamburger menu positioning
|
||||
- Fixed idea type selection menu width in mobile view
|
||||
@@ -226,25 +272,30 @@
|
||||
- **Markdown Support**: Text entries now render markdown properly
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- **UI Refinement**: Reduced border radius for mini calendar date items
|
||||
|
||||
### 📅 **Calendar**
|
||||
- **Navigation**:
|
||||
|
||||
- **Navigation**:
|
||||
- Moved display mode selector for better responsiveness
|
||||
- Added quick "current date" navigation button
|
||||
- **Design**: Various visual improvements
|
||||
|
||||
### ⏱️ **Codetime**
|
||||
|
||||
- **Dashboard**: Removed chart type selector (bar chart is superior to line chart)
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
- **Dynamic Routing**:
|
||||
|
||||
- **Dynamic Routing**:
|
||||
- Module route configs now stored in separate files per module
|
||||
- Implemented dynamic route loading mechanism
|
||||
- Renamed `layout` folder to `routes` for clarity
|
||||
- **Documentation**: Updated `README.md` with latest screenshots and information
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Loading States**: Added descriptive loading messages
|
||||
- **Preloader**: Restored preloader for module preparation
|
||||
- **Date Input**: Migrated to `react-datepicker` from `react-date-picker`
|
||||
@@ -256,43 +307,52 @@
|
||||
## 📌 **dev 25w20 (5/12/2025 - 5/19/2025)**
|
||||
|
||||
### 💰 **Wallet**
|
||||
- **Transaction Management**:
|
||||
|
||||
- **Transaction Management**:
|
||||
- Fixed transaction order updates when dates are modified
|
||||
- Resolved critical dashboard loading crash
|
||||
|
||||
### ✅ **Todo List**
|
||||
|
||||
- **Task Creation**: Fixed failure when priority is not selected
|
||||
- **UI Improvements**:
|
||||
- **UI Improvements**:
|
||||
- Fixed modification drawer not closing after task deletion
|
||||
- Renamed `ModifyTaskWindow` to `ModifyTaskDrawer` for clarity
|
||||
|
||||
### 💡 **Idea Box**
|
||||
|
||||
- **Data Sync**: Fixed tag count updates when navigating between folders
|
||||
|
||||
### 📅 **Calendar**
|
||||
|
||||
- **Dashboard Integration**: Added event detail viewing in dashboard widget
|
||||
- **UI Fixes**:
|
||||
- **UI Fixes**:
|
||||
- Resolved z-index issues with event tooltips covering sidebar
|
||||
- Fixed event item updates when details are changed
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Navigation**: Replaced `window.location.href` with `useNavigate` to prevent page reloads
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Input Focus**: Fixed search field focus triggering on side action button clicks
|
||||
- **Code Cleanup**: Removed suspicious debug statements
|
||||
|
||||
## 📌 **dev 25w19 (5/5/2025 - 5/12/2025)**
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Portal System**: Created universal portal component for modals and pop-ups outside main app tree
|
||||
- **Migration**: Migrated all modals to new universal portal system
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **Bug Fixes**: Resolved various critical issues
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
- **Performance**:
|
||||
|
||||
- **Performance**:
|
||||
- Fixed critical `useCallback` related bugs
|
||||
- Massive `FormModal` optimization by eliminating unnecessary re-renders
|
||||
- **Button Components**: Enhanced styling for different button states
|
||||
@@ -300,160 +360,194 @@
|
||||
- **Text Selection**: Text now highlights in theme color when selected
|
||||
|
||||
### 👤 **Account**
|
||||
|
||||
- **Avatar Management**: Fixed remove button visibility when no avatar exists
|
||||
|
||||
### ✅ **Todo List**
|
||||
|
||||
- **Visual Feedback**: Completed tasks now struck through in mini calendar
|
||||
|
||||
## 📌 **dev 25w18 (4/28/2025 - 5/4/2025)**
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Development Tools**: Reintroduced Storybook for debugging and testing
|
||||
- **Bug Fixes**:
|
||||
- **Bug Fixes**:
|
||||
- Fixed sidebar hamburger menu icon state restoration
|
||||
- Resolved critical TailwindCSS color palette issues
|
||||
- **Modal System**: Moved modals to external portal at document body
|
||||
|
||||
### 📅 **Calendar**
|
||||
|
||||
- **Code Quality**: Major code refactoring
|
||||
- **Recurring Events**: Added ability to exclude specific dates from recurring events
|
||||
|
||||
## 📌 **dev 25w17 (4/20/2025 - 4/27/2025)**
|
||||
|
||||
### ⏱️ **Codetime**
|
||||
|
||||
- **VS Code Extension**: Published custom extension for system integration and time tracking
|
||||
|
||||
## 📌 **dev 25w16 (4/13/2025 - 4/19/2025)**
|
||||
|
||||
### 📅 **Calendar**
|
||||
|
||||
- **Recurring Events**: Now properly displayed in calendar interface
|
||||
|
||||
### 📚 **Books Library**
|
||||
|
||||
- **Libgen Integration**: Added online/offline status indicator for host availability
|
||||
|
||||
### ⏱️ **Codetime**
|
||||
|
||||
- **GitHub Integration**: Added API endpoint for generating summary images for GitHub profiles
|
||||
|
||||
### 🔐 **Authentication**
|
||||
|
||||
- **Cookie Management**: Renamed `token` to `session` for better clarity
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Performance**: Optimized by removing unnecessary re-renders
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **Configuration**: Moved Puppeteer executable path to `.env` for better configurability
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- **Date Range**: Fixed sidebar date range selector functionality
|
||||
|
||||
## 📌 **dev 25w15 (4/6/2025 - 4/12/2025)**
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **FormModal**: Added support for additional custom fields and `CheckboxInput`
|
||||
|
||||
### 📊 **Server Status**
|
||||
|
||||
- **Dashboard**: Added empty state screen for storage status widget
|
||||
|
||||
### 📅 **Calendar**
|
||||
- **UI Enhancements**:
|
||||
|
||||
- **UI Enhancements**:
|
||||
- Improved overall calendar component design
|
||||
- Added event detail popup on mini calendar date hover
|
||||
- Added quick access button to calendar sidebar
|
||||
- **Recurring Events**: Created form component for recurring time selection
|
||||
|
||||
### ✅ **Todo List**
|
||||
|
||||
- **Calendar Integration**: Tasks with due dates automatically added to calendar
|
||||
|
||||
### 🎬 **Movies**
|
||||
- **Calendar Integration**:
|
||||
|
||||
- **Calendar Integration**:
|
||||
- Removed manual "Add to Calendar" button
|
||||
- Movies with tickets automatically added to calendar
|
||||
|
||||
## 📌 **dev 25w14 (3/30/2025 - 4/5/2025)**
|
||||
|
||||
### 🎨 **Personalization**
|
||||
|
||||
- **API Integration**: Fixed Pixabay API tooltip display when key already exists
|
||||
|
||||
### 🗃️ **Moment Vault**
|
||||
|
||||
- **Mobile Fix**: Resolved photo display issue when pressing "Add" button on mobile
|
||||
|
||||
### 📅 **Calendar**
|
||||
- **User Experience**:
|
||||
|
||||
- **User Experience**:
|
||||
- Added event info tooltips on calendar clicks
|
||||
- Enhanced event creation with `Location`, `Reference Link`, and `Description` fields
|
||||
- Further improved calendar styling
|
||||
- **AI Integration**: Added OpenAI GPT-4o image parsing for event details
|
||||
|
||||
### 🎸 **Guitar Tabs**
|
||||
|
||||
- **Performance**: Implemented on-demand audio file loading
|
||||
|
||||
### ✅ **Todo List**
|
||||
- **Flexibility**:
|
||||
|
||||
- **Flexibility**:
|
||||
- Added time inclusion toggle for task due dates
|
||||
- Fixed database recording issues in task creation/modification
|
||||
|
||||
### 🎬 **Movies**
|
||||
|
||||
- **Calendar Integration**: Added ticket-to-calendar button
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Text Input**: Fixed line break application in text area
|
||||
- **ListBox**: Changed icon from chevron down to chevron up-down
|
||||
- **FormModal**: Added `TextAreaInput` component
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **Database**: Utilized PocketBase's `View` collection for entry tracking via SQL
|
||||
|
||||
## 📌 **dev 25w13 (3/23/2025 - 3/29/2025)**
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Image Generation**: Added AI text-to-image tab in image picker modal
|
||||
- **Delete Confirmation**: Added text input confirmation for delete modals
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Bug Fixes**: Resolved duration formatting issues
|
||||
|
||||
### 🗃️ **Moment Vault**
|
||||
|
||||
- **Entry Types**: Added support for image entries
|
||||
- **Text Wrapping**: Fixed text entry display issues
|
||||
|
||||
### 🎬 **Movies**
|
||||
|
||||
- **API**: Fixed various endpoint bugs
|
||||
|
||||
### 📅 **Calendar**
|
||||
|
||||
- **Performance**: Improved rendering optimization
|
||||
- **Event Management**:
|
||||
- **Event Management**:
|
||||
- Start/end time fields now include both date and time
|
||||
- Fixed event fetching when date/view mode changes
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- **Mini Calendar**: Fixed date formatting in sidebar
|
||||
|
||||
### ✅ **Todo List**
|
||||
|
||||
- **Performance**: Resolved slow data loading issues
|
||||
|
||||
## 📌 **dev 25w12 (3/16/2025 - 3/22/2025)**
|
||||
|
||||
### 🔧 **API**
|
||||
- **Architecture**:
|
||||
|
||||
- **Architecture**:
|
||||
- Moved type interfaces to respective module directories
|
||||
- Continued Controller + Service refactoring
|
||||
- Improved error handling logic
|
||||
- Restructured `src` folder to `modules` and `core` only
|
||||
- **Middleware**: Replaced `validationMiddleware` with `asyncWrapper` for comprehensive error handling
|
||||
- **Error Management**: Moved all try-catch blocks to `asyncWrapper`
|
||||
- **Organization**:
|
||||
- **Organization**:
|
||||
- Moved locale files to respective module directories
|
||||
- Relocated `.env` files to `env` directory
|
||||
- **Build System**: Migrated bundler from `pkgroll` to `bun`
|
||||
- **Schema Management**: Added automatic `schema.json` scanning and database import script
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **State Management**: Replaced all `useReducer` with `useState`
|
||||
- **Routing**: Reconstructed mechanism, combining configurations into single file
|
||||
- **Dependencies**: Pruned unused dependencies and removed empty modules
|
||||
- **Entry Point**: Renamed from `main.tsx` to `index.tsx`
|
||||
- **Frontend**: Removed PocketBase dependency
|
||||
- **Data Fetching**:
|
||||
- **Data Fetching**:
|
||||
- Continued `FormModal` migration
|
||||
- Completed `@tanstack/query` integration
|
||||
- Migrated from `APIFallbackComponent` to `QueryWrapper`
|
||||
@@ -463,37 +557,46 @@
|
||||
- **Plugin Management**: Removed problematic `eslint-plugin-import`
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- **Mini Calendar**: Fixed date range search parameter setting
|
||||
|
||||
### ✅ **Todo List**
|
||||
|
||||
- **Bug Fixes**: Resolved issues from recent code refactor
|
||||
|
||||
### 📅 **Calendar**
|
||||
|
||||
- **Date Parsing**: Fixed mini calendar date parsing issues
|
||||
|
||||
### 🎬 **New Modules**
|
||||
|
||||
- **YouTube Summarizer**: Created and completed full module
|
||||
- **Currency Converter**: Created and completed full module
|
||||
|
||||
### 📚 **Books Library**
|
||||
|
||||
- **Barcode Scanning**: Added barcode scan button beside search bar
|
||||
|
||||
### 🎸 **Guitar Tabs**
|
||||
|
||||
- **Random Loading**: Added random tab loading button beside search bar
|
||||
- **Metadata**: Fixed tab metadata update issues after editing
|
||||
|
||||
### 🎨 **Personalization**
|
||||
|
||||
- **Localization**: Added missing locales for background image adjustment modal
|
||||
|
||||
### 🗃️ **Moment Vault**
|
||||
|
||||
- **Entry Types**: Added support for text entries
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Deployment**: Created GitHub workflow for automatic deployment and versioning
|
||||
- **Mobile**: Fixed sidebar subsection collapse on mobile
|
||||
- **ESLint**: Removed problematic `eslint-plugin-import` and fixed all errors
|
||||
- **Development**: Added numerous `package.json` commands
|
||||
- **Components**:
|
||||
- **Components**:
|
||||
- Created reusable `TextAreaInput` with fixes
|
||||
- Removed `APIFallbackComponent`
|
||||
- Added `formats` prop to `QRCodeScanner`
|
||||
@@ -502,6 +605,7 @@
|
||||
- **Storybook**: Removed due to customization limitations
|
||||
|
||||
### 📖 **Documentation**
|
||||
|
||||
- **Installation**: Updated guide to align with latest codebase structure
|
||||
- **User Guides**: Added brief system guides
|
||||
|
||||
@@ -510,18 +614,22 @@
|
||||
## 📌 **dev 25w11 (3/9/2025 - 3/15/2025)**
|
||||
|
||||
### 📖 **Documentation**
|
||||
|
||||
- **Navigation**: Fixed section detection and highlighting inconsistencies in right navigation bar
|
||||
- **Installation**: Updated guide to align with latest codebase structure
|
||||
|
||||
### 💡 **Idea Box**
|
||||
|
||||
- **Text Handling**: Fixed overflow issues with long words
|
||||
- **Link Management**: Resolved link content and OG data update bugs
|
||||
|
||||
### 📝 **Change Log**
|
||||
|
||||
- **Migration**: Moved all entries from local database to public GitHub repository
|
||||
- **AI Assistance**: Used ChatGPT to refine entries from 25w01 onwards
|
||||
|
||||
### 🚇 **Railway Map**
|
||||
|
||||
- **New Module**: Complete railway mapping system
|
||||
- **Visualization**: Integrated d3.js for dynamic route visualization
|
||||
- **Mapping**: Integrated Leaflet.js for interactive Earth map of MRT lines/stations
|
||||
@@ -529,27 +637,32 @@
|
||||
- **UX**: Enhanced interactions with smooth navigation animations
|
||||
|
||||
### 📅 **Calendar**
|
||||
|
||||
- **Performance**: Data now fetched based on selected date range instead of everything
|
||||
|
||||
### 🔐 **Authentication**
|
||||
|
||||
- **2FA**: Added external authenticator app support as toggleable security
|
||||
- **Fallback**: Email OTP option for users without authenticator apps
|
||||
- **Error Handling**: Improved logic throughout
|
||||
|
||||
### 🎨 **Personalization**
|
||||
|
||||
- **Font Selector**: Availability now depends on `gcloud` key presence in API vault
|
||||
|
||||
### 🌐 **Localization**
|
||||
|
||||
- **Conditional UI**: Sign-in button only shows if `VITE_LOCALIZATION_MANAGER_URL` exists
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Utilities**: Replaced custom functions with optimized Node.js modules
|
||||
- **Organization**:
|
||||
- **Organization**:
|
||||
- Moved non-module files from `modules` to `core`
|
||||
- Reorganized providers/interfaces/constants to respective modules
|
||||
- Eliminated redundant constant files
|
||||
- **Naming**: Standardized `useXXXContext` to `useXXX`
|
||||
- **Theme Management**:
|
||||
- **Theme Management**:
|
||||
- Centralized theme states in `usePersonalization`
|
||||
- Renamed `useThemeColors` to `useComponentsBg`
|
||||
- **Configuration**: Migrated `VITE_GOOGLE_API_KEY` to API key vault
|
||||
@@ -558,8 +671,9 @@
|
||||
- **Component Architecture**: Refactored components into independent `lifeforge-ui` package
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Component Library**: Abstracted reusable components to `lifeforge-ui`
|
||||
- **Button Variants**:
|
||||
- **Button Variants**:
|
||||
- Renamed `no-bg` to `plain`
|
||||
- Added `tertiary` variant
|
||||
- Improved overall styling
|
||||
@@ -570,6 +684,7 @@
|
||||
- **Sidebar**: Main items with subsections open menus without navigation
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **File Management**: Renamed `uploads` to `medium`
|
||||
- **Route Organization**: Split `/users` subroutes into multiple files
|
||||
- **Architecture**: Continued Controller + Service refactoring
|
||||
@@ -579,39 +694,47 @@
|
||||
## 📌 **dev 25w10 (3/2/2025 - 3/8/2025)**
|
||||
|
||||
### 💡 **Idea Box**
|
||||
|
||||
- **Drag & Drop**: Prevented folders from being dropped into themselves
|
||||
- **UI Fixes**:
|
||||
- **UI Fixes**:
|
||||
- Fixed intermittent tag icon display issues
|
||||
- Improved filtering to show nested folder contents when filtering by tags
|
||||
- Made titles optional for link-type ideas
|
||||
|
||||
### 🗃️ **Moment Vault**
|
||||
|
||||
- **Audio Recording**: Fixed occasional capture issues
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- **Bug Fixes**: Resolved asset selection errors when switching transaction types
|
||||
- **Heatmap**: Users can toggle displayed transaction types in calendar heatmap
|
||||
- **Filtering**: Category selections now dynamically filter based on transaction type
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **API Functions**: Refactored `APIRequest` to `fetchAPI` for clarity
|
||||
- **Input Focus**: Action buttons in text inputs now auto-focus the field
|
||||
- **QR Scanner**: Implemented reusable QR Code Scanner component for Form Modal
|
||||
|
||||
### ⏱️ **Codetime**
|
||||
|
||||
- **Analytics**: Introduced time charts for project and language usage tracking
|
||||
|
||||
### 🔑 **API Keys**
|
||||
|
||||
- **Organization**: Moved module from "Settings" to "Confidential" section
|
||||
- **Usability**: Added copy button for API keys
|
||||
|
||||
### 🎬 **Movies**
|
||||
|
||||
- **New Module**: Complete movie management system
|
||||
- **TMDB Integration**: Search and import modal for movie data
|
||||
- **CRUD Operations**: Full create, read, update, delete functionality
|
||||
- **Ticket Management**: Ability to attach ticket data to movie entries
|
||||
|
||||
### 🌐 **Localization**
|
||||
|
||||
- **Architecture**: Complete restructure to align with latest locale folder system
|
||||
|
||||
---
|
||||
@@ -619,20 +742,25 @@
|
||||
## 📌 **dev 25w08 (2/16/2025 - 2/22/2025)**
|
||||
|
||||
### 🎸 **Guitar Tabs**
|
||||
|
||||
- **UI Fix**: Corrected "Sort By" selector text to reflect actual selection
|
||||
|
||||
### 🎨 **Personalization**
|
||||
|
||||
- **Theme Conflict**: Resolved DaisyUI overwriting neutral gray variant
|
||||
|
||||
### 🗃️ **Moment Vault**
|
||||
|
||||
- **New Module**: Life event recording system
|
||||
- **Audio Features**: Record life events as audio with optional OpenAI Whisper transcription
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- **Architecture**: Started migration to `controller + service` pattern
|
||||
- **Testing**: Scrapped initial test codebase, began rewriting
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **React Router**: Upgraded to version 7 for improved performance
|
||||
|
||||
---
|
||||
@@ -640,6 +768,7 @@
|
||||
## 📌 **dev 25w07 (2/9/2025 - 2/15/2025)**
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **UI Fix**: Refactored OTP screen, fixed dark mode color inconsistency
|
||||
|
||||
---
|
||||
@@ -647,9 +776,11 @@
|
||||
## 📌 **dev 25w06 (2/2/2025 - 2/8/2025)**
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Color System**: Rewrote TailwindCSS color palette to correctly convert `oklch` to `RGB`
|
||||
|
||||
### 📧 **Mail Inbox**
|
||||
|
||||
- **Bulk Operations**: Implemented bulk selection and deletion of mail entries
|
||||
|
||||
---
|
||||
@@ -657,6 +788,7 @@
|
||||
## 📌 **dev 25w05 (1/26/2025 - 2/1/2025)**
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Framework Upgrade**: Upgraded TailwindCSS to version 4.0
|
||||
- **Performance**: Memoized all context provider values
|
||||
- **Component Optimization**: Added `react-compiler` for UI performance
|
||||
@@ -668,25 +800,30 @@
|
||||
- **ESLint**: Added plugin for alphabetical component prop sorting
|
||||
|
||||
### 📧 **Mail Inbox**
|
||||
|
||||
- **Complete Overhaul**: Redesigned UI for better user experience
|
||||
- **Gmail Integration**:
|
||||
- **Gmail Integration**:
|
||||
- Synchronized with Gmail, storing emails in custom database
|
||||
- Real-time email synchronization with automatic importing
|
||||
- **Navigation**: Added sidebar, search bar, and pagination
|
||||
- **Viewing**: Users can view mail content directly in UI
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- **Theme Consistency**: Improved light mode across components
|
||||
- **Navigation**: Fixed icon flashing between routes
|
||||
|
||||
### 👗 **Virtual Wardrobe**
|
||||
|
||||
- **Session Cart**: Temporary storage for clothing sets before checkout
|
||||
|
||||
### 🌐 **Localization**
|
||||
|
||||
- **Structure**: Revamped folder structure, segmented translations into namespaces
|
||||
- **Completion**: Finished translations for previously untranslated sections
|
||||
|
||||
### 📱 **Module Removal**
|
||||
|
||||
- **Spotify**: Completely removed from system
|
||||
|
||||
---
|
||||
@@ -694,9 +831,11 @@
|
||||
## 📌 **dev 25w04 (1/19/2025 - 1/25/2025)**
|
||||
|
||||
### 🎵 **Music**
|
||||
|
||||
- **Hotkeys**: Added spacebar support for play/pause functionality
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- **Dependencies**: Upgraded all project dependencies to latest versions
|
||||
- **ESLint**: Migrated to latest release for improved linting
|
||||
- **Structure**: Large-scale folder refactoring, organized components into structured directory
|
||||
@@ -705,12 +844,15 @@
|
||||
- **Organization**: Moved routing and entry files into `core` directory
|
||||
|
||||
### 🔐 **Passwords**
|
||||
|
||||
- **State Management**: Implemented context providers to reduce redundant code
|
||||
|
||||
### 📊 **Server Status**
|
||||
|
||||
- **Code Quality**: Extensive refactor for efficiency and maintainability
|
||||
|
||||
### 👗 **Virtual Wardrobe**
|
||||
|
||||
- **New Module**: Complete clothing item management system
|
||||
- **CRUD Operations**: Full functionality for wardrobe entries
|
||||
- **Filtering**: Added sidebar with filtering options
|
||||
@@ -739,32 +881,39 @@
|
||||
## 📌 **dev 25w02 (1/5/2025 - 1/11/2025)**
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- Fixed issue where financial reports displayed incorrect calculations across multiple years
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- Reduced header icon size for better adaptability on small screens
|
||||
- Reorganized module categories for a more intuitive user experience
|
||||
- Fixed modal layering issues in the Pixabay search filter within the image picker modal
|
||||
- Addressed minor light mode inconsistencies
|
||||
|
||||
### 🛍️ **Wishlist**
|
||||
|
||||
- Improved responsiveness for a smoother mobile experience
|
||||
|
||||
### 🔧 **Module Tools**
|
||||
|
||||
- Completely revamped the utility tool system
|
||||
- Added help screen to guide users through features
|
||||
- Implemented multilingual support
|
||||
- Introduced ability to list and delete modules dynamically
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- Updated `README.md` with latest information and screenshots
|
||||
- Replaced `tsx` runtime with `bun run` for improved build performance
|
||||
- Modified build options to split Node modules into chunks during production builds for better performance
|
||||
|
||||
### 🔧 **API**
|
||||
|
||||
- Integrated OpenAI's GPT-4o-mini model and developed `fetchOpenAI` utility function
|
||||
|
||||
### 💲 **OpenAI API Pricing**
|
||||
|
||||
- **New Module**: Created module allowing users to view OpenAI API pricing directly within the system
|
||||
|
||||
---
|
||||
@@ -774,6 +923,7 @@ Of course, here is the `dev 25w01` change log entry rewritten to match the style
|
||||
## 📌 **dev 25w01 (12/29/2024 - 1/4/2025)**
|
||||
|
||||
### 🏗️ **Code Architecture**
|
||||
|
||||
- 🎉 Happy New Year! 🎉
|
||||
- Optimized stylesheets by removing excessive and unused TailwindCSS classes.
|
||||
- Enhanced type declarations for UI components for better TypeScript support.
|
||||
@@ -790,9 +940,11 @@ Of course, here is the `dev 25w01` change log entry rewritten to match the style
|
||||
- Refactored sidebar logic and enhanced type declarations for improved maintainability.
|
||||
|
||||
### 📊 **Dashboard**
|
||||
|
||||
- Switched the previous quote API to AI-generated quotes for dynamic content.
|
||||
|
||||
### 💡 **Idea Box**
|
||||
|
||||
- Resolved an issue with OG image aspect ratio inconsistencies.
|
||||
- Implemented a tagging system, enabling users to categorize ideas with custom tags.
|
||||
- Added search functionality within idea containers and folders.
|
||||
@@ -802,24 +954,30 @@ Of course, here is the `dev 25w01` change log entry rewritten to match the style
|
||||
- Fixed an issue where moving ideas out of folders was not properly reflected in the UI.
|
||||
|
||||
### 🎨 **UI & Components**
|
||||
|
||||
- Continued improvements to theme consistency across dark and light modes.
|
||||
- Fixed multiple responsive UI issues.
|
||||
- Password input fields are now hidden by default and will only reveal content when users press and hold the eye button.
|
||||
|
||||
### 💰 **Wallet**
|
||||
|
||||
- Corrected text inconsistencies in sidebar buttons.
|
||||
- Increased the resolution of receipt images extracted from PDF files for better accuracy.
|
||||
|
||||
### 🔐 **Authentication**
|
||||
|
||||
- Switched to using PocketBase’s built-in OTP feature, replacing the previous OTP implementation.
|
||||
|
||||
### 🧩 **Sudoku**
|
||||
|
||||
- **New Module**: Introduced a new module allowing users to generate and print Sudoku boards.
|
||||
|
||||
### 📓 **Journal**
|
||||
|
||||
- Fixed image display and upload bugs.
|
||||
|
||||
### 👤 **Account**
|
||||
|
||||
- Addressed UI responsiveness issues.
|
||||
|
||||
---
|
||||
|
||||
438
LICENSE
Normal file
438
LICENSE
Normal file
@@ -0,0 +1,438 @@
|
||||
Attribution-NonCommercial-ShareAlike 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
||||
Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-NonCommercial-ShareAlike 4.0 International Public License
|
||||
("Public License"). To the extent this Public License may be
|
||||
interpreted as a contract, You are granted the Licensed Rights in
|
||||
consideration of Your acceptance of these terms and conditions, and the
|
||||
Licensor grants You such rights in consideration of benefits the
|
||||
Licensor receives from making the Licensed Material available under
|
||||
these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-NC-SA Compatible License means a license listed at
|
||||
creativecommons.org/compatiblelicenses, approved by Creative
|
||||
Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name
|
||||
of a Creative Commons Public License. The License Elements of this
|
||||
Public License are Attribution, NonCommercial, and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
k. NonCommercial means not primarily intended for or directed towards
|
||||
commercial advantage or monetary compensation. For purposes of
|
||||
this Public License, the exchange of the Licensed Material for
|
||||
other material subject to Copyright and Similar Rights by digital
|
||||
file-sharing or similar means is NonCommercial provided there is
|
||||
no payment of monetary compensation in connection with the
|
||||
exchange.
|
||||
|
||||
l. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
m. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
n. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part, for NonCommercial purposes only; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material for
|
||||
NonCommercial purposes only.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. Additional offer from the Licensor -- Adapted Material.
|
||||
Every recipient of Adapted Material from You
|
||||
automatically receives an offer from the Licensor to
|
||||
exercise the Licensed Rights in the Adapted Material
|
||||
under the conditions of the Adapter's License You apply.
|
||||
|
||||
c. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties, including when
|
||||
the Licensed Material is used other than for NonCommercial
|
||||
purposes.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
b. ShareAlike.
|
||||
|
||||
In addition to the conditions in Section 3(a), if You Share
|
||||
Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter's License You apply must be a Creative Commons
|
||||
license with the same License Elements, this version or
|
||||
later, or a BY-NC-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the
|
||||
Adapter's License You apply. You may satisfy this condition
|
||||
in any reasonable manner based on the medium, means, and
|
||||
context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms
|
||||
or conditions on, or apply any Effective Technological
|
||||
Measures to, Adapted Material that restrict exercise of the
|
||||
rights granted under the Adapter's License You apply.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database for NonCommercial purposes
|
||||
only;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material,
|
||||
including for purposes of Section 3(b); and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
|
||||
@@ -20,8 +20,8 @@ export default {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
tsconfigRootDir: __dirname
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
{
|
||||
"name": "lifeforge-documentation",
|
||||
"name": "lifeforge-docs",
|
||||
"private": true,
|
||||
"description": "Documentation for the LifeForge system",
|
||||
"version": "0.0.0",
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
"author": "LifeForge (https://github.com/Lifeforge-app)",
|
||||
"homepage": "https://docs.lifeforge.melvinchia.dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Lifeforge-app/lifeforge.git",
|
||||
"directory": "apps/docs"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -14,6 +22,7 @@
|
||||
"@mdx-js/mdx": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@mdx-js/rollup": "^3.1.0",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/react-custom-scrollbars": "^4.0.13",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.6",
|
||||
@@ -35,4 +44,4 @@
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^5.4.19"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -23,7 +23,7 @@ const components: MDXComponents = {
|
||||
},
|
||||
h6(properties) {
|
||||
return (
|
||||
<h6 {...properties} className="text-primary font-medium sm:text-lg" />
|
||||
<h6 {...properties} className="text-custom-500 font-medium sm:text-lg" />
|
||||
)
|
||||
},
|
||||
h1(properties) {
|
||||
@@ -61,7 +61,7 @@ const components: MDXComponents = {
|
||||
a(properties) {
|
||||
return (
|
||||
<Link
|
||||
className="text-primary font-medium underline"
|
||||
className="text-custom-500 font-medium underline"
|
||||
to={properties.href || ''}
|
||||
>
|
||||
{properties.children}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { useMemo } from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import SECTIONS from "../../../constants/Sections";
|
||||
import { toLinkCase, toTitleCase } from "../../../utils/string";
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { useMemo } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
import SECTIONS from '../../../constants/Sections'
|
||||
import { toLinkCase, toTitleCase } from '../../../utils/string'
|
||||
|
||||
function NavigationBar() {
|
||||
const location = useLocation();
|
||||
const location = useLocation()
|
||||
const currentGroup = useMemo(
|
||||
() => location.pathname.split("/")[1],
|
||||
() => location.pathname.split('/')[1],
|
||||
[location]
|
||||
);
|
||||
)
|
||||
const currentSection = useMemo(
|
||||
() => location.pathname.split("/")[2],
|
||||
() => location.pathname.split('/')[2],
|
||||
[location]
|
||||
);
|
||||
)
|
||||
|
||||
const nextSection = useMemo(() => {
|
||||
const sectionLinkCase = Object.fromEntries(
|
||||
Object.entries(SECTIONS).map(([title, items]) => {
|
||||
return [toLinkCase(title), items.map(toLinkCase)];
|
||||
return [toLinkCase(title), items.map(toLinkCase)]
|
||||
})
|
||||
);
|
||||
)
|
||||
|
||||
const currentGroupIndex =
|
||||
Object.keys(sectionLinkCase).indexOf(currentGroup);
|
||||
const currentGroupIndex = Object.keys(sectionLinkCase).indexOf(currentGroup)
|
||||
|
||||
if (currentGroupIndex === -1) return null;
|
||||
if (currentGroupIndex === -1) return null
|
||||
|
||||
const currentSectionIndex =
|
||||
sectionLinkCase[currentGroup].indexOf(currentSection);
|
||||
sectionLinkCase[currentGroup].indexOf(currentSection)
|
||||
|
||||
if (currentSectionIndex === -1) return null;
|
||||
if (currentSectionIndex === -1) return null
|
||||
|
||||
if (currentSectionIndex === sectionLinkCase[currentGroup].length - 1) {
|
||||
if (currentGroupIndex === Object.keys(sectionLinkCase).length - 1) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -42,36 +42,35 @@ function NavigationBar() {
|
||||
section:
|
||||
sectionLinkCase[
|
||||
Object.keys(sectionLinkCase)[currentGroupIndex + 1]
|
||||
][0],
|
||||
};
|
||||
][0]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
group: currentGroup,
|
||||
section: sectionLinkCase[currentGroup][currentSectionIndex + 1],
|
||||
};
|
||||
}, [currentGroup, currentSection]);
|
||||
section: sectionLinkCase[currentGroup][currentSectionIndex + 1]
|
||||
}
|
||||
}, [currentGroup, currentSection])
|
||||
|
||||
const lastSection = useMemo(() => {
|
||||
const sectionLinkCase = Object.fromEntries(
|
||||
Object.entries(SECTIONS).map(([title, items]) => {
|
||||
return [toLinkCase(title), items.map(toLinkCase)];
|
||||
return [toLinkCase(title), items.map(toLinkCase)]
|
||||
})
|
||||
);
|
||||
)
|
||||
|
||||
const currentGroupIndex =
|
||||
Object.keys(sectionLinkCase).indexOf(currentGroup);
|
||||
const currentGroupIndex = Object.keys(sectionLinkCase).indexOf(currentGroup)
|
||||
|
||||
if (currentGroupIndex === -1) return null;
|
||||
if (currentGroupIndex === -1) return null
|
||||
|
||||
const currentSectionIndex =
|
||||
sectionLinkCase[currentGroup].indexOf(currentSection);
|
||||
sectionLinkCase[currentGroup].indexOf(currentSection)
|
||||
|
||||
if (currentSectionIndex === -1) return null;
|
||||
if (currentSectionIndex === -1) return null
|
||||
|
||||
if (currentSectionIndex === 0) {
|
||||
if (currentGroupIndex === 0) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -80,24 +79,24 @@ function NavigationBar() {
|
||||
sectionLinkCase[Object.keys(sectionLinkCase)[currentGroupIndex - 1]][
|
||||
sectionLinkCase[Object.keys(sectionLinkCase)[currentGroupIndex - 1]]
|
||||
.length - 1
|
||||
],
|
||||
};
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
group: currentGroup,
|
||||
section: sectionLinkCase[currentGroup][currentSectionIndex - 1],
|
||||
};
|
||||
}, [currentGroup, currentSection]);
|
||||
section: sectionLinkCase[currentGroup][currentSectionIndex - 1]
|
||||
}
|
||||
}, [currentGroup, currentSection])
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between mt-12">
|
||||
<div className="mt-12 flex items-center justify-between">
|
||||
{lastSection ? (
|
||||
<Link
|
||||
to={`/${lastSection.group}/${lastSection.section}`}
|
||||
className="text-lg flex items-center font-medium gap-2 text-bg-100 hover:underline"
|
||||
className="text-bg-100 flex items-center gap-2 text-lg font-medium hover:underline"
|
||||
>
|
||||
<Icon icon="tabler:arrow-left" className="w-5 h-5 shrink-0 -mb-1" />
|
||||
<Icon icon="tabler:arrow-left" className="-mb-1 h-5 w-5 shrink-0" />
|
||||
{toTitleCase(lastSection.section)}
|
||||
</Link>
|
||||
) : (
|
||||
@@ -106,14 +105,14 @@ function NavigationBar() {
|
||||
{nextSection && (
|
||||
<Link
|
||||
to={`/${nextSection.group}/${nextSection.section}`}
|
||||
className="text-lg flex items-center font-medium gap-2 text-bg-100 hover:underline"
|
||||
className="text-bg-100 flex items-center gap-2 text-lg font-medium hover:underline"
|
||||
>
|
||||
{toTitleCase(nextSection.section)}
|
||||
<Icon icon="tabler:arrow-right" className="w-5 h-5 shrink-0 -mb-1" />
|
||||
<Icon icon="tabler:arrow-right" className="-mb-1 h-5 w-5 shrink-0" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default NavigationBar;
|
||||
export default NavigationBar
|
||||
|
||||
@@ -1,69 +1,72 @@
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Outlet, useLocation } from "react-router-dom";
|
||||
import NavigationBar from "./components/NavigationBar";
|
||||
import { useEffect } from "react";
|
||||
import Scrollbars from "react-custom-scrollbars";
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { useEffect } from 'react'
|
||||
import Scrollbars from 'react-custom-scrollbars'
|
||||
import { Outlet, useLocation } from 'react-router-dom'
|
||||
|
||||
import NavigationBar from './components/NavigationBar'
|
||||
|
||||
function Boilerplate() {
|
||||
const location = useLocation();
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => {
|
||||
const hash = location.hash;
|
||||
const hash = location.hash
|
||||
|
||||
if (hash) {
|
||||
const element = document.getElementById(hash.slice(1));
|
||||
const element = document.getElementById(hash.slice(1))
|
||||
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
} else {
|
||||
document
|
||||
.querySelector("section")
|
||||
?.parentElement?.parentElement?.scrollTo(0, 0);
|
||||
.querySelector('section')
|
||||
?.parentElement?.parentElement?.scrollTo(0, 0)
|
||||
}
|
||||
}, [location]);
|
||||
}, [location])
|
||||
|
||||
return (
|
||||
<article className="flex-1 relative xl:ml-[20rem] h-full p-6 sm:p-12 !pb-0 overflow-y-auto min-h-0">
|
||||
<article className="relative h-full min-h-0 flex-1 overflow-y-auto p-6 !pb-0 sm:p-12 xl:ml-[20rem]">
|
||||
<Scrollbars
|
||||
autoHide
|
||||
autoHideDuration={200}
|
||||
autoHideTimeout={1000}
|
||||
renderThumbVertical={({ style, ...props }) => (
|
||||
<div
|
||||
{...props}
|
||||
style={{
|
||||
...style,
|
||||
}}
|
||||
className="bg-bg-800 rounded-md"
|
||||
style={{
|
||||
...style
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
autoHide
|
||||
autoHideTimeout={1000}
|
||||
autoHideDuration={200}
|
||||
>
|
||||
<div className="w-full lg:w-[calc(100%-20rem)] min-w-0">
|
||||
<div className="w-full min-w-0 lg:w-[calc(100%-20rem)]">
|
||||
<Outlet />
|
||||
<NavigationBar />
|
||||
<hr className="my-12 border-t-[1.5px] border-bg-800" />
|
||||
<hr className="border-bg-800 my-12 border-t-[1.5px]" />
|
||||
<div className="flex flex-col items-center justify-center gap-2 pb-6 sm:pb-12">
|
||||
<div className="flex items-center gap-2 text-bg-500">
|
||||
<Icon icon="tabler:creative-commons" className="size-6" />
|
||||
<Icon icon="tabler:creative-commons-by" className="size-6" />
|
||||
<Icon icon="tabler:creative-commons-nc" className="size-6" />
|
||||
<Icon icon="tabler:creative-commons-sa" className="size-6" />
|
||||
<div className="text-bg-500 flex items-center gap-2">
|
||||
<Icon className="size-6" icon="tabler:creative-commons" />
|
||||
<Icon className="size-6" icon="tabler:creative-commons-by" />
|
||||
<Icon className="size-6" icon="tabler:creative-commons-nc" />
|
||||
<Icon className="size-6" icon="tabler:creative-commons-sa" />
|
||||
</div>
|
||||
<p className="text-center text-sm text-bg-500">
|
||||
A project by{" "}
|
||||
<p className="text-bg-500 text-center text-sm">
|
||||
A project by{' '}
|
||||
<a
|
||||
className="text-primary underline"
|
||||
target="_blank"
|
||||
className="text-custom-500 underline"
|
||||
href="https://thecodeblog.net"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Melvin Chia
|
||||
</a>{" "}
|
||||
licensed under{" "}
|
||||
</a>{' '}
|
||||
licensed under{' '}
|
||||
<a
|
||||
className="text-primary underline"
|
||||
target="_blank"
|
||||
className="text-custom-500 underline"
|
||||
href="https://creativecommons.org/licenses/by-nc-sa/4.0/"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
CC BY-NC-SA 4.0
|
||||
</a>
|
||||
@@ -73,7 +76,7 @@ function Boilerplate() {
|
||||
</div>
|
||||
</Scrollbars>
|
||||
</article>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Boilerplate;
|
||||
export default Boilerplate
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
function CustomLink({ text, to }: { text: string; to: string }) {
|
||||
return (
|
||||
<Link
|
||||
to={to}
|
||||
className="mt-6 text-lg flex items-center font-medium gap-2 text-primary hover:underline"
|
||||
className="text-custom-500 mt-6 flex items-center gap-2 text-lg font-medium hover:underline"
|
||||
>
|
||||
{text}
|
||||
<Icon icon="tabler:arrow-right" className="w-5 h-5 -mb-1" />
|
||||
<Icon icon="tabler:arrow-right" className="-mb-1 h-5 w-5" />
|
||||
</Link>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomLink;
|
||||
export default CustomLink
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
function Header({
|
||||
sidebarOpen,
|
||||
setSidebarOpen,
|
||||
setSidebarOpen
|
||||
}: {
|
||||
sidebarOpen: boolean;
|
||||
setSidebarOpen: (value: boolean) => void;
|
||||
sidebarOpen: boolean
|
||||
setSidebarOpen: (value: boolean) => void
|
||||
}) {
|
||||
return (
|
||||
<header className="w-full z-[55] sticky top-0 p-3 gap-8 flex items-center px-5 border-b-[1.5px] bg-bg-900 border-bg-800">
|
||||
<header className="bg-bg-900 border-bg-800 sticky top-0 z-[55] flex w-full items-center gap-8 border-b-[1.5px] p-3 px-5">
|
||||
<Link to="/" className="flex items-center gap-2">
|
||||
<Icon icon="tabler:hammer" className="w-8 h-8 text-primary" />
|
||||
<h1 className="text-2xl font-semibold hidden [@media(min-width:420px)]:block">
|
||||
<Icon icon="tabler:hammer" className="text-custom-500 h-8 w-8" />
|
||||
<h1 className="hidden text-2xl font-semibold [@media(min-width:420px)]:block">
|
||||
LifeForge
|
||||
<span className="text-2xl ml-1 text-primary">.</span>
|
||||
<span className="text-xl ml-1 font-medium">Docs</span>
|
||||
<span className="text-custom-500 ml-1 text-2xl">.</span>
|
||||
<span className="ml-1 text-xl font-medium">Docs</span>
|
||||
</h1>
|
||||
<span className="text-xl ml-1 font-medium [@media(min-width:420px)]:hidden">
|
||||
<span className="ml-1 text-xl font-medium [@media(min-width:420px)]:hidden">
|
||||
Docs.
|
||||
</span>
|
||||
</Link>
|
||||
<div className="flex w-full justify-end items-center gap-2">
|
||||
<search className="bg-bg-800/50 p-2 mr-2 pl-4 w-80 lg:w-96 gap-2 rounded-lg hidden md:flex items-center">
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<Icon icon="tabler:search" className="w-5 h-5 text-bg-400" />
|
||||
<div className="flex w-full items-center justify-end gap-2">
|
||||
<search className="bg-bg-800/50 mr-2 hidden w-80 items-center gap-2 rounded-lg p-2 pl-4 md:flex lg:w-96">
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Icon icon="tabler:search" className="text-bg-400 h-5 w-5" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search documentation..."
|
||||
className="p-1 bg-transparent w-full focus:outline-none placeholder-bg-400"
|
||||
className="placeholder-bg-400 w-full bg-transparent p-1 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center text-bg-400 bg-bg-800/90 p-1 px-1.5 rounded-md">
|
||||
<Icon icon="tabler:command" className="w-4 h-4" />
|
||||
<span className="text-bg-400 text-sm ml-0.5">K</span>
|
||||
<div className="text-bg-400 bg-bg-800/90 flex items-center rounded-md p-1 px-1.5">
|
||||
<Icon icon="tabler:command" className="h-4 w-4" />
|
||||
<span className="text-bg-400 ml-0.5 text-sm">K</span>
|
||||
</div>
|
||||
</search>
|
||||
<button className="p-2">
|
||||
<Icon icon="uil:moon" className="w-6 h-6 text-bg-400" />
|
||||
<Icon icon="uil:moon" className="text-bg-400 h-6 w-6" />
|
||||
</button>
|
||||
<a
|
||||
href="https://github.com/melvinchia3636/lifeforge-documentation"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="p-2 block"
|
||||
className="block p-2"
|
||||
>
|
||||
<Icon icon="uil:github" className="w-6 h-6 text-bg-400" />
|
||||
<Icon icon="uil:github" className="text-bg-400 h-6 w-6" />
|
||||
</a>
|
||||
<button
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
className="p-2 block xl:hidden"
|
||||
className="block p-2 xl:hidden"
|
||||
>
|
||||
<Icon icon="tabler:menu" className="w-6 h-6 text-bg-400" />
|
||||
<Icon icon="tabler:menu" className="text-bg-400 h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Header;
|
||||
export default Header
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import React from "react";
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import React from 'react'
|
||||
|
||||
function Notes({
|
||||
className,
|
||||
children,
|
||||
children
|
||||
}: {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`quote w-full p-4 bg-bg-800/70 mt-6 rounded-md border-l-4 border-primary ${className}`}
|
||||
className={`quote bg-bg-800/70 border-custom-500 mt-6 w-full rounded-md border-l-4 p-4 ${className}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon icon="tabler:info-circle" className="w-6 h-6 text-primary" />
|
||||
<Icon icon="tabler:info-circle" className="text-custom-500 h-6 w-6" />
|
||||
<h3 className="text-xl font-semibold">Notes</h3>
|
||||
</div>
|
||||
<p className="text-base -mt-2">{children}</p>
|
||||
<p className="-mt-2 text-base">{children}</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Notes;
|
||||
export default Notes
|
||||
|
||||
@@ -1,185 +1,186 @@
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { toLinkCase, toTitleCase } from "../utils/string";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { toLinkCase, toTitleCase } from '../utils/string'
|
||||
|
||||
function Rightbar() {
|
||||
const [allSections, setAllSections] = useState<string[]>([]);
|
||||
const [activeSection, setActiveSection] = useState<string>("");
|
||||
const location = useLocation();
|
||||
const userClickedRef = useRef(false);
|
||||
const userClickTimeoutRef = useRef<number | null>(null);
|
||||
const [allSections, setAllSections] = useState<string[]>([])
|
||||
const [activeSection, setActiveSection] = useState<string>('')
|
||||
const location = useLocation()
|
||||
const userClickedRef = useRef(false)
|
||||
const userClickTimeoutRef = useRef<number | null>(null)
|
||||
|
||||
// Apply aria-current attribute whenever activeSection changes
|
||||
useEffect(() => {
|
||||
if (activeSection) {
|
||||
document.querySelectorAll("li[aria-current=page]").forEach((li) => {
|
||||
li.removeAttribute("aria-current");
|
||||
});
|
||||
document.querySelectorAll('li[aria-current=page]').forEach(li => {
|
||||
li.removeAttribute('aria-current')
|
||||
})
|
||||
|
||||
const activeLink = document.querySelector(`li a#${activeSection}`);
|
||||
const activeLink = document.querySelector(`li a#${activeSection}`)
|
||||
if (activeLink?.parentElement) {
|
||||
activeLink.parentElement.setAttribute("aria-current", "page");
|
||||
activeLink.parentElement.setAttribute('aria-current', 'page')
|
||||
}
|
||||
}
|
||||
}, [activeSection]);
|
||||
}, [activeSection])
|
||||
|
||||
useEffect(() => {
|
||||
const sections = document.querySelectorAll("article section");
|
||||
const _allSections: string[] = [];
|
||||
sections.forEach((heading) => {
|
||||
_allSections.push(heading.querySelector("h2,h6")?.textContent || "");
|
||||
});
|
||||
setAllSections(_allSections);
|
||||
const sections = document.querySelectorAll('article section')
|
||||
const _allSections: string[] = []
|
||||
sections.forEach(heading => {
|
||||
_allSections.push(heading.querySelector('h2,h6')?.textContent || '')
|
||||
})
|
||||
setAllSections(_allSections)
|
||||
|
||||
setActiveSection("");
|
||||
setActiveSection('')
|
||||
|
||||
const sectionIntersectionRatios = new Map<string, number>();
|
||||
const sectionIntersectionRatios = new Map<string, number>()
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries => {
|
||||
// Don't update if the user just clicked a link
|
||||
if (userClickedRef.current) return;
|
||||
if (userClickedRef.current) return
|
||||
|
||||
entries.forEach((entry) => {
|
||||
const id = entry.target.id || "";
|
||||
const sanitizedId = toLinkCase(id);
|
||||
entries.forEach(entry => {
|
||||
const id = entry.target.id || ''
|
||||
const sanitizedId = toLinkCase(id)
|
||||
|
||||
sectionIntersectionRatios.set(sanitizedId, entry.intersectionRatio);
|
||||
});
|
||||
sectionIntersectionRatios.set(sanitizedId, entry.intersectionRatio)
|
||||
})
|
||||
|
||||
let highestRatio = 0;
|
||||
let mostVisibleSection = "";
|
||||
let highestRatio = 0
|
||||
let mostVisibleSection = ''
|
||||
|
||||
sectionIntersectionRatios.forEach((ratio, id) => {
|
||||
if (ratio > highestRatio) {
|
||||
highestRatio = ratio;
|
||||
mostVisibleSection = id;
|
||||
highestRatio = ratio
|
||||
mostVisibleSection = id
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (mostVisibleSection && mostVisibleSection !== activeSection) {
|
||||
setActiveSection(mostVisibleSection);
|
||||
setActiveSection(mostVisibleSection)
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
|
||||
rootMargin: "-10% 0px -70% 0px",
|
||||
rootMargin: '-10% 0px -70% 0px'
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
sections.forEach((section) => {
|
||||
sections.forEach(section => {
|
||||
if (section.id) {
|
||||
observer.observe(section);
|
||||
observer.observe(section)
|
||||
} else {
|
||||
const heading = section.querySelector("h2,h6");
|
||||
const heading = section.querySelector('h2,h6')
|
||||
if (heading && heading.textContent) {
|
||||
section.id = toLinkCase(heading.textContent.replace(/\./g, ""));
|
||||
observer.observe(section);
|
||||
section.id = toLinkCase(heading.textContent.replace(/\./g, ''))
|
||||
observer.observe(section)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// Set the first section as active after processing all sections
|
||||
if (_allSections.length > 0) {
|
||||
const firstSectionId = toLinkCase(_allSections[0].replace(/\./g, ""));
|
||||
setActiveSection(firstSectionId);
|
||||
const firstSectionId = toLinkCase(_allSections[0].replace(/\./g, ''))
|
||||
setActiveSection(firstSectionId)
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.querySelectorAll("li[aria-current=page]").forEach((li) => {
|
||||
li.removeAttribute("aria-current");
|
||||
});
|
||||
observer.disconnect();
|
||||
document.querySelectorAll('li[aria-current=page]').forEach(li => {
|
||||
li.removeAttribute('aria-current')
|
||||
})
|
||||
observer.disconnect()
|
||||
|
||||
if (userClickTimeoutRef.current) {
|
||||
window.clearTimeout(userClickTimeoutRef.current);
|
||||
window.clearTimeout(userClickTimeoutRef.current)
|
||||
}
|
||||
};
|
||||
}, [location]);
|
||||
}
|
||||
}, [location])
|
||||
|
||||
const handleSectionClick = (itemId: string) => {
|
||||
// Set active section immediately
|
||||
setActiveSection(itemId);
|
||||
setActiveSection(itemId)
|
||||
|
||||
// Manually scroll to the section
|
||||
setTimeout(() => {
|
||||
const sectionElement = document.getElementById(itemId);
|
||||
const sectionElement = document.getElementById(itemId)
|
||||
if (sectionElement) {
|
||||
sectionElement.scrollIntoView({ behavior: "smooth" });
|
||||
sectionElement.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}, 0);
|
||||
}, 0)
|
||||
|
||||
// Temporarily disable the intersection observer's effect
|
||||
userClickedRef.current = true;
|
||||
userClickedRef.current = true
|
||||
|
||||
// Clear any existing timeout
|
||||
if (userClickTimeoutRef.current) {
|
||||
window.clearTimeout(userClickTimeoutRef.current);
|
||||
window.clearTimeout(userClickTimeoutRef.current)
|
||||
}
|
||||
|
||||
// Re-enable the intersection observer after a delay
|
||||
userClickTimeoutRef.current = window.setTimeout(() => {
|
||||
userClickedRef.current = false;
|
||||
}, 1000); // 1 second delay to allow scroll to finish
|
||||
};
|
||||
userClickedRef.current = false
|
||||
}, 1000) // 1 second delay to allow scroll to finish
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="w-80 fixed top-20 hidden lg:block right-0 h-full p-12 overflow-y-auto min-h-0">
|
||||
<aside className="fixed top-20 right-0 hidden h-full min-h-0 w-80 overflow-y-auto p-12 lg:block">
|
||||
<h2 className="text-lg font-semibold">On This Page</h2>
|
||||
<ul className="mt-4 relative before:z-[-1] isolate before:h-full before:border-r-[1.5px] before:border-bg-800 before:absolute before:top-0 before:left-0">
|
||||
<ul className="before:border-bg-800 relative isolate mt-4 before:absolute before:top-0 before:left-0 before:z-[-1] before:h-full before:border-r-[1.5px]">
|
||||
{allSections.map((item, index) => {
|
||||
const itemId = toLinkCase(item.replace(/\./g, ""));
|
||||
const itemId = toLinkCase(item.replace(/\./g, ''))
|
||||
return (
|
||||
<a
|
||||
key={index}
|
||||
id={itemId}
|
||||
href={`#${itemId}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault(); // Prevent default anchor behavior
|
||||
handleSectionClick(itemId);
|
||||
onClick={e => {
|
||||
e.preventDefault() // Prevent default anchor behavior
|
||||
handleSectionClick(itemId)
|
||||
}}
|
||||
className="py-2 block px-4 cursor-pointer aria-[current=page]:font-semibold aria-[current=page]:text-primary aria-[current=page]:border-l-[2.5px] aria-[current=page]:border-primary text-bg-500 hover:text-bg-100 hover:font-medium"
|
||||
aria-current={activeSection === itemId ? "page" : undefined}
|
||||
className="aria-[current=page]:text-custom-500 aria-[current=page]:border-custom-500 text-bg-500 hover:text-bg-100 block cursor-pointer px-4 py-2 hover:font-medium aria-[current=page]:border-l-[2.5px] aria-[current=page]:font-semibold"
|
||||
aria-current={activeSection === itemId ? 'page' : undefined}
|
||||
>
|
||||
{item}
|
||||
</a>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
<a
|
||||
href={`https://github.com/melvinchia3636/lifeforge-documentation/edit/main/src/contents/${
|
||||
location.pathname.split("/")?.[1]
|
||||
location.pathname.split('/')?.[1]
|
||||
}/${toTitleCase(
|
||||
location.pathname.split("/")?.[2]?.replace(/-/g, " ") || ""
|
||||
location.pathname.split('/')?.[2]?.replace(/-/g, ' ') || ''
|
||||
)}.mdx`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-6 flex items-center font-medium gap-2 text-bg-100 hover:underline"
|
||||
className="text-bg-100 mt-6 flex items-center gap-2 font-medium hover:underline"
|
||||
>
|
||||
Edit this page
|
||||
<Icon icon="tabler:arrow-up-right" className="w-5 h-5 -mb-1" />
|
||||
<Icon icon="tabler:arrow-up-right" className="-mb-1 h-5 w-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/melvinchia3636/lifeforge/issues/new"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-4 flex items-center font-medium gap-2 text-bg-100 hover:underline"
|
||||
className="text-bg-100 mt-4 flex items-center gap-2 font-medium hover:underline"
|
||||
>
|
||||
Issue Report
|
||||
<Icon icon="tabler:arrow-up-right" className="w-5 h-5 -mb-1" />
|
||||
<Icon icon="tabler:arrow-up-right" className="-mb-1 h-5 w-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/melvinchia3636/lifeforge"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-4 flex items-center font-medium gap-2 text-bg-100 hover:underline"
|
||||
className="text-bg-100 mt-4 flex items-center gap-2 font-medium hover:underline"
|
||||
>
|
||||
Star on GitHub
|
||||
<Icon icon="tabler:arrow-up-right" className="w-5 h-5 -mb-1" />
|
||||
<Icon icon="tabler:arrow-up-right" className="-mb-1 h-5 w-5" />
|
||||
</a>
|
||||
</aside>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Rightbar;
|
||||
export default Rightbar
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { toLinkCase } from "../utils/string";
|
||||
import SECTIONS from "../constants/Sections";
|
||||
import { Scrollbars } from "react-custom-scrollbars";
|
||||
import { Scrollbars } from 'react-custom-scrollbars'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
import SECTIONS from '../constants/Sections'
|
||||
import { toLinkCase } from '../utils/string'
|
||||
|
||||
function Sidebar({
|
||||
sidebarOpen,
|
||||
setSidebarOpen,
|
||||
setSidebarOpen
|
||||
}: {
|
||||
sidebarOpen: boolean;
|
||||
setSidebarOpen: (value: boolean) => void;
|
||||
sidebarOpen: boolean
|
||||
setSidebarOpen: (value: boolean) => void
|
||||
}) {
|
||||
const location = useLocation();
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`w-full h-screen fixed top-0 left-0 transition-all ${
|
||||
className={`fixed top-0 left-0 h-screen w-full transition-all ${
|
||||
sidebarOpen
|
||||
? "bg-black/20 backdrop-blur-md z-40"
|
||||
: "bg-transparent filter-none z-[-1]"
|
||||
? 'z-40 bg-black/20 backdrop-blur-md'
|
||||
: 'z-[-1] bg-transparent filter-none'
|
||||
}`}
|
||||
></div>
|
||||
<aside
|
||||
className={`${
|
||||
sidebarOpen ? "translate-x-0" : "-translate-x-full xl:translate-x-0"
|
||||
} w-full sm:w-3/4 md:w-1/2 xl:w-80 z-50 bg-bg-900 h-[calc(100%-2rem)] transition-all fixed left-0 flex-1 overflow-y-auto`}
|
||||
sidebarOpen ? 'translate-x-0' : '-translate-x-full xl:translate-x-0'
|
||||
} bg-bg-900 fixed left-0 z-50 h-[calc(100%-2rem)] w-full flex-1 overflow-y-auto transition-all sm:w-3/4 md:w-1/2 xl:w-80`}
|
||||
>
|
||||
<Scrollbars
|
||||
renderThumbVertical={({ style, ...props }) => (
|
||||
<div
|
||||
{...props}
|
||||
style={{
|
||||
...style,
|
||||
...style
|
||||
}}
|
||||
className="bg-bg-800 rounded-md"
|
||||
/>
|
||||
@@ -40,24 +41,24 @@ function Sidebar({
|
||||
autoHideTimeout={1000}
|
||||
autoHideDuration={200}
|
||||
>
|
||||
<div className="p-12 space-y-6">
|
||||
<div className="space-y-6 p-12">
|
||||
{Object.entries(SECTIONS).map(([title, items]) => (
|
||||
<div key={title}>
|
||||
<h2 className="text-lg font-semibold">{title}</h2>
|
||||
<div className="mt-4 relative before:z-[-1] isolate before:h-full before:border-r-[1.5px] before:border-bg-800 before:absolute before:top-0 before:left-0">
|
||||
{items.map((item) => (
|
||||
<div className="before:border-bg-800 relative isolate mt-4 before:absolute before:top-0 before:left-0 before:z-[-1] before:h-full before:border-r-[1.5px]">
|
||||
{items.map(item => (
|
||||
<Link
|
||||
onClick={() => {
|
||||
document.querySelector("main")?.scrollTo(0, 0);
|
||||
setSidebarOpen(false);
|
||||
document.querySelector('main')?.scrollTo(0, 0)
|
||||
setSidebarOpen(false)
|
||||
}}
|
||||
to={`/${toLinkCase(title)}/${toLinkCase(item)}`}
|
||||
key={`${title}-${item}`}
|
||||
className={`py-2 px-4 block cursor-pointer transition-all ${
|
||||
className={`block cursor-pointer px-4 py-2 transition-all ${
|
||||
location.pathname ===
|
||||
`/${toLinkCase(title)}/${toLinkCase(item)}`
|
||||
? "font-semibold text-primary border-l-[2.5px] border-primary hover:border-primary"
|
||||
: "text-bg-500 hover:text-bg-100 hover:font-medium"
|
||||
? 'text-custom-500 border-custom-500 hover:border-custom-500 border-l-[2.5px] font-semibold'
|
||||
: 'text-bg-500 hover:text-bg-100 hover:font-medium'
|
||||
}`}
|
||||
>
|
||||
{item}
|
||||
@@ -70,7 +71,7 @@ function Sidebar({
|
||||
</Scrollbars>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
export default Sidebar
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import React from "react";
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import React from 'react'
|
||||
|
||||
function Warning({
|
||||
children,
|
||||
className,
|
||||
className
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`quote w-full p-4 bg-bg-800/70 mt-6 rounded-md border-l-4 border-amber-500 ${className}`}
|
||||
className={`quote bg-bg-800/70 mt-6 w-full rounded-md border-l-4 border-amber-500 p-4 ${className}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon icon="tabler:alert-triangle" className="w-6 h-6 text-amber-500" />
|
||||
<Icon icon="tabler:alert-triangle" className="h-6 w-6 text-amber-500" />
|
||||
<h3 className="text-xl font-semibold">Warning</h3>
|
||||
</div>
|
||||
<p className="text-base -mt-2">{children}</p>
|
||||
<p className="-mt-2 text-base">{children}</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Warning;
|
||||
export default Warning
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
const SECTIONS = {
|
||||
"Getting Started": [
|
||||
"Introduction",
|
||||
"Installation",
|
||||
"Configuration",
|
||||
"Deployment",
|
||||
'Getting Started': [
|
||||
'Introduction',
|
||||
'Installation',
|
||||
'Configuration',
|
||||
'Deployment'
|
||||
],
|
||||
"User Guide": [
|
||||
"Quick Start",
|
||||
"Dashboard",
|
||||
"Modules",
|
||||
"Personalization",
|
||||
"API Keys",
|
||||
'User Guide': [
|
||||
'Quick Start',
|
||||
'Dashboard',
|
||||
'Modules',
|
||||
'Personalization',
|
||||
'API Keys'
|
||||
],
|
||||
"Developer Guide": [
|
||||
"Architecture",
|
||||
"UI Library",
|
||||
"API Reference",
|
||||
"Localization",
|
||||
"Extending Modules",
|
||||
"Contributing",
|
||||
],
|
||||
};
|
||||
'Developer Guide': [
|
||||
'Architecture',
|
||||
'UI Library',
|
||||
'API Reference',
|
||||
'Localization',
|
||||
'Extending Modules',
|
||||
'Contributing'
|
||||
]
|
||||
}
|
||||
|
||||
export default SECTIONS;
|
||||
export default SECTIONS
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Icon } from "@iconify/react";
|
||||
import Notes from "../../components/Notes";
|
||||
import { Icon } from '@iconify/react'
|
||||
|
||||
import Notes from '../../components/Notes'
|
||||
|
||||
###### Configuration
|
||||
|
||||
@@ -9,7 +10,7 @@ Time to configure the system. This is the most annoying part of setting up Lifef
|
||||
|
||||
---
|
||||
|
||||
<section id="database-configuration">
|
||||
<section id="database-pre-configuration">
|
||||
## Database Pre-Configuration
|
||||
|
||||
First of all, you'll need to configure your Pocketbase database. First, you'll have to somehow get your hands on the local IP of your server. Depending on the OS you're using, the command might differ. Once done, you may proceed to fire up the database that you have just downloaded.
|
||||
@@ -21,7 +22,8 @@ cd database && ./pocketbase serve --http="192.168.x.x:8090"
|
||||
where <code>192.168.x.x</code> is the local IP of your server.
|
||||
|
||||
<Notes>
|
||||
Small tips: place an ampersand (<code>&</code>) at the end of the command to run it in the background.
|
||||
Small tips: place an ampersand (<code>&</code>) at the end of the command to
|
||||
run it in the background.
|
||||
</Notes>
|
||||
|
||||
If you're starting the database instance for the very first time (which you probably are), you'll be given a link in the terminal to create an admin account, like so:
|
||||
@@ -35,7 +37,9 @@ Head to the link and fill in the details to create an admin account. Alternative
|
||||
```
|
||||
|
||||
<Notes>
|
||||
Make sure to set a decently strong password for your admin account, as the database will eventually be exposed to the public internet.If someone maliciously gains access to your database, they can do **A LOT** of damage.
|
||||
Make sure to set a decently strong password for your admin account, as the
|
||||
database will eventually be exposed to the public internet.If someone
|
||||
maliciously gains access to your database, they can do **A LOT** of damage.
|
||||
</Notes>
|
||||
|
||||
Once logged in, head to the settings page (the bottommost icon on the left sidebar). In the **Application** tab, there will be two input box at the top for the **Application Name** and **Application URL**. Fill in the details accordingly. The **Application URL** should be the local IP of your server for now, e.g. <code>http://192.168.x.x:8090</code>.
|
||||
@@ -44,21 +48,22 @@ Once logged in, head to the settings page (the bottommost icon on the left sideb
|
||||
|
||||
### Mail Settings
|
||||
|
||||
Next, head to the **settings** page again and click on the **Mail settings** tab. Change the **Sender name** to your liking (eg. Lifeforge. Support) and fill in the **Sender email** with your email address. This is the email address that will be used to send emails to users (which is you) for the purpose of one-time password (OTP), password reset, etc. Next, check the **Use SMTP mail server** checkbox and fill in the required fields.
|
||||
Next, head to the **settings** page again and click on the **Mail settings** tab. Change the **Sender name** to your liking (eg. Lifeforge. Support) and fill in the **Sender email** with your email address. This is the email address that will be used to send emails to users (which is you) for the purpose of one-time password (OTP), password reset, etc. Next, check the **Use SMTP mail server** checkbox and fill in the required fields.
|
||||
|
||||
[](/assets/configurations/pocketbase-mail-settings.png)
|
||||
|
||||
I'm using Gmail for this, so I'll fill in the following fields:
|
||||
|
||||
| Field | Value |
|
||||
|----------------------------|-------------------------------------------------------------------------------------------------------|
|
||||
| **SMTP Server** | smtp.gmail.com |
|
||||
| **SMTP Port** | 587 |
|
||||
| **SMTP Username** | \<YOUR_GMAIL_EMAIL\> |
|
||||
| **SMTP Password** | \<YOUR_GMAIL_APP_PASSWORD\> |
|
||||
| Field | Value |
|
||||
| ----------------- | --------------------------- |
|
||||
| **SMTP Server** | smtp.gmail.com |
|
||||
| **SMTP Port** | 587 |
|
||||
| **SMTP Username** | \<YOUR_GMAIL_EMAIL\> |
|
||||
| **SMTP Password** | \<YOUR_GMAIL_APP_PASSWORD\> |
|
||||
|
||||
<Notes>
|
||||
You can generate an app password for your Gmail account by following the instructions [here](https://support.google.com/accounts/answer/185833?hl=en).
|
||||
You can generate an app password for your Gmail account by following the
|
||||
instructions [here](https://support.google.com/accounts/answer/185833?hl=en).
|
||||
</Notes>
|
||||
|
||||
Once done, there will be a **Send test email** button showing up at the bottom right corner. Click on it to send a test email to your email address. If you receive the email, then the mail settings are configured correctly.
|
||||
@@ -66,6 +71,7 @@ Once done, there will be a **Send test email** button showing up at the bottom r
|
||||
[](/assets/configurations/pocketbase-send-test-email.png)
|
||||
|
||||
And that's it! You have successfully configured your Pocketbase database. Now, let's move on to the frontend configuration.
|
||||
|
||||
</section>
|
||||
|
||||
<section id="backend-configuration">
|
||||
@@ -74,13 +80,12 @@ And that's it! You have successfully configured your Pocketbase database. Now, l
|
||||
Next, navigate to the <code>lifeforge-api</code> directory that you cloned earlier. You'll see a file named <code>.env.example</code> in the <code>.env</code> directory. Copy this file and rename it to <code>.env.local</code>. Open it in your favourite text editor and fill in the following fields:
|
||||
|
||||
| Field | Value |
|
||||
|-----------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| **MASTER_KEY** | A random string that you can generate using a password generator. This key is used to encrypt sensitive data. |
|
||||
| **PB_HOST** | The URL to your Pocketbase (Make sure to put the full URL, including the <code>http://</code> prefix) server |
|
||||
| **PB_HOST** | The URL to your Pocketbase (Make sure to put the full URL, including the <code>http://</code> prefix) server |
|
||||
| **PB_EMAIL** | The admin email of your Pocketbase server |
|
||||
| **PB_PASSWORD** | The admin password of your Pocketbase server |
|
||||
| **PORT** | The port that you prefer to run your server on (A number between 1 to 65535) |
|
||||
|
||||
| **PORT** | The port that you prefer to run your server on (A number between 1 to 65535) |
|
||||
|
||||
### PDF Thumbnail Generation
|
||||
|
||||
@@ -102,7 +107,7 @@ to:
|
||||
```xml
|
||||
<policy domain="coder" rights="read|write" pattern="PDF" />
|
||||
```
|
||||
|
||||
|
||||
Then, without needing to restart the server, you should be able to generate thumbnails for PDF files.
|
||||
|
||||
### CORS Configuration
|
||||
@@ -133,10 +138,12 @@ where <code>XXXX</code> should be the port that you have set in the <code>.env.l
|
||||
[](/assets/configurations/api-running.png)
|
||||
|
||||
<Notes>
|
||||
If you don't plan to develop the API server, you can run the <code>bun start</code> to start the server in production mode.
|
||||
If you don't plan to develop the API server, you can run the{' '}
|
||||
<code>bun start</code> to start the server in production mode.
|
||||
</Notes>
|
||||
|
||||
And that's it! You have successfully configured your Lifeforge API server. Now, let's continue tackling the database configuration.
|
||||
|
||||
</section>
|
||||
|
||||
<section id="database-configuration">
|
||||
@@ -174,7 +181,8 @@ Click on the **users** collection, which should be located at the top of the lis
|
||||
For now, you just have to fill in the **email**, **password**, **username** and **name** fields. Make sure to check the **Verified** checkbox as well. Once done, click on the **Create** button at the bottom right corner of the screen.
|
||||
|
||||
<Notes>
|
||||
You may use a completely different set of credentials from the admin account that you have just created.
|
||||
You may use a completely different set of credentials from the admin account
|
||||
that you have just created.
|
||||
</Notes>
|
||||
|
||||
### Setup OAuth Providers (Optional)
|
||||
@@ -183,13 +191,13 @@ Lifeforge. provides two OAuth providers for users to sign in with: Google and Gi
|
||||
|
||||
[](/assets/configurations/pocketbase-oauth-providers.png)
|
||||
|
||||
|
||||
<Notes>
|
||||
For the Google OAuth provider, you'll need to create a project in the Google Cloud console and enable the Google OAuth API. You can find the instructions [here](https://developers.google.com/identity/protocols/oauth2).
|
||||
|
||||
For the Github OAuth provider, you'll need to create a Github OAuth app. You can find the instructions [here](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app).
|
||||
|
||||
Make sure to point the **Redirect URL** to <code>\<YOUR_FRONTEND_SERVER_URL\>/auth</code>.
|
||||
|
||||
</Notes>
|
||||
|
||||
</section>
|
||||
@@ -197,14 +205,16 @@ Make sure to point the **Redirect URL** to <code>\<YOUR_FRONTEND_SERVER_URL\>/au
|
||||
<section id="frontend-configuration">
|
||||
## Frontend Configuration
|
||||
|
||||
This is the last step towards setting up your Lifeforge system. Navigate to the <code>lifeforge</code> directory that you cloned earlier. You will find a <code>.env.example</code> file in the root directory. Copy this file and rename it to <code>.env.local</code>. There is only one field that you need to fill in:
|
||||
This is the last step towards setting up your Lifeforge system. Navigate to the <code>lifeforge</code> directory that you cloned earlier. You will find a <code>.env.example</code> file in the root directory. Copy this file and rename it to <code>.env.local</code>. There is only one field that you need to fill in:
|
||||
|
||||
| Field | Value |
|
||||
|-----------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| **VITE_API_HOST** | The URL to your Lifeforge API server. This should be the URL that your API server is running on. (Make sure to put full URL, including the <code>http(s)://</code>prefix) |
|
||||
| Field | Value |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **VITE_API_HOST** | The URL to your Lifeforge API server. This should be the URL that your API server is running on. (Make sure to put full URL, including the <code>http(s)://</code>prefix) |
|
||||
|
||||
<Notes>
|
||||
For the Google API key, you might have to search up how to generate one from the Google Cloud console. It won't be covered in this documentation. Make sure to enable the Google Maps and Google Fonts API for the key.
|
||||
For the Google API key, you might have to search up how to generate one from
|
||||
the Google Cloud console. It won't be covered in this documentation. Make sure
|
||||
to enable the Google Maps and Google Fonts API for the key.
|
||||
</Notes>
|
||||
|
||||
Finally, you can start the dev server by running the following command:
|
||||
@@ -215,7 +225,6 @@ bun dev
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
---
|
||||
|
||||
And that's it! You have successfully configured your Lifeforge system. Now you should be able to start the system locally and access it from your browser. Now, let's move on to the next section to learn how to make it available anywhere you go.
|
||||
And that's it! You have successfully configured your Lifeforge system. Now you should be able to start the system locally and access it from your browser. Now, let's move on to the next section to learn how to make it available anywhere you go.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Icon } from "@iconify/react";
|
||||
import CustomLink from "../../components/CustomLink";
|
||||
import { Icon } from '@iconify/react'
|
||||
|
||||
import CustomLink from '../../components/CustomLink'
|
||||
|
||||
###### Deployment
|
||||
|
||||
# Make it Available Anywhere You Go
|
||||
|
||||
Now that you have successfully set up your Lifeforge system, it's time to deploy it to a server so that you can access it from anywhere. In this guide, I'll show you how to deploy Lifeforge to a cloud server using Vercel and Render, but you can also deploy it to any other cloud server that supports Node.js applications.
|
||||
@@ -14,6 +16,7 @@ Now that you have successfully set up your Lifeforge system, it's time to deploy
|
||||
Login to your Vercel account and create a new project. Connect your GitHub repository to Vercel and deploy the frontend repository. Make sure to copy and paste the environment variables from your <code>.env</code> file to the Vercel dashboard.
|
||||
|
||||
Wait for the deployment to finish, and you should see a link to your Lifeforge frontend. Click on it, and you should be able to access your Lifeforge system. If you want to use a custom domain, you can set it up in the Vercel settings.
|
||||
|
||||
</section>
|
||||
|
||||
<section id="deploying-backend">
|
||||
@@ -31,23 +34,25 @@ git clone https://github.com/melvinchia3636/lifeforge-api-proxy.git
|
||||
|
||||
Then, navigate to the <code>lifeforge-proxy</code> directory and create a <code>.env</code> file. Fill in the following fields:
|
||||
|
||||
| Field | Description |
|
||||
|----------------------------|-------------------------------------------------------------------------------------------------------|
|
||||
| **TARGET_URL** | The public URL of your backend API server (Make sure to put the full URL, including the <code>http(s)://</code> prefix) |
|
||||
| Field | Description |
|
||||
| -------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| **TARGET_URL** | The public URL of your backend API server (Make sure to put the full URL, including the <code>http(s)://</code> prefix) |
|
||||
|
||||
Finally, deploy the proxy server to Render or any other cloud server that supports Node.js applications. Make sure to copy and paste the environment variables from your <code>.env</code> file to the server dashboard.
|
||||
|
||||
Once the deployment is complete, you should see a link to your proxy server. Click on the link and make sure that the server is running correctly. If everything is working fine, make sure to update the <code>VITE_API_HOST</code> field in your frontend <code>.env</code> file to the URL of your proxy server.
|
||||
|
||||
</section>
|
||||
|
||||
<section id="deploying-database">
|
||||
## Deploying Database
|
||||
|
||||
Well, you can just let the database run on your local server. But if you really want to deploy it to a cloud server, there might be several ways to do it, which I'm not going to cover in this guide.
|
||||
Well, you can just let the database run on your local server. But if you really want to deploy it to a cloud server, there might be several ways to do it, which I'm not going to cover in this guide.
|
||||
|
||||
As for the file storage, Pocketbase provides option for you to store you files in the cloud in the form of an S3 bucket. Head to the Pocketbase settings page and under the File Storage section, you can switch the file storage to S3 and fill in the necessary fields to connect to your S3 bucket.
|
||||
|
||||

|
||||
|
||||
</section>
|
||||
|
||||
---
|
||||
@@ -56,4 +61,4 @@ And wallah! You have successfully deployed your Lifeforge system to a cloud serv
|
||||
|
||||
If you're finding the system not to your liking and you just so happen to be a web developer, you can also create your own modules and contribute to the project.
|
||||
|
||||
<CustomLink to="/contribution" text="Learn more about contributing" />
|
||||
<CustomLink to="/contribution" text="Learn more about contributing" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import Notes from "../../components/Notes";
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
|
||||
import Notes from '../../components/Notes'
|
||||
|
||||
###### Installation
|
||||
|
||||
@@ -29,7 +30,6 @@ If you're using Windows to host the Lifeforge system, you might run into some is
|
||||
|
||||
First, clone the Lifeforge frontend repository to your local machine or your server. You can do this by running the following command in your terminal:
|
||||
|
||||
|
||||
```bash
|
||||
git clone https://github.com/lifeforge-app/lifeforge.git
|
||||
```
|
||||
@@ -39,6 +39,7 @@ Then, install the dependencies by running the following command:
|
||||
```bash
|
||||
cd lifeforge && bun install
|
||||
```
|
||||
|
||||
</section>
|
||||
|
||||
<section id="backend-installation">
|
||||
@@ -57,6 +58,7 @@ cd lifeforge-api && bun install
|
||||
```
|
||||
|
||||
After that, you'll need to create a folder named **medium** in the root directory of your server. This folder will be used to temporarily store the media files uploaded by users.
|
||||
|
||||
</section>
|
||||
|
||||
<section id="database-installation">
|
||||
@@ -78,8 +80,9 @@ lifeforge-server (or just put everything in the home directory)
|
||||
├── database
|
||||
└── medium
|
||||
```
|
||||
|
||||
</section>
|
||||
|
||||
---
|
||||
|
||||
And that's it! You're all set for the installation process. But before you can fire up the Lifeforge system, you'll need to configure it. Let's move on to the next section to learn how to do that.
|
||||
And that's it! You're all set for the installation process. But before you can fire up the Lifeforge system, you'll need to configure it. Let's move on to the next section to learn how to do that.
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
import { Icon } from "@iconify/react";
|
||||
import Warning from "../../components/Warning";
|
||||
import CustomLink from "../../components/CustomLink";
|
||||
import { Icon } from '@iconify/react'
|
||||
|
||||
import CustomLink from '../../components/CustomLink'
|
||||
import Warning from '../../components/Warning'
|
||||
|
||||
<Warning className="mb-6">
|
||||
<p>
|
||||
Due to the rapid development of LifeForge, the architecture of this system is constantly undergoing breaking changes. Therefore, this documentation is extremely outdated and incomplete for now. Me being a solo developer, I am unable to keep up with the documentation while also working on the codebase.
|
||||
Due to the rapid development of LifeForge, the architecture of this system
|
||||
is constantly undergoing breaking changes. Therefore, this documentation is
|
||||
extremely outdated and incomplete for now. Me being a solo developer, I am
|
||||
unable to keep up with the documentation while also working on the codebase.
|
||||
</p>
|
||||
<p>
|
||||
Thank you for your interest in LifeForge, and I apologize for the inconvenience. Please note that the documentation will not be updated until the system is more stable and the architecture is finalized. Hopefully, this will happen in the next few months. In the meantime, for those who are experienced with web development, you can still explore the codebase and any form of contribution is highly appreciated.
|
||||
Thank you for your interest in LifeForge, and I apologize for the
|
||||
inconvenience. Please note that the documentation will not be updated until
|
||||
the system is more stable and the architecture is finalized. Hopefully, this
|
||||
will happen in the next few months. In the meantime, for those who are
|
||||
experienced with web development, you can still explore the codebase and any
|
||||
form of contribution is highly appreciated.
|
||||
</p>
|
||||
<p>
|
||||
If you are looking for the latest information about LifeForge, please visit the [changelog](https://github.com/Lifeforge-app/lifeforge/blob/main/CHANGELOG.md).
|
||||
</p>
|
||||
<p>
|
||||
*Sincerely,*
|
||||
</p>
|
||||
<p className="-mt-6">
|
||||
*Melvin Chia, Creator of LifeForge*
|
||||
If you are looking for the latest information about LifeForge, please visit
|
||||
the
|
||||
[changelog](https://github.com/Lifeforge-app/lifeforge/blob/main/CHANGELOG.md).
|
||||
</p>
|
||||
<p>*Sincerely,*</p>
|
||||
<p className="-mt-6">*Melvin Chia, Creator of LifeForge*</p>
|
||||
</Warning>
|
||||
|
||||
<section id="introduction">
|
||||
@@ -30,6 +37,7 @@ Welcome to the official documentation for LifeForge, a comprehensive self-hosted
|
||||

|
||||
|
||||
Before you begin, let's take a look at some background story of this project.
|
||||
|
||||
</section>
|
||||
|
||||
---
|
||||
@@ -68,4 +76,5 @@ A good beginning is half done. Now that half is done, i.e. the basis of the syst
|
||||
## But Wait...
|
||||
|
||||
Did I just mention notion? The absolute GOAT for personal management? Why should you choose Lifeforge instead of Notion? Notion is good, but it has its downsides. First, unlike the completely open sourced Lifeforge, it's a closed sourced program, which means you have absolutely no idea what it's doing with your data. Secondly, it gives you too many customization options, which are sometimes a bit too overwhelming, and you will end up spending more time customizing your system than actually using it. So if you are just looking for a simple but powerful system for personal management, Lifeforge is for you.
|
||||
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Notes from "../../components/Notes";
|
||||
import Notes from '../../components/Notes'
|
||||
|
||||
###### API Keys
|
||||
|
||||
@@ -14,7 +14,9 @@ import Notes from "../../components/Notes";
|
||||
To manage your API keys, head to the **API Keys** page, which is accessible from the sidebar under the **Confidential** section. Here, you can add, edit, and delete API keys for different services. All the API keys you add will be encrypted and stored securely in the database.
|
||||
|
||||
<Notes>
|
||||
For an additional layer of security, accessing the vault requires an OTP (One-Time Password) that will be sent to your email. Therefore, **make sure you have the email settings properly configured in the database settings.**
|
||||
For an additional layer of security, accessing the vault requires an OTP
|
||||
(One-Time Password) that will be sent to your email. Therefore, **make sure
|
||||
you have the email settings properly configured in the database settings.**
|
||||
</Notes>
|
||||
|
||||
Once the OTP is verified, you'll be prompted to create a master password for the API key vault. This master password will be used to encrypt and decrypt the sensitive information stored in the vault.
|
||||
@@ -22,7 +24,11 @@ Once the OTP is verified, you'll be prompted to create a master password for the
|
||||

|
||||
|
||||
<Notes>
|
||||
If you forget the master password, there is NO WAY to recover it, even if you access the database directly, since the password itself is stored in the database as a hash that can only be used for the purpose of verification. You'll need to reset the vault, which will delete all the API keys stored in the vault. Therefore, **make sure to remember the master password**.
|
||||
If you forget the master password, there is NO WAY to recover it, even if you
|
||||
access the database directly, since the password itself is stored in the
|
||||
database as a hash that can only be used for the purpose of verification.
|
||||
You'll need to reset the vault, which will delete all the API keys stored in
|
||||
the vault. Therefore, **make sure to remember the master password**.
|
||||
</Notes>
|
||||
|
||||
### Adding API Keys
|
||||
@@ -30,6 +36,7 @@ If you forget the master password, there is NO WAY to recover it, even if you ac
|
||||
To add a new API key, click on the **Add API Key** button at the top right corner of the page (for first time users, at the center of the page). Fill in the required fields, including the key ID, service name, description, icon, and the key itself. Once done, click on the Save button to add the API key to the vault.
|
||||
|
||||

|
||||
|
||||
</section>
|
||||
|
||||
<section id="module-specific-keys">
|
||||
@@ -40,6 +47,7 @@ Some modules might require you to add API keys to the vault to function properly
|
||||

|
||||
|
||||
There, it will list the keys required by the module. Click on the **Config API Keys** button and it will redirect you to the API Keys page for you to add the required keys. Once you've added the keys, you can go back to the module and it should work properly.
|
||||
|
||||
</section>
|
||||
|
||||
<section id="system-specific-keys">
|
||||
@@ -62,7 +70,8 @@ Lifeforge provides a location autocomplete input field that can be used in forms
|
||||
Head to the [Google Cloud Console](https://console.cloud.google.com/), initialize a project, enable the Places API, and create an API key. Then, add the API key to the vault with the key ID as <code>gcloud</code>
|
||||
|
||||
<Notes>
|
||||
You can enable the Places API in the same project where you have enabled the Google Fonts API, and use the same API key for both services.
|
||||
You can enable the Places API in the same project where you have enabled the
|
||||
Google Fonts API, and use the same API key for both services.
|
||||
</Notes>
|
||||
|
||||
### Pixabay API Key
|
||||
@@ -75,7 +84,9 @@ Head to the [Pixabay API website](https://pixabay.com/service/about/api/), creat
|
||||
The API key is located in the documentation page of the Pixabay API website once you've created an account and logged in, in the green box shown below:
|
||||
|
||||

|
||||
|
||||
</Notes>
|
||||
|
||||
Once you've added the key, if the module you're using supports the Pixabay image picker, you should see the option to select images from Pixabay. One example is the background image selector in the **Personalization** page.
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Notes from '../../components/Notes';
|
||||
import Notes from '../../components/Notes'
|
||||
|
||||
###### Dashboard
|
||||
|
||||
@@ -13,17 +13,25 @@ Every good system has a dashboard, and LifeForge is no exception. The dashboard
|
||||
|
||||
Clicking on the three dots at the top right corner of the dashboard screen, there will be a dropdown menu with two options: **Manage Widgets** and **Unlock/lock Layout**. Click on **Manage Widgets** to open the widget management modal.
|
||||
|
||||
<div className="flex items-center justify-center mt-8">
|
||||
<img src="/assets/user-dashboard/widget-management.png" alt="Widget Management" className="w-[70%] rounded-lg" />
|
||||
<div className="mt-8 flex items-center justify-center">
|
||||
<img
|
||||
src="/assets/user-dashboard/widget-management.png"
|
||||
alt="Widget Management"
|
||||
className="w-[70%] rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Notes>
|
||||
The widgets available in the widget management modal are determined by the widgets provided by the modules you have installed and enabled in the system. If you don't see the widget you want, you might need to install or enable the module that provides the widget.
|
||||
The widgets available in the widget management modal are determined by the
|
||||
widgets provided by the modules you have installed and enabled in the system.
|
||||
If you don't see the widget you want, you might need to install or enable the
|
||||
module that provides the widget.
|
||||
</Notes>
|
||||
|
||||
In the widget management modal, there will be a list of available widgets that you can add to the dashboard. Click on the switch next to the widget name to add or remove the widget from the dashboard. Once done, close the modal, and you will see the widgets appear on the dashboard.
|
||||
|
||||

|
||||
|
||||
</section>
|
||||
|
||||
<section id="adjusting-layout">
|
||||
@@ -44,4 +52,5 @@ Since the system in designed to be used in various devices, the layout will be s
|
||||
## For Fellow Developers
|
||||
|
||||
If you're a developer and you want to create your own widgets for the dashboard, it's pretty straightforward. Head to the [Widget Development Guide](/developer-guide/widget-development) to learn how to create a widget module and add it to the system.
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Notes from '../../components/Notes';
|
||||
import Notes from '../../components/Notes'
|
||||
|
||||
###### Modules
|
||||
|
||||
@@ -7,7 +7,9 @@ import Notes from '../../components/Notes';
|
||||
Modules are the building blocks of LifeForge. They are the functionalities that you can add to the system to enhance its capabilities. Modules can be anything from a simple utility tool to a complex application. You can install and enable modules that you need and disable those you don't in the **Modules** page, which is accessible from the sidebar.
|
||||
|
||||
<Notes>
|
||||
Since the system is still in development, there are a lot of information that I can't provide at the moment. But I'll try my best to give you a general idea of how the modules work and will work in the future.
|
||||
Since the system is still in development, there are a lot of information that
|
||||
I can't provide at the moment. But I'll try my best to give you a general idea
|
||||
of how the modules work and will work in the future.
|
||||
</Notes>
|
||||
|
||||
---
|
||||
@@ -20,22 +22,26 @@ The module list page is where you can see all the modules available in the syste
|
||||
The modules are categorized into different groups, such as **Productivity**, **Lifestyle**, **Utilities**, and more. You can browse through the modules, and, with a flip of a switch, enable or disable the modules as you wish.
|
||||
|
||||

|
||||
|
||||
</section>
|
||||
|
||||
<section id="installing-modules">
|
||||
## Installing Modules
|
||||
|
||||
At the current stage of the system, all of the available modules are already listed in the module list page. But very soon, hopefully within the next few months or so, you'll be able to install modules from the Lifeforge module store, where you can find modules created by other developers, and even publish your own modules for others to use.
|
||||
|
||||
</section>
|
||||
|
||||
<section id="configuring-modules">
|
||||
## Configuring Modules
|
||||
|
||||
Some modules might have configurations that you can adjust to suit your needs. Currently, there are no options to configure the modules, but in the future, you'll be able to configure the modules by clicking on the cog icon next to the module name in the module list page.
|
||||
|
||||
</section>
|
||||
|
||||
<section id="for-fellow-developers">
|
||||
## For Fellow Developers
|
||||
|
||||
If you're a developer and you want to create your own modules for LifeForge, it's pretty straightforward. Head to the [Module Development Guide](/developer-guide/module-development) for a comprehensive guide on how to create a module and add it to the system.
|
||||
|
||||
</section>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Notes from '../../components/Notes';
|
||||
import LightAndDarkMode from './components/LightAndDarkMode';
|
||||
import BgTemp from './components/BgTemp';
|
||||
import Notes from '../../components/Notes'
|
||||
import BgTemp from './components/BgTemp'
|
||||
import LightAndDarkMode from './components/LightAndDarkMode'
|
||||
|
||||
###### Personalization
|
||||
|
||||
@@ -16,6 +16,7 @@ LifeForge is designed to be a personal management system that adapts to your nee
|
||||
The personalization page is where you can adjust the appearance of the system to your liking. You can access the personalization page by clicking on the **Personalization** button in the sidebar, which is located at the bottommost of the sidebar under the **Settings** section.
|
||||
|
||||

|
||||
|
||||
</section>
|
||||
|
||||
<section id="languages">
|
||||
@@ -36,6 +37,7 @@ Although dark mode is the trend nowadays, some people still prefer the light mod
|
||||
<LightAndDarkMode />
|
||||
|
||||
If you want Lifeforge to adhere to your system's appearance settings, you can enable choose the **System** option. This way, Lifeforge will automatically switch between light and dark modes based on your system's appearance settings.
|
||||
|
||||
</section>
|
||||
|
||||
<section id="theme-colors">
|
||||
@@ -76,7 +78,10 @@ Yes, you heard it right, you can set a background image for your system. Click o
|
||||

|
||||
|
||||
<Notes>
|
||||
If you have Pixabay API key set up in the system, there will be an option for you to search for images from Pixabay directly in the image picker modal. Learn more about how to set up the Pixabay API key in the [API Keys Guide](/user-guide/api-keys).
|
||||
If you have Pixabay API key set up in the system, there will be an option for
|
||||
you to search for images from Pixabay directly in the image picker modal.
|
||||
Learn more about how to set up the Pixabay API key in the [API Keys
|
||||
Guide](/user-guide/api-keys).
|
||||
</Notes>
|
||||
|
||||
Once the background image is set, there will be two buttons at the right hand side of the background image config column, **Adjust** and **Remove**.
|
||||
@@ -98,4 +103,4 @@ The font family used in the system is default to [Onest](https://fonts.google.co
|
||||
<Notes>
|
||||
If the Google Cloud API key is not set up in the system, the font family dropdown list will be disabled, and you won't be able to change the font family. Learn more about how to set up the Google Cloud API key in the [API Keys Guide](/user-guide/api-keys).
|
||||
</Notes>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Notes from "../../components/Notes";
|
||||
import Notes from '../../components/Notes'
|
||||
|
||||
###### Quick Start
|
||||
|
||||
@@ -18,7 +18,8 @@ When you open LifeForge, the very first thing you'll see is the login screen. Un
|
||||
Once logged in successfully, you'll be redirected to the dashboard, where you journey with LifeForge begins.
|
||||
|
||||
<Notes>
|
||||
If you have OAuth2 providers set up in the database, there will be a button for each provider below the login button for you to log in with.
|
||||
If you have OAuth2 providers set up in the database, there will be a button
|
||||
for each provider below the login button for you to log in with.
|
||||
</Notes>
|
||||
|
||||
</section>
|
||||
@@ -68,4 +69,5 @@ To manage your API keys, head to the **API Keys** page, which is accessible from
|
||||

|
||||
|
||||
For more information on how to manage API keys, refer to [The API Keys Guide](/user-guide/api-keys).
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useState } from "react";
|
||||
import { useState } from 'react'
|
||||
|
||||
const COLORS = ["bg-slate", "bg-gray", "bg-zinc", "bg-neutral", "bg-stone"];
|
||||
const COLORS = ['bg-slate', 'bg-gray', 'bg-zinc', 'bg-neutral', 'bg-stone']
|
||||
|
||||
function BgTemp() {
|
||||
const [bgTemp, setBgTemp] = useState<string>("bg-slate");
|
||||
const [bgTemp, setBgTemp] = useState<string>('bg-slate')
|
||||
|
||||
return (
|
||||
<div className="w-full min-w-0 flex mt-6">
|
||||
<div className="w-full p-4 rounded-md bg-zinc-800/50">
|
||||
<div className="flex flex-col gap-4 md:flex-row items-center w-full justify-between">
|
||||
<h3 className="text-xl font-semibold w-full text-left">
|
||||
<div className="mt-6 flex w-full min-w-0">
|
||||
<div className="w-full rounded-md bg-zinc-800/50 p-4">
|
||||
<div className="flex w-full flex-col items-center justify-between gap-4 md:flex-row">
|
||||
<h3 className="w-full text-left text-xl font-semibold">
|
||||
Background Temperature Preview
|
||||
</h3>
|
||||
<div className="flex items-center gap-4 p-2">
|
||||
@@ -18,11 +18,11 @@ function BgTemp() {
|
||||
key={index}
|
||||
className={`size-6 rounded-full ${color} bg-bg-500 ${
|
||||
bgTemp === color
|
||||
? "ring-2 ring-zinc-100 ring-offset-2 ring-offset-bg-950"
|
||||
: ""
|
||||
? 'ring-offset-bg-950 ring-2 ring-zinc-100 ring-offset-2'
|
||||
: ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
setBgTemp(color);
|
||||
setBgTemp(color)
|
||||
}}
|
||||
></button>
|
||||
))}
|
||||
@@ -32,11 +32,11 @@ function BgTemp() {
|
||||
key={bgTemp}
|
||||
src={`/assets/bgTemp/${COLORS.indexOf(bgTemp) + 1}.png`}
|
||||
alt=""
|
||||
className="w-full mt-4 rounded-md"
|
||||
className="mt-4 w-full rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default BgTemp;
|
||||
export default BgTemp
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { useState } from "react";
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { useState } from 'react'
|
||||
|
||||
function LightAndDarkMode() {
|
||||
const [mode, setMode] = useState<"light" | "dark">("light");
|
||||
const [mode, setMode] = useState<'light' | 'dark'>('light')
|
||||
|
||||
return (
|
||||
<div className="w-full min-w-0 flex mt-6">
|
||||
<div className="w-full p-4 rounded-md bg-bg-800/50">
|
||||
<div className="flex flex-col gap-4 md:flex-row items-center justify-between">
|
||||
<h3 className="text-xl font-semibold w-full text-left">
|
||||
<div className="mt-6 flex w-full min-w-0">
|
||||
<div className="bg-bg-800/50 w-full rounded-md p-4">
|
||||
<div className="flex flex-col items-center justify-between gap-4 md:flex-row">
|
||||
<h3 className="w-full text-left text-xl font-semibold">
|
||||
Light/Dark Theme Preview
|
||||
</h3>
|
||||
<div className="flex w-full md:w-auto bg-bg-800 rounded-md gap-2">
|
||||
<div className="bg-bg-800 flex w-full gap-2 rounded-md md:w-auto">
|
||||
<button
|
||||
onClick={() => setMode("light")}
|
||||
className={`p-2 px-4 w-1/2 justify-center rounded-md flex items-center font-medium gap-1 ${
|
||||
mode === "light"
|
||||
? "bg-primary text-bg-800"
|
||||
: "bg-bg-800 text-bg-400"
|
||||
onClick={() => setMode('light')}
|
||||
className={`flex w-1/2 items-center justify-center gap-1 rounded-md p-2 px-4 font-medium ${
|
||||
mode === 'light'
|
||||
? 'bg-custom-500 text-bg-800'
|
||||
: 'bg-bg-800 text-bg-400'
|
||||
}`}
|
||||
>
|
||||
<Icon icon="uil:sun" className="w-5 h-5" />
|
||||
<Icon icon="uil:sun" className="h-5 w-5" />
|
||||
Light
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMode("dark")}
|
||||
className={`p-2 px-4 w-1/2 justify-center rounded-md flex items-center font-medium gap-1 ${
|
||||
mode === "dark"
|
||||
? "bg-primary text-bg-800"
|
||||
: "bg-bg-800 text-bg-400"
|
||||
onClick={() => setMode('dark')}
|
||||
className={`flex w-1/2 items-center justify-center gap-1 rounded-md p-2 px-4 font-medium ${
|
||||
mode === 'dark'
|
||||
? 'bg-custom-500 text-bg-800'
|
||||
: 'bg-bg-800 text-bg-400'
|
||||
}`}
|
||||
>
|
||||
<Icon icon="uil:moon" className="w-5 h-5" />
|
||||
<Icon icon="uil:moon" className="h-5 w-5" />
|
||||
Dark
|
||||
</button>
|
||||
</div>
|
||||
@@ -40,11 +40,11 @@ function LightAndDarkMode() {
|
||||
key={mode}
|
||||
src={`/assets/colors/${mode}.png`}
|
||||
alt=""
|
||||
className="w-full mt-4 rounded-md"
|
||||
className="mt-4 w-full rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default LightAndDarkMode;
|
||||
export default LightAndDarkMode
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Albert+Sans:ital,wght@0,100..900;1,100..900&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap");
|
||||
@import url('https://fonts.googleapis.com/css2?family=Albert+Sans:ital,wght@0,100..900;1,100..900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import 'tailwindcss';
|
||||
@import 'lifeforge-ui/dist/index.css';
|
||||
@source "../../../node_modules/lifeforge-ui/dist";
|
||||
|
||||
* {
|
||||
font-family: "Albert Sans", sans-serif;
|
||||
font-family: 'Albert Sans', sans-serif;
|
||||
}
|
||||
|
||||
.hljs * {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
@@ -18,314 +18,10 @@
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
@apply text-bg-100 break-all text-sm sm:text-base;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
@apply text-bg-100 text-sm break-all sm:text-base;
|
||||
}
|
||||
|
||||
article section {
|
||||
@apply scroll-mt-24;
|
||||
}
|
||||
|
||||
.theme-red {
|
||||
--color-custom-50: 255 235 238;
|
||||
--color-custom-100: 255 205 210;
|
||||
--color-custom-200: 239 154 154;
|
||||
--color-custom-300: 229 115 115;
|
||||
--color-custom-400: 239 83 80;
|
||||
--color-custom-500: 244 67 54;
|
||||
--color-custom-600: 229 57 53;
|
||||
--color-custom-700: 211 47 47;
|
||||
--color-custom-800: 198 40 40;
|
||||
--color-custom-900: 183 28 28;
|
||||
}
|
||||
|
||||
.theme-pink {
|
||||
--color-custom-50: 252 228 236;
|
||||
--color-custom-100: 248 187 208;
|
||||
--color-custom-200: 244 143 177;
|
||||
--color-custom-300: 240 98 146;
|
||||
--color-custom-400: 236 64 122;
|
||||
--color-custom-500: 233 30 99;
|
||||
--color-custom-600: 216 27 96;
|
||||
--color-custom-700: 194 24 91;
|
||||
--color-custom-800: 173 20 87;
|
||||
--color-custom-900: 136 14 79;
|
||||
}
|
||||
|
||||
.theme-purple {
|
||||
--color-custom-50: 243 229 245;
|
||||
--color-custom-100: 225 190 231;
|
||||
--color-custom-200: 206 147 216;
|
||||
--color-custom-300: 186 104 200;
|
||||
--color-custom-400: 171 71 188;
|
||||
--color-custom-500: 156 39 176;
|
||||
--color-custom-600: 142 36 170;
|
||||
--color-custom-700: 123 31 162;
|
||||
--color-custom-800: 106 27 154;
|
||||
--color-custom-900: 74 20 140;
|
||||
}
|
||||
|
||||
.theme-deep-purple {
|
||||
--color-custom-50: 237 231 246;
|
||||
--color-custom-100: 209 196 233;
|
||||
--color-custom-200: 179 157 219;
|
||||
--color-custom-300: 149 117 205;
|
||||
--color-custom-400: 126 87 194;
|
||||
--color-custom-500: 103 58 183;
|
||||
--color-custom-600: 94 53 177;
|
||||
--color-custom-700: 81 45 168;
|
||||
--color-custom-800: 69 39 160;
|
||||
--color-custom-900: 49 27 146;
|
||||
}
|
||||
|
||||
.theme-indigo {
|
||||
--color-custom-50: 232 234 246;
|
||||
--color-custom-100: 197 202 233;
|
||||
--color-custom-200: 159 168 218;
|
||||
--color-custom-300: 121 134 203;
|
||||
--color-custom-400: 92 107 192;
|
||||
--color-custom-500: 63 81 181;
|
||||
--color-custom-600: 57 73 171;
|
||||
--color-custom-700: 48 63 159;
|
||||
--color-custom-800: 40 53 147;
|
||||
--color-custom-900: 26 35 126;
|
||||
}
|
||||
|
||||
.theme-blue {
|
||||
--color-custom-50: 227 242 253;
|
||||
--color-custom-100: 187 222 251;
|
||||
--color-custom-200: 144 202 249;
|
||||
--color-custom-300: 100 181 246;
|
||||
--color-custom-400: 66 165 245;
|
||||
--color-custom-500: 33 150 243;
|
||||
--color-custom-600: 30 136 229;
|
||||
--color-custom-700: 25 118 210;
|
||||
--color-custom-800: 21 101 192;
|
||||
--color-custom-900: 13 71 161;
|
||||
}
|
||||
|
||||
.theme-light-blue {
|
||||
--color-custom-50: 225 245 254;
|
||||
--color-custom-100: 179 229 252;
|
||||
--color-custom-200: 129 212 250;
|
||||
--color-custom-300: 79 195 247;
|
||||
--color-custom-400: 41 182 246;
|
||||
--color-custom-500: 3 169 244;
|
||||
--color-custom-600: 3 155 229;
|
||||
--color-custom-700: 2 136 209;
|
||||
--color-custom-800: 2 119 189;
|
||||
--color-custom-900: 1 87 155;
|
||||
}
|
||||
|
||||
.theme-cyan {
|
||||
--color-custom-50: 224 247 250;
|
||||
--color-custom-100: 178 235 242;
|
||||
--color-custom-200: 128 222 234;
|
||||
--color-custom-300: 77 208 225;
|
||||
--color-custom-400: 38 198 218;
|
||||
--color-custom-500: 0 188 212;
|
||||
--color-custom-600: 0 172 193;
|
||||
--color-custom-700: 0 151 167;
|
||||
--color-custom-800: 0 131 143;
|
||||
--color-custom-900: 0 96 100;
|
||||
}
|
||||
|
||||
.theme-teal {
|
||||
--color-custom-50: 224 242 241;
|
||||
--color-custom-100: 178 223 219;
|
||||
--color-custom-200: 128 203 196;
|
||||
--color-custom-300: 77 182 172;
|
||||
--color-custom-400: 38 166 154;
|
||||
--color-custom-500: 0 150 136;
|
||||
--color-custom-600: 0 137 123;
|
||||
--color-custom-700: 0 121 107;
|
||||
--color-custom-800: 0 105 92;
|
||||
--color-custom-900: 0 77 64;
|
||||
}
|
||||
|
||||
.theme-green {
|
||||
--color-custom-50: 232 245 233;
|
||||
--color-custom-100: 200 230 201;
|
||||
--color-custom-200: 165 214 167;
|
||||
--color-custom-300: 129 199 132;
|
||||
--color-custom-400: 102 187 106;
|
||||
--color-custom-500: 76 175 80;
|
||||
--color-custom-600: 67 160 71;
|
||||
--color-custom-700: 56 142 60;
|
||||
--color-custom-800: 46 125 50;
|
||||
--color-custom-900: 27 94 32;
|
||||
}
|
||||
|
||||
.theme-light-green {
|
||||
--color-custom-50: 241 248 233;
|
||||
--color-custom-100: 220 237 200;
|
||||
--color-custom-200: 197 225 165;
|
||||
--color-custom-300: 174 213 129;
|
||||
--color-custom-400: 156 204 101;
|
||||
--color-custom-500: 139 195 74;
|
||||
--color-custom-600: 124 179 66;
|
||||
--color-custom-700: 104 159 56;
|
||||
--color-custom-800: 85 139 47;
|
||||
--color-custom-900: 51 105 30;
|
||||
}
|
||||
|
||||
.theme-lime {
|
||||
--color-custom-50: 249 251 231;
|
||||
--color-custom-100: 240 244 195;
|
||||
--color-custom-200: 230 238 156;
|
||||
--color-custom-300: 220 231 117;
|
||||
--color-custom-400: 212 225 87;
|
||||
--color-custom-500: 205 220 57;
|
||||
--color-custom-600: 192 202 51;
|
||||
--color-custom-700: 175 180 43;
|
||||
--color-custom-800: 158 157 36;
|
||||
--color-custom-900: 130 119 23;
|
||||
}
|
||||
|
||||
.theme-yellow {
|
||||
--color-custom-50: 255 253 231;
|
||||
--color-custom-100: 255 249 196;
|
||||
--color-custom-200: 255 245 157;
|
||||
--color-custom-300: 255 241 118;
|
||||
--color-custom-400: 255 238 88;
|
||||
--color-custom-500: 255 235 59;
|
||||
--color-custom-600: 253 216 53;
|
||||
--color-custom-700: 251 192 45;
|
||||
--color-custom-800: 249 168 37;
|
||||
--color-custom-900: 245 127 23;
|
||||
}
|
||||
|
||||
.theme-amber {
|
||||
--color-custom-50: 255 248 225;
|
||||
--color-custom-100: 255 236 179;
|
||||
--color-custom-200: 255 224 130;
|
||||
--color-custom-300: 255 213 79;
|
||||
--color-custom-400: 255 202 40;
|
||||
--color-custom-500: 255 193 7;
|
||||
--color-custom-600: 255 179 0;
|
||||
--color-custom-700: 255 160 0;
|
||||
--color-custom-800: 255 143 0;
|
||||
--color-custom-900: 255 111 0;
|
||||
}
|
||||
|
||||
.theme-orange {
|
||||
--color-custom-50: 255 243 224;
|
||||
--color-custom-100: 255 224 178;
|
||||
--color-custom-200: 255 204 128;
|
||||
--color-custom-300: 255 183 77;
|
||||
--color-custom-400: 255 167 38;
|
||||
--color-custom-500: 255 152 0;
|
||||
--color-custom-600: 251 140 0;
|
||||
--color-custom-700: 245 124 0;
|
||||
--color-custom-800: 239 108 0;
|
||||
--color-custom-900: 230 81 0;
|
||||
}
|
||||
|
||||
.theme-deep-orange {
|
||||
--color-custom-50: 251 233 231;
|
||||
--color-custom-100: 255 204 188;
|
||||
--color-custom-200: 255 171 145;
|
||||
--color-custom-300: 255 138 101;
|
||||
--color-custom-400: 255 112 67;
|
||||
--color-custom-500: 255 87 34;
|
||||
--color-custom-600: 244 81 30;
|
||||
--color-custom-700: 230 74 25;
|
||||
--color-custom-800: 216 67 21;
|
||||
--color-custom-900: 191 54 12;
|
||||
}
|
||||
|
||||
.theme-brown {
|
||||
--color-custom-50: 239 235 233;
|
||||
--color-custom-100: 215 204 200;
|
||||
--color-custom-200: 188 170 164;
|
||||
--color-custom-300: 161 136 127;
|
||||
--color-custom-400: 141 110 99;
|
||||
--color-custom-500: 121 85 72;
|
||||
--color-custom-600: 109 76 65;
|
||||
--color-custom-700: 93 64 55;
|
||||
--color-custom-800: 78 52 46;
|
||||
--color-custom-900: 62 39 35;
|
||||
}
|
||||
|
||||
.theme-grey {
|
||||
--color-custom-50: 250 250 250;
|
||||
--color-custom-100: 245 245 245;
|
||||
--color-custom-200: 238 238 238;
|
||||
--color-custom-300: 224 224 224;
|
||||
--color-custom-400: 189 189 189;
|
||||
--color-custom-500: 158 158 158;
|
||||
--color-custom-600: 117 117 117;
|
||||
--color-custom-700: 97 97 97;
|
||||
--color-custom-800: 66 66 66;
|
||||
--color-custom-900: 33 33 33;
|
||||
}
|
||||
|
||||
.bg-slate {
|
||||
--color-bg-50: 248 250 252;
|
||||
--color-bg-100: 241 245 249;
|
||||
--color-bg-200: 226 232 240;
|
||||
--color-bg-300: 203 213 225;
|
||||
--color-bg-400: 148 163 184;
|
||||
--color-bg-500: 100 116 139;
|
||||
--color-bg-600: 71 85 105;
|
||||
--color-bg-700: 51 65 85;
|
||||
--color-bg-800: 30 41 59;
|
||||
--color-bg-900: 15 23 42;
|
||||
--color-bg-950: 2 6 23;
|
||||
}
|
||||
|
||||
.bg-gray {
|
||||
--color-bg-50: 249 250 251;
|
||||
--color-bg-100: 243 244 246;
|
||||
--color-bg-200: 229 231 235;
|
||||
--color-bg-300: 209 213 219;
|
||||
--color-bg-400: 156 163 175;
|
||||
--color-bg-500: 107 114 128;
|
||||
--color-bg-600: 75 85 99;
|
||||
--color-bg-700: 55 65 81;
|
||||
--color-bg-800: 31 41 55;
|
||||
--color-bg-900: 17 24 39;
|
||||
--color-bg-950: 3 7 18;
|
||||
}
|
||||
|
||||
.bg-zinc {
|
||||
--color-bg-50: 250 250 250;
|
||||
--color-bg-100: 244 244 245;
|
||||
--color-bg-200: 228 228 231;
|
||||
--color-bg-300: 212 212 216;
|
||||
--color-bg-400: 161 161 170;
|
||||
--color-bg-500: 113 113 122;
|
||||
--color-bg-600: 82 82 91;
|
||||
--color-bg-700: 63 63 70;
|
||||
--color-bg-800: 39 39 42;
|
||||
--color-bg-900: 24 24 27;
|
||||
--color-bg-950: 9 9 11;
|
||||
}
|
||||
|
||||
.bg-neutral {
|
||||
--color-bg-50: 250 250 250;
|
||||
--color-bg-100: 245 245 245;
|
||||
--color-bg-200: 229 229 229;
|
||||
--color-bg-300: 212 212 212;
|
||||
--color-bg-400: 163 163 163;
|
||||
--color-bg-500: 115 115 115;
|
||||
--color-bg-600: 82 82 82;
|
||||
--color-bg-700: 64 64 64;
|
||||
--color-bg-800: 38 38 38;
|
||||
--color-bg-900: 23 23 23;
|
||||
--color-bg-950: 10 10 10;
|
||||
}
|
||||
|
||||
.bg-stone {
|
||||
--color-bg-50: 250 250 249;
|
||||
--color-bg-100: 245 245 244;
|
||||
--color-bg-200: 231 229 228;
|
||||
--color-bg-300: 214 211 209;
|
||||
--color-bg-400: 168 162 158;
|
||||
--color-bg-500: 120 113 108;
|
||||
--color-bg-600: 87 83 78;
|
||||
--color-bg-700: 68 64 60;
|
||||
--color-bg-800: 41 37 36;
|
||||
--color-bg-900: 28 25 23;
|
||||
--color-bg-950: 12 10 9;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { BrowserRouter as Router } from 'react-router-dom'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
</React.StrictMode>
|
||||
);
|
||||
)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
export function toLinkCase(str: string) {
|
||||
return str
|
||||
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
||||
.replace(/[\s_]+/g, "-")
|
||||
.toLowerCase();
|
||||
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
||||
.replace(/[\s_]+/g, '-')
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
export function toTitleCase(str: string) {
|
||||
return str
|
||||
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
||||
.replace(/[\s_]+/g, " ")
|
||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
.replace(/[\s_]+/g, ' ')
|
||||
.replace(/\b\w/g, c => c.toUpperCase())
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["index.html", "./src/**/*.{html,js,jsx,ts,tsx,mdx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#CEDD3E",
|
||||
custom: {
|
||||
50: "rgb(var(--color-custom-50) / <alpha-value>)",
|
||||
100: "rgb(var(--color-custom-100) / <alpha-value>)",
|
||||
200: "rgb(var(--color-custom-200) / <alpha-value>)",
|
||||
300: "rgb(var(--color-custom-300) / <alpha-value>)",
|
||||
400: "rgb(var(--color-custom-400) / <alpha-value>)",
|
||||
500: "rgb(var(--color-custom-500) / <alpha-value>)",
|
||||
600: "rgb(var(--color-custom-600) / <alpha-value>)",
|
||||
700: "rgb(var(--color-custom-700) / <alpha-value>)",
|
||||
800: "rgb(var(--color-custom-800) / <alpha-value>)",
|
||||
900: "rgb(var(--color-custom-900) / <alpha-value>)",
|
||||
},
|
||||
bg: {
|
||||
50: "rgb(var(--color-bg-50) / <alpha-value>) !important",
|
||||
100: "rgb(var(--color-bg-100) / <alpha-value>) !important",
|
||||
200: "rgb(var(--color-bg-200) / <alpha-value>) !important",
|
||||
300: "rgb(var(--color-bg-300) / <alpha-value>) !important",
|
||||
400: "rgb(var(--color-bg-400) / <alpha-value>) !important",
|
||||
500: "rgb(var(--color-bg-500) / <alpha-value>) !important",
|
||||
600: "rgb(var(--color-bg-600) / <alpha-value>) !important",
|
||||
700: "rgb(var(--color-bg-700) / <alpha-value>) !important",
|
||||
800: "rgb(var(--color-bg-800) / <alpha-value>) !important",
|
||||
900: "rgb(var(--color-bg-900) / <alpha-value>) !important",
|
||||
950: "rgb(var(--color-bg-950) / <alpha-value>) !important",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"destination": "/"
|
||||
}
|
||||
]
|
||||
}
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"destination": "/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import mdx from "@mdx-js/rollup";
|
||||
import rehypeHighlight from "rehype-highlight";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import mdx from '@mdx-js/rollup'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import rehypeHighlight from 'rehype-highlight'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
const options = {
|
||||
// See https://mdxjs.com/advanced/plugins
|
||||
remarkPlugins: [remarkGfm],
|
||||
rehypePlugins: [rehypeHighlight],
|
||||
};
|
||||
rehypePlugins: [rehypeHighlight]
|
||||
}
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), mdx(options)],
|
||||
});
|
||||
plugins: [react(), mdx(options), tailwindcss()]
|
||||
})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "@lifeforge/localization-manager",
|
||||
"version": "1.0.0",
|
||||
"name": "lifeforge-localization-manager",
|
||||
"private": true,
|
||||
"description": "An internal tool for managing application localization and translations.",
|
||||
"license": "MIT",
|
||||
"description": "A tool for streamlining the localization process in LifeForge applications",
|
||||
"version": "0.0.0",
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
"author": "LifeForge (https://github.com/Lifeforge-app)",
|
||||
"homepage": "https://docs.lifeforge.melvinchia.dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Lifeforge-app/lifeforge.git",
|
||||
|
||||
1
bun.lock
1
bun.lock
@@ -42,6 +42,7 @@
|
||||
"@mdx-js/mdx": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@mdx-js/rollup": "^3.1.0",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/react-custom-scrollbars": "^4.0.13",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.6",
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
[
|
||||
{
|
||||
"title": "",
|
||||
"items": [
|
||||
"@core/Dashboard"
|
||||
]
|
||||
"items": ["@core/Dashboard"]
|
||||
},
|
||||
{
|
||||
"title": "Productivity",
|
||||
@@ -26,31 +24,19 @@
|
||||
},
|
||||
{
|
||||
"title": "Finance",
|
||||
"items": [
|
||||
"@apps/Wallet",
|
||||
"@apps/Wishlist"
|
||||
]
|
||||
"items": ["@apps/Wallet", "@apps/Wishlist"]
|
||||
},
|
||||
{
|
||||
"title": "Storage",
|
||||
"items": [
|
||||
"@apps/BooksLibrary",
|
||||
"@apps/Music",
|
||||
"@apps/ScoresLibrary"
|
||||
]
|
||||
"items": ["@apps/BooksLibrary", "@apps/Music", "@apps/ScoresLibrary"]
|
||||
},
|
||||
{
|
||||
"title": "Confidential",
|
||||
"items": [
|
||||
"@core/APIKeys",
|
||||
"@apps/Passwords"
|
||||
]
|
||||
"items": ["@core/APIKeys", "@apps/Passwords"]
|
||||
},
|
||||
{
|
||||
"title": "Information",
|
||||
"items": [
|
||||
"@apps/RailwayMap"
|
||||
]
|
||||
"items": ["@apps/RailwayMap"]
|
||||
},
|
||||
{
|
||||
"title": "Utilities",
|
||||
@@ -62,24 +48,14 @@
|
||||
},
|
||||
{
|
||||
"title": "Settings",
|
||||
"items": [
|
||||
"@core/Personalization",
|
||||
"@core/Modules",
|
||||
"@core/Backups"
|
||||
]
|
||||
"items": ["@core/Personalization", "@core/Modules", "@core/Backups"]
|
||||
},
|
||||
{
|
||||
"title": "sso",
|
||||
"items": [
|
||||
"@core/APIExplorer",
|
||||
"@core/LocalizationManager"
|
||||
]
|
||||
"items": ["@core/APIExplorer", "@core/LocalizationManager"]
|
||||
},
|
||||
{
|
||||
"title": "",
|
||||
"items": [
|
||||
"@core/Documentation",
|
||||
"@core/AccountSettings"
|
||||
]
|
||||
"items": ["@core/Documentation", "@core/AccountSettings"]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"name": "lifeforge-monorepo",
|
||||
"private": true,
|
||||
"description": "A comprehensive self-hosted all-in-one personal management system",
|
||||
"version": "25w29.0",
|
||||
"license": "MIT",
|
||||
"version": "0.0.0",
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
"author": "LifeForge (https://github.com/Lifeforge-app)",
|
||||
"homepage": "https://docs.lifeforge.melvinchia.dev",
|
||||
"repository": {
|
||||
@@ -21,7 +21,7 @@
|
||||
"forge": "bun run scripts/forge.ts",
|
||||
"lint:client": "cd client && bun run lint",
|
||||
"start:server": "cd server && bun run start",
|
||||
"postinstall": "bun run build:shared && bun run build:ui",
|
||||
"postinstall": "bun forge build shared && bun forge build ui",
|
||||
"schema:generate:collections": "bun run ./scripts/generateCollectionsSchemas.ts"
|
||||
},
|
||||
"keywords": [
|
||||
@@ -62,4 +62,4 @@
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.31.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { execSync } from 'child_process'
|
||||
|
||||
const PROCESS_ALLOWED = ['build', 'dev', 'types']
|
||||
const PROCESS_ALLOWED = ['build', 'dev', 'types', 'lint']
|
||||
|
||||
const PROJECTS_ALLOWED = {
|
||||
client: 'client',
|
||||
server: 'server',
|
||||
shared: 'shared',
|
||||
ui: 'ui',
|
||||
ui: 'packages/lifeforge-ui',
|
||||
'apps:localization-manager': 'apps/localization-manager',
|
||||
'apps:docs': 'apps/docs'
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "@lifeforge/server",
|
||||
"version": "1.0.0",
|
||||
"name": "lifeforge-server",
|
||||
"private": true,
|
||||
"description": "The server-side API for LifeForge.",
|
||||
"license": "MIT",
|
||||
"description": "The backend server for the LifeForge system",
|
||||
"version": "0.0.0",
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
"author": "LifeForge (https://github.com/Lifeforge-app)",
|
||||
"homepage": "https://docs.lifeforge.melvinchia.dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Lifeforge-app/lifeforge.git",
|
||||
@@ -16,6 +17,7 @@
|
||||
"build": "bun build ./src/index.ts --target bun --minify --production --outfile=./dist/server.js",
|
||||
"dev": "tsx watch --env-file=./env/.env.local ./src/index.ts",
|
||||
"start": "NODE_ENV=production bun --env-file ./env/.env.local ./dist/server.js",
|
||||
"lint": "eslint ./src --fix && prettier --write ./src",
|
||||
"test": "jest",
|
||||
"schema:generate": "bun ./scripts/generateSchema.ts"
|
||||
},
|
||||
|
||||
@@ -163,4 +163,4 @@
|
||||
"year": "Years"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,4 +163,4 @@
|
||||
"year": "Tahun"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,4 +163,4 @@
|
||||
"year": "年"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,4 +163,4 @@
|
||||
"year": "年"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
"days": "Days",
|
||||
"hours": "Hours"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
"days": "Hari",
|
||||
"hours": "Jam"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
"days": "天",
|
||||
"hours": "小时"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
"days": "天",
|
||||
"hours": "小時"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,4 +55,4 @@
|
||||
"misc": {
|
||||
"watched": "Watched on {{date}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,4 +55,4 @@
|
||||
"misc": {
|
||||
"watched": "Ditonton pada {{date}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,4 +55,4 @@
|
||||
"misc": {
|
||||
"watched": "观看于{{date}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,4 +55,4 @@
|
||||
"misc": {
|
||||
"watched": "觀看於{{date}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,4 +65,4 @@
|
||||
"grid": "Grid",
|
||||
"list": "List"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,4 +65,4 @@
|
||||
"grid": "Grid",
|
||||
"list": "Senarai"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,4 +65,4 @@
|
||||
"grid": "网格",
|
||||
"list": "列表"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,4 +65,4 @@
|
||||
"grid": "網格",
|
||||
"list": "列表"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"title": "Sudoku",
|
||||
"description": "Generate printable Sudoku boards infinitely."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"title": "Sudoku",
|
||||
"description": "Jana papan Sudoku yang boleh dicetak tanpa had."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"title": "数独",
|
||||
"description": "无限生成可打印的数独棋盘。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"title": "數獨",
|
||||
"description": "無限生成可打印的數獨棋盤。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +72,4 @@
|
||||
},
|
||||
"imagePreview": "Image Preview",
|
||||
"qrCodeScanner": "QR Code Scanner"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +50,4 @@
|
||||
}
|
||||
},
|
||||
"title": "Localization Manager"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +72,4 @@
|
||||
},
|
||||
"imagePreview": "Pratonton Imej",
|
||||
"qrCodeScanner": "Pengimbas Kod QR"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +50,4 @@
|
||||
}
|
||||
},
|
||||
"title": "Pengurus Lokalisasi"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +72,4 @@
|
||||
},
|
||||
"imagePreview": "图片预览",
|
||||
"qrCodeScanner": "二维码扫描器"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +50,4 @@
|
||||
}
|
||||
},
|
||||
"title": "本地化管理器"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +72,4 @@
|
||||
},
|
||||
"imagePreview": "圖片預覽",
|
||||
"qrCodeScanner": "二維碼掃描器"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +50,4 @@
|
||||
}
|
||||
},
|
||||
"title": "本地化管理器"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
"/youtube-summarizer": "youtubeSummarizer",
|
||||
"/achievements": "achievements",
|
||||
"/blog": "blog"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,24 +6,19 @@
|
||||
* Generated at: 2025-07-20T12:17:56.585Z
|
||||
* Contains: entry
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Entry = z.object({
|
||||
title: z.string(),
|
||||
thoughts: z.string(),
|
||||
difficulty: z.enum(["easy","medium","hard","impossible"]),
|
||||
});
|
||||
difficulty: z.enum(['easy', 'medium', 'hard', 'impossible'])
|
||||
})
|
||||
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
|
||||
export {
|
||||
Entry,
|
||||
};
|
||||
export { Entry }
|
||||
|
||||
export type {
|
||||
IEntry,
|
||||
};
|
||||
export type { IEntry }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,26 +6,21 @@
|
||||
* Generated at: 2025-07-20T12:17:56.591Z
|
||||
* Contains: entry
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Entry = z.object({
|
||||
keyId: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
icon: z.string(),
|
||||
key: z.string(),
|
||||
});
|
||||
key: z.string()
|
||||
})
|
||||
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
|
||||
export {
|
||||
Entry,
|
||||
};
|
||||
export { Entry }
|
||||
|
||||
export type {
|
||||
IEntry,
|
||||
};
|
||||
export type { IEntry }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,38 +6,31 @@
|
||||
* Generated at: 2025-07-20T12:17:56.585Z
|
||||
* Contains: entry, category
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Entry = z.object({
|
||||
content: z.string(),
|
||||
title: z.string(),
|
||||
media: z.array(z.string()),
|
||||
excerpt: z.string(),
|
||||
visibility: z.enum(["private","public","unlisted",""]),
|
||||
visibility: z.enum(['private', 'public', 'unlisted', '']),
|
||||
featured_image: z.string(),
|
||||
labels: z.any(),
|
||||
category: z.string(),
|
||||
});
|
||||
category: z.string()
|
||||
})
|
||||
|
||||
const Category = z.object({
|
||||
name: z.string(),
|
||||
color: z.string(),
|
||||
icon: z.string(),
|
||||
});
|
||||
icon: z.string()
|
||||
})
|
||||
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type ICategory = z.infer<typeof Category>;
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
type ICategory = z.infer<typeof Category>
|
||||
|
||||
export {
|
||||
Entry,
|
||||
Category,
|
||||
};
|
||||
export { Entry, Category }
|
||||
|
||||
export type {
|
||||
IEntry,
|
||||
ICategory,
|
||||
};
|
||||
export type { IEntry, ICategory }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,18 +6,17 @@
|
||||
* Generated at: 2025-07-20T12:17:56.588Z
|
||||
* Contains: collection, language, entry, file_type, file_type_aggregated, language_aggregated, collection_aggregated
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Collection = z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
});
|
||||
icon: z.string()
|
||||
})
|
||||
|
||||
const Language = z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
});
|
||||
icon: z.string()
|
||||
})
|
||||
|
||||
const Entry = z.object({
|
||||
title: z.string(),
|
||||
@@ -35,37 +34,37 @@ const Entry = z.object({
|
||||
thumbnail: z.string(),
|
||||
is_favourite: z.boolean(),
|
||||
is_read: z.boolean(),
|
||||
time_finished: z.string(),
|
||||
});
|
||||
time_finished: z.string()
|
||||
})
|
||||
|
||||
const FileType = z.object({
|
||||
name: z.string(),
|
||||
});
|
||||
name: z.string()
|
||||
})
|
||||
|
||||
const FileTypeAggregated = z.object({
|
||||
name: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
const LanguageAggregated = z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
const CollectionAggregated = z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
type ICollection = z.infer<typeof Collection>;
|
||||
type ILanguage = z.infer<typeof Language>;
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IFileType = z.infer<typeof FileType>;
|
||||
type IFileTypeAggregated = z.infer<typeof FileTypeAggregated>;
|
||||
type ILanguageAggregated = z.infer<typeof LanguageAggregated>;
|
||||
type ICollectionAggregated = z.infer<typeof CollectionAggregated>;
|
||||
type ICollection = z.infer<typeof Collection>
|
||||
type ILanguage = z.infer<typeof Language>
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
type IFileType = z.infer<typeof FileType>
|
||||
type IFileTypeAggregated = z.infer<typeof FileTypeAggregated>
|
||||
type ILanguageAggregated = z.infer<typeof LanguageAggregated>
|
||||
type ICollectionAggregated = z.infer<typeof CollectionAggregated>
|
||||
|
||||
export {
|
||||
Collection,
|
||||
@@ -74,8 +73,8 @@ export {
|
||||
FileType,
|
||||
FileTypeAggregated,
|
||||
LanguageAggregated,
|
||||
CollectionAggregated,
|
||||
};
|
||||
CollectionAggregated
|
||||
}
|
||||
|
||||
export type {
|
||||
ICollection,
|
||||
@@ -84,8 +83,8 @@ export type {
|
||||
IFileType,
|
||||
IFileTypeAggregated,
|
||||
ILanguageAggregated,
|
||||
ICollectionAggregated,
|
||||
};
|
||||
ICollectionAggregated
|
||||
}
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* Generated at: 2025-07-20T12:17:56.585Z
|
||||
* Contains: event, category, category_aggregated, calendar, events_single, events_recurring
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Event = z.object({
|
||||
title: z.string(),
|
||||
@@ -17,47 +16,47 @@ const Event = z.object({
|
||||
location_coords: z.object({ lat: z.number(), lon: z.number() }),
|
||||
reference_link: z.string(),
|
||||
description: z.string(),
|
||||
type: z.enum(["single","recurring"]),
|
||||
});
|
||||
type: z.enum(['single', 'recurring'])
|
||||
})
|
||||
|
||||
const Category = z.object({
|
||||
name: z.string(),
|
||||
color: z.string(),
|
||||
icon: z.string(),
|
||||
});
|
||||
icon: z.string()
|
||||
})
|
||||
|
||||
const CategoryAggregated = z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
color: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
const Calendar = z.object({
|
||||
name: z.string(),
|
||||
color: z.string(),
|
||||
});
|
||||
color: z.string()
|
||||
})
|
||||
|
||||
const EventsSingle = z.object({
|
||||
base_event: z.string(),
|
||||
start: z.string(),
|
||||
end: z.string(),
|
||||
});
|
||||
end: z.string()
|
||||
})
|
||||
|
||||
const EventsRecurring = z.object({
|
||||
recurring_rule: z.string(),
|
||||
duration_amount: z.number(),
|
||||
duration_unit: z.enum(["hour","year","month","day","week"]),
|
||||
duration_unit: z.enum(['hour', 'year', 'month', 'day', 'week']),
|
||||
exceptions: z.any(),
|
||||
base_event: z.string(),
|
||||
});
|
||||
base_event: z.string()
|
||||
})
|
||||
|
||||
type IEvent = z.infer<typeof Event>;
|
||||
type ICategory = z.infer<typeof Category>;
|
||||
type ICategoryAggregated = z.infer<typeof CategoryAggregated>;
|
||||
type ICalendar = z.infer<typeof Calendar>;
|
||||
type IEventsSingle = z.infer<typeof EventsSingle>;
|
||||
type IEventsRecurring = z.infer<typeof EventsRecurring>;
|
||||
type IEvent = z.infer<typeof Event>
|
||||
type ICategory = z.infer<typeof Category>
|
||||
type ICategoryAggregated = z.infer<typeof CategoryAggregated>
|
||||
type ICalendar = z.infer<typeof Calendar>
|
||||
type IEventsSingle = z.infer<typeof EventsSingle>
|
||||
type IEventsRecurring = z.infer<typeof EventsRecurring>
|
||||
|
||||
export {
|
||||
Event,
|
||||
@@ -65,8 +64,8 @@ export {
|
||||
CategoryAggregated,
|
||||
Calendar,
|
||||
EventsSingle,
|
||||
EventsRecurring,
|
||||
};
|
||||
EventsRecurring
|
||||
}
|
||||
|
||||
export type {
|
||||
IEvent,
|
||||
@@ -74,8 +73,8 @@ export type {
|
||||
ICategoryAggregated,
|
||||
ICalendar,
|
||||
IEventsSingle,
|
||||
IEventsRecurring,
|
||||
};
|
||||
IEventsRecurring
|
||||
}
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,18 +6,17 @@
|
||||
* Generated at: 2025-07-20T12:17:56.586Z
|
||||
* Contains: container, entry, folder, tag, tag_aggregated, container_aggregated
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Container = z.object({
|
||||
icon: z.string(),
|
||||
color: z.string(),
|
||||
name: z.string(),
|
||||
cover: z.string(),
|
||||
});
|
||||
cover: z.string()
|
||||
})
|
||||
|
||||
const Entry = z.object({
|
||||
type: z.enum(["text","image","link"]),
|
||||
type: z.enum(['text', 'image', 'link']),
|
||||
image: z.string(),
|
||||
title: z.string(),
|
||||
content: z.string(),
|
||||
@@ -25,31 +24,31 @@ const Entry = z.object({
|
||||
folder: z.string(),
|
||||
pinned: z.boolean(),
|
||||
archived: z.boolean(),
|
||||
tags: z.any(),
|
||||
});
|
||||
tags: z.any()
|
||||
})
|
||||
|
||||
const Folder = z.object({
|
||||
container: z.string(),
|
||||
name: z.string(),
|
||||
color: z.string(),
|
||||
icon: z.string(),
|
||||
parent: z.string(),
|
||||
});
|
||||
parent: z.string()
|
||||
})
|
||||
|
||||
const Tag = z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
color: z.string(),
|
||||
container: z.string(),
|
||||
});
|
||||
container: z.string()
|
||||
})
|
||||
|
||||
const TagAggregated = z.object({
|
||||
name: z.string(),
|
||||
color: z.string(),
|
||||
icon: z.string(),
|
||||
container: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
const ContainerAggregated = z.object({
|
||||
name: z.string(),
|
||||
@@ -58,24 +57,17 @@ const ContainerAggregated = z.object({
|
||||
cover: z.string(),
|
||||
text_count: z.number(),
|
||||
link_count: z.number(),
|
||||
image_count: z.number(),
|
||||
});
|
||||
image_count: z.number()
|
||||
})
|
||||
|
||||
type IContainer = z.infer<typeof Container>;
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IFolder = z.infer<typeof Folder>;
|
||||
type ITag = z.infer<typeof Tag>;
|
||||
type ITagAggregated = z.infer<typeof TagAggregated>;
|
||||
type IContainerAggregated = z.infer<typeof ContainerAggregated>;
|
||||
type IContainer = z.infer<typeof Container>
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
type IFolder = z.infer<typeof Folder>
|
||||
type ITag = z.infer<typeof Tag>
|
||||
type ITagAggregated = z.infer<typeof TagAggregated>
|
||||
type IContainerAggregated = z.infer<typeof ContainerAggregated>
|
||||
|
||||
export {
|
||||
Container,
|
||||
Entry,
|
||||
Folder,
|
||||
Tag,
|
||||
TagAggregated,
|
||||
ContainerAggregated,
|
||||
};
|
||||
export { Container, Entry, Folder, Tag, TagAggregated, ContainerAggregated }
|
||||
|
||||
export type {
|
||||
IContainer,
|
||||
@@ -83,8 +75,8 @@ export type {
|
||||
IFolder,
|
||||
ITag,
|
||||
ITagAggregated,
|
||||
IContainerAggregated,
|
||||
};
|
||||
IContainerAggregated
|
||||
}
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* This file is auto-generated. DO NOT EDIT IT MANUALLY.
|
||||
* You may regenerate it by running `npm run generate:schema:collection`.
|
||||
@@ -7,27 +6,27 @@
|
||||
* Contains schemas for all modules.
|
||||
*/
|
||||
|
||||
export * as VirtualWardrobeCollectionsSchemas from './virtualWardrobe.schema';
|
||||
export * as MusicCollectionsSchemas from './music.schema';
|
||||
export * as CalendarCollectionsSchemas from './calendar.schema';
|
||||
export * as AchievementsCollectionsSchemas from './achievements.schema';
|
||||
export * as PasswordsCollectionsSchemas from './passwords.schema';
|
||||
export * as MomentVaultCollectionsSchemas from './momentVault.schema';
|
||||
export * as BlogCollectionsSchemas from './blog.schema';
|
||||
export * as TodoListCollectionsSchemas from './todoList.schema';
|
||||
export * as IdeaBoxCollectionsSchemas from './ideaBox.schema';
|
||||
export * as RailwayMapCollectionsSchemas from './railwayMap.schema';
|
||||
export * as MoviesCollectionsSchemas from './movies.schema';
|
||||
export * as WalletCollectionsSchemas from './wallet.schema';
|
||||
export * as BooksLibraryCollectionsSchemas from './booksLibrary.schema';
|
||||
export * as ScoresLibraryCollectionsSchemas from './scoresLibrary.schema';
|
||||
export * as CodeTimeCollectionsSchemas from './codeTime.schema';
|
||||
export * as WishlistCollectionsSchemas from './wishlist.schema';
|
||||
export * as ApiKeysCollectionsSchemas from './apiKeys.schema';
|
||||
export * as UserCollectionsSchemas from './user.schema';
|
||||
export * as PixabayCustomSchemas from './pixabay.custom.schema';
|
||||
export * as YoutubeSummarizerCustomSchemas from './youtubeSummarizer.custom.schema';
|
||||
export * as LocationsCustomSchemas from './locations.custom.schema';
|
||||
export * as VirtualWardrobeCollectionsSchemas from './virtualWardrobe.schema'
|
||||
export * as MusicCollectionsSchemas from './music.schema'
|
||||
export * as CalendarCollectionsSchemas from './calendar.schema'
|
||||
export * as AchievementsCollectionsSchemas from './achievements.schema'
|
||||
export * as PasswordsCollectionsSchemas from './passwords.schema'
|
||||
export * as MomentVaultCollectionsSchemas from './momentVault.schema'
|
||||
export * as BlogCollectionsSchemas from './blog.schema'
|
||||
export * as TodoListCollectionsSchemas from './todoList.schema'
|
||||
export * as IdeaBoxCollectionsSchemas from './ideaBox.schema'
|
||||
export * as RailwayMapCollectionsSchemas from './railwayMap.schema'
|
||||
export * as MoviesCollectionsSchemas from './movies.schema'
|
||||
export * as WalletCollectionsSchemas from './wallet.schema'
|
||||
export * as BooksLibraryCollectionsSchemas from './booksLibrary.schema'
|
||||
export * as ScoresLibraryCollectionsSchemas from './scoresLibrary.schema'
|
||||
export * as CodeTimeCollectionsSchemas from './codeTime.schema'
|
||||
export * as WishlistCollectionsSchemas from './wishlist.schema'
|
||||
export * as ApiKeysCollectionsSchemas from './apiKeys.schema'
|
||||
export * as UserCollectionsSchemas from './user.schema'
|
||||
export * as PixabayCustomSchemas from './pixabay.custom.schema'
|
||||
export * as YoutubeSummarizerCustomSchemas from './youtubeSummarizer.custom.schema'
|
||||
export * as LocationsCustomSchemas from './locations.custom.schema'
|
||||
|
||||
export { SchemaWithPB } from './schemaWithPB'
|
||||
export type { ISchemaWithPB } from './schemaWithPB'
|
||||
|
||||
@@ -6,25 +6,20 @@
|
||||
* Generated at: 2025-07-20T12:17:56.585Z
|
||||
* Contains: entry
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Entry = z.object({
|
||||
type: z.enum(["text","audio","video","photos",""]),
|
||||
type: z.enum(['text', 'audio', 'video', 'photos', '']),
|
||||
file: z.array(z.string()),
|
||||
content: z.string(),
|
||||
transcription: z.string(),
|
||||
});
|
||||
transcription: z.string()
|
||||
})
|
||||
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
|
||||
export {
|
||||
Entry,
|
||||
};
|
||||
export { Entry }
|
||||
|
||||
export type {
|
||||
IEntry,
|
||||
};
|
||||
export type { IEntry }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* Generated at: 2025-07-20T12:17:56.586Z
|
||||
* Contains: entry
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Entry = z.object({
|
||||
tmdb_id: z.number(),
|
||||
@@ -27,18 +26,14 @@ const Entry = z.object({
|
||||
theatre_location: z.string(),
|
||||
theatre_location_coords: z.object({ lat: z.number(), lon: z.number() }),
|
||||
theatre_number: z.string(),
|
||||
is_watched: z.boolean(),
|
||||
});
|
||||
is_watched: z.boolean()
|
||||
})
|
||||
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
|
||||
export {
|
||||
Entry,
|
||||
};
|
||||
export { Entry }
|
||||
|
||||
export type {
|
||||
IEntry,
|
||||
};
|
||||
export type { IEntry }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,26 +6,21 @@
|
||||
* Generated at: 2025-07-20T12:17:56.584Z
|
||||
* Contains: entry
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Entry = z.object({
|
||||
name: z.string(),
|
||||
duration: z.string(),
|
||||
author: z.string(),
|
||||
file: z.string(),
|
||||
is_favourite: z.boolean(),
|
||||
});
|
||||
is_favourite: z.boolean()
|
||||
})
|
||||
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
|
||||
export {
|
||||
Entry,
|
||||
};
|
||||
export { Entry }
|
||||
|
||||
export type {
|
||||
IEntry,
|
||||
};
|
||||
export type { IEntry }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* Generated at: 2025-07-20T12:17:56.585Z
|
||||
* Contains: entry
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Entry = z.object({
|
||||
name: z.string(),
|
||||
@@ -16,18 +15,14 @@ const Entry = z.object({
|
||||
password: z.string(),
|
||||
icon: z.string(),
|
||||
color: z.string(),
|
||||
pinned: z.boolean(),
|
||||
});
|
||||
pinned: z.boolean()
|
||||
})
|
||||
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
|
||||
export {
|
||||
Entry,
|
||||
};
|
||||
export { Entry }
|
||||
|
||||
export type {
|
||||
IEntry,
|
||||
};
|
||||
export type { IEntry }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* Generated at: 2025-07-20T12:17:56.586Z
|
||||
* Contains: line, station
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Line = z.object({
|
||||
country: z.string(),
|
||||
@@ -16,8 +15,8 @@ const Line = z.object({
|
||||
name: z.string(),
|
||||
color: z.string(),
|
||||
ways: z.any(),
|
||||
map_paths: z.any(),
|
||||
});
|
||||
map_paths: z.any()
|
||||
})
|
||||
|
||||
const Station = z.object({
|
||||
name: z.string(),
|
||||
@@ -28,21 +27,15 @@ const Station = z.object({
|
||||
map_data: z.any(),
|
||||
type: z.string(),
|
||||
distances: z.any(),
|
||||
map_image: z.string(),
|
||||
});
|
||||
map_image: z.string()
|
||||
})
|
||||
|
||||
type ILine = z.infer<typeof Line>;
|
||||
type IStation = z.infer<typeof Station>;
|
||||
type ILine = z.infer<typeof Line>
|
||||
type IStation = z.infer<typeof Station>
|
||||
|
||||
export {
|
||||
Line,
|
||||
Station,
|
||||
};
|
||||
export { Line, Station }
|
||||
|
||||
export type {
|
||||
ILine,
|
||||
IStation,
|
||||
};
|
||||
export type { ILine, IStation }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const BasePBSchema = z.object({
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* Generated at: 2025-07-20T12:17:56.590Z
|
||||
* Contains: entry, author_aggregated, type, type_aggregated
|
||||
*/
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { SchemaWithPB } from "./schemaWithPB";
|
||||
import { SchemaWithPB } from './schemaWithPB'
|
||||
|
||||
const Entry = z.object({
|
||||
name: z.string(),
|
||||
@@ -19,43 +19,33 @@ const Entry = z.object({
|
||||
pdf: z.string(),
|
||||
audio: z.string(),
|
||||
musescore: z.string(),
|
||||
isFavourite: z.boolean(),
|
||||
});
|
||||
isFavourite: z.boolean()
|
||||
})
|
||||
|
||||
const AuthorAggregated = z.object({
|
||||
name: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
const Type = z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
});
|
||||
icon: z.string()
|
||||
})
|
||||
|
||||
const TypeAggregated = z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IAuthorAggregated = z.infer<typeof AuthorAggregated>;
|
||||
type IType = z.infer<typeof Type>;
|
||||
type ITypeAggregated = z.infer<typeof TypeAggregated>;
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
type IAuthorAggregated = z.infer<typeof AuthorAggregated>
|
||||
type IType = z.infer<typeof Type>
|
||||
type ITypeAggregated = z.infer<typeof TypeAggregated>
|
||||
|
||||
export {
|
||||
Entry,
|
||||
AuthorAggregated,
|
||||
Type,
|
||||
TypeAggregated,
|
||||
};
|
||||
export { Entry, AuthorAggregated, Type, TypeAggregated }
|
||||
|
||||
export type {
|
||||
IEntry,
|
||||
IAuthorAggregated,
|
||||
IType,
|
||||
ITypeAggregated,
|
||||
};
|
||||
export type { IEntry, IAuthorAggregated, IType, ITypeAggregated }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,18 +6,17 @@
|
||||
* Generated at: 2025-07-20T12:17:56.586Z
|
||||
* Contains: list, tag, entry, priority, list_aggregated, tag_aggregated, priority_aggregated
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const List = z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
color: z.string(),
|
||||
});
|
||||
color: z.string()
|
||||
})
|
||||
|
||||
const Tag = z.object({
|
||||
name: z.string(),
|
||||
});
|
||||
name: z.string()
|
||||
})
|
||||
|
||||
const Entry = z.object({
|
||||
summary: z.string(),
|
||||
@@ -28,40 +27,40 @@ const Entry = z.object({
|
||||
tags: z.array(z.string()),
|
||||
priority: z.string(),
|
||||
done: z.boolean(),
|
||||
completed_at: z.string(),
|
||||
});
|
||||
completed_at: z.string()
|
||||
})
|
||||
|
||||
const Priority = z.object({
|
||||
name: z.string(),
|
||||
color: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
const ListAggregated = z.object({
|
||||
name: z.string(),
|
||||
color: z.string(),
|
||||
icon: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
const TagAggregated = z.object({
|
||||
name: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
const PriorityAggregated = z.object({
|
||||
name: z.string(),
|
||||
color: z.string(),
|
||||
amount: z.number(),
|
||||
});
|
||||
amount: z.number()
|
||||
})
|
||||
|
||||
type IList = z.infer<typeof List>;
|
||||
type ITag = z.infer<typeof Tag>;
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IPriority = z.infer<typeof Priority>;
|
||||
type IListAggregated = z.infer<typeof ListAggregated>;
|
||||
type ITagAggregated = z.infer<typeof TagAggregated>;
|
||||
type IPriorityAggregated = z.infer<typeof PriorityAggregated>;
|
||||
type IList = z.infer<typeof List>
|
||||
type ITag = z.infer<typeof Tag>
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
type IPriority = z.infer<typeof Priority>
|
||||
type IListAggregated = z.infer<typeof ListAggregated>
|
||||
type ITagAggregated = z.infer<typeof TagAggregated>
|
||||
type IPriorityAggregated = z.infer<typeof PriorityAggregated>
|
||||
|
||||
export {
|
||||
List,
|
||||
@@ -70,8 +69,8 @@ export {
|
||||
Priority,
|
||||
ListAggregated,
|
||||
TagAggregated,
|
||||
PriorityAggregated,
|
||||
};
|
||||
PriorityAggregated
|
||||
}
|
||||
|
||||
export type {
|
||||
IList,
|
||||
@@ -80,8 +79,8 @@ export type {
|
||||
IPriority,
|
||||
IListAggregated,
|
||||
ITagAggregated,
|
||||
IPriorityAggregated,
|
||||
};
|
||||
IPriorityAggregated
|
||||
}
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* Generated at: 2025-07-20T12:17:56.591Z
|
||||
* Contains: user
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const User = z.object({
|
||||
password: z.string(),
|
||||
@@ -19,13 +18,13 @@ const User = z.object({
|
||||
name: z.string(),
|
||||
avatar: z.string(),
|
||||
dateOfBirth: z.string(),
|
||||
theme: z.enum(["system","light","dark"]),
|
||||
theme: z.enum(['system', 'light', 'dark']),
|
||||
color: z.string(),
|
||||
bgTemp: z.string(),
|
||||
bgImage: z.string(),
|
||||
backdropFilters: z.any(),
|
||||
fontFamily: z.string(),
|
||||
language: z.enum(["zh-CN","en","ms","zh-TW",""]),
|
||||
language: z.enum(['zh-CN', 'en', 'ms', 'zh-TW', '']),
|
||||
moduleConfigs: z.any(),
|
||||
enabledModules: z.any(),
|
||||
dashboardLayout: z.any(),
|
||||
@@ -35,18 +34,14 @@ const User = z.object({
|
||||
masterPasswordHash: z.string(),
|
||||
journalMasterPasswordHash: z.string(),
|
||||
APIKeysMasterPasswordHash: z.string(),
|
||||
twoFASecret: z.string(),
|
||||
});
|
||||
twoFASecret: z.string()
|
||||
})
|
||||
|
||||
type IUser = z.infer<typeof User>;
|
||||
type IUser = z.infer<typeof User>
|
||||
|
||||
export {
|
||||
User,
|
||||
};
|
||||
export { User }
|
||||
|
||||
export type {
|
||||
IUser,
|
||||
};
|
||||
export type { IUser }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* Generated at: 2025-07-20T12:17:56.584Z
|
||||
* Contains: entry, history
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const Entry = z.object({
|
||||
name: z.string(),
|
||||
@@ -23,26 +22,20 @@ const Entry = z.object({
|
||||
purchase_date: z.string(),
|
||||
price: z.number(),
|
||||
notes: z.string(),
|
||||
is_favourite: z.boolean(),
|
||||
});
|
||||
is_favourite: z.boolean()
|
||||
})
|
||||
|
||||
const History = z.object({
|
||||
entries: z.array(z.string()),
|
||||
notes: z.string(),
|
||||
});
|
||||
notes: z.string()
|
||||
})
|
||||
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IHistory = z.infer<typeof History>;
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
type IHistory = z.infer<typeof History>
|
||||
|
||||
export {
|
||||
Entry,
|
||||
History,
|
||||
};
|
||||
export { Entry, History }
|
||||
|
||||
export type {
|
||||
IEntry,
|
||||
IHistory,
|
||||
};
|
||||
export type { IEntry, IHistory }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -6,15 +6,14 @@
|
||||
* Generated at: 2025-07-20T12:17:56.591Z
|
||||
* Contains: list, entry, list_aggregated
|
||||
*/
|
||||
|
||||
import { z } from "zod/v4";
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
const List = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
color: z.string(),
|
||||
icon: z.string(),
|
||||
});
|
||||
icon: z.string()
|
||||
})
|
||||
|
||||
const Entry = z.object({
|
||||
name: z.string(),
|
||||
@@ -23,8 +22,8 @@ const Entry = z.object({
|
||||
image: z.string(),
|
||||
list: z.string(),
|
||||
bought: z.boolean(),
|
||||
bought_at: z.string(),
|
||||
});
|
||||
bought_at: z.string()
|
||||
})
|
||||
|
||||
const ListAggregated = z.object({
|
||||
name: z.string(),
|
||||
@@ -34,24 +33,16 @@ const ListAggregated = z.object({
|
||||
total_count: z.number(),
|
||||
total_amount: z.any(),
|
||||
bought_count: z.number(),
|
||||
bought_amount: z.any(),
|
||||
});
|
||||
bought_amount: z.any()
|
||||
})
|
||||
|
||||
type IList = z.infer<typeof List>;
|
||||
type IEntry = z.infer<typeof Entry>;
|
||||
type IListAggregated = z.infer<typeof ListAggregated>;
|
||||
type IList = z.infer<typeof List>
|
||||
type IEntry = z.infer<typeof Entry>
|
||||
type IListAggregated = z.infer<typeof ListAggregated>
|
||||
|
||||
export {
|
||||
List,
|
||||
Entry,
|
||||
ListAggregated,
|
||||
};
|
||||
export { List, Entry, ListAggregated }
|
||||
|
||||
export type {
|
||||
IList,
|
||||
IEntry,
|
||||
IListAggregated,
|
||||
};
|
||||
export type { IList, IEntry, IListAggregated }
|
||||
|
||||
// -------------------- CUSTOM SCHEMAS --------------------
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// Environment setup
|
||||
"lib": [
|
||||
"ESNext"
|
||||
],
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
// Output configuration
|
||||
@@ -19,11 +17,6 @@
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
"include": ["src"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"client/src",
|
||||
"server/src",
|
||||
"shared/src"
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
@@ -14,18 +10,10 @@
|
||||
// Path resolution
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@server/*": [
|
||||
"./server/src/*"
|
||||
],
|
||||
"@client/*": [
|
||||
"./client/src/*"
|
||||
],
|
||||
"@shared/*": [
|
||||
"./shared/src/*"
|
||||
],
|
||||
"@ui/*": [
|
||||
"./packages/lifeforge-ui/src/*"
|
||||
]
|
||||
"@server/*": ["./server/src/*"],
|
||||
"@client/*": ["./client/src/*"],
|
||||
"@shared/*": ["./shared/src/*"],
|
||||
"@ui/*": ["./packages/lifeforge-ui/src/*"]
|
||||
},
|
||||
// Module resolution
|
||||
"moduleResolution": "bundler",
|
||||
@@ -51,4 +39,4 @@
|
||||
"./shared/src/**/*",
|
||||
"./packages/lifeforge-ui/src/**/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user