Compare commits

..

No commits in common. "main" and "2.0.0" have entirely different histories.
main ... 2.0.0

33 changed files with 1998 additions and 9819 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2025 JSTAR Copyright (c) 2024 JSTAR
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

110
README.md
View File

@ -1,84 +1,66 @@
# 🌟 JSTAR Tab # 🌟 JSTAR Tab v2.0.0
Welcome to **JSTAR Tab**, the ultimate customizable new tab extension for your browser! Whether you're looking for a sleek, modern design, powerful personalization options, or enhanced accessibility, JSTAR Tab has you covered. 🚀 Welcome to **JSTAR Tab v2.0.0**! This is a customizable new tab extension for your browser, designed to enhance your browsing experience with personalization options, shortcuts, and more. 🚀
Transform your browsing experience with custom greetings, motion control, themes, fonts, and more. 🎉 ## 🎉 What's New in v2.0.0?
## ✨ Features - **Improved Shortcuts**: Add, edit, and manage up to 12 shortcuts with ease. 🔗
- **Enhanced Personalization**: Set your display name and toggle anonymous mode to hide your name and shortcuts. 🕵️‍♂️
- **Data Management**: Import, export, and reset your settings and shortcuts. 📦
- **Notifications**: Get instant feedback with our new notification system. 🔔
- **Onboarding Process**: A guided setup to personalize your experience right from the start. 🎓
- **Custom Greeting Formats**: Create personalized greetings using dynamic format tags: ## 📥 Installation
- `{name}`: Your display name
- `{greeting}`: Time-based greeting (e.g., Good morning)
- `{time}`: Current time
- `{date}`: Current date
- `{day}`: Day of the week
- `{month}`: Current month
- `{year}`: Current year
Examples: To set up JSTAR Tab locally, follow these steps:
- "Hello {name}, it's {time} on {day}!"
- "{greeting}, {name}! Today is {date}"
- "Happy {day}, {name}!"
- **Font Selection**: Choose from multiple fonts (Inter, Poppins, Roboto, Montserrat, Quicksand, and Comic Sans) to personalize your experience. 1. **Download the Latest Release**: [JSTAR Tab v2.0.0](https://github.com/DevJSTAR/JSTAR-Tab/releases/latest)
- **Custom Background Images**: Upload and set your favorite images as your new tab background. 2. **Extract the Files**: Unzip the downloaded file to a directory of your choice.
- **Customizable Themes**: Switch between light and dark modes to suit your mood. 3. **Load the Extension**:
- **Motion Preferences**: Control animation intensity across five levels: Default Animations, Subtle Animations, Reduced Motion, Minimal Motion, and No Animations. Perfect for accessibility and performance preferences. - Open your browser and navigate to the extensions page.
- **Privacy Settings**: Includes Anonymous Mode, password protection for individual shortcuts, and more to give you control over your experience. - Enable "Developer mode" (usually a toggle in the top right corner).
- **Shortcut Management**: Add, edit, and remove shortcuts to your favorite websites with an improved grid layout system. Middle-click support allows opening shortcuts in a new tab. - Click "Load unpacked" and select the directory where you extracted the files.
- **History Page**: View and manage your shortcut browsing history from a dedicated page.
- **Search Engine Selection**: Choose your preferred search engine for quick and efficient browsing.
- **Keyboard Shortcuts**: Set up custom keybinds for various actions:
- Open settings
- Toggle anonymous mode
- Change themes
- Open history
- Redirect to a specific URL
- **Data Backup and Restore**: Export and import your settings and shortcuts effortlessly.
## 🌐 Getting Started ## 🛠️ Features
### Installation - **Customizable Themes**: Choose between light and dark themes to suit your mood. 🌗
- **Personalized Greeting**: Set a custom greeting with your name.
- **Search Engine Selection**: Choose your preferred search engine from a variety of options.
- **Shortcut Management**: Easily add, edit, and remove shortcuts to your favorite sites.
- **Data Backup and Restore**: Export your settings and shortcuts to a file and import them back anytime.
- **Onboarding Process**: A step-by-step guide to help you set up your new tab with ease.
1. Visit [JSTAR Tab Website](https://jstartab.netlify.app). ## 🎨 Customizing Your JSTAR Tab
2. Select your browser or mobile browser from the available options. 1. **Onboarding**: When you first install the extension, you'll be guided through an onboarding process to set up your preferences, including your name and preferred search engine.
3. Follow the installation instructions specific to your browser. 2. **Themes**: Switch between light and dark themes using the theme toggle button in the settings. This changes the overall appearance of your new tab.
3. **Greeting**: Personalize your greeting by entering your name in the settings. This will display a friendly message each time you open a new tab.
4. **Shortcuts**: Add shortcuts to your most visited sites. Click the "+" button to add a new shortcut, enter the name and URL, and save. You can also edit or delete shortcuts by right-clicking on them.
5. **Search Engine**: Select your preferred search engine from the settings. This will be used for all searches performed from the new tab.
### Alternative Manual Installation ## 📚 Usage
1. **Download the Latest Release**: [JSTAR Tab GitHub Releases](https://github.com/DevJSTAR/JSTAR-Tab/releases/latest). - **Settings**: Click the settings icon ⚙️ to open the settings panel where you can customize your experience.
2. **Extract the Files**: Unzip the downloaded file to a directory of your choice. - **Add Shortcuts**: Click the "+" button to add a new shortcut. Enter the name and URL, then save.
3. **Load the Extension**: - **Edit Shortcuts**: Right-click on a shortcut to edit or delete it.
- Open your browser and navigate to the extensions page. - **Theme Toggle**: Switch between light and dark themes using the theme toggle button.
- Enable "Developer mode" (usually a toggle in the top right corner).
- Click "Load unpacked" and select the directory where you extracted the files.
## 🎨 Customizing Your Experience ## 📝 Contributing
### **Greeting Formats** We welcome contributions! If you have suggestions or improvements, feel free to fork the repository and submit a pull request. For major changes, please open an issue first to discuss what you would like to change.
Personalize your greeting with dynamic tags listed above. Example:
"Good {greeting}, {name}! It's {time} on {day}!"
### **Themes & Fonts** ## 📄 License
- Toggle between light and dark themes from the settings panel.
- Select from various font options to match your style.
### **Shortcuts & Backgrounds** This project is licensed under the MIT License. See the [LICENSE](https://github.com/DevJSTAR/JSTAR-Tab/blob/main/LICENSE) file for details.
- Add shortcuts with the "+" button.
- Edit or delete shortcuts by right-clicking on them.
- Customize your background by uploading your favorite images.
### **Keyboard Shortcuts** ## ❤️ Acknowledgments
Set up custom keybinds for quick actions like opening settings or switching themes.
## 📄 License - **Font Awesome**: For the beautiful icons used throughout the extension.
- **Google Fonts**: For the sleek and modern typography.
This project is licensed under the [MIT License](https://github.com/DevJSTAR/JSTAR-Tab/blob/main/LICENSE). ## 🌐 Connect with Us
## 🌐 Connect with Us - **[Linktree](https://linktr.ee/jstarsdev)**
- **[Patreon](https://patreon.com/jstarsdev)**
- **[Latest Release](https://github.com/DevJSTAR/JSTAR-Tab/releases/latest)**
- **[Linktree](https://linktr.ee/jstarsdev)** Thank you for using JSTAR Tab! We hope it makes your browsing experience more enjoyable and productive. 🙂
- **[Patreon](https://patreon.com/jstarsdev)**
- **[GitHub Releases](https://github.com/DevJSTAR/JSTAR-Tab/releases/latest)**
Thank you for choosing JSTAR Tab! We hope you enjoy the seamless, personalized, and now even more **accessible** browsing experience it brings. 🚀

View File

@ -1,567 +0,0 @@
:root {
--history-background: var(--card-bg, #ffffff);
--history-text: var(--text-color, #333333);
--history-accent: var(--accent-color, #4a6cf7);
--history-hover: var(--hover-bg, #f5f7ff);
--history-border: var(--border-color, #eaeaea);
--history-danger: #e53935;
--history-danger-hover: #c62828;
--history-success: #43a047;
--history-muted: #757575;
--history-header-height: 120px;
--history-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
--history-radius: 8px;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 0;
font-family: var(--font-family, 'Inter', sans-serif);
overflow-x: hidden;
}
.history-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: var(--history-background);
color: var(--history-text);
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.history-header {
position: sticky;
top: 0;
z-index: 10;
background-color: var(--history-background);
display: flex;
flex-direction: column;
gap: 16px;
padding: 20px 0;
border-bottom: 1px solid var(--history-border);
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.header-left h1 {
font-size: 24px;
font-weight: 600;
margin: 0;
}
.header-right {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.back-button {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
background: var(--history-hover);
cursor: pointer;
transition: background 0.2s;
}
.back-button:hover {
background: var(--history-border);
}
.back-button svg {
width: 20px;
height: 20px;
stroke: var(--history-text);
}
.search-bar {
display: flex;
align-items: center;
background-color: var(--history-hover);
border-radius: 24px;
padding: 8px 16px;
flex: 1;
max-width: 500px;
position: relative;
height: 40px;
}
.search-bar svg {
width: 18px;
height: 18px;
margin-right: 8px;
stroke: var(--history-muted);
}
.search-bar input {
flex: 1;
border: none;
background: transparent;
font-size: 14px;
outline: none;
color: var(--history-text);
}
.search-bar input::placeholder {
color: var(--history-muted);
}
.search-clear {
background: transparent;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
padding: 4px;
border-radius: 50%;
}
.search-clear svg {
width: 16px;
height: 16px;
margin: 0;
}
.search-clear:hover {
background: rgba(0, 0, 0, 0.05);
}
.filter-container {
position: relative;
}
.filter-container select {
appearance: none;
background-color: var(--history-hover);
border: none;
border-radius: 24px;
padding: 8px 16px;
font-size: 14px;
color: var(--history-text);
cursor: pointer;
outline: none;
min-width: 140px;
height: 40px;
}
.filter-container::after {
content: "";
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid var(--history-text);
pointer-events: none;
}
.history-actions {
display: flex;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid var(--history-border);
}
.history-actions button {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 24px;
border: 1px solid var(--history-border);
background: transparent;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.history-actions button svg {
width: 16px;
height: 16px;
stroke: currentColor;
}
.history-actions button:hover {
background: var(--history-hover);
}
.history-actions button:active {
transform: translateY(1px);
}
.history-actions button[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
#clear-all {
color: var(--history-danger);
}
#clear-all:hover {
background: rgba(229, 57, 53, 0.1);
}
.history-content {
flex: 1;
overflow-y: auto;
padding: 20px 0;
display: flex;
flex-direction: column;
gap: 20px;
position: relative;
}
.history-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
gap: 16px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid var(--history-border);
border-top-color: var(--history-accent);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.history-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
text-align: center;
}
.history-empty .empty-icon {
width: 80px;
height: 80px;
margin-bottom: 16px;
color: var(--history-muted);
display: flex;
justify-content: center;
align-items: center;
}
.history-empty .empty-icon svg {
width: 100%;
height: 100%;
}
.history-empty h2 {
font-size: 20px;
font-weight: 600;
margin: 0 0 8px;
}
.history-empty p {
color: var(--history-muted);
max-width: 300px;
}
.history-items {
display: flex;
flex-direction: column;
gap: 20px;
}
.history-date-group {
display: flex;
flex-direction: column;
}
.history-date-header {
font-size: 14px;
font-weight: 600;
color: var(--history-muted);
padding: 8px 0;
border-bottom: 1px solid var(--history-border);
margin-bottom: 8px;
}
.history-item {
display: flex;
align-items: center;
padding: 12px;
border-radius: var(--history-radius);
transition: background 0.2s;
position: relative;
}
.history-item:hover {
background: var(--history-hover);
}
.history-item-checkbox {
margin-right: 12px;
}
.history-item-favicon {
width: 20px;
height: 20px;
margin-right: 12px;
border-radius: 4px;
background: var(--history-border);
overflow: hidden;
}
.history-item-favicon img {
width: 100%;
height: 100%;
object-fit: cover;
}
.history-item-content {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.history-item-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.history-item-url {
font-size: 12px;
color: var(--history-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.history-item-time {
font-size: 12px;
color: var(--history-muted);
margin-left: 16px;
white-space: nowrap;
min-width: 70px;
text-align: right;
}
.history-item-actions {
display: flex;
align-items: center;
gap: 8px;
margin-left: 16px;
opacity: 0;
transition: opacity 0.2s;
}
.history-item:hover .history-item-actions {
opacity: 1;
}
.history-item-action {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
border: none;
background: transparent;
cursor: pointer;
}
.history-item-action:hover {
background: rgba(0, 0, 0, 0.05);
}
.history-item-action svg {
width: 16px;
height: 16px;
stroke: var(--history-muted);
}
.history-item-action.delete:hover svg {
stroke: var(--history-danger);
}
.history-load-more {
display: flex;
justify-content: center;
padding: 20px 0;
}
.history-load-more button {
padding: 8px 24px;
border-radius: 24px;
border: none;
background: var(--history-accent);
color: white;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.history-load-more button:hover {
background: var(--history-accent);
filter: brightness(1.1);
}
.hidden {
display: none !important;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
.modal.hidden {
display: none;
}
.modal:not(.hidden) {
opacity: 1;
visibility: visible;
}
.modal-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: var(--history-background);
border-radius: var(--history-radius);
box-shadow: var(--history-shadow);
position: relative;
z-index: 1;
transform: translateY(20px);
transition: transform 0.3s;
max-width: 90%;
}
.modal:not(.hidden) .modal-content {
transform: translateY(0);
}
.modal-content.confirmation-dialog {
width: 400px;
max-width: 90%;
padding: 24px;
box-sizing: border-box;
opacity: 1 !important;
visibility: visible !important;
}
.history-empty .empty-icon {
width: 80px;
height: 80px;
margin-bottom: 16px;
color: var(--history-muted);
display: flex;
justify-content: center;
align-items: center;
}
.history-empty .empty-icon svg {
width: 100%;
height: 100%;
}
.globe-icon {
width: 16px !important;
height: 16px !important;
stroke: var(--history-muted);
display: block;
margin: 0 auto;
}
.history-item-favicon svg {
width: 16px;
height: 16px;
background: var(--history-hover);
border-radius: 4px;
padding: 2px;
}
.history-empty:not(.hidden) + .history-items {
display: none !important;
}
@media (min-width: 768px) {
.history-header {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.header-right {
justify-content: flex-end;
}
}
@media (max-width: 767px) {
.history-item-time {
display: none;
}
.history-item-actions {
opacity: 1;
}
.history-actions {
justify-content: space-between;
}
.history-actions button span {
display: none;
}
.history-actions button {
padding: 8px;
}
}

View File

@ -1,474 +0,0 @@
.container-ob {
position: fixed;
inset: 0;
z-index: 10000;
background-color: var(--background);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.container-ob::before {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.step-ob {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
max-width: 800px;
width: 100%;
height: 100%;
padding: 2rem;
position: relative;
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.step-ob.active-ob {
display: flex;
opacity: 1;
transform: translateY(0);
z-index: 1;
}
.title-ob {
font-size: 2.5rem;
font-weight: 700;
color: var(--text);
margin-bottom: 1rem;
transform-origin: center;
animation: titlePop-ob 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.subtitle-ob {
font-size: 1.25rem;
color: var(--text-secondary);
margin-bottom: 2.5rem;
max-width: 600px;
}
.options-grid-ob {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
width: 100%;
margin-bottom: 2rem;
}
.step-ob[data-step="3"] .options-grid-ob {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
max-width: 900px;
margin-left: auto;
margin-right: auto;
}
.step-ob[data-step="5"] .options-grid-ob {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
max-width: 900px;
margin-left: auto;
margin-right: auto;
}
.option-card-ob {
background-color: var(--surface);
border: 2px solid var(--border);
border-radius: 16px;
padding: 1.5rem;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
position: relative;
overflow: hidden;
}
.option-card-ob:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px var(--shadow);
border-color: var(--text);
}
.option-card-ob.selected-ob {
border-color: var(--text);
background-color: var(--surface);
transform: scale(1.02);
}
.option-card-ob svg {
width: 48px;
height: 48px;
color: var(--text);
margin-bottom: 1rem;
}
.option-card-ob h3 {
font-size: 1.25rem;
font-weight: 600;
color: var(--text);
margin-bottom: 0.5rem;
}
.option-card-ob p {
font-size: 0.875rem;
color: var(--text-secondary);
}
.option-card-ob[data-engine] img {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 1rem;
}
.nav-ob {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
max-width: 800px;
padding: 0 1rem;
margin-top: auto;
margin-bottom: 40px;
position: relative;
z-index: 2;
}
.button-ob {
background-color: var(--surface);
border: 2px solid var(--border);
border-radius: 12px;
padding: 1rem 2rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--text);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.button-ob:hover {
background-color: var(--primary);
border-color: var(--text);
transform: translateY(-2px);
}
.button-ob svg {
width: 20px;
height: 20px;
}
.button-ob.primary-ob {
background-color: var(--text);
color: var(--background);
border-color: var(--text);
}
.button-ob.primary-ob:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--shadow);
}
.button-ob.disabled-ob,
.button-ob:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
.button-ob.disabled-ob:hover,
.button-ob:disabled:hover {
transform: none;
box-shadow: none;
background-color: var(--surface);
border-color: var(--border);
}
.progress-dots-ob {
display: flex;
gap: 8px;
}
.dot-ob {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--border);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.dot-ob.active-ob {
background-color: var(--text);
transform: scale(1.2);
}
.theme-preview-ob {
width: 100% !important;
border-radius: 16px !important;
padding: 1.5rem !important;
margin-bottom: 1rem !important;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
border: 2px solid var(--border) !important;
}
.theme-preview-ob.light-ob {
background-color: #ffffff !important;
color: #1a1a1a !important;
border: 2px solid #eaeaea !important;
}
.theme-preview-ob.dark-ob {
background-color: #1a1a1a !important;
color: #ffffff !important;
border: 2px solid #333333 !important;
}
.theme-preview-ob.light-ob .preview-search-ob {
background-color: #f0f0f0 !important;
border-color: #dddddd !important;
}
.theme-preview-ob.dark-ob .preview-search-ob {
background-color: #333333 !important;
border-color: #444444 !important;
}
.option-card-ob[data-theme="light"] {
background-color: #ffffff !important;
border: 2px solid #eaeaea !important;
color: #1a1a1a !important;
}
.option-card-ob[data-theme="light"]:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.08) !important;
border-color: #1a1a1a !important;
}
.option-card-ob[data-theme="light"].selected-ob {
border-color: #1a1a1a !important;
background-color: #ffffff !important;
transform: scale(1.02);
}
.option-card-ob[data-theme="light"] h3 {
color: #1a1a1a !important;
}
.option-card-ob[data-theme="light"] p {
color: #666666 !important;
}
.option-card-ob[data-theme="light"] svg {
color: #1a1a1a !important;
}
.option-card-ob[data-theme="dark"] {
background-color: #1a1a1a !important;
border: 2px solid #333333 !important;
color: #ffffff !important;
}
.option-card-ob[data-theme="dark"]:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2) !important;
border-color: #ffffff !important;
}
.option-card-ob[data-theme="dark"].selected-ob {
border-color: #ffffff !important;
background-color: #1a1a1a !important;
transform: scale(1.02);
}
.theme-preview-content-ob {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.preview-search-ob {
width: 100%;
height: 50px;
border-radius: 25px;
background-color: var(--surface);
border: 2px solid var(--border);
}
.preview-shortcuts-ob {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
width: 100%;
}
.preview-shortcut-ob {
width: 100%;
aspect-ratio: 1/1;
border-radius: 12px;
background-color: rgba(0, 0, 0, 0.1);
}
.dark-ob .preview-shortcut-ob {
background-color: rgba(255, 255, 255, 0.1);
}
.font-preview-ob {
font-size: 3rem;
margin-bottom: 1rem;
line-height: 1;
}
.name-input-container-ob {
width: 100%;
max-width: 500px;
margin: 2rem 0;
position: relative;
}
.name-input-ob {
width: 100%;
border: none !important;
background: transparent !important;
font-size: 4rem !important;
font-weight: 600 !important;
color: var(--text) !important;
text-align: left !important;
border-radius: 0 !important;
padding: 0.5rem 0 !important;
border-bottom: 2px solid #a3a3a3 !important;
transition: all 0.3s ease !important;
outline: none !important;
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
}
.name-input-ob:focus {
border-color: var(--text);
}
.name-input-ob::placeholder {
color: var(--text-secondary);
opacity: 0.5;
}
.name-input-ob:-webkit-autofill,
.name-input-ob:-webkit-autofill:hover,
.name-input-ob:-webkit-autofill:focus {
-webkit-text-fill-color: var(--text) !important;
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
transition: background-color 5000s ease-in-out 0s !important;
background-clip: content-box !important;
}
@keyframes titlePop-ob {
0% { transform: scale(0.8); opacity: 0; }
50% { transform: scale(1.05); }
100% { transform: scale(1); opacity: 1; }
}
@keyframes slideInUp-ob {
0% { transform: translateY(40px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
@keyframes fadeIn-ob {
0% { opacity: 0; }
100% { opacity: 1; }
}
.search-engine-preview-ob {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin-top: 1rem;
}
.search-engine-preview-ob img {
width: 24px;
height: 24px;
border-radius: 50%;
}
.animate-in-ob {
animation: slideInUp-ob 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@media (max-width: 768px) {
.options-grid-ob {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.title-ob {
font-size: 2rem;
}
.subtitle-ob {
font-size: 1rem;
}
.name-input-ob {
font-size: 1.5rem;
}
.nav-ob {
padding: 0.5rem;
margin-bottom: 30px;
}
}
@media (max-width: 576px) {
.step-ob {
padding: 1.5rem 1rem;
}
.options-grid-ob,
.step-ob[data-step="3"] .options-grid-ob,
.step-ob[data-step="5"] .options-grid-ob {
grid-template-columns: 1fr;
gap: 1rem;
}
.option-card-ob {
padding: 1rem;
}
.title-ob {
font-size: 1.75rem;
}
.button-ob {
padding: 0.75rem 1.5rem;
}
.font-preview-ob {
font-size: 2.5rem;
}
}
@media (min-width: 577px) and (max-width: 991px) {
.step-ob[data-step="3"] .options-grid-ob,
.step-ob[data-step="5"] .options-grid-ob {
grid-template-columns: repeat(2, 1fr);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,144 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Browsing History - JSTAR Tab</title>
<link rel="icon" href="/images/favicon.png">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/history.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Poppins:wght@400;500;600&family=Roboto:wght@400;500;700&family=Montserrat:wght@400;500;600&family=Quicksand:wght@400;500;600&display=swap" rel="stylesheet">
</head>
<body>
<!-- SVG Icons Definitions -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="icon-back" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"></path>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</symbol>
<symbol id="icon-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</symbol>
<symbol id="icon-clock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</symbol>
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</symbol>
<symbol id="icon-filter" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
</symbol>
<symbol id="icon-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</symbol>
<symbol id="icon-alert-triangle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
<line x1="12" y1="9" x2="12" y2="13"></line>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</symbol>
</svg>
<div class="history-container">
<!-- Header -->
<header class="history-header">
<div class="header-left">
<button class="back-button" title="Back to Home">
<svg><use xlink:href="#icon-back"></use></svg>
</button>
<h1>Browsing History</h1>
</div>
<div class="header-right">
<div class="search-bar">
<svg><use xlink:href="#icon-search"></use></svg>
<input type="text" placeholder="Search history">
<button class="search-clear" title="Clear search">
<svg><use xlink:href="#icon-x"></use></svg>
</button>
</div>
</div>
</header>
<!-- Action Bar -->
<div class="history-actions">
<button id="delete-selected" disabled>
<svg><use xlink:href="#icon-trash"></use></svg>
<span>Delete Selected</span>
</button>
<button id="clear-all">
<svg><use xlink:href="#icon-trash"></use></svg>
<span>Clear All History</span>
</button>
</div>
<!-- Content Area -->
<div class="history-content">
<!-- Loading state -->
<div class="history-loading">
<div class="loading-spinner"></div>
<p>Loading your browsing history...</p>
</div>
<!-- Empty state -->
<div class="history-empty hidden">
<div class="empty-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="8" y1="15" x2="16" y2="15"></line>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>
</div>
<h2>No history found</h2>
<p>There are no items in your browsing history that match your search.</p>
</div>
<!-- History items will be rendered here -->
<div class="history-items"></div>
<!-- Load more button -->
<div class="history-load-more hidden">
<button>Load More</button>
</div>
</div>
</div>
<!-- Confirmation Dialog -->
<div id="confirmation-dialog" class="modal hidden">
<div class="modal-backdrop"></div>
<div class="modal-content confirmation-dialog">
<div class="confirmation-icon">
<svg><use xlink:href="#icon-alert-triangle"/></svg>
</div>
<div class="confirmation-content">
<h3 id="confirmation-title">Delete items?</h3>
<p id="confirmation-message">Are you sure you want to delete these items from your history?</p>
</div>
<div class="modal-actions">
<button id="confirm-no" class="btn-primary">Cancel</button>
<button id="confirm-yes" class="btn-primary btn-danger">Delete</button>
</div>
</div>
</div>
<!-- Notification Container -->
<div id="notification-container"></div>
<script src="js/storage.js"></script>
<script src="js/notifications.js"></script>
<script src="js/history.js"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

BIN
images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

1459
index.html

File diff suppressed because it is too large Load Diff

View File

@ -1,159 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const MAX_IMAGE_SIZE_MB = 5;
const MAX_USER_BACKGROUNDS = 10;
const backgroundUpload = document.getElementById('background-upload');
const backgroundPreviewGrid = document.getElementById('background-preview-grid');
const resetBackground = document.getElementById('default-background');
resetBackground.style.order = '-1';
backgroundPreviewGrid.prepend(resetBackground);
loadBackgrounds();
setSavedBackground();
backgroundUpload.addEventListener('change', (event) => {
const file = event.target.files[0];
if (!file) return;
const maxSizeBytes = MAX_IMAGE_SIZE_MB * 1024 * 1024;
if (file.size > maxSizeBytes) {
notifications.show(`Image exceeds size limit (max ${MAX_IMAGE_SIZE_MB} MB)`, 'error');
event.target.value = '';
return;
}
const existingBackgrounds = JSON.parse(Storage.get('backgrounds') || '[]');
if (existingBackgrounds.length >= MAX_USER_BACKGROUNDS) {
notifications.show(`Maximum custom backgrounds limit reached!`, 'error');
event.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = function (e) {
const imageUrl = e.target.result;
addBackgroundPreview(imageUrl, false);
saveBackground(imageUrl);
setCustomBackground(imageUrl);
notifications.show('Background uploaded successfully!', 'success');
};
reader.readAsDataURL(file);
});
resetBackground.addEventListener('click', () => {
const currentBackground = Storage.get('customBackground');
if (!currentBackground) return;
document.body.style.backgroundImage = '';
Storage.remove('customBackground');
removeAllSelection();
notifications.show('Background reset to default!', 'success');
});
function loadBackgrounds() {
try {
const backgrounds = JSON.parse(Storage.get('backgrounds') || '[]');
if (!Array.isArray(backgrounds)) {
throw new Error('Invalid backgrounds format');
}
backgrounds.forEach((bg) => {
if (typeof bg === 'string' &&
(bg.startsWith('data:image/') || bg.startsWith('images/backgrounds/'))) {
addBackgroundPreview(bg, false);
}
});
} catch (e) {
console.error('Failed to load backgrounds:', e);
Storage.set('backgrounds', '[]');
notifications.show('Corrupted backgrounds data - resetting', 'error');
}
}
function setSavedBackground() {
const customBackground = Storage.get('customBackground');
if (customBackground) {
setCustomBackground(customBackground, true);
}
}
function addBackgroundPreview(imageUrl, isPredefined) {
const preview = document.createElement('div');
preview.className = 'background-preview' + (isPredefined ? ' predefined' : ' custom');
preview.style.backgroundImage = `url(${imageUrl})`;
preview.dataset.url = imageUrl;
if (Storage.get('customBackground') === imageUrl) {
preview.classList.add('selected');
}
preview.addEventListener('click', () => {
if (Storage.get('customBackground') === imageUrl) return;
setCustomBackground(imageUrl);
removeAllSelection();
preview.classList.add('selected');
});
if (!isPredefined) {
const removeIcon = document.createElement('span');
removeIcon.className = 'remove-icon';
removeIcon.innerHTML = '<i class="fas fa-times"></i>';
removeIcon.addEventListener('click', (e) => {
e.stopPropagation();
const wasSelected = preview.classList.contains('selected');
if (wasSelected) {
document.body.style.backgroundImage = '';
Storage.remove('customBackground');
}
removeBackground(imageUrl);
preview.remove();
notifications.show('Background removed!', 'success');
});
preview.appendChild(removeIcon);
}
const insertPosition = isPredefined ? 1 : backgroundPreviewGrid.children.length;
backgroundPreviewGrid.insertBefore(preview, backgroundPreviewGrid.children[insertPosition]);
}
function removeAllSelection() {
document.querySelectorAll('.background-preview').forEach(preview => {
preview.classList.remove('selected');
});
}
function removeBackground(imageUrl) {
const backgrounds = JSON.parse(Storage.get('backgrounds') || '[]');
Storage.set('backgrounds', JSON.stringify(backgrounds.filter(bg => bg !== imageUrl)));
}
function saveBackground(imageUrl) {
const backgrounds = JSON.parse(Storage.get('backgrounds') || '[]');
if (!backgrounds.includes(imageUrl)) {
backgrounds.push(imageUrl);
Storage.set('backgrounds', JSON.stringify(backgrounds));
}
}
function setCustomBackground(imageUrl, initialLoad = false) {
document.body.style.backgroundImage = `url(${imageUrl})`;
Storage.set('customBackground', imageUrl);
if (!initialLoad) {
removeAllSelection();
const targetPreview = document.querySelector(`.background-preview[data-url="${imageUrl}"]`);
if (targetPreview) targetPreview.classList.add('selected');
}
}
const predefinedImages = [
'images/backgrounds/cherry.png',
'images/backgrounds/mommies.png',
'images/backgrounds/peachs-castle.png',
'images/backgrounds/windows-xp.jpg',
];
predefinedImages.forEach(image => addBackgroundPreview(image, true));
});

View File

@ -1,86 +0,0 @@
const CacheUpdater = {
update: () => {
const shortcuts = Storage.get('shortcuts') || [];
const faviconUrls = shortcuts.map(shortcut =>
`https://www.google.com/s2/favicons?domain=${encodeURIComponent(new URL(shortcut.url).hostname)}&sz=64`
);
if (navigator.serviceWorker?.controller && faviconUrls.length > 0) {
navigator.serviceWorker.controller.postMessage({
action: 'updateFavicons',
urls: faviconUrls
});
}
}
};
const cacheHandler = {
SHORTCUT_CACHE_DURATION: 7 * 24 * 60 * 60 * 1000,
init() {
this.cleanExpiredCache();
},
cleanExpiredCache() {
const cache = this.getAllCache();
const now = Date.now();
Object.entries(cache).forEach(([key, value]) => {
if (key.startsWith('shortcut_') && value.expiry && value.expiry < now) {
this.removeFromCache(key);
}
});
},
addToCache(key, value, isSearchEngine = false) {
const cacheItem = {
value,
timestamp: Date.now(),
expiry: isSearchEngine ? null : Date.now() + this.SHORTCUT_CACHE_DURATION
};
localStorage.setItem(key, JSON.stringify(cacheItem));
},
getFromCache(key) {
const item = localStorage.getItem(key);
if (!item) return null;
try {
const cacheItem = JSON.parse(item);
if (cacheItem.expiry && cacheItem.expiry < Date.now()) {
this.removeFromCache(key);
return null;
}
return cacheItem.value;
} catch (error) {
console.error('Cache read error:', error);
return null;
}
},
removeFromCache(key) {
localStorage.removeItem(key);
},
getAllCache() {
const cache = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith('shortcut_') || key.startsWith('search_engine_')) {
try {
cache[key] = JSON.parse(localStorage.getItem(key));
} catch (error) {
console.error('Cache read error:', error);
}
}
}
return cache;
}
};
document.addEventListener('DOMContentLoaded', () => {
cacheHandler.init();
});

View File

@ -1,343 +0,0 @@
const GridLayout = {
defaults: {
type: 'default',
columns: 6,
gap: 16,
size: 80,
resizable: false
},
layouts: {
default: {
columns: 6,
gap: 16,
size: 80
},
compact: {
columns: 5,
gap: 8,
size: 70
},
comfortable: {
columns: 3,
gap: 24,
size: 100
},
list: {
columns: 1,
gap: 12,
size: 60
},
custom: {}
},
init: function() {
const settings = this.getSettings();
this.applyLayout(settings);
this.setupEventListeners();
this.toggleCustomSettings(settings.type === 'custom');
this.updateUI(settings);
},
getSettings: function() {
const storedSettings = Storage.get('gridLayout');
return { ...this.defaults, ...storedSettings };
},
saveSettings: function(settings) {
Storage.set('gridLayout', settings);
},
updateUI: function(settings) {
const layoutTypeSelect = document.getElementById('grid-layout-type');
if (layoutTypeSelect) {
layoutTypeSelect.value = settings.type;
const event = new Event('change');
layoutTypeSelect.dispatchEvent(event);
const customSelectDiv = layoutTypeSelect.closest('.custom-select');
if (customSelectDiv) {
const selectSelected = customSelectDiv.querySelector('.select-selected');
if (selectSelected) {
const selectedOption = layoutTypeSelect.options[layoutTypeSelect.selectedIndex];
selectSelected.textContent = selectedOption.textContent;
}
}
}
const columnsInput = document.getElementById('grid-columns');
const gapInput = document.getElementById('grid-gap');
const sizeInput = document.getElementById('grid-size');
if (columnsInput) columnsInput.value = settings.columns;
if (gapInput) gapInput.value = settings.gap;
if (sizeInput) {
sizeInput.value = settings.size;
sizeInput.disabled = !settings.resizable;
if (!settings.resizable) {
sizeInput.setAttribute('title', 'Enable \'Resizable Items\' to customize item size');
sizeInput.style.cursor = 'not-allowed';
} else {
sizeInput.removeAttribute('title');
sizeInput.style.cursor = 'auto';
}
}
const resizableToggle = document.getElementById('toggle-resizable');
if (resizableToggle) resizableToggle.checked = settings.resizable;
this.toggleCustomSettings(settings.type === 'custom');
},
applyLayout: function(settings) {
const grid = document.getElementById('shortcuts-grid');
if (!grid) return;
grid.classList.remove('grid-default', 'grid-compact', 'grid-comfortable', 'grid-list', 'grid-custom');
grid.classList.add(`grid-${settings.type}`);
if (settings.type === 'custom') {
grid.style.gridTemplateColumns = `repeat(${settings.columns}, minmax(${settings.size}px, 1fr))`;
grid.style.gap = `${settings.gap}px`;
} else {
const layoutConfig = this.layouts[settings.type];
if (layoutConfig) {
grid.style.gridTemplateColumns = `repeat(${layoutConfig.columns}, minmax(${layoutConfig.size}px, 1fr))`;
grid.style.gap = `${layoutConfig.gap}px`;
if (settings.type === 'list') {
grid.style.gridTemplateColumns = '1fr';
}
} else {
grid.style.gridTemplateColumns = '';
grid.style.gap = '';
}
}
this.applyResizable(settings.resizable);
},
setupEventListeners: function() {
const layoutTypeSelect = document.getElementById('grid-layout-type');
if (layoutTypeSelect) {
layoutTypeSelect.addEventListener('change', () => {
const settings = this.getSettings();
settings.type = layoutTypeSelect.value;
this.saveSettings(settings);
this.applyLayout(settings);
this.toggleCustomSettings(settings.type === 'custom');
});
}
const columnsInput = document.getElementById('grid-columns');
const gapInput = document.getElementById('grid-gap');
const sizeInput = document.getElementById('grid-size');
const validateInputValue = (input) => {
const min = parseInt(input.getAttribute('min'), 10);
const max = parseInt(input.getAttribute('max'), 10);
let value = parseInt(input.value, 10);
if (isNaN(value)) {
value = parseInt(input.defaultValue, 10);
}
if (value < min) value = min;
if (value > max) value = max;
input.value = value;
return value;
};
[columnsInput, gapInput, sizeInput].forEach(input => {
if (input) {
input.addEventListener('input', () => {
validateInputValue(input);
});
input.addEventListener('change', () => {
const value = validateInputValue(input);
const settings = this.getSettings();
settings[input.id.split('-')[1]] = value;
this.saveSettings(settings);
this.applyLayout(settings);
});
}
});
const resizableToggle = document.getElementById('toggle-resizable');
if (resizableToggle) {
resizableToggle.addEventListener('change', () => {
const settings = this.getSettings();
settings.resizable = resizableToggle.checked;
this.saveSettings(settings);
this.applyLayout(settings);
this.toggleItemSizeInput(settings.resizable);
});
}
const resetButton = document.getElementById('reset-layout');
if (resetButton) {
resetButton.addEventListener('click', () => {
shortcuts.showConfirmDialog(
'Reset Layout',
'Are you sure you want to reset the layout settings to default?',
() => this.resetToDefaults()
);
});
}
},
toggleItemSizeInput: function(enabled) {
const sizeInput = document.getElementById('grid-size');
if (sizeInput) {
sizeInput.disabled = !enabled;
if (enabled) {
sizeInput.removeAttribute('title');
sizeInput.style.cursor = 'auto';
} else {
sizeInput.setAttribute('title', 'Enable \'Resizable Items\' to customize item size');
sizeInput.style.cursor = 'not-allowed';
}
}
},
toggleCustomSettings: function(show) {
const customSettings = document.getElementById('custom-grid-settings');
if (customSettings) {
if (show) {
customSettings.classList.remove('hidden');
} else {
customSettings.classList.add('hidden');
}
}
},
applyResizable: function(resizable) {
const shortcuts = document.querySelectorAll('.shortcut');
shortcuts.forEach(shortcut => {
const existingHandle = shortcut.querySelector('.resize-handle');
if (existingHandle) {
existingHandle.remove();
}
shortcut.classList.remove('resizable');
if (resizable) {
shortcut.classList.add('resizable');
const resizeHandle = document.createElement('div');
resizeHandle.className = 'resize-handle';
shortcut.appendChild(resizeHandle);
this.setupResizeEvents(shortcut, resizeHandle);
}
});
},
setupResizeEvents: function(shortcut, handle) {
let startX, startY, startWidth, startHeight;
const startResize = (e) => {
e.preventDefault();
shortcut.classList.add('resizing');
startX = e.clientX;
startY = e.clientY;
startWidth = shortcut.offsetWidth;
startHeight = shortcut.offsetHeight;
document.addEventListener('mousemove', resize);
document.addEventListener('mouseup', stopResize);
};
const resize = (e) => {
const newWidth = startWidth + (e.clientX - startX);
const newHeight = startHeight + (e.clientY - startY);
shortcut.style.width = `${Math.max(80, newWidth)}px`;
shortcut.style.height = `${Math.max(80, newHeight)}px`;
};
const stopResize = () => {
shortcut.classList.remove('resizing');
document.removeEventListener('mousemove', resize);
document.removeEventListener('mouseup', stopResize);
};
handle.addEventListener('mousedown', startResize);
},
resetToDefaults: function() {
this.saveSettings(this.defaults);
this.updateUI(this.defaults);
this.applyLayout(this.defaults);
this.toggleCustomSettings(false);
const visibilitySettings = {
showGreeting: true,
showSearch: true,
showShortcuts: true,
showAddButton: true,
showGrid: true
};
Storage.set('visibility', visibilitySettings);
Storage.set('show_greeting', true);
Storage.set('show_search', true);
Storage.set('show_shortcuts', true);
Storage.set('show_addShortcut', true);
const greetingToggle = document.getElementById('toggle-greeting');
const searchToggle = document.getElementById('toggle-search');
const shortcutsToggle = document.getElementById('toggle-shortcuts');
const addButtonToggle = document.getElementById('toggle-add-shortcut');
if (greetingToggle) greetingToggle.checked = true;
if (searchToggle) searchToggle.checked = true;
if (shortcutsToggle) shortcutsToggle.checked = true;
if (addButtonToggle) addButtonToggle.checked = true;
const greeting = document.getElementById('greeting');
const search = document.getElementById('search-container');
const shortcuts = document.getElementById('shortcuts-grid');
const addButton = document.getElementById('add-shortcut');
const showElement = (element) => {
if (element) {
element.style.visibility = 'visible';
element.style.opacity = '1';
element.style.position = 'relative';
element.style.pointerEvents = 'auto';
}
};
showElement(greeting);
showElement(search);
showElement(shortcuts);
showElement(addButton);
},
reset: function() {
this.resetToDefaults();
notifications.show('Layout settings reset to defaults.', 'success');
return this.defaults;
}
};
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM content loaded - initializing grid layout');
GridLayout.init();
});
window.addEventListener('load', () => {
console.log('Window fully loaded - ensuring grid layout is initialized');
setTimeout(() => {
if (!window.gridLayoutInitialized) {
GridLayout.init();
}
}, 500);
});

View File

@ -1,904 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const historyContainer = document.querySelector('.history-container');
const searchInput = document.querySelector('.search-bar input');
const searchClear = document.querySelector('.search-clear');
const selectAllCheckbox = document.querySelector('#select-all');
const deleteSelectedBtn = document.querySelector('#delete-selected');
const clearAllBtn = document.querySelector('#clear-all');
const historyContent = document.querySelector('.history-content');
const historyItems = document.querySelector('.history-items');
const historyLoading = document.querySelector('.history-loading');
const historyEmpty = document.querySelector('.history-empty');
const loadMoreBtn = document.querySelector('.history-load-more button');
const confirmationDialog = document.querySelector('#confirmation-dialog');
const confirmTitle = document.querySelector('#confirmation-title');
const confirmMessage = document.querySelector('#confirmation-message');
const confirmYesBtn = document.querySelector('#confirm-yes');
const confirmNoBtn = document.querySelector('#confirm-no');
const searchResultsCount = document.querySelector('.search-results-count');
const browserInfo = detectBrowser();
console.log(`Detected browser: ${browserInfo.name} ${browserInfo.version}`);
const faviconCache = new Map();
let state = {
history: [],
filteredHistory: [],
searchTerm: '',
selectedItems: new Set(),
isLoading: true,
page: 1,
hasMore: true,
itemsPerPage: 100,
currentAction: null
};
initEvents();
loadHistory();
function initEvents() {
if (searchInput) searchInput.addEventListener('input', debounce(handleSearch, 300));
if (searchClear) {
searchClear.addEventListener('click', clearSearch);
hideElement(searchClear);
}
if (selectAllCheckbox) selectAllCheckbox.addEventListener('change', handleSelectAll);
if (deleteSelectedBtn) deleteSelectedBtn.addEventListener('click', () => {
if (state.selectedItems.size === 0) {
showNotification('No items selected', 'error');
return;
}
state.currentAction = 'delete-selected';
confirmTitle.textContent = 'Delete selected items?';
confirmMessage.textContent = 'This will remove the selected browsing history items.';
showConfirmationDialog();
});
if (clearAllBtn) clearAllBtn.addEventListener('click', () => {
if (state.filteredHistory.length === 0) {
showNotification('No history to clear', 'error');
return;
}
state.currentAction = 'clear-all';
confirmTitle.textContent = 'Clear all history?';
confirmMessage.textContent = 'This will remove all your browsing history.';
showConfirmationDialog();
});
if (loadMoreBtn) loadMoreBtn.addEventListener('click', loadMoreHistory);
if (confirmYesBtn) confirmYesBtn.addEventListener('click', handleConfirmedDelete);
if (confirmNoBtn) confirmNoBtn.addEventListener('click', hideConfirmationDialog);
const modalBackdrop = document.querySelector('.modal-backdrop');
if (modalBackdrop) {
modalBackdrop.addEventListener('click', hideConfirmationDialog);
}
const backButton = document.querySelector('.back-button');
if (backButton) {
backButton.addEventListener('click', () => {
window.history.back();
});
}
}
function detectBrowser() {
const userAgent = navigator.userAgent;
let browserName = "Unknown";
let version = "";
if (userAgent.indexOf("Firefox") > -1) {
browserName = "Firefox";
version = userAgent.match(/Firefox\/([0-9.]+)/)[1];
} else if (userAgent.indexOf("Edg") > -1) {
browserName = "Edge";
version = userAgent.match(/Edg\/([0-9.]+)/)[1];
} else if (userAgent.indexOf("Chrome") > -1) {
browserName = "Chrome";
version = userAgent.match(/Chrome\/([0-9.]+)/)[1];
} else if (userAgent.indexOf("Safari") > -1) {
browserName = "Safari";
version = userAgent.match(/Safari\/([0-9.]+)/)[1];
} else if (userAgent.indexOf("OPR") > -1 || userAgent.indexOf("Opera") > -1) {
browserName = "Opera";
version = userAgent.match(/(?:OPR|Opera)\/([0-9.]+)/)[1];
}
return { name: browserName, version: version };
}
function extractDomain(url) {
try {
const urlObj = new URL(url);
return urlObj.hostname;
} catch (e) {
return url;
}
}
async function loadHistory() {
showLoading();
try {
state.page = 1;
state.hasMore = true;
if (typeof chrome !== 'undefined' && chrome.history) {
console.log('Using Chrome history API');
const historyData = await fetchChromeHistory();
state.history = historyData;
state.filteredHistory = historyData;
if (historyData.length >= state.itemsPerPage) {
state.hasMore = true;
} else {
state.hasMore = false;
}
} else if (typeof browser !== 'undefined' && browser.history) {
console.log('Using Firefox history API');
const historyData = await fetchFirefoxHistory();
state.history = historyData;
state.filteredHistory = historyData;
if (historyData.length >= state.itemsPerPage) {
state.hasMore = true;
} else {
state.hasMore = false;
}
} else {
console.warn('No browser history API available');
state.history = [];
state.filteredHistory = [];
showNotification('Cannot access browser history. Make sure permissions are granted.', 'error');
}
state.isLoading = false;
updateUI();
if (state.filteredHistory.length === 0) {
if (state.searchTerm) {
showEmptyState('No history found', 'No results match your search. Try different keywords.');
} else {
showEmptyState('No history found', 'Your browsing history will appear here.');
}
}
} catch (error) {
console.error('Error loading history:', error);
hideLoading();
showEmptyState('Error loading history', 'There was a problem accessing your browsing history. Make sure the extension has the necessary permissions.');
}
}
async function fetchChromeHistory() {
return new Promise((resolve, reject) => {
const searchParams = getHistorySearchParams();
chrome.history.search(searchParams, (historyItems) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
return;
}
const formattedItems = historyItems.map(item => ({
id: item.id,
url: item.url,
title: item.title || extractDomain(item.url),
lastVisitTime: item.lastVisitTime,
visitCount: item.visitCount,
domain: extractDomain(item.url)
}));
resolve(formattedItems);
});
});
}
async function fetchFirefoxHistory() {
return new Promise((resolve, reject) => {
const searchParams = getHistorySearchParams();
browser.history.search(searchParams).then(historyItems => {
const formattedItems = historyItems.map(item => ({
id: item.id,
url: item.url,
title: item.title || extractDomain(item.url),
lastVisitTime: item.lastVisitTime,
visitCount: item.visitCount,
domain: extractDomain(item.url)
}));
resolve(formattedItems);
}).catch(error => {
reject(error);
});
});
}
function getHistorySearchParams() {
return {
text: state.searchTerm,
maxResults: state.itemsPerPage * 3,
startTime: 0
};
}
async function loadMoreHistory() {
if (!state.hasMore) {
hideElement(document.querySelector('.history-load-more'));
return;
}
state.page++;
if (typeof chrome !== 'undefined' && chrome.history) {
try {
const lastItem = state.filteredHistory[state.filteredHistory.length - 1];
const startTime = lastItem ? lastItem.lastVisitTime - 1 : 0;
const searchParams = {
text: state.searchTerm,
maxResults: state.itemsPerPage * 2,
startTime: 0,
endTime: startTime
};
chrome.history.search(searchParams, (historyItems) => {
if (chrome.runtime.lastError) {
console.error('Error loading more history:', chrome.runtime.lastError);
return;
}
const existingIds = new Set(state.filteredHistory.map(item => item.id));
const newItems = historyItems
.filter(item => !existingIds.has(item.id))
.map(item => ({
id: item.id,
url: item.url,
title: item.title || extractDomain(item.url),
lastVisitTime: item.lastVisitTime,
visitCount: item.visitCount,
domain: extractDomain(item.url)
}));
if (newItems.length === 0) {
state.hasMore = false;
hideElement(document.querySelector('.history-load-more'));
return;
}
state.history = [...state.history, ...newItems];
state.filteredHistory = [...state.filteredHistory, ...newItems];
renderMoreHistoryItems(newItems);
if (newItems.length < state.itemsPerPage) {
state.hasMore = false;
hideElement(document.querySelector('.history-load-more'));
}
});
} catch (error) {
console.error('Error loading more history:', error);
state.hasMore = false;
hideElement(document.querySelector('.history-load-more'));
}
} else if (typeof browser !== 'undefined' && browser.history) {
try {
const lastItem = state.filteredHistory[state.filteredHistory.length - 1];
const startTime = lastItem ? lastItem.lastVisitTime - 1 : 0;
const searchParams = {
text: state.searchTerm,
maxResults: state.itemsPerPage * 2,
startTime: 0,
endTime: startTime
};
const historyItems = await browser.history.search(searchParams);
const existingIds = new Set(state.filteredHistory.map(item => item.id));
const newItems = historyItems
.filter(item => !existingIds.has(item.id))
.map(item => ({
id: item.id,
url: item.url,
title: item.title || extractDomain(item.url),
lastVisitTime: item.lastVisitTime,
visitCount: item.visitCount,
domain: extractDomain(item.url)
}));
if (newItems.length === 0) {
state.hasMore = false;
hideElement(document.querySelector('.history-load-more'));
return;
}
state.history = [...state.history, ...newItems];
state.filteredHistory = [...state.filteredHistory, ...newItems];
renderMoreHistoryItems(newItems);
if (newItems.length < state.itemsPerPage) {
state.hasMore = false;
hideElement(document.querySelector('.history-load-more'));
}
} catch (error) {
console.error('Error loading more history:', error);
state.hasMore = false;
hideElement(document.querySelector('.history-load-more'));
}
}
}
function renderMoreHistoryItems(items) {
if (items.length === 0) return;
const groupedItems = groupByDate(items);
const historyItemsContainer = document.querySelector('.history-items');
for (const [date, dateItems] of Object.entries(groupedItems)) {
let dateGroup = document.querySelector(`.history-date-group[data-date="${date}"]`);
if (!dateGroup) {
dateGroup = document.createElement('div');
dateGroup.className = 'history-date-group';
dateGroup.setAttribute('data-date', date);
const dateHeader = document.createElement('div');
dateHeader.className = 'history-date-header';
dateHeader.textContent = formatDateHeading(date);
dateGroup.appendChild(dateHeader);
historyItemsContainer.appendChild(dateGroup);
}
for (const item of dateItems) {
const itemElement = createHistoryItemElement(item);
dateGroup.appendChild(itemElement);
}
}
}
function handleSearch() {
const searchTerm = searchInput.value.trim().toLowerCase();
state.searchTerm = searchTerm;
if (searchTerm.length > 0) {
showElement(searchClear);
} else {
hideElement(searchClear);
}
state.page = 1;
state.hasMore = true;
loadHistory();
}
function clearSearch() {
if (searchInput) {
searchInput.value = '';
state.searchTerm = '';
hideElement(searchClear);
state.page = 1;
state.hasMore = true;
loadHistory();
}
}
function applySearch() {
if (state.searchTerm.length > 0) {
const filtered = state.history.filter(item => {
const title = (item.title || '').toLowerCase();
const url = (item.url || '').toLowerCase();
const domain = (item.domain || '').toLowerCase();
return title.includes(state.searchTerm) ||
url.includes(state.searchTerm) ||
domain.includes(state.searchTerm);
});
state.filteredHistory = filtered;
} else {
state.filteredHistory = state.history;
}
updateUI();
}
function updateUI() {
hideLoading();
if (state.filteredHistory.length === 0) {
showEmptyState('No history found', 'No results match your search. Try different keywords.');
} else {
hideEmptyState();
}
renderHistoryItems(state.filteredHistory);
updateActionButtonsState();
if (state.hasMore) {
showElement(document.querySelector('.history-load-more'));
} else {
hideElement(document.querySelector('.history-load-more'));
}
if (searchResultsCount) {
if (state.searchTerm) {
searchResultsCount.textContent = `${state.filteredHistory.length} results found for "${state.searchTerm}"`;
showElement(searchResultsCount);
} else {
hideElement(searchResultsCount);
}
}
}
function renderHistoryItems(items) {
if (!historyItems) return;
historyItems.innerHTML = '';
if (items.length === 0) {
showEmptyState();
return;
}
const groupedItems = groupByDate(items);
for (const [date, dateItems] of Object.entries(groupedItems)) {
const dateGroup = document.createElement('div');
dateGroup.className = 'history-date-group';
dateGroup.setAttribute('data-date', date);
const dateHeader = document.createElement('div');
dateHeader.className = 'history-date-header';
dateHeader.textContent = formatDateHeading(date);
dateGroup.appendChild(dateHeader);
for (const item of dateItems) {
const itemElement = createHistoryItemElement(item);
dateGroup.appendChild(itemElement);
}
historyItems.appendChild(dateGroup);
}
}
function createHistoryItemElement(item) {
const historyItem = document.createElement('div');
historyItem.className = 'history-item';
historyItem.setAttribute('data-id', item.id);
const checkbox = document.createElement('div');
checkbox.className = 'history-item-checkbox';
const checkboxInput = document.createElement('input');
checkboxInput.type = 'checkbox';
checkboxInput.checked = state.selectedItems.has(item.id);
checkboxInput.addEventListener('change', (e) => {
handleItemSelection(item.id, e.target.checked);
});
checkbox.appendChild(checkboxInput);
const favicon = document.createElement('div');
favicon.className = 'history-item-favicon';
let faviconUrl = faviconCache.get(item.domain);
if (!faviconUrl) {
faviconUrl = `https://www.google.com/s2/favicons?domain=${item.domain}&sz=32`;
faviconCache.set(item.domain, faviconUrl);
}
const faviconImg = document.createElement('img');
faviconImg.src = faviconUrl;
faviconImg.alt = '';
faviconImg.onerror = () => {
favicon.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="globe-icon">
<circle cx="12" cy="12" r="10"></circle>
<line x1="2" y1="12" x2="22" y2="12"></line>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg>
`;
};
favicon.appendChild(faviconImg);
const content = document.createElement('div');
content.className = 'history-item-content';
const title = document.createElement('div');
title.className = 'history-item-title';
title.textContent = item.title || extractDomain(item.url);
const url = document.createElement('div');
url.className = 'history-item-url';
url.textContent = item.url;
content.appendChild(title);
content.appendChild(url);
const time = document.createElement('div');
time.className = 'history-item-time';
time.textContent = formatTime(new Date(item.lastVisitTime));
const actions = document.createElement('div');
actions.className = 'history-item-actions';
const openAction = document.createElement('button');
openAction.className = 'history-item-action';
openAction.title = 'Open in new tab';
openAction.innerHTML = '<svg><use xlink:href="#icon-external-link"></use></svg>';
openAction.addEventListener('click', (e) => {
e.stopPropagation();
openLink(item.url);
});
const deleteAction = document.createElement('button');
deleteAction.className = 'history-item-action delete';
deleteAction.title = 'Delete';
deleteAction.innerHTML = '<svg><use xlink:href="#icon-trash"></use></svg>';
deleteAction.addEventListener('click', (e) => {
e.stopPropagation();
deleteHistoryItem(item.id);
});
actions.appendChild(openAction);
actions.appendChild(deleteAction);
content.addEventListener('click', () => {
const isSelected = state.selectedItems.has(item.id);
checkboxInput.checked = !isSelected;
handleItemSelection(item.id, !isSelected);
});
historyItem.appendChild(checkbox);
historyItem.appendChild(favicon);
historyItem.appendChild(content);
historyItem.appendChild(time);
historyItem.appendChild(actions);
return historyItem;
}
function handleSelectAll(e) {
const isChecked = e.target.checked;
if (isChecked) {
state.filteredHistory.forEach(item => {
state.selectedItems.add(item.id);
});
} else {
state.selectedItems.clear();
}
const checkboxes = document.querySelectorAll('.history-item-checkbox input');
checkboxes.forEach(checkbox => {
const itemId = checkbox.closest('.history-item').getAttribute('data-id');
checkbox.checked = state.selectedItems.has(itemId);
});
updateActionButtonsState();
}
function handleItemSelection(id, isSelected) {
if (isSelected) {
state.selectedItems.add(id);
} else {
state.selectedItems.delete(id);
}
updateActionButtonsState();
}
function updateActionButtonsState() {
if (deleteSelectedBtn) {
deleteSelectedBtn.disabled = state.selectedItems.size === 0;
}
if (clearAllBtn) {
clearAllBtn.disabled = state.filteredHistory.length === 0;
}
const selectAllCheckbox = document.querySelector('#select-all');
if (selectAllCheckbox) {
if (state.filteredHistory.length === 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.disabled = true;
} else {
selectAllCheckbox.disabled = false;
selectAllCheckbox.checked = state.filteredHistory.length > 0 &&
state.selectedItems.size === state.filteredHistory.length;
}
}
}
function deleteHistoryItem(id) {
state.currentAction = 'delete-item';
state.currentItemId = id;
confirmTitle.textContent = 'Delete item?';
confirmMessage.textContent = 'This will remove this item from your browsing history.';
showConfirmationDialog();
}
function removeHistoryItemFromState(id) {
state.selectedItems.delete(id);
state.history = state.history.filter(item => item.id !== id);
state.filteredHistory = state.filteredHistory.filter(item => item.id !== id);
const itemElement = document.querySelector(`.history-item[data-id="${id}"]`);
if (itemElement) {
const dateGroup = itemElement.closest('.history-date-group');
itemElement.remove();
if (dateGroup && dateGroup.querySelectorAll('.history-item').length === 0) {
dateGroup.remove();
}
}
updateActionButtonsState();
if (state.filteredHistory.length === 0) {
if (state.searchTerm) {
showEmptyState('No history found', 'No results match your search. Try different keywords.');
} else {
showEmptyState('No history found', 'Your browsing history will appear here.');
}
}
}
function handleConfirmedDelete() {
hideConfirmationDialog();
if (state.currentAction === 'delete-selected') {
deleteSelectedItems();
} else if (state.currentAction === 'clear-all') {
clearAllHistory();
} else if (state.currentAction === 'delete-item') {
const id = state.currentItemId;
if (typeof chrome !== 'undefined' && chrome.history) {
chrome.history.deleteUrl({ url: state.history.find(item => item.id === id).url }, () => {
removeHistoryItemFromState(id);
showNotification('Item deleted', 'success');
});
} else if (typeof browser !== 'undefined' && browser.history) {
browser.history.deleteUrl({ url: state.history.find(item => item.id === id).url }).then(() => {
removeHistoryItemFromState(id);
showNotification('Item deleted', 'success');
});
} else {
removeHistoryItemFromState(id);
showNotification('Item deleted', 'success');
}
}
state.currentAction = null;
state.currentItemId = null;
}
function deleteSelectedItems() {
const selectedIds = Array.from(state.selectedItems);
if (selectedIds.length === 0) {
showNotification('No items selected', 'error');
return;
}
const selectedUrls = state.history
.filter(item => state.selectedItems.has(item.id))
.map(item => item.url);
if (typeof chrome !== 'undefined' && chrome.history) {
let deletedCount = 0;
selectedUrls.forEach(url => {
chrome.history.deleteUrl({ url }, () => {
deletedCount++;
if (deletedCount === selectedUrls.length) {
selectedIds.forEach(id => removeHistoryItemFromState(id));
state.selectedItems.clear();
updateActionButtonsState();
showNotification(`${selectedUrls.length} items deleted`, 'success');
}
});
});
} else if (typeof browser !== 'undefined' && browser.history) {
Promise.all(selectedUrls.map(url => browser.history.deleteUrl({ url })))
.then(() => {
selectedIds.forEach(id => removeHistoryItemFromState(id));
state.selectedItems.clear();
updateActionButtonsState();
showNotification(`${selectedUrls.length} items deleted`, 'success');
})
.catch(error => {
console.error('Error deleting history items:', error);
showNotification('Error deleting items', 'error');
});
} else {
selectedIds.forEach(id => removeHistoryItemFromState(id));
state.selectedItems.clear();
updateActionButtonsState();
showNotification(`${selectedUrls.length} items deleted`, 'success');
}
}
function clearAllHistory() {
if (state.filteredHistory.length === 0) {
showNotification('No history to clear', 'error');
return;
}
if (typeof chrome !== 'undefined' && chrome.history) {
chrome.history.deleteAll(() => {
state.history = [];
state.filteredHistory = [];
state.selectedItems.clear();
historyItems.innerHTML = '';
updateActionButtonsState();
showEmptyState('History cleared', 'Your browsing history has been cleared.');
showNotification('History cleared', 'success');
});
} else if (typeof browser !== 'undefined' && browser.history) {
browser.history.deleteAll().then(() => {
state.history = [];
state.filteredHistory = [];
state.selectedItems.clear();
historyItems.innerHTML = '';
updateActionButtonsState();
showEmptyState('History cleared', 'Your browsing history has been cleared.');
showNotification('History cleared', 'success');
});
} else {
state.history = [];
state.filteredHistory = [];
state.selectedItems.clear();
historyItems.innerHTML = '';
updateActionButtonsState();
showEmptyState('History cleared', 'Your browsing history has been cleared.');
showNotification('History cleared', 'success');
}
}
function showLoading() {
if (historyLoading) {
showElement(historyLoading);
}
hideEmptyState();
if (historyItems) {
historyItems.innerHTML = '';
}
}
function hideLoading() {
if (historyLoading) {
hideElement(historyLoading);
}
}
function showEmptyState(title = 'No history found', message = 'There are no items in your browsing history that match your search.') {
if (!historyEmpty) return;
const titleElement = historyEmpty.querySelector('h2');
const messageElement = historyEmpty.querySelector('p');
if (titleElement) titleElement.textContent = title;
if (messageElement) messageElement.textContent = message;
showElement(historyEmpty);
}
function hideEmptyState() {
if (historyEmpty) {
hideElement(historyEmpty);
}
}
function showElement(element) {
if (element) element.classList.remove('hidden');
}
function hideElement(element) {
if (element) element.classList.add('hidden');
}
function showConfirmationDialog() {
if (confirmationDialog) {
showElement(confirmationDialog);
}
}
function hideConfirmationDialog() {
if (confirmationDialog) {
hideElement(confirmationDialog);
}
}
function showNotification(message, type = 'success') {
if (window.notifications && typeof window.notifications.show === 'function') {
window.notifications.show(message, type);
} else {
console.log(`Notification: ${message} (${type})`);
}
}
function openLink(url) {
window.open(url, '_blank');
}
function formatDateHeading(dateStr) {
const date = new Date(dateStr);
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
if (date.toDateString() === today.toDateString()) {
return 'Today';
} else if (date.toDateString() === yesterday.toDateString()) {
return 'Yesterday';
} else {
return date.toLocaleDateString(undefined, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
}
function formatTime(date) {
return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
}
function groupByDate(items) {
const grouped = {};
items.forEach(item => {
const date = new Date(item.lastVisitTime);
const dateKey = date.toDateString();
if (!grouped[dateKey]) {
grouped[dateKey] = [];
}
grouped[dateKey].push(item);
});
for (const date in grouped) {
grouped[date].sort((a, b) => b.lastVisitTime - a.lastVisitTime);
}
return grouped;
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
});

View File

@ -1,313 +0,0 @@
const FORBIDDEN_KEYS = [
'Tab', 'CapsLock', 'Meta', 'ContextMenu',
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
'Home', 'End', 'PageUp', 'PageDown', 'Insert', 'Delete', 'ScrollLock', 'Pause', 'NumLock',
'/'
];
const keybinds = {
bindings: {},
init() {
this.bindings = Storage.get('keybinds') || {};
this.setupDefaultKeybinds();
const urlInput = document.getElementById('keybind-url');
const urlComboInput = document.getElementById('keybind-url-combo');
const historyComboInput = document.getElementById('keybind-history-combo');
if (this.bindings.url) {
urlInput.value = this.bindings.url.url || '';
urlComboInput.value = this.bindings.url.keys || '';
}
if (this.bindings.history && historyComboInput) {
historyComboInput.value = this.bindings.history.keys || '';
}
let lastSavedUrl = urlInput.value;
function isValidUrl(string) {
try {
const urlString = string.match(/^https?:\/\//) ? string : `https://${string}`;
new URL(urlString);
return true;
} catch (_) {
return false;
}
}
urlInput.addEventListener('input', () => {
if (!this.bindings.url) {
this.bindings.url = { url: '', keys: '' };
}
this.bindings.url.url = urlInput.value;
Storage.set('keybinds', this.bindings);
});
urlInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
urlInput.blur();
}
});
urlInput.addEventListener('blur', () => {
const currentUrl = urlInput.value.trim();
if (currentUrl === lastSavedUrl) {
return;
}
if (currentUrl) {
if (isValidUrl(currentUrl)) {
lastSavedUrl = currentUrl;
notifications.show('URL saved.', 'success');
} else {
notifications.show('Please enter a valid URL.', 'error');
urlInput.value = lastSavedUrl;
this.bindings.url.url = lastSavedUrl;
Storage.set('keybinds', this.bindings);
}
}
});
const keybindInputs = document.querySelectorAll('[id^="keybind-"]');
keybindInputs.forEach(input => {
if (input.id === 'keybind-url') {
const urlBinding = this.bindings['url'];
if (urlBinding && urlBinding.url) {
input.value = urlBinding.url;
}
input.addEventListener('input', () => {
if (this.bindings['url']) {
this.bindings['url'].url = input.value;
Storage.set('keybinds', this.bindings);
}
});
return;
}
const action = input.id.replace('keybind-url-combo', 'url').replace('keybind-', '');
if (this.bindings[action]) {
input.value = this.bindings[action].keys;
}
let currentKeys = new Set();
let isProcessingKeybind = false;
input.addEventListener('keydown', (e) => {
e.preventDefault();
if (e.key === 'Escape') {
input.blur();
return;
}
if (e.ctrlKey) {
notifications.show('CTRL key combinations are not allowed.', 'error');
isProcessingKeybind = true;
return;
}
if (FORBIDDEN_KEYS.includes(e.key)) {
notifications.show('This key cannot be used as a keybind.', 'error');
isProcessingKeybind = true;
return;
}
isProcessingKeybind = false;
if (e.key !== 'Alt' && e.key !== 'Shift') {
currentKeys.add(e.key);
}
if (e.altKey) currentKeys.add('Alt');
if (e.shiftKey) currentKeys.add('Shift');
input.value = Array.from(currentKeys).join('+');
});
input.addEventListener('keyup', (e) => {
if (isProcessingKeybind) {
currentKeys.clear();
return;
}
if (e.key === 'Alt' || e.key === 'Shift') {
if (currentKeys.size === 1) {
notifications.show('Add another key with Alt or Shift.', 'error');
}
currentKeys.clear();
input.value = this.bindings[action]?.keys || '';
return;
}
const combo = Array.from(currentKeys).join('+');
if (!combo) return;
const duplicate = Object.entries(this.bindings).find(([key, value]) =>
value.keys === combo && key !== action
);
if (duplicate) {
notifications.show('This keybind is already in use.', 'error');
currentKeys.clear();
input.value = this.bindings[action]?.keys || '';
return;
}
this.bindings[action] = {
keys: combo,
url: action === 'url' ? document.getElementById('keybind-url').value : null
};
Storage.set('keybinds', this.bindings);
notifications.show('Keybind saved.', 'success');
});
input.addEventListener('blur', () => {
currentKeys.clear();
input.value = this.bindings[action]?.keys || '';
});
});
document.querySelectorAll('.clear-keybind').forEach(button => {
button.addEventListener('click', () => {
const action = button.dataset.for;
const input = document.getElementById(`keybind-${action}-combo`) ||
document.getElementById(`keybind-${action}`);
input.value = '';
if (action === 'url') {
document.getElementById('keybind-url').value = '';
}
delete this.bindings[action];
Storage.set('keybinds', this.bindings);
notifications.show('Keybind removed.', 'success');
});
});
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || !Storage.get('onboardingComplete')) return;
const keys = [];
if (e.altKey) keys.push('Alt');
if (e.shiftKey) keys.push('Shift');
if (e.key !== 'Alt' && e.key !== 'Shift') keys.push(e.key);
const combo = keys.join('+');
Object.entries(this.bindings).forEach(([action, binding]) => {
if (binding.keys === combo) {
e.preventDefault();
this.executeAction(action, binding);
}
});
});
},
setupDefaultKeybinds() {
const defaultBindings = {
settings: { keys: 'Shift+S' },
anonymous: { keys: 'Shift+X' },
theme: { keys: 'Shift+T' },
history: { keys: 'Shift+H' },
url: { keys: 'Shift+Q', url: '' }
};
Object.entries(defaultBindings).forEach(([action, binding]) => {
if (!this.bindings[action]) {
this.bindings[action] = binding;
}
});
Storage.set('keybinds', this.bindings);
},
executeAction(action, binding) {
if (!Storage.get('onboardingComplete')) return;
const settingsPage = document.getElementById('settings-page');
const passwordDialog = document.getElementById('password-dialog');
if (passwordDialog && !passwordDialog.classList.contains('hidden')) {
const cancelBtn = document.getElementById('cancel-password');
if (cancelBtn) {
cancelBtn.click();
} else {
passwordDialog.classList.remove('active');
setTimeout(() => {
passwordDialog.classList.add('hidden');
}, 300);
}
}
if (action === 'settings' && settingsPage.classList.contains('active')) {
settingsPage.classList.remove('active');
setTimeout(() => {
settingsPage.classList.add('hidden');
}, 300);
}
const activeModal = document.querySelector('.modal.active');
switch (action) {
case 'settings':
if (settingsPage.classList.contains('hidden')) {
settings.updateSettingsUI();
settingsPage.classList.remove('hidden');
setTimeout(() => {
settingsPage.classList.add('active');
}, 10);
} else {
settings.updateSettingsUI();
}
break;
case 'add-shortcut':
const currentShortcuts = Storage.get('shortcuts') || [];
if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) {
return;
}
const shortcutModal = document.getElementById('add-shortcut-modal');
if (shortcutModal === activeModal) {
closeModal(shortcutModal);
} else {
openModal(shortcutModal);
}
break;
case 'anonymous':
const isAnonymous = Storage.get('anonymousMode') || false;
Storage.set('anonymousMode', !isAnonymous);
if (!isAnonymous) {
const randomName = anonymousNames.generate();
Storage.set('anonymousName', randomName);
} else {
Storage.remove('anonymousName');
}
shortcuts.render();
updateGreeting();
break;
case 'theme':
settings.toggleTheme();
break;
case 'history':
window.location.href = 'history.html';
break;
case 'url':
if (binding.url) {
const url = binding.url;
const fullUrl = url.startsWith('http://') || url.startsWith('https://') ?
url : `https://${url}`;
window.location.href = fullUrl;
}
break;
}
}
};

View File

@ -1,145 +1,98 @@
if ('serviceWorker' in navigator) { // Update greeting based on time of day and user settings
navigator.serviceWorker.register('/sw.js'); function updateGreeting() {
} const greeting = document.getElementById('greeting');
if (!greeting) return;
async function updateGreeting() {
const greeting = document.getElementById('greeting'); const hour = new Date().getHours();
if (!greeting) return; const isAnonymous = Storage.get('anonymousMode') || false;
const userName = isAnonymous ?
const customFormat = Storage.get('customGreeting'); (Storage.get('anonymousName') || anonymousNames.generate()) :
if (customFormat) { (Storage.get('userName') || 'Friend');
const formattedGreeting = await settings.formatGreeting(customFormat);
if (formattedGreeting) { let timeGreeting = 'Hello';
greeting.textContent = formattedGreeting; if (hour >= 5 && hour < 12) timeGreeting = 'Good Morning';
greeting.style.opacity = '0'; else if (hour >= 12 && hour < 17) timeGreeting = 'Good Afternoon';
setTimeout(() => { else if (hour >= 17 && hour < 20) timeGreeting = 'Good Evening';
greeting.style.opacity = '1'; else timeGreeting = 'Good Night';
}, 100);
return; greeting.textContent = `${timeGreeting}, ${userName}!`;
} greeting.style.opacity = '0';
} setTimeout(() => {
greeting.style.opacity = '1';
const hour = new Date().getHours(); }, 100);
const isAnonymous = Storage.get('anonymousMode') || false; }
const userName = isAnonymous ?
(Storage.get('anonymousName') || anonymousNames.generate()) : // Set up event listeners for modal interactions
(Storage.get('userName') || 'Friend'); function initModalHandlers() {
const modals = document.querySelectorAll('.modal');
let timeGreeting = 'Hello';
if (hour >= 5 && hour < 12) timeGreeting = 'Good Morning'; modals.forEach(modal => {
else if (hour >= 12 && hour < 17) timeGreeting = 'Good Afternoon'; modal.addEventListener('click', (e) => {
else if (hour >= 17 && hour < 20) timeGreeting = 'Good Evening'; if (e.target === modal) {
else timeGreeting = 'Good Night'; closeModal(modal);
}
greeting.textContent = `${timeGreeting}, ${userName}!`; });
greeting.style.opacity = '0';
setTimeout(() => { const modalContent = modal.querySelector('.modal-content');
greeting.style.opacity = '1'; if (modalContent) {
}, 100); modalContent.addEventListener('click', (e) => {
} e.stopPropagation();
});
function initModalHandlers() { }
const modals = document.querySelectorAll('.modal'); });
}
modals.forEach(modal => {
modal.addEventListener('click', (e) => { // Open modal with animation
if (e.target === modal && !modal.classList.contains('onboarding-modal')) { function openModal(modal) {
closeModal(modal); if (!modal) return;
} modal.classList.remove('hidden');
}); requestAnimationFrame(() => {
modal.classList.add('active');
const modalContent = modal.querySelector('.modal-content'); });
if (modalContent) { }
modalContent.addEventListener('click', (e) => {
e.stopPropagation(); // Close modal with animation
}); function closeModal(modal) {
} if (!modal) return;
modal.classList.remove('active');
document.querySelectorAll('.modal .close-button').forEach(button => { setTimeout(() => {
button.addEventListener('click', () => { modal.classList.add('hidden');
const modal = button.closest('.modal'); }, 300);
if (modal) { }
closeModal(modal);
} // Initialize application
}); document.addEventListener('DOMContentLoaded', () => {
}); // Apply visibility settings
}); ['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
} const isVisible = Storage.get(`show_${element}`);
if (isVisible === false) {
function openModal(modal) { const elementNode = document.getElementById(element === 'search' ? 'search-container' : element);
if (!modal) return; if (elementNode) elementNode.style.display = 'none';
}
const contextMenu = document.querySelector('.context-menu'); });
if (contextMenu) {
contextMenu.classList.add('hidden'); // Start onboarding or show main content
} if (!Storage.get('onboardingComplete')) {
onboarding.start();
modal.classList.remove('hidden'); } else {
requestAnimationFrame(() => { document.getElementById('main-content').classList.remove('hidden');
modal.classList.add('active'); }
});
} // Initialize features
search.init();
function closeModal(modal) { shortcuts.init();
if (!modal) return; settings.init();
modal.classList.remove('active'); initModalHandlers();
setTimeout(() => {
modal.classList.add('hidden'); // Set up greeting
}, 300); updateGreeting();
} setInterval(updateGreeting, 60000);
document.addEventListener('DOMContentLoaded', () => { // Settings button handler
if (typeof settings !== 'undefined' && typeof settings.updateVisibility === 'function') { const settingsButton = document.getElementById('settings-button');
settings.updateVisibility(); const settingsModal = document.getElementById('settings-modal');
} else {
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => { settingsButton.addEventListener('click', () => {
const isVisible = Storage.get(`show_${element}`); openModal(settingsModal);
if (isVisible === false) { });
const elementNode = document.getElementById(element === 'search' ? 'search-container' : });
element === 'addShortcut' ? 'add-shortcut' : element);
if (elementNode) {
elementNode.style.visibility = 'hidden';
elementNode.style.opacity = '0';
elementNode.style.position = 'absolute';
elementNode.style.pointerEvents = 'none';
}
}
});
}
if (!Storage.get('onboardingComplete')) {
onboarding.start();
} else {
document.getElementById('main-content').classList.remove('hidden');
}
search.init();
shortcuts.init();
settings.init();
initModalHandlers();
updateGreeting();
setInterval(updateGreeting, 60000);
const settingsButton = document.getElementById('settings-button');
const settingsModal = document.getElementById('settings-modal');
settingsButton.addEventListener('click', () => {
openModal(settingsModal);
});
keybinds.init();
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const activeModal = document.querySelector('.modal.active');
if (activeModal && !activeModal.matches('#settings-modal')) {
const primaryButton = activeModal.querySelector('.btn-primary');
if (primaryButton) {
primaryButton.click();
}
}
}
});

View File

@ -1,121 +1,131 @@
class NotificationSystem { // Notification System Class
constructor() { class NotificationSystem {
this.container = document.getElementById('notification-container'); constructor() {
this.notifications = new Map(); this.container = document.getElementById('notification-container');
} this.notifications = new Map();
}
show(message, type = 'info', duration = 3000) {
const id = Date.now().toString(); // Display a new notification
const notification = document.createElement('div'); show(message, type = 'info', duration = 3000) {
notification.className = `notification notification-${type}`; const id = Date.now().toString();
const notification = document.createElement('div');
const icon = this.createIcon(type); notification.className = `notification notification-${type}`;
const content = this.createContent(message);
const closeBtn = this.createCloseButton(id); // Create notification elements
const progress = this.createProgressBar(type); const icon = this.createIcon(type);
const content = this.createContent(message);
notification.appendChild(icon); const closeBtn = this.createCloseButton(id);
notification.appendChild(content); const progress = this.createProgressBar(type);
notification.appendChild(closeBtn);
notification.appendChild(progress); // Assemble notification
notification.appendChild(icon);
this.container.appendChild(notification); notification.appendChild(content);
notification.appendChild(closeBtn);
setTimeout(() => this.remove(id), duration); notification.appendChild(progress);
this.notifications.set(id, { this.container.appendChild(notification);
element: notification,
duration // Set removal timer
}); setTimeout(() => this.remove(id), duration);
this.updateProgress(id); // Store notification reference
this.notifications.set(id, {
return id; element: notification,
} duration
});
remove(id) {
const notification = this.notifications.get(id); this.updateProgress(id);
if (notification) {
notification.element.style.animation = 'slideOutRight 0.3s cubic-bezier(0.16, 1, 0.3, 1)'; return id;
setTimeout(() => { }
notification.element.remove();
this.notifications.delete(id); // Remove a notification
}, 300); remove(id) {
} const notification = this.notifications.get(id);
} if (notification) {
notification.element.style.animation = 'slideOutRight 0.3s cubic-bezier(0.16, 1, 0.3, 1)';
updateProgress(id) { setTimeout(() => {
const notification = this.notifications.get(id); notification.element.remove();
if (notification) { this.notifications.delete(id);
const progress = notification.element.querySelector('.notification-progress'); }, 300);
const startTime = Date.now(); }
}
const update = () => {
const elapsed = Date.now() - startTime; // Update progress bar
const percent = 100 - (elapsed / notification.duration * 100); updateProgress(id) {
const notification = this.notifications.get(id);
if (percent > 0) { if (notification) {
progress.style.width = `${percent}%`; const progress = notification.element.querySelector('.notification-progress');
requestAnimationFrame(update); const startTime = Date.now();
}
}; const update = () => {
const elapsed = Date.now() - startTime;
requestAnimationFrame(update); const percent = 100 - (elapsed / notification.duration * 100);
}
} if (percent > 0) {
progress.style.width = `${percent}%`;
createIcon(type) { requestAnimationFrame(update);
const icon = document.createElement('i'); }
switch(type) { };
case 'success':
icon.className = 'fas fa-check-circle'; requestAnimationFrame(update);
icon.style.color = 'var(--success-color, #4caf50)'; }
break; }
case 'error':
icon.className = 'fas fa-times-circle'; // Helper methods for creating notification elements
icon.style.color = 'var(--error-color, #f44336)'; createIcon(type) {
break; const icon = document.createElement('i');
case 'info': switch(type) {
default: case 'success':
icon.className = 'fas fa-info-circle'; icon.className = 'fas fa-check-circle';
icon.style.color = 'var(--info-color, #2196f3)'; icon.style.color = 'var(--success-color, #4caf50)';
break; break;
} case 'error':
return icon; icon.className = 'fas fa-times-circle';
} icon.style.color = 'var(--error-color, #f44336)';
break;
createContent(message) { case 'info':
const content = document.createElement('div'); default:
content.className = 'notification-content'; icon.className = 'fas fa-info-circle';
content.innerHTML = message; icon.style.color = 'var(--info-color, #2196f3)';
return content; break;
} }
return icon;
createCloseButton(id) { }
const closeBtn = document.createElement('button');
closeBtn.className = 'notification-close'; createContent(message) {
closeBtn.innerHTML = '<i class="fas fa-times"></i>'; const content = document.createElement('div');
closeBtn.onclick = () => this.remove(id); content.className = 'notification-content';
return closeBtn; content.textContent = message;
} return content;
}
createProgressBar(type) {
const progress = document.createElement('div'); createCloseButton(id) {
progress.className = 'notification-progress'; const closeBtn = document.createElement('button');
switch(type) { closeBtn.className = 'notification-close';
case 'success': closeBtn.innerHTML = '<i class="fas fa-times"></i>';
progress.style.background = 'var(--success-color, #4caf50)'; closeBtn.onclick = () => this.remove(id);
break; return closeBtn;
case 'error': }
progress.style.background = 'var(--error-color, #f44336)';
break; createProgressBar(type) {
case 'info': const progress = document.createElement('div');
default: progress.className = 'notification-progress';
progress.style.background = 'var(--info-color, #2196f3)'; switch(type) {
break; case 'success':
} progress.style.background = 'var(--success-color, #4caf50)';
return progress; break;
} case 'error':
} progress.style.background = 'var(--error-color, #f44336)';
break;
case 'info':
default:
progress.style.background = 'var(--info-color, #2196f3)';
break;
}
return progress;
}
}
// Initialize the notification system
const notifications = new NotificationSystem(); const notifications = new NotificationSystem();

View File

@ -1,337 +1,73 @@
const onboarding = { // Onboarding module
currentStep: 1, const onboarding = {
totalSteps: 5, // Check if onboarding is complete
settings: {}, isComplete: () => {
lastNotification: 0, return Storage.get('onboardingComplete') === true;
isCompleting: false, },
notificationShown: false,
// Start the onboarding process
showNotification(message, type = 'info') { start: () => {
const now = Date.now(); const modal = document.getElementById('onboarding-modal');
if (now - this.lastNotification >= 500) { const mainContent = document.getElementById('main-content');
this.lastNotification = now;
notifications.show(message, type); if (!onboarding.isComplete()) {
} modal.classList.remove('hidden');
}, modal.classList.add('active');
mainContent.classList.add('hidden');
isComplete: () => {
return Storage.get('onboardingComplete') === true; document.getElementById('next-step-btn').addEventListener('click', () => onboarding.nextStep(1));
}, document.getElementById('complete-setup-btn').addEventListener('click', onboarding.complete);
start: () => { // Set up search engine selection
const onboardingContainer = document.getElementById('onboarding-container'); const engines = document.querySelectorAll('.search-engine-option');
const mainContent = document.getElementById('main-content'); engines.forEach(engine => {
const fileInput = document.getElementById('onboarding-import'); engine.addEventListener('click', () => {
engines.forEach(e => e.classList.remove('selected'));
document.getElementById('notification-container').style.zIndex = "20000"; engine.classList.add('selected');
});
if (!onboarding.isComplete()) { });
document.body.style.overflow = 'hidden'; } else {
onboardingContainer.classList.remove('hidden'); modal.classList.add('hidden');
mainContent.classList.remove('hidden');
onboarding.initProgressDots(); }
onboarding.setupEventListeners(); },
const theme = Storage.get('theme') || 'light'; // Move to the next step in onboarding
document.body.setAttribute('data-theme', theme); nextStep: (currentStep) => {
const currentStepEl = document.querySelector(`[data-step="${currentStep}"]`);
document.querySelectorAll('.step-ob').forEach(step => { const nextStepEl = document.querySelector(`[data-step="${currentStep + 1}"]`);
if (step.dataset.step !== "1") { const name = document.getElementById('user-name').value.trim();
step.classList.remove('active-ob');
} if (!name) {
}); notifications.show('Please enter your name!', 'error');
return;
const firstStep = document.querySelector('.step-ob[data-step="1"]'); }
firstStep.classList.add('active-ob');
Storage.set('userName', name);
document.getElementById('prev-step').style.visibility = 'hidden';
currentStepEl.classList.add('hidden');
const nextButton = document.getElementById('next-step'); nextStepEl.classList.remove('hidden');
nextButton.innerHTML = 'Next <svg><use href="#icon-arrow-right"/></svg>'; nextStepEl.classList.add('visible');
},
if (onboarding.currentStep > 1) {
nextButton.disabled = true; // Complete the onboarding process
nextButton.classList.add('disabled-ob'); complete: () => {
} const selectedEngine = document.querySelector('.search-engine-option.selected');
if (!selectedEngine) {
mainContent.classList.add('hidden'); notifications.show('Please select a search engine!', 'error');
} else { return;
mainContent.classList.remove('hidden'); }
}
const searchEngine = selectedEngine.dataset.engine;
fileInput.addEventListener('change', async (e) => { Storage.set('searchEngine', searchEngine);
if (e.target.files.length > 0) { Storage.set('onboardingComplete', true);
try {
const file = e.target.files[0]; const modal = document.getElementById('onboarding-modal');
const text = await file.text(); const mainContent = document.getElementById('main-content');
const data = JSON.parse(text);
modal.classList.add('hidden');
if (!data.settings || !data.shortcuts || !Array.isArray(data.shortcuts)) { mainContent.classList.remove('hidden');
throw new Error('Invalid data structure'); notifications.show('Welcome to your new tab! 👋', 'success');
} updateGreeting();
}
Object.entries(data.settings).forEach(([key, value]) => { };
Storage.set(key, value);
});
Storage.set('shortcuts', data.shortcuts);
if (data.keybinds) {
Storage.set('keybinds', data.keybinds);
}
Storage.set('onboardingComplete', true);
localStorage.setItem('showWelcomeAfterImport', 'true');
window.location.reload();
} catch (error) {
onboarding.showNotification('Failed to import data: Invalid file format!', 'error');
fileInput.value = '';
}
}
});
},
setupEventListeners: () => {
document.querySelectorAll('.option-card-ob').forEach(card => {
card.addEventListener('click', () => {
const step = card.closest('.step-ob');
const stepNumber = parseInt(step.dataset.step);
const cards = step.querySelectorAll('.option-card-ob');
const nextButton = document.getElementById('next-step');
if (card.dataset.action === 'import-data') {
if (!card.classList.contains('selected-ob')) {
document.getElementById('onboarding-import').click();
}
cards.forEach(c => c.classList.remove('selected-ob'));
card.classList.add('selected-ob');
return;
}
cards.forEach(c => c.classList.remove('selected-ob'));
card.classList.add('selected-ob');
card.style.transform = 'scale(1.05)';
setTimeout(() => {
card.style.transform = 'scale(1.02)';
}, 150);
nextButton.disabled = false;
nextButton.classList.remove('disabled-ob');
if (card.dataset.theme) {
onboarding.settings.theme = card.dataset.theme;
document.body.setAttribute('data-theme', card.dataset.theme);
} else if (card.dataset.font) {
onboarding.settings.fontFamily = card.dataset.font;
document.documentElement.style.setProperty('--font-family', card.dataset.font);
} else if (card.dataset.engine) {
onboarding.settings.searchEngine = card.dataset.engine;
}
});
});
const nameInput = document.getElementById('user-name');
const nextButton = document.getElementById('next-step');
nameInput.addEventListener('input', (e) => {
const name = e.target.value.trim();
if (name) {
onboarding.settings.userName = name;
if (onboarding.currentStep === 4) {
nextButton.disabled = false;
nextButton.classList.remove('disabled-ob');
}
} else {
if (onboarding.currentStep === 4) {
nextButton.disabled = true;
nextButton.classList.add('disabled-ob');
}
}
});
document.getElementById('prev-step').addEventListener('click', () => {
if (onboarding.currentStep > 1) {
onboarding.navigateToStep(onboarding.currentStep - 1);
}
});
document.getElementById('next-step').addEventListener('click', () => {
let canProceed = true;
if (onboarding.currentStep === 2 && !onboarding.settings.theme) {
onboarding.showNotification('Please select a theme!', 'error');
canProceed = false;
}
else if (onboarding.currentStep === 3 && !onboarding.settings.fontFamily) {
onboarding.showNotification('Please select a font!', 'error');
canProceed = false;
}
else if (onboarding.currentStep === 4) {
const name = document.getElementById('user-name').value.trim();
if (!name) {
onboarding.showNotification('Please enter your name!', 'error');
canProceed = false;
} else {
onboarding.settings.userName = name;
}
}
else if (onboarding.currentStep === 5 && !onboarding.settings.searchEngine) {
onboarding.showNotification('Please select a search engine!', 'error');
canProceed = false;
}
if (canProceed) {
if (onboarding.currentStep < onboarding.totalSteps) {
onboarding.navigateToStep(onboarding.currentStep + 1);
} else {
onboarding.isCompleting = true;
localStorage.setItem('showWelcomeAfterImport', 'true');
onboarding.complete();
}
}
});
document.querySelectorAll('.step-ob').forEach(step => {
if (step.dataset.step !== "1" && step.dataset.step !== "4") {
const firstOption = step.querySelector('.option-card-ob');
if (firstOption) {
setTimeout(() => {
firstOption.click();
}, 100);
}
}
});
},
navigateToStep: (step) => {
const prevButton = document.getElementById('prev-step');
const nextButton = document.getElementById('next-step');
const currentStepEl = document.querySelector(`.step-ob[data-step="${onboarding.currentStep}"]`);
if (currentStepEl) {
currentStepEl.classList.remove('active-ob');
}
setTimeout(() => {
const targetStepEl = document.querySelector(`.step-ob[data-step="${step}"]`);
if (targetStepEl) {
targetStepEl.classList.add('active-ob');
}
onboarding.currentStep = step;
prevButton.style.visibility = step === 1 ? 'hidden' : 'visible';
if (step === onboarding.totalSteps) {
nextButton.innerHTML = 'Get Started <svg><use href="#icon-sparkle"/></svg>';
} else {
nextButton.innerHTML = 'Next <svg><use href="#icon-arrow-right"/></svg>';
}
if ((step === 2 && !onboarding.settings.theme) ||
(step === 3 && !onboarding.settings.fontFamily) ||
(step === 5 && !onboarding.settings.searchEngine)) {
nextButton.disabled = true;
nextButton.classList.add('disabled-ob');
} else if (step === 4) {
const name = document.getElementById('user-name').value.trim();
if (!name) {
nextButton.disabled = true;
nextButton.classList.add('disabled-ob');
} else {
nextButton.disabled = false;
nextButton.classList.remove('disabled-ob');
}
} else if (step === 1) {
nextButton.disabled = false;
nextButton.classList.remove('disabled-ob');
}
onboarding.updateProgressDots();
}, 100);
},
initProgressDots: () => {
const container = document.querySelector('.progress-dots-ob');
container.innerHTML = '';
for (let i = 0; i < onboarding.totalSteps; i++) {
const dot = document.createElement('div');
dot.className = 'dot-ob' + (i === 0 ? ' active-ob' : '');
container.appendChild(dot);
}
},
updateProgressDots: () => {
const dots = document.querySelectorAll('.dot-ob');
dots.forEach((dot, index) => {
dot.classList.toggle('active-ob', index + 1 === onboarding.currentStep);
});
},
complete: () => {
const onboardingContainer = document.getElementById('onboarding-container');
const mainContent = document.getElementById('main-content');
if (!onboarding.settings.theme) onboarding.settings.theme = 'light';
if (!onboarding.settings.fontFamily) onboarding.settings.fontFamily = 'Inter';
if (!onboarding.settings.searchEngine) onboarding.settings.searchEngine = 'google';
if (!onboarding.settings.userName) onboarding.settings.userName = 'User';
document.body.setAttribute('data-theme', onboarding.settings.theme);
document.documentElement.style.setProperty('--font-family', onboarding.settings.fontFamily);
Object.entries(onboarding.settings).forEach(([key, value]) => {
Storage.set(key, value);
});
if (!Storage.get('keybinds')) {
Storage.set('keybinds', {
settings: { keys: 'Shift+S' },
anonymous: { keys: 'Shift+X' },
theme: { keys: 'Shift+T' },
history: { keys: 'Shift+H' },
url: { keys: 'Shift+Q', url: '' }
});
}
Storage.set('onboardingComplete', true);
setTimeout(() => {
onboardingContainer.classList.add('hidden');
mainContent.classList.remove('hidden');
document.body.style.overflow = '';
search.init();
shortcuts.init();
settings.init();
updateGreeting();
setTimeout(() => {
if (!onboarding.notificationShown) {
onboarding.notificationShown = true;
onboarding.showNotification('Welcome to your new JSTAR Tab! 🎉', 'success');
}
}, 100);
}, 500);
}
};
document.addEventListener('DOMContentLoaded', () => {
onboarding.start();
if (onboarding.isComplete() && localStorage.getItem('showWelcomeAfterImport') === 'true') {
localStorage.removeItem('showWelcomeAfterImport');
setTimeout(() => {
notifications.show('Welcome to your new JSTAR Tab! 🎉', 'success');
}, 500);
}
});

View File

@ -1,80 +1,47 @@
const search = { const search = {
engines: { // Supported search engines and their URLs
google: { engines: {
url: 'https://www.google.com/search?q=', google: 'https://www.google.com/search?q=',
icon: 'https://www.google.com/s2/favicons?domain=google.com&sz=32', bing: 'https://www.bing.com/search?q=',
name: 'Google' duckduckgo: 'https://duckduckgo.com/?q=',
}, brave: 'https://search.brave.com/search?q=',
bing: { qwant: 'https://www.qwant.com/?q=',
url: 'https://www.bing.com/search?q=', searxng: 'https://searx.org/search?q='
icon: 'https://www.google.com/s2/favicons?domain=bing.com&sz=32', },
name: 'Bing'
}, // Perform search using selected engine
duckduckgo: { perform: () => {
url: 'https://duckduckgo.com/?q=', const searchBar = document.getElementById('search-bar');
icon: 'https://www.google.com/s2/favicons?domain=duckduckgo.com&sz=32', const query = searchBar.value.trim();
name: 'DuckDuckGo' const engine = Storage.get('searchEngine') || 'google';
},
brave: { if (query) {
url: 'https://search.brave.com/search?q=', const searchUrl = search.engines[engine] + encodeURIComponent(query);
icon: 'https://www.google.com/s2/favicons?domain=brave.com&sz=32', window.location.href = searchUrl;
name: 'Brave' }
}, },
qwant: {
url: 'https://www.qwant.com/?q=', // Initialize search functionality
icon: 'https://www.google.com/s2/favicons?domain=qwant.com&sz=32', init: () => {
name: 'Qwant' const searchBar = document.getElementById('search-bar');
}, const searchButton = document.getElementById('search-button');
searxng: {
url: 'https://searx.be/search?q=', searchBar.addEventListener('keypress', (e) => {
icon: 'https://www.google.com/s2/favicons?domain=searx.be&sz=32', if (e.key === 'Enter') {
name: 'SearXNG' search.perform();
} }
}, });
init: () => { searchButton.addEventListener('click', search.perform);
const searchBar = document.getElementById('search-bar');
const searchButton = document.getElementById('search-button'); // Global keyboard shortcut to focus search bar
document.addEventListener('keydown', (e) => {
searchBar.addEventListener('keypress', (e) => { if (e.key === '/' &&
if (e.key === 'Enter') { !['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) &&
search.perform(); window.getSelection().toString() === '') {
} e.preventDefault();
}); searchBar.focus();
}
searchButton.addEventListener('click', search.perform); });
}
document.addEventListener('keydown', (e) => { };
if (e.key === '/' &&
!['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) &&
window.getSelection().toString() === '') {
e.preventDefault();
searchBar.focus();
}
});
const searchEngine = Storage.get('searchEngine') || 'google';
search.updateSearchEngineIcon(searchEngine);
},
updateSearchEngineIcon(engine) {
const searchIcon = document.querySelector('#search-container .search-icon img');
if (!searchIcon) return;
searchIcon.src = this.engines[engine].icon;
},
perform: () => {
const searchBar = document.getElementById('search-bar');
const query = searchBar.value.trim();
const engine = Storage.get('searchEngine') || 'google';
if (query) {
const searchUrl = search.engines[engine].url + encodeURIComponent(query);
window.location.href = searchUrl;
}
}
};
document.addEventListener('DOMContentLoaded', () => {
search.init();
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,9 @@
/**
* Storage utility object for managing localStorage operations
* All methods handle JSON parsing/stringifying and error cases
*/
const Storage = { const Storage = {
// Retrieve and parse stored value
get: (key) => { get: (key) => {
try { try {
return JSON.parse(localStorage.getItem(key)); return JSON.parse(localStorage.getItem(key));
@ -7,6 +12,7 @@ const Storage = {
} }
}, },
// Store value as JSON string
set: (key, value) => { set: (key, value) => {
try { try {
localStorage.setItem(key, JSON.stringify(value)); localStorage.setItem(key, JSON.stringify(value));
@ -16,6 +22,7 @@ const Storage = {
} }
}, },
// Delete specific key
remove: (key) => { remove: (key) => {
try { try {
localStorage.removeItem(key); localStorage.removeItem(key);
@ -25,6 +32,7 @@ const Storage = {
} }
}, },
// Remove all stored data
clear: () => { clear: () => {
try { try {
localStorage.clear(); localStorage.clear();

View File

@ -1,93 +0,0 @@
const versionUrl = 'https://www.junaid.xyz/projects/jstar-tab/version.txt';
const manifestVersion = chrome.runtime.getManifest().version;
function compareVersions(version1, version2) {
const v1 = version1.split('.').map(Number);
const v2 = version2.split('.').map(Number);
for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
const diff = (v1[i] || 0) - (v2[i] || 0);
if (diff !== 0) return diff;
}
return 0;
}
async function checkForUpdate() {
try {
const response = await fetch(versionUrl, { cache: 'no-store' });
const latestVersion = await response.text();
handleVersionComparison(latestVersion);
} catch (error) {
const cachedResponse = await caches.match(versionUrl);
if (cachedResponse) {
const cachedVersion = await cachedResponse.text();
handleVersionComparison(cachedVersion, true);
} else {
updateVersionIcon(null);
}
}
}
function handleVersionComparison(latestVersion, isCached = false) {
latestVersion = latestVersion.trim();
const comparison = compareVersions(latestVersion, manifestVersion);
updateVersionIcon(comparison, latestVersion);
if (comparison > 0) {
const alertMessage = `New version ${latestVersion} available! ` +
`<a href="https://github.com/DevJSTAR/JSTAR-Tab/releases/${latestVersion}" ` +
`target="_blank" style="color: #2196F3;">Update now</a>`;
if (isCached) {
notifications.show(`${alertMessage} (Showing cached version)`, 'info', 8000);
} else {
notifications.show(alertMessage, 'info');
}
}
}
function updateVersionIcon(versionComparison, latestVersion) {
const versionIcon = document.getElementById('version-icon');
if (!versionIcon) return;
versionIcon.className = 'version-icon fas';
versionIcon.style.color = '';
versionIcon.removeAttribute('title');
latestVersion = latestVersion.trim();
if (versionComparison === 0) {
versionIcon.classList.add('fa-check-circle');
versionIcon.style.color = '#4caf50';
versionIcon.title = 'Youre up to date! Enjoy the latest features.';
} else if (versionComparison > 0) {
versionIcon.classList.add('fa-exclamation-circle');
versionIcon.style.color = '#ff9800';
versionIcon.title = `A newer version (${latestVersion}) is available! Dont miss out on the new goodies.`;
} else if (versionComparison < 0) {
versionIcon.classList.add('fa-question-circle');
versionIcon.style.color = '#2196f3';
versionIcon.title = 'Whoa! Youre ahead of the curve. Are you from the future?';
} else {
versionIcon.classList.add('fa-times-circle');
versionIcon.style.color = '#f44336';
versionIcon.title = 'Unable to check the version. Is the internet sleeping?';
}
}
function showUpdateNotification(latestVersion) {
const message = `Version ${latestVersion} is available! <a href="https://github.com/DevJSTAR/JSTAR-Tab/releases/${latestVersion}" target="_blank">Update now</a>!`;
notifications.show(message, 'info');
}
checkForUpdate();
document.addEventListener('DOMContentLoaded', () => {
const version = chrome.runtime.getManifest().version;
const versionElement = document.getElementById('extension-version');
if (versionElement) {
versionElement.innerHTML = `JSTAR Tab v<a href="https://github.com/DevJSTAR/JSTAR-Tab/releases/${version}" target="_blank" style="color: inherit;">${version}</a> <span id="version-icon" class="version-icon"></span>`;
}
});

View File

@ -1,36 +1,36 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "JSTAR Tab", "name": "JSTAR Tab",
"version": "3.2.0", "version": "2.0.0",
"description": "JSTAR Tab is a sleek, customizable new tab extension with personalized greetings, shortcuts, anonymous mode, search engine settings, themes, data management, and more, for an enhanced browsing experience.", "description": "JSTAR Tab is a sleek, customizable new tab extension with personalized greetings, shortcuts, anonymous mode, search engine settings, themes, data management, and more, for an enhanced browsing experience.",
"chrome_url_overrides": { "chrome_url_overrides": {
"newtab": "index.html" "newtab": "index.html"
}, },
"permissions": [ "permissions": [
"storage", "storage",
"favicon", "favicon"
"history" ],
], "icons": {
"icons": { "16": "images/icon16.png",
"16": "images/icon16.png", "48": "images/icon48.png",
"48": "images/icon48.png", "128": "images/icon128.png"
"128": "images/icon128.png" },
}, "action": {
"action": { "default_title": "New JSTAR Tab",
"default_title": "New JSTAR Tab", "default_icon": {
"default_icon": { "16": "images/icon16.png",
"16": "images/icon16.png", "48": "images/icon48.png",
"48": "images/icon48.png", "128": "images/icon128.png"
"128": "images/icon128.png" }
} },
}, "author": "JSTAR",
"author": "JSTAR", "homepage_url": "https://github.com/DevJSTAR/JSTAR-Tab",
"homepage_url": "https://github.com/DevJSTAR/JSTAR-Tab", "update_url": "https://github.com/DevJSTAR/JSTAR-Tab/releases/latest",
"web_accessible_resources": [{ "web_accessible_resources": [{
"resources": [ "resources": [
"fonts/*", "fonts/*",
"images/*" "images/*"
], ],
"matches": ["<all_urls>"] "matches": ["<all_urls>"]
}] }]
} }

699
style.css Normal file
View File

@ -0,0 +1,699 @@
/* Root variables for light theme */
:root {
--primary: #f5f5f5;
--primary-hover: #e0e0e0;
--background: #ffffff;
--surface: #fafafa;
--border: #eaeaea;
--text: #1a1a1a;
--text-secondary: #666666;
--shadow: rgba(0, 0, 0, 0.08);
--modal-backdrop: rgba(0, 0, 0, 0.5);
--scrollbar-thumb: #e0e0e0;
--scrollbar-track: #f5f5f5;
--modal-background: #ffffff;
}
/* Dark theme variables */
[data-theme="dark"] {
--primary: #1a1a1a;
--primary-hover: #2a2a2a;
--background: #000000;
--surface: #111111;
--border: #333333;
--text: #ffffff;
--text-secondary: #999999;
--shadow: rgba(0, 0, 0, 0.3);
--modal-backdrop: rgba(255, 255, 255, 0.1);
--scrollbar-thumb: #333333;
--scrollbar-track: #1a1a1a;
--modal-background: #1a1a1a;
}
/* Dark theme button styles */
[data-theme="dark"] .btn-primary {
background: var(--primary-hover);
color: var(--text);
}
[data-theme="dark"] .btn-primary:hover {
background: #3a3a3a;
}
/* Global styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Inter', -apple-system, sans-serif;
}
body {
background: var(--background);
color: var(--text);
min-height: 100vh;
}
.hidden {
display: none !important;
}
/* Modal styles */
.modal {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
background: var(--modal-backdrop);
}
.modal-actions {
display: flex;
gap: 1rem;
margin-top: 1rem;
}
.modal-actions button {
flex: 1;
}
.modal.active {
opacity: 1;
visibility: visible;
}
.modal-content {
background: var(--modal-background);
border-radius: 24px;
padding: 2rem;
width: 90%;
max-width: 480px;
transform: translateY(20px);
opacity: 0;
transition: all 0.3s ease;
box-shadow: 0 10px 25px var(--shadow);
}
/* Modal animations */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal.active .modal-content {
transform: translateY(0);
opacity: 1;
animation: modalSlideIn 0.3s ease forwards;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Form element styles */
select {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid var(--border);
border-radius: 12px;
background: var(--surface);
color: var(--text);
font-size: 1rem;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23666666%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.4-12.8z%22%2F%3E%3C%2Fsvg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
background-size: 0.65em auto;
}
select:focus {
outline: none;
border-color: var(--text);
}
/* Step styles */
.step h2 {
font-size: 1.75rem;
margin-bottom: 1rem;
font-weight: 700;
}
.step p {
color: var(--text-secondary);
margin-bottom: 2rem;
}
.input-group {
margin-bottom: 1.5rem;
}
input[type="text"] {
width: 100%;
padding: 1rem;
border: 2px solid var(--border);
border-radius: 12px;
background: var(--surface);
color: var(--text);
font-size: 1rem;
}
input[type="text"]:focus {
outline: none;
border-color: var(--text);
}
/* Main content styles */
.center-container {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 3rem;
padding: 2rem;
position: relative;
}
#greeting {
font-size: 2rem;
font-weight: 700;
margin: 0;
opacity: 0;
animation: fadeIn 0.5s ease forwards;
height: 48px;
visibility: hidden;
}
#greeting:not([style*="visibility"]) {
visibility: visible;
}
.search-container {
width: 100%;
max-width: 640px;
height: 56px;
visibility: visible;
}
/* Search engine options */
.search-engine-options {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin: 2rem 0;
}
.search-engine-option {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1.5rem;
border-radius: 16px;
background: var(--surface);
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 8px var(--shadow);
border: 2px solid transparent;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.search-engine-option:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--shadow);
}
.search-engine-option.selected {
background: var(--surface);
border: 2px solid var(--border);
}
.search-engine-option img {
width: 32px;
height: 32px;
border-radius: 4px;
}
.search-engine-option span {
font-size: 0.875rem;
color: var(--text);
text-align: center;
}
/* Search bar styles */
.search-wrapper {
position: relative;
}
#search-bar {
width: 100%;
padding: 1.25rem 1.5rem;
border: none;
border-radius: 16px;
background: var(--surface);
color: var(--text);
font-size: 1rem;
box-shadow: 0 4px 24px var(--shadow);
}
.search-icon {
position: absolute;
right: 1.5rem;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--text);
cursor: pointer;
padding: 0.5rem;
}
/* Shortcuts styles */
.shortcuts-container {
width: 100%;
max-width: 640px;
}
#add-shortcut {
width: 40px;
height: 40px;
border-radius: 50%;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
background: var(--primary);
color: var(--text);
border: none;
cursor: pointer;
transition: background 0.2s ease;
}
#add-shortcut:hover {
background: var(--primary-hover);
}
#shortcuts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.shortcut {
background: var(--surface);
border-radius: 12px;
padding: 1rem;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
box-shadow: 0 2px 10px var(--shadow);
}
.shortcut:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px var(--shadow);
}
.shortcut img {
width: 24px;
height: 24px;
border-radius: 6px;
}
.shortcut span {
font-size: 0.75rem;
text-align: center;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.shortcut.blurred {
filter: blur(4px);
opacity: 0.7;
}
.shortcut.blurred:hover {
filter: blur(0);
opacity: 1;
}
/* Settings styles */
.settings-button {
position: fixed;
bottom: 2rem;
right: 2rem;
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--surface);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
color: var(--text);
box-shadow: 0 4px 24px var(--shadow);
}
.settings-button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px var(--shadow);
}
.settings-panel {
max-height: 80vh;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}
.settings-panel::-webkit-scrollbar {
width: 8px;
}
.settings-panel::-webkit-scrollbar-track {
background: var(--scrollbar-track);
border-radius: 0 24px 24px 0;
}
.settings-panel::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-thumb);
border-radius: 4px;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 2rem;
}
.modal-header h2 {
font-size: 1.5rem;
font-weight: 600;
}
.btn-icon {
background: none;
border: none;
color: var(--text);
cursor: pointer;
padding: 0.5rem;
font-size: 1.25rem;
opacity: 0.6;
}
.btn-icon:hover {
opacity: 1;
}
.settings-section {
padding: 1.5rem 0;
border-bottom: 1px solid var(--border);
}
.settings-section:last-child {
border-bottom: none;
}
.settings-section h3 {
margin-bottom: 1.5rem;
font-weight: 600;
}
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 0;
}
.setting-label {
font-weight: 500;
}
.data-management-buttons {
display: flex;
flex-direction: column;
gap: 1rem;
}
.btn-danger {
background: #dc3545 !important;
color: white !important;
}
.btn-danger:hover {
background: #c82333 !important;
}
/* Toggle switch styles */
.toggle {
position: relative;
display: inline-block;
width: 50px;
height: 26px;
}
.toggle input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--border);
border-radius: 34px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 3px;
bottom: 3px;
background-color: var(--background);
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: var(--text);
}
input:checked + .toggle-slider:before {
transform: translateX(24px);
}
/* Button styles */
.btn-primary {
background: var(--primary);
color: var(--text);
border: none;
padding: 1rem 2rem;
border-radius: 12px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
width: 100%;
}
.btn-primary:hover {
background: var(--primary-hover);
}
/* Notification styles */
#notification-container {
position: fixed;
top: 1.5rem;
right: 1.5rem;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 1rem;
pointer-events: none;
}
.notification {
background: var(--surface);
color: var(--text);
padding: 1.25rem 1.5rem;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(var(--shadow-rgb), 0.1);
display: flex;
align-items: center;
gap: 1.25rem;
pointer-events: auto;
position: relative;
overflow: hidden;
animation: slideInRight 0.4s cubic-bezier(0.16, 1, 0.3, 1), fadeIn 0.4s ease;
max-width: 420px;
border: 1px solid rgba(var(--text-rgb), 0.1);
}
.notification-content {
flex: 1;
font-size: 0.95rem;
line-height: 1.5;
}
.notification-close {
background: none;
border: none;
color: var(--text);
cursor: pointer;
padding: 0.5rem;
opacity: 0.6;
}
.notification-close:hover {
opacity: 1;
background: rgba(var(--text-rgb), 0.1);
}
.notification-progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background: var(--primary);
opacity: 0.8;
transition: width 0.1s linear;
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* About section styles */
.about-content {
text-align: center;
color: var(--text-secondary);
padding: 1rem 0;
}
.about-content a {
color: var(--text);
text-decoration: none;
}
.about-content a:hover {
text-decoration: underline;
}
.about-content .version {
font-size: 1.1rem;
font-weight: 600;
color: var(--text);
margin-bottom: 0.5rem;
}
.about-content .description {
margin-bottom: 1rem;
}
.about-content .features {
font-size: 0.9rem;
margin-bottom: 1rem;
}
.about-content .copyright {
font-size: 0.8rem;
margin-top: 1rem;
}
.made-with {
margin-top: 0.5rem;
font-size: 0.875rem;
}
.made-with i {
color: #ff6b6b;
}
/* Context menu styles */
.context-menu {
position: fixed;
background: var(--surface);
border-radius: 8px;
padding: 0.5rem;
box-shadow: 0 2px 10px var(--shadow);
z-index: 1000;
}
.context-menu.hidden {
display: none;
}
.context-menu-item {
padding: 0.75rem 1rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
border-radius: 4px;
}
.context-menu-item:hover {
background: var(--primary);
}
.context-menu-item i {
width: 16px;
}
/* Hidden element styles */
.search-container.hidden,
#greeting.hidden,
#shortcuts-grid.hidden,
#add-shortcut.hidden {
visibility: hidden !important;
opacity: 0 !important;
position: absolute !important;
pointer-events: none !important;
}

60
sw.js
View File

@ -1,60 +0,0 @@
const CACHE_PREFIX = 'jstartab-cache';
const VERSION_URL = 'https://www.junaid.xyz/projects/jstar-tab/version.txt';
const STATIC_CACHE = 'jstartab-static';
self.addEventListener('install', (event) => {
self.skipWaiting();
event.waitUntil(
caches.open(STATIC_CACHE).then(cache => cache.add(VERSION_URL))
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(clients.claim());
});
self.addEventListener('message', async (event) => {
if (event.data.action === 'updateFavicons') {
const faviconUrls = event.data.urls;
const currentDate = new Date();
const cacheName = `${CACHE_PREFIX}-${currentDate.toISOString().split('T')[0]}`;
const cache = await caches.open(cacheName);
await cache.addAll(faviconUrls);
const keys = await caches.keys();
keys.forEach((key) => {
if (key.startsWith(CACHE_PREFIX) && key !== cacheName) {
caches.delete(key);
}
});
}
});
self.addEventListener('fetch', (event) => {
const request = event.request;
if (event.request.url.startsWith('https://www.google.com/s2/favicons')) {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request).then((fetchResponse) => {
const cacheCopy = fetchResponse.clone();
caches.open(`${CACHE_PREFIX}-${new Date().toISOString().split('T')[0]}`)
.then((cache) => cache.put(event.request, cacheCopy));
return fetchResponse;
});
})
);
}
if (request.url === VERSION_URL) {
event.respondWith(
caches.open(STATIC_CACHE).then(cache =>
fetch(request).then(networkResponse => {
cache.put(request, networkResponse.clone());
return networkResponse;
}).catch(() => cache.match(request))
)
);
}
});