Compare commits

...

33 Commits
2.6.0 ... main

Author SHA1 Message Date
JSTAR
9d9305b69a
Add files via upload 2025-04-18 22:19:06 +05:00
JSTAR
ac0a56c08e
Update README.md 2025-04-18 21:51:26 +05:00
JSTAR
f2b3a43135
Add files via upload 2025-04-18 21:35:21 +05:00
JSTAR
7cc5e4010e
Update README.md 2025-03-23 23:50:17 +05:00
JSTAR
46ef89458d
Delete style.css 2025-03-23 23:34:19 +05:00
JSTAR
e8b5dba408
Add files via upload 2025-03-23 23:34:00 +05:00
JSTAR
293a1e9e8a
Delete images/favicon.ico 2025-03-23 23:30:09 +05:00
JSTAR
1b4cbc6e27
Delete images/icon16.png 2025-03-23 23:30:01 +05:00
JSTAR
196051f263
Delete images/icon48.png 2025-03-23 23:29:51 +05:00
JSTAR
76c663c0bf
Delete images/icon128.png 2025-03-23 23:29:38 +05:00
JSTAR
3e6b86b79d
Update LICENSE 2025-02-23 14:08:35 +05:00
JSTAR
683f6b7532
Update README.md 2024-12-15 22:43:59 +05:00
JSTAR
56fe2fd9f2
Update README.md 2024-12-15 22:42:58 +05:00
JSTAR
ab9d9a1849
Add files via upload 2024-12-14 15:19:39 +05:00
JSTAR
ed9aa01ea0
Add files via upload 2024-12-14 15:03:57 +05:00
JSTAR
5c6dc07bb4
Update index.html 2024-12-14 14:54:51 +05:00
JSTAR
6e076575f2
Update README.md 2024-12-14 14:52:27 +05:00
JSTAR
99b7538de4
Update style.css 2024-12-14 14:41:18 +05:00
JSTAR
659ca86f17
Update index.html 2024-12-14 14:38:10 +05:00
JSTAR
1b7211317a
Merge pull request #2 from EquaTechnologies/main
JSTAR Tab v2.6.2
2024-12-14 14:37:16 +05:00
Equa
87f8e0ff11
Delete version.json (not needed file) 2024-12-13 21:39:00 +02:00
Equa
8a326c7cdb
Update shortcuts.js 2024-12-13 21:18:26 +02:00
Equa
a872db13a2
Update index.html 2024-12-13 21:06:46 +02:00
Equa
787413d027
Update style.css 2024-12-13 21:02:08 +02:00
Equa
1b67360deb
Update style.css 2024-12-13 20:58:04 +02:00
Equa
a409389215
Update style.css 2024-12-13 20:23:09 +02:00
Equa
888580b342
Update style.css 2024-12-13 20:08:54 +02:00
Equa
6b0735351d
Update manifest.json 2024-12-13 20:04:16 +02:00
Equa
66b70330b9
Update version.json 2024-12-13 20:03:44 +02:00
JSTAR
1faabe03c7
Delete version.json 2024-12-13 23:02:58 +05:00
Equa
a7f7b07377
Update style.css button style 2024-12-13 20:00:35 +02:00
Equa
2074b0db58
Update README.md
skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma skibidi sigma
2024-12-13 19:55:34 +02:00
JSTAR
350e65ff29
Create version.json 2024-12-13 00:00:57 +05:00
33 changed files with 9818 additions and 2993 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 JSTAR Copyright (c) 2025 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

122
README.md
View File

@ -1,80 +1,84 @@
# 🌟 JSTAR Tab v2.6.0 # 🌟 JSTAR Tab
Welcome to **JSTAR Tab v2.6.0**! This version builds upon the previous release with new features and improvements to enhance your browsing experience even further. 🚀 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. 🚀
## 🎉 What's New in v2.6.0? Transform your browsing experience with custom greetings, motion control, themes, fonts, and more. 🎉
- **Enhanced Onboarding Process**: The onboarding experience has been revamped to include options for starting fresh or importing data, making it easier to get started. ## ✨ Features
- **Keyboard Shortcuts**: Added the ability to configure keyboard shortcuts for quicker navigation and actions.
- **Improved Data Management**: Enhanced import/export functionality with better validation and error handling.
- **Custom Greeting Enhancements**: Added new format variables for custom greetings, allowing for more personalized messages.
- **UI and UX Improvements**: Various updates to the user interface for a more seamless experience, including responsive design adjustments.
## 📥 Installation - **Custom Greeting Formats**: Create personalized greetings using dynamic format tags:
- `{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
1. Visit [JSTAR Tab Website](https://jstartab.netlify.app) Examples:
2. Select your browser or mobile browser from the available options. - "Hello {name}, it's {time} on {day}!"
3. Follow the installation instructions specific to your browser. - "{greeting}, {name}! Today is {date}"
- "Happy {day}, {name}!"
Alternative Manual Installation: - **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.6.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.
## 🛠️ Features ## 🌐 Getting Started
- **Custom Greeting Format**: Create unique greetings using various format tags. 📝 ### Installation
- **Customizable Themes**: Choose between light and dark themes to suit your mood. 🌗
- **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.
- **Keyboard Shortcuts**: Configure shortcuts for quick access to features.
## 🎨 Customizing Your Greeting 1. Visit [JSTAR Tab Website](https://jstartab.netlify.app).
2. Select your browser or mobile browser from the available options.
3. Follow the installation instructions specific to your browser.
1. **Format Tags**: Use these tags in your custom greeting: ### Alternative Manual 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
2. **Examples**:
- "Hello {name}, it's {time} on {day}!"
- "{greeting}, {name}! Today is {date}"
- "Happy {day}, {name}!"
## 📚 Usage 1. **Download the Latest Release**: [JSTAR Tab GitHub Releases](https://github.com/DevJSTAR/JSTAR-Tab/releases/latest).
2. **Extract the Files**: Unzip the downloaded file to a directory of your choice.
3. **Load the Extension**:
- Open your browser and navigate to the extensions page.
- Enable "Developer mode" (usually a toggle in the top right corner).
- Click "Load unpacked" and select the directory where you extracted the files.
- **Settings**: Click the settings icon ⚙️ to open the settings panel where you can customize your experience. ## 🎨 Customizing Your Experience
- **Custom Greeting**: Enter your preferred greeting format in the settings panel.
- **Add Shortcuts**: Click the "+" button to add a new shortcut. Enter the name and URL, then save.
- **Edit Shortcuts**: Right-click on a shortcut to edit or delete it.
- **Theme Toggle**: Switch between light and dark themes using the theme toggle button.
- **Keyboard Shortcuts**: Use configured shortcuts for quick actions.
## 📝 Contributing ### **Greeting Formats**
Personalize your greeting with dynamic tags listed above. Example:
"Good {greeting}, {name}! It's {time} on {day}!"
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. ### **Themes & Fonts**
- Toggle between light and dark themes from the settings panel.
- Select from various font options to match your style.
## 📄 License ### **Shortcuts & Backgrounds**
- Add shortcuts with the "+" button.
- Edit or delete shortcuts by right-clicking on them.
- Customize your background by uploading your favorite images.
This project is licensed under the [MIT License](https://github.com/DevJSTAR/JSTAR-Tab/blob/main/LICENSE). ### **Keyboard Shortcuts**
Set up custom keybinds for quick actions like opening settings or switching themes.
## ❤️ Acknowledgments ## 📄 License
- **Font Awesome**: For the beautiful icons used throughout the extension. This project is licensed under the [MIT License](https://github.com/DevJSTAR/JSTAR-Tab/blob/main/LICENSE).
- **Google Fonts**: For the sleek and modern typography.
## 🌐 Connect with Us ## 🌐 Connect with Us
- **[Linktree](https://linktr.ee/jstarsdev)** - **[Linktree](https://linktr.ee/jstarsdev)**
- **[Patreon](https://patreon.com/jstarsdev)** - **[Patreon](https://patreon.com/jstarsdev)**
- **[Latest Release](https://github.com/DevJSTAR/JSTAR-Tab/releases/latest)** - **[GitHub Releases](https://github.com/DevJSTAR/JSTAR-Tab/releases/latest)**
Thank you for using JSTAR Tab! We hope it makes your browsing experience more enjoyable and productive. 🙂 Thank you for choosing JSTAR Tab! We hope you enjoy the seamless, personalized, and now even more **accessible** browsing experience it brings. 🚀

567
css/history.css Normal file
View File

@ -0,0 +1,567 @@
: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;
}
}

474
css/onboarding.css Normal file
View File

@ -0,0 +1,474 @@
.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);
}
}

2718
css/style.css Normal file

File diff suppressed because it is too large Load Diff

144
history.html Normal file
View File

@ -0,0 +1,144 @@
<!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.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

BIN
images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 479 B

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 978 B

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
images/secrecy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

1519
index.html

File diff suppressed because it is too large Load Diff

159
js/backgrounds.js Normal file
View File

@ -0,0 +1,159 @@
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));
});

86
js/cache-handler.js Normal file
View File

@ -0,0 +1,86 @@
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();
});

343
js/grid-layout.js Normal file
View File

@ -0,0 +1,343 @@
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);
});

904
js/history.js Normal file
View File

@ -0,0 +1,904 @@
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,265 +1,313 @@
// List of keys that cannot be used as keybinds const FORBIDDEN_KEYS = [
const FORBIDDEN_KEYS = [ 'Tab', 'CapsLock', 'Meta', 'ContextMenu',
'Tab', 'CapsLock', 'Meta', 'ContextMenu', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'Home', 'End', 'PageUp', 'PageDown', 'Insert', 'Delete', 'ScrollLock', 'Pause', 'NumLock',
'Home', 'End', 'PageUp', 'PageDown', 'Insert', 'Delete', 'ScrollLock', 'Pause', 'NumLock' '/'
]; ];
const keybinds = { const keybinds = {
bindings: {}, bindings: {},
init() { init() {
this.bindings = Storage.get('keybinds') || {}; this.bindings = Storage.get('keybinds') || {};
// URL keybind handling this.setupDefaultKeybinds();
const urlInput = document.getElementById('keybind-url');
const urlComboInput = document.getElementById('keybind-url-combo'); const urlInput = document.getElementById('keybind-url');
const urlComboInput = document.getElementById('keybind-url-combo');
if (this.bindings.url) { const historyComboInput = document.getElementById('keybind-history-combo');
urlInput.value = this.bindings.url.url || '';
urlComboInput.value = this.bindings.url.keys || ''; if (this.bindings.url) {
} urlInput.value = this.bindings.url.url || '';
urlComboInput.value = this.bindings.url.keys || '';
let lastSavedUrl = urlInput.value; }
function isValidUrl(string) { if (this.bindings.history && historyComboInput) {
try { historyComboInput.value = this.bindings.history.keys || '';
const urlString = string.match(/^https?:\/\//) ? string : `https://${string}`; }
new URL(urlString);
return true; let lastSavedUrl = urlInput.value;
} catch (_) {
return false; function isValidUrl(string) {
} try {
} const urlString = string.match(/^https?:\/\//) ? string : `https://${string}`;
new URL(urlString);
urlInput.addEventListener('input', () => { return true;
if (!this.bindings.url) { } catch (_) {
this.bindings.url = { url: '', keys: '' }; return false;
} }
this.bindings.url.url = urlInput.value; }
Storage.set('keybinds', this.bindings);
}); urlInput.addEventListener('input', () => {
if (!this.bindings.url) {
urlInput.addEventListener('keydown', (e) => { this.bindings.url = { url: '', keys: '' };
if (e.key === 'Enter') { }
e.preventDefault(); this.bindings.url.url = urlInput.value;
urlInput.blur(); Storage.set('keybinds', this.bindings);
} });
});
urlInput.addEventListener('keydown', (e) => {
urlInput.addEventListener('blur', () => { if (e.key === 'Enter') {
const currentUrl = urlInput.value.trim(); e.preventDefault();
urlInput.blur();
if (currentUrl === lastSavedUrl) { }
return; });
}
urlInput.addEventListener('blur', () => {
if (currentUrl) { const currentUrl = urlInput.value.trim();
if (isValidUrl(currentUrl)) {
lastSavedUrl = currentUrl; if (currentUrl === lastSavedUrl) {
notifications.show('URL saved.', 'success'); return;
} else { }
notifications.show('Please enter a valid URL.', 'error');
urlInput.value = lastSavedUrl; if (currentUrl) {
this.bindings.url.url = lastSavedUrl; if (isValidUrl(currentUrl)) {
Storage.set('keybinds', this.bindings); lastSavedUrl = currentUrl;
} notifications.show('URL saved.', 'success');
} } else {
}); notifications.show('Please enter a valid URL.', 'error');
urlInput.value = lastSavedUrl;
// Keybind input handling this.bindings.url.url = lastSavedUrl;
const keybindInputs = document.querySelectorAll('[id^="keybind-"]'); Storage.set('keybinds', this.bindings);
keybindInputs.forEach(input => { }
if (input.id === 'keybind-url') { }
const urlBinding = this.bindings['url']; });
if (urlBinding && urlBinding.url) {
input.value = urlBinding.url; const keybindInputs = document.querySelectorAll('[id^="keybind-"]');
} keybindInputs.forEach(input => {
if (input.id === 'keybind-url') {
input.addEventListener('input', () => { const urlBinding = this.bindings['url'];
if (this.bindings['url']) { if (urlBinding && urlBinding.url) {
this.bindings['url'].url = input.value; input.value = urlBinding.url;
Storage.set('keybinds', this.bindings); }
}
}); input.addEventListener('input', () => {
return; if (this.bindings['url']) {
} this.bindings['url'].url = input.value;
Storage.set('keybinds', this.bindings);
const action = input.id.replace('keybind-url-combo', 'url').replace('keybind-', ''); }
if (this.bindings[action]) { });
input.value = this.bindings[action].keys; return;
} }
let currentKeys = new Set(); const action = input.id.replace('keybind-url-combo', 'url').replace('keybind-', '');
let isProcessingKeybind = false; if (this.bindings[action]) {
input.value = this.bindings[action].keys;
input.addEventListener('keydown', (e) => { }
e.preventDefault();
let currentKeys = new Set();
if (e.key === 'Escape') { let isProcessingKeybind = false;
input.blur();
return; input.addEventListener('keydown', (e) => {
} e.preventDefault();
if (e.ctrlKey) { if (e.key === 'Escape') {
notifications.show('CTRL key combinations are not allowed.', 'error'); input.blur();
isProcessingKeybind = true; return;
return; }
}
if (e.ctrlKey) {
if (FORBIDDEN_KEYS.includes(e.key)) { notifications.show('CTRL key combinations are not allowed.', 'error');
notifications.show('This key cannot be used as a keybind.', 'error'); isProcessingKeybind = true;
isProcessingKeybind = true; return;
return; }
}
if (FORBIDDEN_KEYS.includes(e.key)) {
isProcessingKeybind = false; notifications.show('This key cannot be used as a keybind.', 'error');
isProcessingKeybind = true;
if (e.key !== 'Alt' && e.key !== 'Shift') { return;
currentKeys.add(e.key); }
}
if (e.altKey) currentKeys.add('Alt'); isProcessingKeybind = false;
if (e.shiftKey) currentKeys.add('Shift');
if (e.key !== 'Alt' && e.key !== 'Shift') {
input.value = Array.from(currentKeys).join('+'); currentKeys.add(e.key);
}); }
if (e.altKey) currentKeys.add('Alt');
input.addEventListener('keyup', (e) => { if (e.shiftKey) currentKeys.add('Shift');
if (isProcessingKeybind) {
currentKeys.clear(); input.value = Array.from(currentKeys).join('+');
return; });
}
input.addEventListener('keyup', (e) => {
if (e.key === 'Alt' || e.key === 'Shift') { if (isProcessingKeybind) {
if (currentKeys.size === 1) { currentKeys.clear();
notifications.show('Add another key with Alt or Shift.', 'error'); return;
} }
currentKeys.clear();
input.value = this.bindings[action]?.keys || ''; if (e.key === 'Alt' || e.key === 'Shift') {
return; if (currentKeys.size === 1) {
} notifications.show('Add another key with Alt or Shift.', 'error');
}
const combo = Array.from(currentKeys).join('+'); currentKeys.clear();
input.value = this.bindings[action]?.keys || '';
if (!combo) return; return;
}
const duplicate = Object.entries(this.bindings).find(([key, value]) =>
value.keys === combo && key !== action const combo = Array.from(currentKeys).join('+');
);
if (!combo) return;
if (duplicate) {
notifications.show('This keybind is already in use.', 'error'); const duplicate = Object.entries(this.bindings).find(([key, value]) =>
currentKeys.clear(); value.keys === combo && key !== action
input.value = this.bindings[action]?.keys || ''; );
return;
} if (duplicate) {
notifications.show('This keybind is already in use.', 'error');
this.bindings[action] = { currentKeys.clear();
keys: combo, input.value = this.bindings[action]?.keys || '';
url: action === 'url' ? document.getElementById('keybind-url').value : null return;
}; }
Storage.set('keybinds', this.bindings);
notifications.show('Keybind saved.', 'success'); this.bindings[action] = {
}); keys: combo,
url: action === 'url' ? document.getElementById('keybind-url').value : null
input.addEventListener('blur', () => { };
currentKeys.clear(); Storage.set('keybinds', this.bindings);
input.value = this.bindings[action]?.keys || ''; notifications.show('Keybind saved.', 'success');
}); });
});
input.addEventListener('blur', () => {
// Clear keybind button handling currentKeys.clear();
document.querySelectorAll('.clear-keybind').forEach(button => { input.value = this.bindings[action]?.keys || '';
button.addEventListener('click', () => { });
const action = button.dataset.for; });
const input = document.getElementById(`keybind-${action}-combo`) ||
document.getElementById(`keybind-${action}`); document.querySelectorAll('.clear-keybind').forEach(button => {
button.addEventListener('click', () => {
input.value = ''; const action = button.dataset.for;
if (action === 'url') { const input = document.getElementById(`keybind-${action}-combo`) ||
document.getElementById('keybind-url').value = ''; document.getElementById(`keybind-${action}`);
}
input.value = '';
delete this.bindings[action]; if (action === 'url') {
Storage.set('keybinds', this.bindings); document.getElementById('keybind-url').value = '';
notifications.show('Keybind removed.', 'success'); }
});
}); delete this.bindings[action];
Storage.set('keybinds', this.bindings);
// Global keybind listener notifications.show('Keybind removed.', 'success');
document.addEventListener('keydown', (e) => { });
if (e.target.tagName === 'INPUT') return; });
const keys = []; document.addEventListener('keydown', (e) => {
if (e.altKey) keys.push('Alt'); if (e.target.tagName === 'INPUT' || !Storage.get('onboardingComplete')) return;
if (e.shiftKey) keys.push('Shift');
if (e.key !== 'Alt' && e.key !== 'Shift') keys.push(e.key); const keys = [];
if (e.altKey) keys.push('Alt');
const combo = keys.join('+'); if (e.shiftKey) keys.push('Shift');
if (e.key !== 'Alt' && e.key !== 'Shift') keys.push(e.key);
Object.entries(this.bindings).forEach(([action, binding]) => {
if (binding.keys === combo) { const combo = keys.join('+');
e.preventDefault();
this.executeAction(action, binding); Object.entries(this.bindings).forEach(([action, binding]) => {
} if (binding.keys === combo) {
}); e.preventDefault();
}); this.executeAction(action, binding);
}, }
});
// Execute the action associated with a keybind });
executeAction(action, binding) { },
const activeModal = document.querySelector('.modal.active');
if (activeModal) { setupDefaultKeybinds() {
closeModal(activeModal); const defaultBindings = {
} settings: { keys: 'Shift+S' },
anonymous: { keys: 'Shift+X' },
switch (action) { theme: { keys: 'Shift+T' },
case 'settings': history: { keys: 'Shift+H' },
const settingsModal = document.getElementById('settings-modal'); url: { keys: 'Shift+Q', url: '' }
if (settingsModal === activeModal) { };
notifications.show('Settings closed.', 'info');
settings.updateSettingsUI(); Object.entries(defaultBindings).forEach(([action, binding]) => {
} else { if (!this.bindings[action]) {
notifications.show('Opening settings...', 'info'); this.bindings[action] = binding;
settings.updateSettingsUI(); }
openModal(settingsModal); });
}
break; Storage.set('keybinds', this.bindings);
case 'add-shortcut': },
const currentShortcuts = Storage.get('shortcuts') || [];
if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) { executeAction(action, binding) {
notifications.show('Maximum shortcuts limit reached!', 'error'); if (!Storage.get('onboardingComplete')) return;
return;
} const settingsPage = document.getElementById('settings-page');
const passwordDialog = document.getElementById('password-dialog');
const shortcutModal = document.getElementById('add-shortcut-modal');
if (shortcutModal === activeModal) { if (passwordDialog && !passwordDialog.classList.contains('hidden')) {
notifications.show('Add shortcut closed.', 'info'); const cancelBtn = document.getElementById('cancel-password');
} else { if (cancelBtn) {
notifications.show('Opening add shortcut...', 'info'); cancelBtn.click();
openModal(shortcutModal); } else {
} passwordDialog.classList.remove('active');
break; setTimeout(() => {
case 'anonymous': passwordDialog.classList.add('hidden');
settings.toggleAnonymousMode(); }, 300);
break; }
case 'theme': }
settings.toggleTheme();
break; if (action === 'settings' && settingsPage.classList.contains('active')) {
case 'url': settingsPage.classList.remove('active');
if (binding.url) { setTimeout(() => {
const url = binding.url; settingsPage.classList.add('hidden');
const fullUrl = url.startsWith('http://') || url.startsWith('https://') ? }, 300);
url : `https://${url}`; }
notifications.show(`Redirecting to ${url}...`, 'info'); const activeModal = document.querySelector('.modal.active');
setTimeout(() => {
window.location.href = fullUrl; switch (action) {
}, 1000); case 'settings':
} else { if (settingsPage.classList.contains('hidden')) {
notifications.show('No URL set for this keybind.', 'error'); settings.updateSettingsUI();
} settingsPage.classList.remove('hidden');
break; 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,128 +1,145 @@
// Greeting functionality if ('serviceWorker' in navigator) {
async function updateGreeting() { navigator.serviceWorker.register('/sw.js');
const greeting = document.getElementById('greeting'); }
if (!greeting) return;
async function updateGreeting() {
const customFormat = Storage.get('customGreeting'); const greeting = document.getElementById('greeting');
if (customFormat) { if (!greeting) return;
const formattedGreeting = await settings.formatGreeting(customFormat);
if (formattedGreeting) { const customFormat = Storage.get('customGreeting');
greeting.textContent = formattedGreeting; if (customFormat) {
greeting.style.opacity = '0'; const formattedGreeting = await settings.formatGreeting(customFormat);
setTimeout(() => { if (formattedGreeting) {
greeting.style.opacity = '1'; greeting.textContent = formattedGreeting;
}, 100); greeting.style.opacity = '0';
return; setTimeout(() => {
} greeting.style.opacity = '1';
} }, 100);
return;
const hour = new Date().getHours(); }
const isAnonymous = Storage.get('anonymousMode') || false; }
const userName = isAnonymous ?
(Storage.get('anonymousName') || anonymousNames.generate()) : const hour = new Date().getHours();
(Storage.get('userName') || 'Friend'); const isAnonymous = Storage.get('anonymousMode') || false;
const userName = isAnonymous ?
let timeGreeting = 'Hello'; (Storage.get('anonymousName') || anonymousNames.generate()) :
if (hour >= 5 && hour < 12) timeGreeting = 'Good Morning'; (Storage.get('userName') || 'Friend');
else if (hour >= 12 && hour < 17) timeGreeting = 'Good Afternoon';
else if (hour >= 17 && hour < 20) timeGreeting = 'Good Evening'; let timeGreeting = 'Hello';
else timeGreeting = 'Good Night'; if (hour >= 5 && hour < 12) timeGreeting = 'Good Morning';
else if (hour >= 12 && hour < 17) timeGreeting = 'Good Afternoon';
greeting.textContent = `${timeGreeting}, ${userName}!`; else if (hour >= 17 && hour < 20) timeGreeting = 'Good Evening';
greeting.style.opacity = '0'; else timeGreeting = 'Good Night';
setTimeout(() => {
greeting.style.opacity = '1'; greeting.textContent = `${timeGreeting}, ${userName}!`;
}, 100); greeting.style.opacity = '0';
} setTimeout(() => {
greeting.style.opacity = '1';
// Modal handling }, 100);
function initModalHandlers() { }
const modals = document.querySelectorAll('.modal');
function initModalHandlers() {
modals.forEach(modal => { const modals = document.querySelectorAll('.modal');
modal.addEventListener('click', (e) => {
if (e.target === modal && !modal.classList.contains('onboarding-modal')) { modals.forEach(modal => {
closeModal(modal); modal.addEventListener('click', (e) => {
} if (e.target === modal && !modal.classList.contains('onboarding-modal')) {
}); closeModal(modal);
}
const modalContent = modal.querySelector('.modal-content'); });
if (modalContent) {
modalContent.addEventListener('click', (e) => { const modalContent = modal.querySelector('.modal-content');
e.stopPropagation(); if (modalContent) {
}); modalContent.addEventListener('click', (e) => {
} e.stopPropagation();
});
document.querySelectorAll('.modal .close-button').forEach(button => { }
button.addEventListener('click', () => {
const modal = button.closest('.modal'); document.querySelectorAll('.modal .close-button').forEach(button => {
if (modal) { button.addEventListener('click', () => {
closeModal(modal); const modal = button.closest('.modal');
} if (modal) {
}); closeModal(modal);
}); }
}); });
} });
});
function openModal(modal) { }
if (!modal) return;
modal.classList.remove('hidden'); function openModal(modal) {
requestAnimationFrame(() => { if (!modal) return;
modal.classList.add('active');
}); const contextMenu = document.querySelector('.context-menu');
} if (contextMenu) {
contextMenu.classList.add('hidden');
function closeModal(modal) { }
if (!modal) return;
modal.classList.remove('active'); modal.classList.remove('hidden');
setTimeout(() => { requestAnimationFrame(() => {
modal.classList.add('hidden'); modal.classList.add('active');
}, 300); });
} }
// Application initialization function closeModal(modal) {
document.addEventListener('DOMContentLoaded', () => { if (!modal) return;
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => { modal.classList.remove('active');
const isVisible = Storage.get(`show_${element}`); setTimeout(() => {
if (isVisible === false) { modal.classList.add('hidden');
const elementNode = document.getElementById(element === 'search' ? 'search-container' : element); }, 300);
if (elementNode) elementNode.style.display = 'none'; }
}
}); document.addEventListener('DOMContentLoaded', () => {
if (typeof settings !== 'undefined' && typeof settings.updateVisibility === 'function') {
if (!Storage.get('onboardingComplete')) { settings.updateVisibility();
onboarding.start(); } else {
} else { ['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
document.getElementById('main-content').classList.remove('hidden'); const isVisible = Storage.get(`show_${element}`);
} if (isVisible === false) {
const elementNode = document.getElementById(element === 'search' ? 'search-container' :
search.init(); element === 'addShortcut' ? 'add-shortcut' : element);
shortcuts.init();
settings.init(); if (elementNode) {
initModalHandlers(); elementNode.style.visibility = 'hidden';
elementNode.style.opacity = '0';
updateGreeting(); elementNode.style.position = 'absolute';
setInterval(updateGreeting, 60000); elementNode.style.pointerEvents = 'none';
}
const settingsButton = document.getElementById('settings-button'); }
const settingsModal = document.getElementById('settings-modal'); });
}
settingsButton.addEventListener('click', () => {
openModal(settingsModal); if (!Storage.get('onboardingComplete')) {
}); onboarding.start();
} else {
keybinds.init(); document.getElementById('main-content').classList.remove('hidden');
}); }
// Global keydown event handler search.init();
document.addEventListener('keydown', (e) => { shortcuts.init();
if (e.key === 'Enter') { settings.init();
const activeModal = document.querySelector('.modal.active'); initModalHandlers();
if (activeModal && !activeModal.matches('#settings-modal')) {
const primaryButton = activeModal.querySelector('.btn-primary'); updateGreeting();
if (primaryButton) { setInterval(updateGreeting, 60000);
primaryButton.click();
} 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,131 +1,121 @@
// Notification System Class class NotificationSystem {
class NotificationSystem { constructor() {
constructor() { this.container = document.getElementById('notification-container');
this.container = document.getElementById('notification-container'); this.notifications = new Map();
this.notifications = new Map(); }
}
show(message, type = 'info', duration = 3000) {
// Display a new notification const id = Date.now().toString();
show(message, type = 'info', duration = 3000) { const notification = document.createElement('div');
const id = Date.now().toString(); notification.className = `notification notification-${type}`;
const notification = document.createElement('div');
notification.className = `notification notification-${type}`; const icon = this.createIcon(type);
const content = this.createContent(message);
// Create notification elements const closeBtn = this.createCloseButton(id);
const icon = this.createIcon(type); const progress = this.createProgressBar(type);
const content = this.createContent(message);
const closeBtn = this.createCloseButton(id); notification.appendChild(icon);
const progress = this.createProgressBar(type); notification.appendChild(content);
notification.appendChild(closeBtn);
// Assemble notification notification.appendChild(progress);
notification.appendChild(icon);
notification.appendChild(content); this.container.appendChild(notification);
notification.appendChild(closeBtn);
notification.appendChild(progress); setTimeout(() => this.remove(id), duration);
this.container.appendChild(notification); this.notifications.set(id, {
element: notification,
// Set removal timer duration
setTimeout(() => this.remove(id), duration); });
// Store notification reference this.updateProgress(id);
this.notifications.set(id, {
element: notification, return id;
duration }
});
remove(id) {
this.updateProgress(id); const notification = this.notifications.get(id);
if (notification) {
return id; notification.element.style.animation = 'slideOutRight 0.3s cubic-bezier(0.16, 1, 0.3, 1)';
} setTimeout(() => {
notification.element.remove();
// Remove a notification this.notifications.delete(id);
remove(id) { }, 300);
const notification = this.notifications.get(id); }
if (notification) { }
notification.element.style.animation = 'slideOutRight 0.3s cubic-bezier(0.16, 1, 0.3, 1)';
setTimeout(() => { updateProgress(id) {
notification.element.remove(); const notification = this.notifications.get(id);
this.notifications.delete(id); if (notification) {
}, 300); const progress = notification.element.querySelector('.notification-progress');
} const startTime = Date.now();
}
const update = () => {
// Update progress bar const elapsed = Date.now() - startTime;
updateProgress(id) { const percent = 100 - (elapsed / notification.duration * 100);
const notification = this.notifications.get(id);
if (notification) { if (percent > 0) {
const progress = notification.element.querySelector('.notification-progress'); progress.style.width = `${percent}%`;
const startTime = Date.now(); requestAnimationFrame(update);
}
const update = () => { };
const elapsed = Date.now() - startTime;
const percent = 100 - (elapsed / notification.duration * 100); requestAnimationFrame(update);
}
if (percent > 0) { }
progress.style.width = `${percent}%`;
requestAnimationFrame(update); createIcon(type) {
} const icon = document.createElement('i');
}; switch(type) {
case 'success':
requestAnimationFrame(update); icon.className = 'fas fa-check-circle';
} icon.style.color = 'var(--success-color, #4caf50)';
} break;
case 'error':
// Helper methods for creating notification elements icon.className = 'fas fa-times-circle';
createIcon(type) { icon.style.color = 'var(--error-color, #f44336)';
const icon = document.createElement('i'); break;
switch(type) { case 'info':
case 'success': default:
icon.className = 'fas fa-check-circle'; icon.className = 'fas fa-info-circle';
icon.style.color = 'var(--success-color, #4caf50)'; icon.style.color = 'var(--info-color, #2196f3)';
break; break;
case 'error': }
icon.className = 'fas fa-times-circle'; return icon;
icon.style.color = 'var(--error-color, #f44336)'; }
break;
case 'info': createContent(message) {
default: const content = document.createElement('div');
icon.className = 'fas fa-info-circle'; content.className = 'notification-content';
icon.style.color = 'var(--info-color, #2196f3)'; content.innerHTML = message;
break; return content;
} }
return icon;
} createCloseButton(id) {
const closeBtn = document.createElement('button');
createContent(message) { closeBtn.className = 'notification-close';
const content = document.createElement('div'); closeBtn.innerHTML = '<i class="fas fa-times"></i>';
content.className = 'notification-content'; closeBtn.onclick = () => this.remove(id);
content.textContent = message; return closeBtn;
return content; }
}
createProgressBar(type) {
createCloseButton(id) { const progress = document.createElement('div');
const closeBtn = document.createElement('button'); progress.className = 'notification-progress';
closeBtn.className = 'notification-close'; switch(type) {
closeBtn.innerHTML = '<i class="fas fa-times"></i>'; case 'success':
closeBtn.onclick = () => this.remove(id); progress.style.background = 'var(--success-color, #4caf50)';
return closeBtn; break;
} case 'error':
progress.style.background = 'var(--error-color, #f44336)';
createProgressBar(type) { break;
const progress = document.createElement('div'); case 'info':
progress.className = 'notification-progress'; default:
switch(type) { progress.style.background = 'var(--info-color, #2196f3)';
case 'success': break;
progress.style.background = 'var(--success-color, #4caf50)'; }
break; return progress;
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,136 +1,337 @@
// Onboarding module const onboarding = {
const onboarding = { currentStep: 1,
// Core functions totalSteps: 5,
isComplete: () => { settings: {},
return Storage.get('onboardingComplete') === true; lastNotification: 0,
}, isCompleting: false,
notificationShown: false,
start: () => {
const modal = document.getElementById('onboarding-modal'); showNotification(message, type = 'info') {
const mainContent = document.getElementById('main-content'); const now = Date.now();
const importDataBtn = document.getElementById('import-data-btn'); if (now - this.lastNotification >= 500) {
const startFreshBtn = document.getElementById('start-fresh-btn'); this.lastNotification = now;
const fileInput = document.getElementById('onboarding-import'); notifications.show(message, type);
}
if (!onboarding.isComplete()) { },
modal.classList.remove('hidden');
modal.classList.add('active'); isComplete: () => {
mainContent.classList.add('hidden'); return Storage.get('onboardingComplete') === true;
},
startFreshBtn.addEventListener('click', () => {
document.querySelector('[data-step="1"]').classList.add('hidden'); start: () => {
document.querySelector('[data-step="2"]').classList.remove('hidden'); const onboardingContainer = document.getElementById('onboarding-container');
document.getElementById('next-step-btn').addEventListener('click', () => onboarding.nextStep(2)); const mainContent = document.getElementById('main-content');
}); const fileInput = document.getElementById('onboarding-import');
importDataBtn.addEventListener('click', () => fileInput.click()); document.getElementById('notification-container').style.zIndex = "20000";
// Data import handling if (!onboarding.isComplete()) {
fileInput.addEventListener('change', async (e) => { document.body.style.overflow = 'hidden';
if (e.target.files.length > 0) { onboardingContainer.classList.remove('hidden');
try {
const file = e.target.files[0]; onboarding.initProgressDots();
const text = await file.text(); onboarding.setupEventListeners();
const data = JSON.parse(text);
const theme = Storage.get('theme') || 'light';
if (!data.settings || typeof data.settings !== 'object' || document.body.setAttribute('data-theme', theme);
!Array.isArray(data.shortcuts)) {
throw new Error('Invalid data structure'); document.querySelectorAll('.step-ob').forEach(step => {
return; if (step.dataset.step !== "1") {
} step.classList.remove('active-ob');
}
Object.entries(data.settings).forEach(([key, value]) => { });
Storage.set(key, value);
}); const firstStep = document.querySelector('.step-ob[data-step="1"]');
firstStep.classList.add('active-ob');
Storage.set('shortcuts', data.shortcuts);
document.getElementById('prev-step').style.visibility = 'hidden';
if (data.keybinds) {
Storage.set('keybinds', data.keybinds); const nextButton = document.getElementById('next-step');
} nextButton.innerHTML = 'Next <svg><use href="#icon-arrow-right"/></svg>';
// Initialize components if (onboarding.currentStep > 1) {
search.init(); nextButton.disabled = true;
shortcuts.init(); nextButton.classList.add('disabled-ob');
settings.init(); }
updateGreeting();
document.body.setAttribute('data-theme', data.settings.theme || 'light'); mainContent.classList.add('hidden');
} else {
// Update UI visibility mainContent.classList.remove('hidden');
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => { }
const isVisible = data.settings[`show_${element}`];
const elementNode = document.getElementById(element === 'search' ? 'search-container' : element); fileInput.addEventListener('change', async (e) => {
if (elementNode) { if (e.target.files.length > 0) {
elementNode.style.display = isVisible === false ? 'none' : 'block'; try {
} const file = e.target.files[0];
}); const text = await file.text();
const data = JSON.parse(text);
Storage.set('onboardingComplete', true);
modal.classList.add('hidden'); if (!data.settings || !data.shortcuts || !Array.isArray(data.shortcuts)) {
mainContent.classList.remove('hidden'); throw new Error('Invalid data structure');
}
const userName = Storage.get('userName') || 'Guest';
notifications.show(`Welcome back, ${userName}! 👋`, 'success'); Object.entries(data.settings).forEach(([key, value]) => {
} catch (error) { Storage.set(key, value);
notifications.show('Failed to import data: Invalid file format!', 'error'); });
fileInput.value = '';
} Storage.set('shortcuts', data.shortcuts);
}
}); if (data.keybinds) {
Storage.set('keybinds', data.keybinds);
// Search engine selection }
const engines = document.querySelectorAll('.search-engine-option');
engines.forEach(engine => { Storage.set('onboardingComplete', true);
engine.addEventListener('click', () => {
engines.forEach(e => e.classList.remove('selected')); localStorage.setItem('showWelcomeAfterImport', 'true');
engine.classList.add('selected');
}); window.location.reload();
}); } catch (error) {
onboarding.showNotification('Failed to import data: Invalid file format!', 'error');
document.getElementById('complete-setup-btn').addEventListener('click', onboarding.complete); fileInput.value = '';
} else { }
modal.classList.add('hidden'); }
mainContent.classList.remove('hidden'); });
} },
},
setupEventListeners: () => {
// Onboarding step navigation document.querySelectorAll('.option-card-ob').forEach(card => {
nextStep: (currentStep) => { card.addEventListener('click', () => {
const currentStepEl = document.querySelector(`[data-step="${currentStep}"]`); const step = card.closest('.step-ob');
const nextStepEl = document.querySelector(`[data-step="${currentStep + 1}"]`); const stepNumber = parseInt(step.dataset.step);
const cards = step.querySelectorAll('.option-card-ob');
if (currentStep === 2) { const nextButton = document.getElementById('next-step');
const name = document.getElementById('user-name').value.trim();
if (!name) { if (card.dataset.action === 'import-data') {
notifications.show('Please enter your name!', 'error'); if (!card.classList.contains('selected-ob')) {
return; document.getElementById('onboarding-import').click();
} }
Storage.set('userName', name); cards.forEach(c => c.classList.remove('selected-ob'));
} card.classList.add('selected-ob');
return;
currentStepEl.classList.add('hidden'); }
nextStepEl.classList.remove('hidden');
nextStepEl.classList.add('visible'); cards.forEach(c => c.classList.remove('selected-ob'));
}, card.classList.add('selected-ob');
// Finalize onboarding card.style.transform = 'scale(1.05)';
complete: () => { setTimeout(() => {
const selectedEngine = document.querySelector('.search-engine-option.selected'); card.style.transform = 'scale(1.02)';
if (!selectedEngine) { }, 150);
notifications.show('Please select a search engine!', 'error');
return; nextButton.disabled = false;
} nextButton.classList.remove('disabled-ob');
const searchEngine = selectedEngine.dataset.engine; if (card.dataset.theme) {
Storage.set('searchEngine', searchEngine); onboarding.settings.theme = card.dataset.theme;
Storage.set('onboardingComplete', true); document.body.setAttribute('data-theme', card.dataset.theme);
} else if (card.dataset.font) {
const modal = document.getElementById('onboarding-modal'); onboarding.settings.fontFamily = card.dataset.font;
const mainContent = document.getElementById('main-content'); document.documentElement.style.setProperty('--font-family', card.dataset.font);
} else if (card.dataset.engine) {
modal.classList.add('hidden'); onboarding.settings.searchEngine = card.dataset.engine;
mainContent.classList.remove('hidden'); }
notifications.show('Welcome to your new tab! 👋', 'success'); });
updateGreeting(); });
}
}; 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,47 +1,80 @@
const search = { const search = {
// Supported search engines and their URLs engines: {
engines: { google: {
google: 'https://www.google.com/search?q=', url: 'https://www.google.com/search?q=',
bing: 'https://www.bing.com/search?q=', icon: 'https://www.google.com/s2/favicons?domain=google.com&sz=32',
duckduckgo: 'https://duckduckgo.com/?q=', name: 'Google'
brave: 'https://search.brave.com/search?q=', },
qwant: 'https://www.qwant.com/?q=', bing: {
searxng: 'https://searx.org/search?q=' url: 'https://www.bing.com/search?q=',
}, icon: 'https://www.google.com/s2/favicons?domain=bing.com&sz=32',
name: 'Bing'
// Perform search using selected engine },
perform: () => { duckduckgo: {
const searchBar = document.getElementById('search-bar'); url: 'https://duckduckgo.com/?q=',
const query = searchBar.value.trim(); icon: 'https://www.google.com/s2/favicons?domain=duckduckgo.com&sz=32',
const engine = Storage.get('searchEngine') || 'google'; name: 'DuckDuckGo'
},
if (query) { brave: {
const searchUrl = search.engines[engine] + encodeURIComponent(query); url: 'https://search.brave.com/search?q=',
window.location.href = searchUrl; icon: 'https://www.google.com/s2/favicons?domain=brave.com&sz=32',
} name: 'Brave'
}, },
qwant: {
// Initialize search functionality url: 'https://www.qwant.com/?q=',
init: () => { icon: 'https://www.google.com/s2/favicons?domain=qwant.com&sz=32',
const searchBar = document.getElementById('search-bar'); name: 'Qwant'
const searchButton = document.getElementById('search-button'); },
searxng: {
searchBar.addEventListener('keypress', (e) => { url: 'https://searx.be/search?q=',
if (e.key === 'Enter') { icon: 'https://www.google.com/s2/favicons?domain=searx.be&sz=32',
search.perform(); name: 'SearXNG'
} }
}); },
searchButton.addEventListener('click', search.perform); init: () => {
const searchBar = document.getElementById('search-bar');
// Global keyboard shortcut to focus search bar const searchButton = document.getElementById('search-button');
document.addEventListener('keydown', (e) => {
if (e.key === '/' && searchBar.addEventListener('keypress', (e) => {
!['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) && if (e.key === 'Enter') {
window.getSelection().toString() === '') { search.perform();
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,9 +1,4 @@
/**
* 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));
@ -12,7 +7,6 @@ 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));
@ -22,7 +16,6 @@ const Storage = {
} }
}, },
// Delete specific key
remove: (key) => { remove: (key) => {
try { try {
localStorage.removeItem(key); localStorage.removeItem(key);
@ -32,7 +25,6 @@ const Storage = {
} }
}, },
// Remove all stored data
clear: () => { clear: () => {
try { try {
localStorage.clear(); localStorage.clear();

93
js/version.js Normal file
View File

@ -0,0 +1,93 @@
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,35 +1,36 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "JSTAR Tab", "name": "JSTAR Tab",
"version": "2.6.0", "version": "3.2.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": { ],
"16": "images/icon16.png", "icons": {
"48": "images/icon48.png", "16": "images/icon16.png",
"128": "images/icon128.png" "48": "images/icon48.png",
}, "128": "images/icon128.png"
"action": { },
"default_title": "New JSTAR Tab", "action": {
"default_icon": { "default_title": "New JSTAR Tab",
"16": "images/icon16.png", "default_icon": {
"48": "images/icon48.png", "16": "images/icon16.png",
"128": "images/icon128.png" "48": "images/icon48.png",
} "128": "images/icon128.png"
}, }
"author": "JSTAR", },
"homepage_url": "https://github.com/DevJSTAR/JSTAR-Tab", "author": "JSTAR",
"web_accessible_resources": [{ "homepage_url": "https://github.com/DevJSTAR/JSTAR-Tab",
"resources": [ "web_accessible_resources": [{
"fonts/*", "resources": [
"images/*" "fonts/*",
], "images/*"
"matches": ["<all_urls>"] ],
}] "matches": ["<all_urls>"]
}]
} }

992
style.css
View File

@ -1,992 +0,0 @@
/* 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(0, 0, 0, 0.75);
--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: #eeeeee;
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: 0rem;
}
.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;
padding-bottom: 0;
}
.settings-section h3 {
margin-bottom: 1.5rem;
font-weight: 600;
}
.setting-item {
display: flex;
flex-direction: column;
padding: 0.75rem 0;
}
.setting-item.horizontal {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.setting-item .setting-label {
font-weight: 500;
margin-bottom: 0.5rem;
}
.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%;
transition: transform 0.3s ease;
}
input:checked + .toggle-slider {
background-color: var(--text);
}
input:checked + .toggle-slider:before {
transform: translateX(24px);
}
#keybind-url-combo {
margin-bottom: 0.5rem;
}
#keybind-url {
margin-top: 0.5rem;
}
.keybind-container {
display: flex;
gap: 0.5rem;
align-items: center;
}
.clear-keybind {
padding: 0.25rem 0.5rem;
background: var(--accent);
border: none;
border-radius: 4px;
color: var(--text);
cursor: pointer;
font-size: 1.2rem;
line-height: 1;
}
.clear-keybind:hover {
opacity: 0.8;
}
.format-hint {
display: block;
font-size: 0.75rem;
color: var(--text-secondary);
opacity: 0.7;
margin-top: 0.25rem;
}
/* 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;
}
/* Mobile-first responsive styles */
@media screen and (max-width: 768px) {
.center-container {
padding: 1rem;
gap: 2rem;
}
#greeting {
font-size: 1.5rem;
height: 36px;
}
.search-container {
max-width: 100%;
}
#search-bar {
padding: 1rem;
font-size: 0.9rem;
}
.search-engine-options {
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
}
.search-engine-option {
padding: 1rem;
}
.search-engine-option img {
width: 24px;
height: 24px;
}
#shortcuts-grid {
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 0.75rem;
padding: 0.5rem;
}
.shortcut {
padding: 0.75rem;
}
.shortcut img {
width: 24px;
height: 24px;
}
.shortcut span {
font-size: 0.75rem;
}
.modal-content {
width: 95%;
padding: 1.5rem;
border-radius: 16px;
}
.step h2 {
font-size: 1.5rem;
}
.setting-item {
padding: 0.5rem 0;
}
.setting-label {
font-size: 0.9rem;
}
input[type="text"], select {
padding: 0.75rem;
font-size: 0.9rem;
}
.notification {
width: 90%;
max-width: none;
margin: 0.5rem;
padding: 0.75rem;
}
}
/* Styles for very small screens */
@media screen and (max-width: 480px) {
#greeting {
font-size: 1.25rem;
height: 32px;
}
.search-engine-options {
display: grid;
grid-template-columns: 1fr;
gap: 0.5rem;
padding: 0.5rem;
}
#shortcuts-grid {
grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
gap: 0.5rem;
}
.modal-content {
padding: 1rem;
}
.modal-actions {
flex-direction: column;
gap: 0.5rem;
}
.modal-actions button {
width: 100%;
}
.format-hint {
font-size: 0.7rem;
line-height: 1.2;
}
}
/* Landscape orientation adjustments */
@media screen and (max-height: 480px) and (orientation: landscape) {
.center-container {
padding: 0.5rem;
gap: 1rem;
}
#greeting {
font-size: 1.25rem;
height: 32px;
margin-bottom: 0;
}
.modal-content {
height: 90vh;
overflow-y: auto;
}
.search-engine-options {
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
}
}
/* Mobile-specific styles for onboarding */
@media screen and (max-width: 480px) {
.search-engine-options {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
padding: 0.5rem;
}
.search-engine-option {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 1rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
aspect-ratio: 1;
}
.search-engine-option img {
width: 24px;
height: 24px;
margin-bottom: 0.5rem;
}
.search-engine-option span {
font-size: 0.85rem;
font-weight: 500;
}
.search-engine-option.selected {
background: #eeeeee;
border: 2px solid var(--border);
}
.onboarding-modal .modal-content {
width: 90%;
max-width: none;
padding: 1.25rem;
}
.step h2 {
font-size: 1.35rem;
margin-bottom: 1rem;
}
#complete-setup-btn {
width: 100%;
margin-top: 1rem;
padding: 0.875rem;
font-size: 0.95rem;
}
}
.import-options {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
width: 100%;
max-width: 300px;
margin: 0 auto;
}
.or-divider {
position: relative;
width: 100%;
text-align: center;
margin: 0.5rem 0;
}
.or-divider::before,
.or-divider::after {
content: '';
position: absolute;
top: 50%;
width: calc(50% - 24px);
height: 1px;
background-color: var(--border);
}
.or-divider::before {
left: 0;
}
.or-divider::after {
right: 0;
}
.or-divider span {
background-color: var(--background);
padding: 0 8px;
color: var(--text);
font-size: 0.9rem;
}

60
sw.js Normal file
View File

@ -0,0 +1,60 @@
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))
)
);
}
});