mirror of
https://github.com/DevJSTAR/JSTAR-Tab.git
synced 2025-04-18 17:35:26 +00:00
Compare commits
3 Commits
7cc5e4010e
...
9d9305b69a
Author | SHA1 | Date | |
---|---|---|---|
|
9d9305b69a | ||
|
ac0a56c08e | ||
|
f2b3a43135 |
30
README.md
30
README.md
|
@ -1,18 +1,18 @@
|
|||
# 🌟 JSTAR Tab
|
||||
|
||||
Welcome to **JSTAR Tab**, the ultimate customizable new tab extension for your browser! Whether you're looking for a sleek, modern design or powerful personalization options, JSTAR Tab has you covered. 🚀
|
||||
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. 🚀
|
||||
|
||||
Transform your browsing experience with custom greetings, themes, fonts, shortcuts, and more. 🎉
|
||||
Transform your browsing experience with custom greetings, motion control, themes, fonts, and more. 🎉
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **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
|
||||
- `{name}`: Your display name
|
||||
- `{greeting}`: Time-based greeting (e.g., Good morning)
|
||||
- `{time}`: Current time
|
||||
- `{date}`: Current date
|
||||
- `{day}`: Day of the week
|
||||
- `{month}`: Current month
|
||||
- `{year}`: Current year
|
||||
|
||||
Examples:
|
||||
|
@ -23,14 +23,17 @@ Transform your browsing experience with custom greetings, themes, fonts, shortcu
|
|||
- **Font Selection**: Choose from multiple fonts (Inter, Poppins, Roboto, Montserrat, Quicksand, and Comic Sans) to personalize your experience.
|
||||
- **Custom Background Images**: Upload and set your favorite images as your new tab background.
|
||||
- **Customizable Themes**: Switch between light and dark modes to suit your mood.
|
||||
- **Shortcut Management**: Add, edit, and remove shortcuts to your favorite websites with an improved grid layout system.
|
||||
- **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.
|
||||
- **Privacy Settings**: Includes Anonymous Mode, password protection for individual shortcuts, and more to give you control over your experience.
|
||||
- **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.
|
||||
- **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
|
||||
- Add a new shortcut
|
||||
- Toggle anonymous mode
|
||||
- Change themes
|
||||
- Redirect to a specific URL (with a notification and "Redirecting to..." message)
|
||||
- Open history
|
||||
- Redirect to a specific URL
|
||||
- **Data Backup and Restore**: Export and import your settings and shortcuts effortlessly.
|
||||
|
||||
## 🌐 Getting Started
|
||||
|
@ -53,7 +56,8 @@ Transform your browsing experience with custom greetings, themes, fonts, shortcu
|
|||
## 🎨 Customizing Your Experience
|
||||
|
||||
### **Greeting Formats**
|
||||
Personalize your greeting with dynamic tags listed above. Example: "Good {greeting}, {name}! It's {time} on {day}!"
|
||||
Personalize your greeting with dynamic tags listed above. Example:
|
||||
"Good {greeting}, {name}! It's {time} on {day}!"
|
||||
|
||||
### **Themes & Fonts**
|
||||
- Toggle between light and dark themes from the settings panel.
|
||||
|
@ -77,4 +81,4 @@ This project is licensed under the [MIT License](https://github.com/DevJSTAR/JST
|
|||
- **[Patreon](https://patreon.com/jstarsdev)**
|
||||
- **[GitHub Releases](https://github.com/DevJSTAR/JSTAR-Tab/releases/latest)**
|
||||
|
||||
Thank you for choosing JSTAR Tab! We hope you enjoy the seamless and personalized browsing experience it brings. 🚀
|
||||
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
567
css/history.css
Normal 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;
|
||||
}
|
||||
}
|
|
@ -219,27 +219,82 @@
|
|||
}
|
||||
|
||||
.theme-preview-ob {
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
border: 2px solid var(--border);
|
||||
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: white;
|
||||
color: #1a1a1a;
|
||||
background-color: #ffffff !important;
|
||||
color: #1a1a1a !important;
|
||||
border: 2px solid #eaeaea !important;
|
||||
}
|
||||
|
||||
.theme-preview-ob.dark-ob {
|
||||
background-color: #1a1a1a;
|
||||
color: white;
|
||||
background-color: #1a1a1a !important;
|
||||
color: #ffffff !important;
|
||||
border: 2px solid #333333 !important;
|
||||
}
|
||||
|
||||
.theme-preview-ob.light-ob .preview-search-ob {
|
||||
background-color: #f0f0f0;
|
||||
border-color: #dddddd;
|
||||
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 {
|
||||
|
|
492
css/style.css
492
css/style.css
|
@ -1263,21 +1263,25 @@ input:checked + .toggle-slider:before {
|
|||
border-radius: 8px;
|
||||
padding: 1rem 0.2rem;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease, transform 0.3s ease;
|
||||
transition: transform 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#back-to-home:hover {
|
||||
color: var(--primary-hover);
|
||||
transform: scale(1.15);
|
||||
color: var(--text);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
#back-to-home i {
|
||||
font-size: 1.25rem;
|
||||
#back-to-home svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
#back-to-home:hover i {
|
||||
transform: scale(1.2);
|
||||
#back-to-home:hover svg {
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
|
||||
.settings-sidebar-header {
|
||||
|
@ -2237,4 +2241,478 @@ input[type="range"]::-webkit-slider-thumb:hover {
|
|||
.color-setting input[type="color"]::-webkit-color-swatch {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:root, [data-theme] {
|
||||
--theme-transition-speed: 0.2s;
|
||||
transition: background-color var(--theme-transition-speed) ease,
|
||||
color var(--theme-transition-speed) ease,
|
||||
border-color var(--theme-transition-speed) ease;
|
||||
}
|
||||
|
||||
.instant-theme-change {
|
||||
--theme-transition-speed: 0s !important;
|
||||
}
|
||||
.subtle-motion * {
|
||||
animation-duration: 0.4s !important;
|
||||
transition-duration: 0.4s !important;
|
||||
}
|
||||
|
||||
.subtle-motion *:not(:root *) {
|
||||
transition-property: transform, opacity, visibility, box-shadow !important;
|
||||
}
|
||||
|
||||
.reduced-motion * {
|
||||
animation-duration: 0.25s !important;
|
||||
transition-duration: 0.25s !important;
|
||||
transition-timing-function: ease-out !important;
|
||||
}
|
||||
|
||||
.reduced-motion *:not(:root *) {
|
||||
transition-property: transform, opacity, visibility, box-shadow !important;
|
||||
}
|
||||
|
||||
.reduced-motion [class*='SlideIn'] {
|
||||
animation-name: fadeIn !important;
|
||||
}
|
||||
|
||||
.minimal-motion * {
|
||||
animation-duration: 0.1s !important;
|
||||
transition-duration: 0.1s !important;
|
||||
transition-timing-function: ease-out !important;
|
||||
}
|
||||
|
||||
.minimal-motion *:not(:root *) {
|
||||
transition-property: transform, opacity, visibility, box-shadow !important;
|
||||
}
|
||||
|
||||
.minimal-motion [class*='SlideIn'],
|
||||
.minimal-motion [style*="animation"] {
|
||||
animation-name: fadeIn !important;
|
||||
}
|
||||
|
||||
.no-motion * {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
body:not(.subtle-motion):not(.reduced-motion):not(.minimal-motion):not(.no-motion) * {
|
||||
animation-duration: 0.25s !important;
|
||||
transition-duration: 0.25s !important;
|
||||
transition-timing-function: ease-out !important;
|
||||
}
|
||||
|
||||
body:not(.subtle-motion):not(.reduced-motion):not(.minimal-motion):not(.no-motion) *:not(:root *) {
|
||||
transition-property: transform, opacity, visibility, box-shadow !important;
|
||||
}
|
||||
|
||||
body:not(.subtle-motion):not(.reduced-motion):not(.minimal-motion):not(.no-motion) [class*='SlideIn'] {
|
||||
animation-name: fadeIn !important;
|
||||
}
|
||||
}
|
||||
|
||||
.confirmation-dialog {
|
||||
max-width: 400px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.confirmation-icon, .lock-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.confirmation-icon svg, .lock-icon svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.confirmation-icon svg {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.lock-icon svg {
|
||||
color: #9333EA;
|
||||
}
|
||||
|
||||
.confirmation-content {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.confirmation-content h3 {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.confirmation-content p {
|
||||
font-size: 0.9375rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--text-color);
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.dialog-close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dialog-close svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.password-input-container {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.secrecy-note {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
margin: 16px 0;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
color: var(--text-secondary);
|
||||
text-align: left;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.secrecy-note img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
object-fit: contain;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.secrecy-note a {
|
||||
color: #9333EA;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.secrecy-note a:hover {
|
||||
color: #7e22ce;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .secrecy-note {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.password-input-container input {
|
||||
width: 100% !important;
|
||||
padding: 0.75rem 1rem !important;
|
||||
border: 2px solid var(--border) !important;
|
||||
border-radius: 12px !important;
|
||||
background-color: var(--surface) !important;
|
||||
color: var(--text) !important;
|
||||
font-size: 0.9375rem !important;
|
||||
transition: all 0.2s ease !important;
|
||||
}
|
||||
|
||||
.password-input-container input:focus {
|
||||
outline: none !important;
|
||||
border-color: var(--accent-color) !important;
|
||||
}
|
||||
|
||||
.password-error {
|
||||
color: #ff4d4f !important;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.password-protected {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.password-protected::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='14' height='14'%3E%3Crect x='3' y='11' width='18' height='11' rx='2' ry='2' fill='black' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3C/rect%3E%3Cpath d='M7 11V7a5 5 0 0 1 10 0v4' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3C/path%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
body[data-theme="dark"] .password-protected::before {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='14' height='14'%3E%3Crect x='3' y='11' width='18' height='11' rx='2' ry='2' fill='white' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3C/rect%3E%3Cpath d='M7 11V7a5 5 0 0 1 10 0v4' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.checkbox-container {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.dialog.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dialog-container {
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
max-width: 90%;
|
||||
width: 400px;
|
||||
overflow: hidden;
|
||||
transform: translateY(-20px);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.dialog.active .dialog-container {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dialog-message {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.shortcut-protection-manager {
|
||||
margin-top: 20px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.shortcut-protection-manager h4 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.shortcut-selector-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.shortcut-dropdown {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.shortcut-dropdown-selected {
|
||||
background: var(--surface);
|
||||
padding: 1rem 1.25rem;
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.875rem;
|
||||
color: var(--text);
|
||||
position: relative;
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.shortcut-dropdown-selected::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 1.25rem;
|
||||
top: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-right: 2px solid var(--text);
|
||||
border-bottom: 2px solid var(--text);
|
||||
transform: translateY(-70%) rotate(45deg);
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.shortcut-dropdown-selected.active::after {
|
||||
transform: translateY(-30%) rotate(-135deg);
|
||||
}
|
||||
|
||||
.shortcut-dropdown-items {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.5rem);
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 99;
|
||||
background: var(--surface);
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px var(--shadow);
|
||||
}
|
||||
|
||||
.shortcut-dropdown-items.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.shortcut-dropdown-item {
|
||||
padding: 0.75rem 1.25rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.875rem;
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.shortcut-dropdown-item:hover {
|
||||
background-color: var(--primary);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.shortcut-dropdown-selected:hover {
|
||||
border-color: var(--text);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px var(--shadow);
|
||||
}
|
||||
|
||||
.shortcut-dropdown-selected:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px var(--shadow);
|
||||
}
|
||||
|
||||
.selected-shortcuts-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.shortcut-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--accent-light);
|
||||
border-radius: 20px;
|
||||
padding: 6px 12px;
|
||||
font-size: 0.9rem;
|
||||
gap: 8px;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.shortcut-chip img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.remove-chip-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.remove-chip-btn:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.empty-protection-state {
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-shortcuts-message {
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin-top: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .shortcut-chip {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .remove-chip-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
144
history.html
Normal file
144
history.html
Normal 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>
|
BIN
images/secrecy.png
Normal file
BIN
images/secrecy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
419
index.html
419
index.html
|
@ -220,9 +220,13 @@
|
|||
<span>Personalization</span>
|
||||
</a>
|
||||
<a href="#appearance" class="settings-nav-item" data-section="appearance">
|
||||
<svg><use href="#icon-sparkle"/></svg>
|
||||
<svg><use href="#icon-paint"/></svg>
|
||||
<span>Appearance</span>
|
||||
</a>
|
||||
<a href="#privacy" class="settings-nav-item" data-section="privacy">
|
||||
<svg><use href="#icon-lock"/></svg>
|
||||
<span>Privacy</span>
|
||||
</a>
|
||||
<a href="#search" class="settings-nav-item" data-section="search">
|
||||
<svg><use href="#icon-search"/></svg>
|
||||
<span>Search</span>
|
||||
|
@ -263,16 +267,6 @@
|
|||
<div class="setting-description">This name will be used in your greeting message</div>
|
||||
<input type="text" id="settings-name" placeholder="Enter your name">
|
||||
</div>
|
||||
<div class="setting-item horizontal">
|
||||
<div>
|
||||
<div class="setting-label">Anonymous Mode</div>
|
||||
<div class="setting-description">Hide your real name and use a randomly generated one</div>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="toggle-anonymous">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
|
@ -292,6 +286,28 @@
|
|||
<span class="format-hint">Available variables: {name}, {greeting}, {time}, {date}, {day}, {month}, {year}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<div class="settings-card-icon">
|
||||
<svg><use href="#icon-bell"/></svg>
|
||||
</div>
|
||||
<div class="settings-card-title">
|
||||
<h4>Notifications</h4>
|
||||
<p>Configure update notifications and alerts</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item horizontal">
|
||||
<div>
|
||||
<div class="setting-label">Update Alerts</div>
|
||||
<div class="setting-description">Get notified when new updates are available</div>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="toggle-update-alerts" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="appearance" class="settings-section">
|
||||
|
@ -315,7 +331,29 @@
|
|||
<svg><use href="#icon-light-mode"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Icon style setting used to be here (will be added in full release) -->
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">Icon Style</div>
|
||||
<div class="setting-description">Choose between linear or solid icon styles</div>
|
||||
<div class="custom-select">
|
||||
<select id="icon-style-select">
|
||||
<option value="linear" class="icon-style-linear">Linear</option>
|
||||
<option value="solid" class="icon-style-solid">Solid</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">Motion Preference</div>
|
||||
<div class="setting-description">Control how animations and transitions appear</div>
|
||||
<div class="custom-select">
|
||||
<select id="motion-preference-select">
|
||||
<option value="default">Default Animations</option>
|
||||
<option value="subtle">Subtle Animations</option>
|
||||
<option value="reduced">Reduced Motion</option>
|
||||
<option value="minimal">Minimal Motion</option>
|
||||
<option value="disabled">No Animations</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
|
@ -452,28 +490,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<div class="settings-card-icon">
|
||||
<svg><use href="#icon-bell"/></svg>
|
||||
</div>
|
||||
<div class="settings-card-title">
|
||||
<h4>Notifications</h4>
|
||||
<p>Configure update notifications and alerts</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item horizontal">
|
||||
<div>
|
||||
<div class="setting-label">Update Alerts</div>
|
||||
<div class="setting-description">Get notified when new updates are available</div>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="toggle-update-alerts" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<div class="settings-card-icon">
|
||||
|
@ -555,13 +571,6 @@
|
|||
<button class="clear-keybind" data-for="settings">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">Add Shortcut</div>
|
||||
<div class="keybind-container">
|
||||
<input type="text" id="keybind-add-shortcut" placeholder="Press keys..." readonly>
|
||||
<button class="clear-keybind" data-for="add-shortcut">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">Toggle Anonymous Mode</div>
|
||||
<div class="keybind-container">
|
||||
|
@ -576,6 +585,13 @@
|
|||
<button class="clear-keybind" data-for="theme">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">History Page</div>
|
||||
<div class="keybind-container">
|
||||
<input type="text" id="keybind-history" placeholder="Press keys..." readonly>
|
||||
<button class="clear-keybind" data-for="history">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">Quick URL</div>
|
||||
<div class="keybind-container">
|
||||
|
@ -587,6 +603,49 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="privacy" class="settings-section">
|
||||
<h3>Privacy</h3>
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<div class="settings-card-icon">
|
||||
<svg><use href="#icon-lock"/></svg>
|
||||
</div>
|
||||
<div class="settings-card-title">
|
||||
<h4>Privacy Settings</h4>
|
||||
<p>Control privacy and security features</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item horizontal">
|
||||
<div>
|
||||
<div class="setting-label">Anonymous Mode</div>
|
||||
<div class="setting-description">Hide your real name and use a randomly generated one</div>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="toggle-anonymous">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="setting-item horizontal">
|
||||
<div>
|
||||
<div class="setting-label">Password Protected Shortcuts</div>
|
||||
<div class="setting-description">Add password protection to specific shortcuts</div>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="toggle-shortcuts-password">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="password-protection-settings" class="setting-item hidden">
|
||||
<div class="setting-label">Master Password</div>
|
||||
<div class="setting-description">Set a master password for your protected shortcuts</div>
|
||||
<div class="input-group">
|
||||
<input type="password" id="master-password" placeholder="Enter master password">
|
||||
</div>
|
||||
<button id="save-master-password" class="btn-primary">Save Password</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="backgrounds" class="settings-section">
|
||||
<h3>Backgrounds</h3>
|
||||
<div class="settings-card">
|
||||
|
@ -707,6 +766,56 @@
|
|||
</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 href="#icon-alert-triangle"/></svg>
|
||||
</div>
|
||||
<div class="confirmation-content">
|
||||
<h3 id="confirmation-title">Confirm Action</h3>
|
||||
<p id="confirmation-message">Are you sure you want to proceed with this action?</p>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button id="confirm-action" class="btn-primary btn-danger">Confirm</button>
|
||||
<button id="cancel-action" class="btn-primary">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Dialog -->
|
||||
<div id="password-dialog" class="modal hidden">
|
||||
<div class="modal-backdrop"></div>
|
||||
<div class="modal-content confirmation-dialog">
|
||||
<div class="confirmation-icon lock-icon">
|
||||
<svg><use href="#icon-lock"/></svg>
|
||||
</div>
|
||||
<div class="confirmation-content">
|
||||
<h3>Password Required</h3>
|
||||
<p>This shortcut is password protected. Please enter the password to continue.</p>
|
||||
<div class="secrecy-note">
|
||||
<img src="images/secrecy.png" alt="Secrecy">
|
||||
<div>
|
||||
Try <a href="https://github.com/DevJSTAR/Secrecy/releases/latest" target="_blank">Secrecy</a> for full browser password protection.
|
||||
JSTAR Tab only protects shortcuts within this page.
|
||||
</div>
|
||||
</div>
|
||||
<div class="password-input-container">
|
||||
<input type="password" id="shortcut-password" placeholder="Enter password">
|
||||
</div>
|
||||
<div id="password-error" class="password-error hidden">Incorrect password. Please try again.</div>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button id="submit-password" class="btn-primary">Submit</button>
|
||||
<button id="cancel-password" class="btn-primary">Cancel</button>
|
||||
</div>
|
||||
<button id="close-password-dialog" class="dialog-close">
|
||||
<svg><use href="#icon-close"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Container -->
|
||||
<div id="notification-container"></div>
|
||||
|
||||
|
@ -730,12 +839,6 @@
|
|||
<line x1="19" y1="12" x2="5" y2="12"/>
|
||||
<polyline points="12 19 5 12 12 5"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Arrow Right Icon -->
|
||||
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
<polyline points="12 5 19 12 12 19"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Image/Background Icon -->
|
||||
<symbol id="icon-image" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
|
@ -744,6 +847,15 @@
|
|||
<polyline points="21 15 16 10 5 21"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Theme/Appearance Icon -->
|
||||
<symbol id="icon-palette" 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 cx="12" cy="12" r="4"/>
|
||||
<line x1="21.17" y1="8" x2="12" y2="8"/>
|
||||
<line x1="3.95" y1="6.06" x2="8.54" y2="14"/>
|
||||
<line x1="10.88" y1="21.94" x2="15.46" y2="14"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Database Icon -->
|
||||
<symbol id="icon-database" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<ellipse cx="12" cy="5" rx="9" ry="3"/>
|
||||
|
@ -789,24 +901,6 @@
|
|||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Text Icon-->
|
||||
<symbol id="icon-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 7V4h16v3"/>
|
||||
<path d="M9 20h6"/>
|
||||
<path d="M12 4v16"/>
|
||||
</symbol>
|
||||
|
||||
<!-- User Icon -->
|
||||
<symbol id="icon-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Sparkle Icon -->
|
||||
<symbol id="icon-sparkle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 3l1.88 5.79a2 2 0 0 0 1.33 1.33L21 12l-5.79 1.88a2 2 0 0 0-1.33 1.33L12 21l-1.88-5.79a2 2 0 0 0-1.33-1.33L3 12l5.79-1.88a2 2 0 0 0 1.33-1.33L12 3z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Settings Icon -->
|
||||
<symbol id="icon-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
|
@ -876,10 +970,205 @@
|
|||
<line x1="12" y1="8" x2="12.01" y2="8"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Paint Brush Icon -->
|
||||
<symbol id="icon-paint" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 3l1.88 5.79a2 2 0 0 0 1.33 1.33L21 12l-5.79 1.88a2 2 0 0 0-1.33 1.33L12 21l-1.88-5.79a2 2 0 0 0-1.33-1.33L3 12l5.79-1.88a2 2 0 0 0 1.33-1.33L12 3z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Shortcuts Icon -->
|
||||
<symbol id="icon-shortcuts" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Grid Icon -->
|
||||
<symbol id="icon-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
||||
<line x1="3" y1="9" x2="21" y2="9"/>
|
||||
<line x1="9" y1="21" x2="9" y2="9"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Alert Triangle Icon -->
|
||||
<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"/>
|
||||
<line x1="12" y1="9" x2="12" y2="13"/>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Lock Icon -->
|
||||
<symbol id="icon-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<!-- SVG Definitions (Solid Icons) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
|
||||
<!-- Image/Background Icon Solid -->
|
||||
<symbol id="icon-image-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm-7 12.5L8.5 12 5 17h14l-4.5-6-2.5 4.5z"/>
|
||||
<circle fill="currentColor" cx="8.5" cy="8.5" r="1.5"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Theme/Appearance Icon Solid -->
|
||||
<symbol id="icon-palette-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
|
||||
<circle fill="currentColor" cx="12" cy="12" r="4"/>
|
||||
<path fill="currentColor" d="M21.17 8h-9.17M3.95 6.06l4.59 7.94M10.88 21.94l4.58-7.94"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Database Icon Solid -->
|
||||
<symbol id="icon-database-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12 2C6 2 3 3.34 3 5v14c0 1.66 3 3 9 3s9-1.34 9-3V5c0-1.66-3-3-9-3zm0 3c4.42 0 7 .84 7 1.5S16.42 8 12 8s-7-.84-7-1.5S7.58 5 12 5zm0 14c-4.42 0-7-.84-7-1.5V15c1.49.66 4.18 1 7 1s5.51-.34 7-1v2.5c0 .66-2.58 1.5-7 1.5zm0-6c-4.42 0-7-.84-7-1.5V9c1.49.66 4.18 1 7 1s5.51-.34 7-1v2.5c0 .66-2.58 1.5-7 1.5zm0-6c-4.42 0-7-.84-7-1.5V9c1.49.66 4.18 1 7 1s5.51-.34 7-1v2.5c0 .66-2.58 1.5-7 1.5z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Chevron Down Icon Solid -->
|
||||
<symbol id="icon-chevron-down-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- External Link Icon Solid -->
|
||||
<symbol id="icon-external-link-solid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Edit Icon Solid -->
|
||||
<symbol id="icon-edit-solid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Delete Icon Solid -->
|
||||
<symbol id="icon-delete-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M3 6h18v0h-18z"/>
|
||||
<path fill="currentColor" 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 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M10 11v6M14 11v6"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Import Icon Solid -->
|
||||
<symbol id="icon-import-solid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
<polyline points="7 10 12 15 17 10"/>
|
||||
<line x1="12" y1="15" x2="12" y2="3"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Export Icon Solid -->
|
||||
<symbol id="icon-export-solid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
<polyline points="7 10 12 5 17 10"/>
|
||||
<line x1="12" y1="5" x2="12" y2="15"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Search Icon Solid -->
|
||||
<symbol id="icon-search-solid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Plus Icon Solid -->
|
||||
<symbol id="icon-plus-solid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="12" y1="5" x2="12" y2="19"/>
|
||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Settings Icon Solid -->
|
||||
<symbol id="icon-settings-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||
<circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Light Mode (Sun) Icon Solid -->
|
||||
<symbol id="icon-light-mode-solid" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="5" fill="currentColor"/>
|
||||
<line x1="12" y1="1" x2="12" y2="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="12" y1="21" x2="12" y2="23" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="1" y1="12" x2="3" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="21" y1="12" x2="23" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Dark Mode (Moon) Icon Solid -->
|
||||
<symbol id="icon-dark-mode-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Close/X Icon Solid -->
|
||||
<symbol id="icon-close-solid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Message Icon Solid -->
|
||||
<symbol id="icon-message-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Layout Icon Solid -->
|
||||
<symbol id="icon-layout-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm0 6H9v12H5V5h14v4z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Bell Icon Solid -->
|
||||
<symbol id="icon-bell-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9zm-4.27 13a2 2 0 0 1-3.46 0"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Sparkle Icon -->
|
||||
<symbol id="icon-sparkle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 3l1.88 5.79a2 2 0 0 0 1.33 1.33L21 12l-5.79 1.88a2 2 0 0 0-1.33 1.33L12 21l-1.88-5.79a2 2 0 0 0-1.33-1.33L3 12l5.79-1.88a2 2 0 0 0 1.33-1.33L12 3z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="icon-sparkle-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12 3l1.88 5.79a2 2 0 0 0 1.33 1.33L21 12l-5.79 1.88a2 2 0 0 0-1.33 1.33L12 21l-1.88-5.79a2 2 0 0 0-1.33-1.33L3 12l5.79-1.88a2 2 0 0 0 1.33-1.33L12 3z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- User Icon -->
|
||||
<symbol id="icon-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="icon-user-solid" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12 3a4 4 0 1 0 0 8 4 4 0 0 0 0-8zm0 10c-4.42 0-8 3.58-8 8v1h16v-1c0-4.42-3.58-8-8-8z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- SVG Definitions -->
|
||||
<symbol id="icon-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 7V4h16v3"/>
|
||||
<path d="M9 20h6"/>
|
||||
<path d="M12 4v16"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="icon-text-solid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 7V4h16v3"/>
|
||||
<path d="M9 20h6"/>
|
||||
<path d="M12 4v16"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Arrow Right Icon -->
|
||||
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
<polyline points="12 5 19 12 12 19"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Alert Triangle Icon -->
|
||||
<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"/>
|
||||
<line x1="12" y1="9" x2="12" y2="13"/>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Incognito Icon -->
|
||||
<symbol id="icon-incognito" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 19h16"/>
|
||||
<path d="M6.5 17C7.3 17 9 13.5 9 10c0-3.5 1.6-6 3-6s3 2.5 3 6c0 3.5 1.7 7 2.5 7"/>
|
||||
<path d="M8 14c0 2 2 3 4 3s4-1 4-3"/>
|
||||
</symbol>
|
||||
|
||||
</svg>
|
||||
|
||||
<!-- JavaScript Files -->
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const MAX_IMAGE_SIZE_MB = 1.5;
|
||||
const MAX_IMAGE_SIZE_MB = 5;
|
||||
const MAX_USER_BACKGROUNDS = 10;
|
||||
|
||||
const backgroundUpload = document.getElementById('background-upload');
|
||||
|
|
|
@ -179,7 +179,11 @@ const GridLayout = {
|
|||
const resetButton = document.getElementById('reset-layout');
|
||||
if (resetButton) {
|
||||
resetButton.addEventListener('click', () => {
|
||||
this.resetToDefaults();
|
||||
shortcuts.showConfirmDialog(
|
||||
'Reset Layout',
|
||||
'Are you sure you want to reset the layout settings to default?',
|
||||
() => this.resetToDefaults()
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -325,10 +329,12 @@ const GridLayout = {
|
|||
};
|
||||
|
||||
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();
|
||||
|
|
904
js/history.js
Normal file
904
js/history.js
Normal 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);
|
||||
};
|
||||
}
|
||||
});
|
|
@ -11,13 +11,20 @@ const keybinds = {
|
|||
init() {
|
||||
this.bindings = Storage.get('keybinds') || {};
|
||||
|
||||
this.setupDefaultKeybinds();
|
||||
|
||||
const urlInput = document.getElementById('keybind-url');
|
||||
const urlComboInput = document.getElementById('keybind-url-combo');
|
||||
const historyComboInput = document.getElementById('keybind-history-combo');
|
||||
|
||||
if (this.bindings.url) {
|
||||
urlInput.value = this.bindings.url.url || '';
|
||||
urlComboInput.value = this.bindings.url.keys || '';
|
||||
}
|
||||
|
||||
if (this.bindings.history && historyComboInput) {
|
||||
historyComboInput.value = this.bindings.history.keys || '';
|
||||
}
|
||||
|
||||
let lastSavedUrl = urlInput.value;
|
||||
|
||||
|
@ -184,7 +191,7 @@ const keybinds = {
|
|||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
if (e.target.tagName === 'INPUT' || !Storage.get('onboardingComplete')) return;
|
||||
|
||||
const keys = [];
|
||||
if (e.altKey) keys.push('Alt');
|
||||
|
@ -201,10 +208,44 @@ const keybinds = {
|
|||
});
|
||||
});
|
||||
},
|
||||
|
||||
setupDefaultKeybinds() {
|
||||
const defaultBindings = {
|
||||
settings: { keys: 'Shift+S' },
|
||||
anonymous: { keys: 'Shift+X' },
|
||||
theme: { keys: 'Shift+T' },
|
||||
history: { keys: 'Shift+H' },
|
||||
url: { keys: 'Shift+Q', url: '' }
|
||||
};
|
||||
|
||||
Object.entries(defaultBindings).forEach(([action, binding]) => {
|
||||
if (!this.bindings[action]) {
|
||||
this.bindings[action] = binding;
|
||||
}
|
||||
});
|
||||
|
||||
Storage.set('keybinds', this.bindings);
|
||||
},
|
||||
|
||||
executeAction(action, binding) {
|
||||
if (!Storage.get('onboardingComplete')) return;
|
||||
|
||||
const settingsPage = document.getElementById('settings-page');
|
||||
if (settingsPage.classList.contains('active')) {
|
||||
const passwordDialog = document.getElementById('password-dialog');
|
||||
|
||||
if (passwordDialog && !passwordDialog.classList.contains('hidden')) {
|
||||
const cancelBtn = document.getElementById('cancel-password');
|
||||
if (cancelBtn) {
|
||||
cancelBtn.click();
|
||||
} else {
|
||||
passwordDialog.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
passwordDialog.classList.add('hidden');
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'settings' && settingsPage.classList.contains('active')) {
|
||||
settingsPage.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
settingsPage.classList.add('hidden');
|
||||
|
@ -216,50 +257,55 @@ const keybinds = {
|
|||
switch (action) {
|
||||
case 'settings':
|
||||
if (settingsPage.classList.contains('hidden')) {
|
||||
notifications.show('Opening settings.', 'info');
|
||||
settings.updateSettingsUI();
|
||||
settingsPage.classList.remove('hidden');
|
||||
setTimeout(() => {
|
||||
settingsPage.classList.add('active');
|
||||
}, 10);
|
||||
} else {
|
||||
notifications.show('Settings closed.', 'info');
|
||||
settings.updateSettingsUI();
|
||||
}
|
||||
break;
|
||||
case 'add-shortcut':
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) {
|
||||
notifications.show('Maximum shortcuts limit reached!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcutModal = document.getElementById('add-shortcut-modal');
|
||||
if (shortcutModal === activeModal) {
|
||||
notifications.show('Add shortcut menu closed.', 'info');
|
||||
closeModal(shortcutModal);
|
||||
} else {
|
||||
notifications.show('Opening add shortcut menu.', 'info');
|
||||
openModal(shortcutModal);
|
||||
}
|
||||
break;
|
||||
case 'anonymous':
|
||||
settings.toggleAnonymousMode();
|
||||
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}`;
|
||||
|
||||
notifications.show(`Redirecting to ${url}...`, 'info');
|
||||
setTimeout(() => {
|
||||
window.location.href = fullUrl;
|
||||
}, 1000);
|
||||
} else {
|
||||
notifications.show('No URL set for this keybind.', 'error');
|
||||
|
||||
window.location.href = fullUrl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -293,6 +293,16 @@ const onboarding = {
|
|||
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(() => {
|
||||
|
|
247
js/settings.js
247
js/settings.js
|
@ -4,6 +4,7 @@ const defaultSettings = {
|
|||
anonymousMode: false,
|
||||
searchEngine: 'Google',
|
||||
updateAlerts: true,
|
||||
motionPreference: 'default',
|
||||
show_greeting: true,
|
||||
show_search: true,
|
||||
show_shortcuts: true,
|
||||
|
@ -43,6 +44,13 @@ const defaultSettings = {
|
|||
'--toggle-bg': '#333333',
|
||||
'--toggle-bg-active': 'var(--text)',
|
||||
'--toggle-knob': 'var(--background)'
|
||||
},
|
||||
keybinds: {
|
||||
settings: { keys: 'Shift+S' },
|
||||
anonymous: { keys: 'Shift+X' },
|
||||
theme: { keys: 'Shift+T' },
|
||||
history: { keys: 'Shift+H' },
|
||||
url: { keys: 'Shift+Q', url: '' }
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -81,20 +89,93 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
updateAlerts !== false;
|
||||
});
|
||||
|
||||
const updateIconStyle = (style) => {
|
||||
const icons = document.querySelectorAll('svg use');
|
||||
icons.forEach(icon => {
|
||||
const iconId = icon.getAttribute('href').substring(1);
|
||||
let newIconId;
|
||||
|
||||
if (style === 'solid') {
|
||||
if (iconId.endsWith('-solid')) {
|
||||
newIconId = iconId;
|
||||
} else {
|
||||
newIconId = `${iconId}-solid`;
|
||||
}
|
||||
} else {
|
||||
newIconId = iconId.endsWith('-solid') ? iconId.slice(0, -6) : iconId;
|
||||
}
|
||||
|
||||
icon.setAttribute('href', `#${newIconId}`);
|
||||
});
|
||||
};
|
||||
|
||||
const savedIconStyle = Storage.get('iconStyle') || 'linear';
|
||||
document.getElementById('icon-style-select').value = savedIconStyle;
|
||||
updateIconStyle(savedIconStyle);
|
||||
|
||||
document.getElementById('icon-style-select').addEventListener('change', (event) => {
|
||||
const selectedStyle = event.target.value;
|
||||
Storage.set('iconStyle', selectedStyle);
|
||||
updateIconStyle(selectedStyle);
|
||||
});
|
||||
|
||||
const savedMotionPreference = Storage.get('motionPreference') || 'default';
|
||||
document.getElementById('motion-preference-select').value = savedMotionPreference;
|
||||
updateMotionPreference(savedMotionPreference);
|
||||
|
||||
document.getElementById('motion-preference-select').addEventListener('change', (event) => {
|
||||
const selectedPreference = event.target.value;
|
||||
Storage.set('motionPreference', selectedPreference);
|
||||
updateMotionPreference(selectedPreference);
|
||||
notifications.show(`Motion preference updated to ${selectedPreference}!`, 'success');
|
||||
});
|
||||
|
||||
function updateMotionPreference(preference) {
|
||||
document.body.classList.remove('subtle-motion', 'reduced-motion', 'minimal-motion', 'no-motion');
|
||||
|
||||
switch(preference) {
|
||||
case 'subtle':
|
||||
document.body.classList.add('subtle-motion');
|
||||
break;
|
||||
case 'reduced':
|
||||
document.body.classList.add('reduced-motion');
|
||||
break;
|
||||
case 'minimal':
|
||||
document.body.classList.add('minimal-motion');
|
||||
break;
|
||||
case 'disabled':
|
||||
document.body.classList.add('no-motion');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const settings = {
|
||||
GREETING_MAX_LENGTH: 60,
|
||||
|
||||
toggleTheme: () => {
|
||||
const currentTheme = document.body.getAttribute('data-theme');
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
|
||||
const motionPreference = Storage.get('motionPreference') || 'default';
|
||||
if (motionPreference !== 'default') {
|
||||
document.body.classList.add('instant-theme-change');
|
||||
}
|
||||
|
||||
document.body.setAttribute('data-theme', newTheme);
|
||||
Storage.set('theme', newTheme);
|
||||
|
||||
const iconStyle = Storage.get('iconStyle') || 'linear';
|
||||
const themeIcon = document.querySelector('#toggle-theme svg use');
|
||||
const lightModeIcon = newTheme === 'dark' ? 'dark-mode' : 'light-mode';
|
||||
const darkModeIcon = newTheme === 'dark' ? 'light-mode' : 'dark-mode';
|
||||
const lightModeIcon = iconStyle === 'solid' ? 'icon-light-mode-solid' : 'icon-light-mode';
|
||||
const darkModeIcon = iconStyle === 'solid' ? 'icon-dark-mode-solid' : 'icon-dark-mode';
|
||||
|
||||
themeIcon.setAttribute('href', `#icon-${newTheme === 'dark' ? darkModeIcon : lightModeIcon}`);
|
||||
themeIcon.setAttribute('href', `#${newTheme === 'dark' ? lightModeIcon : darkModeIcon}`);
|
||||
|
||||
if (motionPreference !== 'default') {
|
||||
setTimeout(() => {
|
||||
document.body.classList.remove('instant-theme-change');
|
||||
}, 50);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
@ -304,9 +385,29 @@ const settings = {
|
|||
fontSizeSlider.addEventListener('input', (e) => updateFontSize(e.target.value));
|
||||
fontSizeNumber.addEventListener('change', (e) => updateFontSize(e.target.value));
|
||||
|
||||
resetFontSize.addEventListener('click', settings.resetTypography);
|
||||
resetLightColors.addEventListener('click', () => settings.resetColors('light'));
|
||||
resetDarkColors.addEventListener('click', () => settings.resetColors('dark'));
|
||||
resetFontSize.addEventListener('click', () => {
|
||||
shortcuts.showConfirmDialog(
|
||||
'Reset Typography',
|
||||
'Are you sure you want to reset font settings to default?',
|
||||
settings.resetTypography
|
||||
);
|
||||
});
|
||||
|
||||
resetLightColors.addEventListener('click', () => {
|
||||
shortcuts.showConfirmDialog(
|
||||
'Reset Light Colors',
|
||||
'Are you sure you want to reset light mode colors to default?',
|
||||
() => settings.resetColors('light')
|
||||
);
|
||||
});
|
||||
|
||||
resetDarkColors.addEventListener('click', () => {
|
||||
shortcuts.showConfirmDialog(
|
||||
'Reset Dark Colors',
|
||||
'Are you sure you want to reset dark mode colors to default?',
|
||||
() => settings.resetColors('dark')
|
||||
);
|
||||
});
|
||||
|
||||
settings.initColorSettings();
|
||||
|
||||
|
@ -316,9 +417,10 @@ const settings = {
|
|||
const savedTheme = Storage.get('theme') || 'light';
|
||||
document.body.setAttribute('data-theme', savedTheme);
|
||||
|
||||
const iconStyle = Storage.get('iconStyle') || 'linear';
|
||||
const themeIcon = document.querySelector('#toggle-theme svg use');
|
||||
const lightModeIcon = 'light-mode';
|
||||
const darkModeIcon = 'dark-mode';
|
||||
const lightModeIcon = iconStyle === 'solid' ? 'light-mode-solid' : 'light-mode';
|
||||
const darkModeIcon = iconStyle === 'solid' ? 'dark-mode-solid' : 'dark-mode';
|
||||
|
||||
themeIcon.setAttribute('href', `#icon-${savedTheme === 'dark' ? darkModeIcon : lightModeIcon}`);
|
||||
|
||||
|
@ -369,11 +471,13 @@ const settings = {
|
|||
const userName = Storage.get('userName') || '';
|
||||
const isAnonymous = Storage.get('anonymousMode') || false;
|
||||
const currentEngine = Storage.get('searchEngine') || 'google';
|
||||
const masterPassword = Storage.get('masterPassword') || '';
|
||||
|
||||
document.getElementById('settings-name').value = userName;
|
||||
document.getElementById('toggle-anonymous').checked = isAnonymous;
|
||||
document.getElementById('search-engine-select').value = currentEngine;
|
||||
document.getElementById('toggle-update-alerts').checked = updateAlerts;
|
||||
document.getElementById('master-password').value = masterPassword;
|
||||
|
||||
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
||||
const isVisible = Storage.get(`show_${element}`);
|
||||
|
@ -392,6 +496,7 @@ const settings = {
|
|||
document.getElementById('font-family-select').value = fontFamily;
|
||||
document.getElementById('font-size-slider').value = fontSize;
|
||||
document.getElementById('font-size-number').value = fontSize;
|
||||
document.getElementById('motion-preference-select').value = Storage.get('motionPreference') || 'default';
|
||||
},
|
||||
|
||||
formatGreeting: (format) => {
|
||||
|
@ -450,6 +555,11 @@ const settings = {
|
|||
|
||||
exportData: () => {
|
||||
try {
|
||||
let masterPassword = Storage.get('masterPassword') || '';
|
||||
if (masterPassword) {
|
||||
masterPassword = btoa(masterPassword);
|
||||
}
|
||||
|
||||
const data = {
|
||||
settings: {
|
||||
theme: Storage.get('theme') || defaultSettings.theme,
|
||||
|
@ -459,6 +569,11 @@ const settings = {
|
|||
searchEngine: Storage.get('searchEngine') || 'Google',
|
||||
customGreeting: Storage.get('customGreeting') || '',
|
||||
updateAlerts: Storage.get('updateAlerts') !== false,
|
||||
iconStyle: Storage.get('iconStyle') || 'linear',
|
||||
motionPreference: Storage.get('motionPreference') || 'default',
|
||||
|
||||
passwordProtectionEnabled: Storage.get('passwordProtectionEnabled') || false,
|
||||
masterPassword: masterPassword,
|
||||
|
||||
fontFamily: Storage.get('fontFamily') || defaultSettings.fontFamily,
|
||||
fontSize: Storage.get('fontSize') || defaultSettings.fontSize,
|
||||
|
@ -649,6 +764,12 @@ const settings = {
|
|||
}
|
||||
});
|
||||
|
||||
if (data.settings.passwordProtectionEnabled) {
|
||||
if (typeof shortcuts.createShortcutProtectionManager === 'function') {
|
||||
shortcuts.createShortcutProtectionManager();
|
||||
}
|
||||
}
|
||||
|
||||
Storage.set('shortcuts', data.shortcuts);
|
||||
|
||||
const validatedKeybinds = {};
|
||||
|
@ -678,6 +799,8 @@ const settings = {
|
|||
settings.initColorSettings();
|
||||
}
|
||||
|
||||
updateIconStyle(data.settings.iconStyle || 'linear');
|
||||
|
||||
if (data.settings.fontFamily) {
|
||||
document.documentElement.style.setProperty('--font-family', data.settings.fontFamily);
|
||||
}
|
||||
|
@ -701,6 +824,18 @@ const settings = {
|
|||
Storage.remove('customBackground');
|
||||
}
|
||||
|
||||
let masterPassword = data.settings.masterPassword || '';
|
||||
if (masterPassword) {
|
||||
try {
|
||||
masterPassword = atob(masterPassword);
|
||||
Storage.set('masterPassword', masterPassword);
|
||||
settings.updateSettingsUI();
|
||||
} catch (e) {
|
||||
console.error('Error decoding master password:', e);
|
||||
notifications.show('Failed to decode master password!', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
notifications.show('Data imported successfully!', 'success');
|
||||
} catch (error) {
|
||||
notifications.show('Failed to import data: Invalid file format!', 'error');
|
||||
|
@ -709,50 +844,27 @@ const settings = {
|
|||
},
|
||||
|
||||
resetData: () => {
|
||||
Object.entries(defaultSettings).forEach(([key, value]) => {
|
||||
Storage.set(key, value);
|
||||
});
|
||||
|
||||
Storage.remove('shortcuts');
|
||||
Storage.remove('keybinds');
|
||||
Storage.remove('anonymousName');
|
||||
Storage.remove('customGreeting');
|
||||
|
||||
if (typeof GridLayout !== 'undefined') {
|
||||
if (GridLayout.reset) {
|
||||
GridLayout.reset();
|
||||
} else if (GridLayout.defaults) {
|
||||
Storage.set('gridLayout', GridLayout.defaults);
|
||||
if (GridLayout.init) {
|
||||
shortcuts.showConfirmDialog(
|
||||
'Reset All Data',
|
||||
'Are you sure you want to reset all data? This action cannot be undone.',
|
||||
() => {
|
||||
try {
|
||||
Storage.remove('customGreeting');
|
||||
Storage.clear();
|
||||
|
||||
Storage.set('keybinds', defaultSettings.keybinds);
|
||||
|
||||
closeModal(document.getElementById('settings-modal'));
|
||||
notifications.show('All data has been reset!', 'success');
|
||||
setTimeout(() => {
|
||||
GridLayout.init();
|
||||
}, 100);
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
notifications.show('Failed to reset data!', 'error');
|
||||
console.error('Reset error:', error);
|
||||
}
|
||||
} else {
|
||||
Storage.remove('gridLayout');
|
||||
}
|
||||
} else {
|
||||
Storage.remove('gridLayout');
|
||||
}
|
||||
|
||||
if (typeof GridLayout === 'undefined' || !GridLayout.reset) {
|
||||
const defaultVisibility = {
|
||||
showGreeting: true,
|
||||
showSearch: true,
|
||||
showShortcuts: true,
|
||||
showAddButton: true,
|
||||
showGrid: true
|
||||
};
|
||||
Storage.set('visibility', defaultVisibility);
|
||||
}
|
||||
|
||||
settings.updateSettingsUI();
|
||||
settings.updateVisibility();
|
||||
settings.updateTypography();
|
||||
shortcuts.render();
|
||||
document.body.setAttribute('data-theme', 'light');
|
||||
|
||||
notifications.show('All data has been reset!', 'success');
|
||||
);
|
||||
},
|
||||
|
||||
updateTypography: () => {
|
||||
|
@ -911,22 +1023,27 @@ settings.initDataManagement = () => {
|
|||
});
|
||||
|
||||
resetBtn.addEventListener('click', () => {
|
||||
const confirmReset = confirm('Are you sure you want to reset all data? This action cannot be undone.');
|
||||
|
||||
if (confirmReset) {
|
||||
try {
|
||||
Storage.remove('customGreeting');
|
||||
Storage.clear();
|
||||
closeModal(document.getElementById('settings-modal'));
|
||||
notifications.show('All data has been reset!', 'success');
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
notifications.show('Failed to reset data!', 'error');
|
||||
console.error('Reset error:', error);
|
||||
shortcuts.showConfirmDialog(
|
||||
'Reset All Data',
|
||||
'Are you sure you want to reset all data? This action cannot be undone.',
|
||||
() => {
|
||||
try {
|
||||
Storage.remove('customGreeting');
|
||||
Storage.clear();
|
||||
|
||||
Storage.set('keybinds', defaultSettings.keybinds);
|
||||
|
||||
closeModal(document.getElementById('settings-modal'));
|
||||
notifications.show('All data has been reset!', 'success');
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
notifications.show('Failed to reset data!', 'error');
|
||||
console.error('Reset error:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -942,6 +1059,8 @@ function initCustomSelects() {
|
|||
|
||||
if (nativeSelect.id === 'search-engine-select') {
|
||||
nativeSelect.value = Storage.get('searchEngine') || 'google';
|
||||
} else if (nativeSelect.id === 'icon-style-select') {
|
||||
nativeSelect.value = Storage.get('iconStyle') || 'linear';
|
||||
} else if (nativeSelect.id === 'font-family-select') {
|
||||
nativeSelect.value = Storage.get('fontFamily') || 'Inter';
|
||||
selectedDiv.style.fontFamily = nativeSelect.value;
|
||||
|
|
566
js/shortcuts.js
566
js/shortcuts.js
|
@ -14,7 +14,7 @@ const shortcuts = {
|
|||
}
|
||||
},
|
||||
|
||||
add: (url, name) => {
|
||||
add: (url, name, isPasswordProtected = false) => {
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) {
|
||||
notifications.show('Maximum shortcuts limit reached!', 'error');
|
||||
|
@ -27,7 +27,11 @@ const shortcuts = {
|
|||
return;
|
||||
}
|
||||
|
||||
currentShortcuts.push({ url: formattedUrl, name });
|
||||
currentShortcuts.push({
|
||||
url: formattedUrl,
|
||||
name,
|
||||
isPasswordProtected: isPasswordProtected || false
|
||||
});
|
||||
Storage.set('shortcuts', currentShortcuts);
|
||||
shortcuts.render();
|
||||
CacheUpdater.update();
|
||||
|
@ -42,9 +46,131 @@ const shortcuts = {
|
|||
CacheUpdater.update();
|
||||
},
|
||||
|
||||
edit: (index, newUrl, newName) => {
|
||||
showConfirmDialog: (title, message, onConfirm) => {
|
||||
const dialog = document.getElementById('confirmation-dialog');
|
||||
const titleEl = document.getElementById('confirmation-title');
|
||||
const messageEl = document.getElementById('confirmation-message');
|
||||
const confirmBtn = document.getElementById('confirm-action');
|
||||
const cancelBtn = document.getElementById('cancel-action');
|
||||
|
||||
titleEl.textContent = title;
|
||||
messageEl.textContent = message;
|
||||
|
||||
dialog.classList.remove('hidden');
|
||||
setTimeout(() => dialog.classList.add('active'), 10);
|
||||
|
||||
const closeDialog = () => {
|
||||
dialog.classList.remove('active');
|
||||
setTimeout(() => dialog.classList.add('hidden'), 300);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
onConfirm();
|
||||
closeDialog();
|
||||
confirmBtn.removeEventListener('click', handleConfirm);
|
||||
cancelBtn.removeEventListener('click', handleCancel);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
closeDialog();
|
||||
confirmBtn.removeEventListener('click', handleConfirm);
|
||||
cancelBtn.removeEventListener('click', handleCancel);
|
||||
};
|
||||
|
||||
confirmBtn.addEventListener('click', handleConfirm);
|
||||
cancelBtn.addEventListener('click', handleCancel);
|
||||
},
|
||||
|
||||
showPasswordDialog: (shortcut, callback) => {
|
||||
const dialog = document.getElementById('password-dialog');
|
||||
const passwordInput = document.getElementById('shortcut-password');
|
||||
const submitBtn = document.getElementById('submit-password');
|
||||
const cancelBtn = document.getElementById('cancel-password');
|
||||
const closeBtn = document.getElementById('close-password-dialog');
|
||||
const errorMsg = document.getElementById('password-error');
|
||||
const contextMenu = document.getElementById('context-menu');
|
||||
|
||||
if (contextMenu) {
|
||||
contextMenu.classList.add('hidden');
|
||||
}
|
||||
|
||||
if (errorMsg) {
|
||||
errorMsg.classList.add('hidden');
|
||||
}
|
||||
|
||||
if (passwordInput) {
|
||||
passwordInput.value = '';
|
||||
}
|
||||
|
||||
if (dialog) {
|
||||
dialog.classList.remove('hidden');
|
||||
setTimeout(() => {
|
||||
dialog.classList.add('active');
|
||||
if (passwordInput) {
|
||||
passwordInput.focus();
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialog.classList.remove('active');
|
||||
setTimeout(() => dialog.classList.add('hidden'), 300);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
const password = passwordInput.value;
|
||||
const masterPassword = Storage.get('masterPassword');
|
||||
|
||||
if (!masterPassword) {
|
||||
errorMsg.textContent = "No master password set. Please set one in settings.";
|
||||
errorMsg.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (password === masterPassword) {
|
||||
closeDialog();
|
||||
callback();
|
||||
submitBtn.removeEventListener('click', handleSubmit);
|
||||
cancelBtn.removeEventListener('click', handleCancel);
|
||||
closeBtn.removeEventListener('click', handleCancel);
|
||||
passwordInput.removeEventListener('keydown', handleKeydown);
|
||||
} else {
|
||||
errorMsg.textContent = "Incorrect password. Please try again.";
|
||||
errorMsg.classList.remove('hidden');
|
||||
passwordInput.value = '';
|
||||
passwordInput.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
closeDialog();
|
||||
submitBtn.removeEventListener('click', handleSubmit);
|
||||
cancelBtn.removeEventListener('click', handleCancel);
|
||||
closeBtn.removeEventListener('click', handleCancel);
|
||||
passwordInput.removeEventListener('keydown', handleKeydown);
|
||||
};
|
||||
|
||||
const handleKeydown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSubmit();
|
||||
} else if (e.key === 'Escape') {
|
||||
handleCancel();
|
||||
}
|
||||
};
|
||||
|
||||
submitBtn.addEventListener('click', handleSubmit);
|
||||
cancelBtn.addEventListener('click', handleCancel);
|
||||
closeBtn.addEventListener('click', handleCancel);
|
||||
passwordInput.addEventListener('keydown', handleKeydown);
|
||||
},
|
||||
|
||||
edit: (index, newUrl, newName, isPasswordProtected) => {
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
currentShortcuts[index] = { url: newUrl, name: newName };
|
||||
currentShortcuts[index] = {
|
||||
url: newUrl,
|
||||
name: newName,
|
||||
isPasswordProtected: isPasswordProtected || false
|
||||
};
|
||||
Storage.set('shortcuts', currentShortcuts);
|
||||
shortcuts.render();
|
||||
notifications.show('Shortcut updated!', 'success');
|
||||
|
@ -82,7 +208,7 @@ const shortcuts = {
|
|||
|
||||
currentShortcuts.forEach((shortcut, index) => {
|
||||
const element = document.createElement('div');
|
||||
element.className = `shortcut ${isAnonymous ? 'blurred' : ''}`;
|
||||
element.className = `shortcut ${isAnonymous ? 'blurred' : ''} ${shortcut.isPasswordProtected ? 'password-protected' : ''}`;
|
||||
|
||||
element.dataset.index = index;
|
||||
|
||||
|
@ -99,10 +225,42 @@ const shortcuts = {
|
|||
|
||||
element.addEventListener('click', (e) => {
|
||||
if (!grid.classList.contains('grid-draggable') || !e.target.closest('.shortcut').classList.contains('drag-active')) {
|
||||
if (e.ctrlKey) {
|
||||
if (shortcut.isPasswordProtected) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const openShortcut = () => {
|
||||
if (e.ctrlKey || e.which === 2 || e.button === 1) {
|
||||
window.open(shortcut.url, '_blank');
|
||||
} else {
|
||||
window.location.href = shortcut.url;
|
||||
}
|
||||
};
|
||||
|
||||
shortcuts.showPasswordDialog(shortcut, openShortcut);
|
||||
return false;
|
||||
} else {
|
||||
if (e.ctrlKey || e.which === 2 || e.button === 1) {
|
||||
window.open(shortcut.url, '_blank');
|
||||
} else {
|
||||
window.location.href = shortcut.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
element.addEventListener('mousedown', (e) => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
|
||||
if (shortcut.isPasswordProtected) {
|
||||
const openShortcut = () => {
|
||||
window.open(shortcut.url, '_blank');
|
||||
};
|
||||
|
||||
shortcuts.showPasswordDialog(shortcut, openShortcut);
|
||||
} else {
|
||||
window.open(shortcut.url, '_blank');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -133,6 +291,14 @@ const shortcuts = {
|
|||
},
|
||||
|
||||
init: () => {
|
||||
const masterPasswordInput = document.getElementById('master-password');
|
||||
if (masterPasswordInput) {
|
||||
const savedPassword = Storage.get('masterPassword');
|
||||
if (savedPassword) {
|
||||
masterPasswordInput.value = savedPassword;
|
||||
}
|
||||
}
|
||||
|
||||
const addShortcutButton = document.getElementById('add-shortcut');
|
||||
const modal = document.getElementById('add-shortcut-modal');
|
||||
const closeBtn = modal.querySelector('.close-modal');
|
||||
|
@ -174,12 +340,18 @@ const shortcuts = {
|
|||
if (url && name) {
|
||||
try {
|
||||
new URL(url);
|
||||
shortcuts.add(url, name);
|
||||
|
||||
const isPasswordProtectionEnabled = Storage.get('passwordProtectionEnabled') || false;
|
||||
const passwordProtectCheckbox = document.getElementById('protect-shortcut');
|
||||
const isPasswordProtected = isPasswordProtectionEnabled && passwordProtectCheckbox && passwordProtectCheckbox.checked;
|
||||
|
||||
shortcuts.add(url, name, isPasswordProtected);
|
||||
modal.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
modal.classList.add('hidden');
|
||||
urlInput.value = '';
|
||||
nameInput.value = '';
|
||||
if (passwordProtectCheckbox) passwordProtectCheckbox.checked = false;
|
||||
}, 300);
|
||||
notifications.show('Shortcut added successfully!', 'success');
|
||||
} catch (e) {
|
||||
|
@ -197,6 +369,8 @@ const shortcuts = {
|
|||
modal.classList.add('hidden');
|
||||
urlInput.value = '';
|
||||
nameInput.value = '';
|
||||
const passwordProtectCheckbox = document.getElementById('protect-shortcut');
|
||||
if (passwordProtectCheckbox) passwordProtectCheckbox.checked = false;
|
||||
}, 300);
|
||||
};
|
||||
}
|
||||
|
@ -204,6 +378,120 @@ const shortcuts = {
|
|||
});
|
||||
}
|
||||
|
||||
const anonymousTogglePrivacy = document.getElementById('toggle-anonymous-privacy');
|
||||
if (anonymousTogglePrivacy) {
|
||||
anonymousTogglePrivacy.checked = Storage.get('anonymousMode') || false;
|
||||
|
||||
anonymousTogglePrivacy.addEventListener('change', () => {
|
||||
const anonymousToggle = document.getElementById('toggle-anonymous');
|
||||
if (anonymousToggle) {
|
||||
anonymousToggle.checked = anonymousTogglePrivacy.checked;
|
||||
anonymousToggle.dispatchEvent(new Event('change'));
|
||||
} else {
|
||||
shortcuts.toggleAnonymousMode();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const shortcutsPasswordToggle = document.getElementById('toggle-shortcuts-password');
|
||||
if (shortcutsPasswordToggle) {
|
||||
const isEnabled = Storage.get('passwordProtectionEnabled') || false;
|
||||
shortcutsPasswordToggle.checked = isEnabled;
|
||||
const passwordSettings = document.getElementById('password-protection-settings');
|
||||
|
||||
if (passwordSettings) {
|
||||
if (isEnabled) {
|
||||
passwordSettings.classList.remove('hidden');
|
||||
} else {
|
||||
passwordSettings.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
shortcutsPasswordToggle.addEventListener('change', () => {
|
||||
const isEnabled = shortcutsPasswordToggle.checked;
|
||||
Storage.set('passwordProtectionEnabled', isEnabled);
|
||||
|
||||
if (passwordSettings) {
|
||||
if (isEnabled) {
|
||||
passwordSettings.classList.remove('hidden');
|
||||
const masterPasswordInput = document.getElementById('master-password');
|
||||
if (masterPasswordInput) {
|
||||
setTimeout(() => {
|
||||
masterPasswordInput.focus();
|
||||
}, 10);
|
||||
}
|
||||
} else {
|
||||
passwordSettings.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
shortcuts.updateAddShortcutModal();
|
||||
|
||||
if (isEnabled) {
|
||||
const masterPassword = Storage.get('masterPassword');
|
||||
|
||||
if (!masterPassword) {
|
||||
const masterPasswordInput = document.getElementById('master-password');
|
||||
if (masterPasswordInput) {
|
||||
masterPasswordInput.focus();
|
||||
notifications.show('Please set a master password!', 'warning');
|
||||
}
|
||||
} else {
|
||||
notifications.show('Password protection enabled!', 'success');
|
||||
shortcuts.createShortcutProtectionManager();
|
||||
}
|
||||
} else {
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
currentShortcuts.forEach(shortcut => {
|
||||
shortcut.isPasswordProtected = false;
|
||||
});
|
||||
Storage.set('shortcuts', currentShortcuts);
|
||||
shortcuts.render();
|
||||
|
||||
notifications.show('Password protection disabled!', 'info');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Storage.get('passwordProtectionEnabled')) {
|
||||
shortcuts.createShortcutProtectionManager();
|
||||
}
|
||||
|
||||
const saveMasterPasswordBtn = document.getElementById('save-master-password');
|
||||
if (saveMasterPasswordBtn) {
|
||||
saveMasterPasswordBtn.addEventListener('click', () => {
|
||||
const masterPasswordInput = document.getElementById('master-password');
|
||||
if (masterPasswordInput) {
|
||||
const password = masterPasswordInput.value.trim();
|
||||
|
||||
if (password) {
|
||||
Storage.set('masterPassword', password);
|
||||
|
||||
notifications.show('Master password updated!', 'success');
|
||||
|
||||
const shortcutsPasswordToggle = document.getElementById('toggle-shortcuts-password');
|
||||
if (shortcutsPasswordToggle && !shortcutsPasswordToggle.checked) {
|
||||
shortcutsPasswordToggle.checked = true;
|
||||
Storage.set('passwordProtectionEnabled', true);
|
||||
const passwordSettings = document.getElementById('password-protection-settings');
|
||||
if (passwordSettings) {
|
||||
passwordSettings.classList.remove('hidden');
|
||||
}
|
||||
shortcuts.updateAddShortcutModal();
|
||||
|
||||
shortcuts.createShortcutProtectionManager();
|
||||
} else {
|
||||
shortcuts.createShortcutProtectionManager();
|
||||
}
|
||||
} else {
|
||||
notifications.show('Please enter a valid password!', 'error');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
shortcuts.updateAddShortcutModal();
|
||||
|
||||
const contextMenu = document.getElementById('context-menu');
|
||||
if (contextMenu) {
|
||||
contextMenu.addEventListener('click', (e) => {
|
||||
|
@ -222,6 +510,11 @@ const shortcuts = {
|
|||
urlInput.value = shortcut.url;
|
||||
nameInput.value = shortcut.name;
|
||||
|
||||
const protectCheckbox = document.getElementById('protect-shortcut-edit');
|
||||
if (protectCheckbox) {
|
||||
protectCheckbox.checked = shortcut.isPasswordProtected || false;
|
||||
}
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('active');
|
||||
|
||||
|
@ -243,8 +536,14 @@ const shortcuts = {
|
|||
if (newUrl && newName) {
|
||||
const formattedUrl = shortcuts.validateAndFormatUrl(newUrl);
|
||||
if (formattedUrl) {
|
||||
shortcuts.edit(index, formattedUrl, newName);
|
||||
const isPasswordProtectionEnabled = Storage.get('passwordProtectionEnabled') || false;
|
||||
const isPasswordProtected = isPasswordProtectionEnabled &&
|
||||
protectCheckbox && protectCheckbox.checked;
|
||||
|
||||
shortcuts.edit(index, formattedUrl, newName, isPasswordProtected);
|
||||
closeModal();
|
||||
|
||||
shortcuts.createShortcutProtectionManager();
|
||||
} else {
|
||||
notifications.show('Invalid URL format!', 'error');
|
||||
}
|
||||
|
@ -256,13 +555,29 @@ const shortcuts = {
|
|||
cancelButton.onclick = closeModal;
|
||||
}
|
||||
} else if (action === 'delete') {
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
const shortcut = currentShortcuts[index];
|
||||
|
||||
shortcuts.showConfirmDialog(
|
||||
'Delete Shortcut',
|
||||
`Are you sure you want to delete "${shortcut.name}"?`,
|
||||
() => {
|
||||
shortcuts.remove(index);
|
||||
shortcuts.createShortcutProtectionManager();
|
||||
}
|
||||
);
|
||||
} else if (action === 'open-new-tab') {
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
const shortcut = currentShortcuts[index];
|
||||
|
||||
if (shortcut && shortcut.url) {
|
||||
if (shortcut.isPasswordProtected) {
|
||||
shortcuts.showPasswordDialog(shortcut, () => {
|
||||
window.open(shortcut.url, '_blank');
|
||||
});
|
||||
} else {
|
||||
window.open(shortcut.url, '_blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,5 +586,240 @@ const shortcuts = {
|
|||
}
|
||||
|
||||
shortcuts.render();
|
||||
},
|
||||
|
||||
createShortcutProtectionManager: () => {
|
||||
const passwordSettings = document.getElementById('password-protection-settings');
|
||||
if (!passwordSettings) return;
|
||||
|
||||
let protectionManager = document.getElementById('shortcut-protection-manager');
|
||||
if (!protectionManager) {
|
||||
protectionManager = document.createElement('div');
|
||||
protectionManager.id = 'shortcut-protection-manager';
|
||||
protectionManager.className = 'shortcut-protection-manager';
|
||||
|
||||
const managerTitle = document.createElement('h4');
|
||||
managerTitle.textContent = 'Protect Specific Shortcuts';
|
||||
|
||||
const managerDescription = document.createElement('p');
|
||||
managerDescription.className = 'setting-description';
|
||||
managerDescription.textContent = 'Select which shortcuts to password protect:';
|
||||
|
||||
protectionManager.appendChild(managerTitle);
|
||||
protectionManager.appendChild(managerDescription);
|
||||
|
||||
passwordSettings.appendChild(protectionManager);
|
||||
} else {
|
||||
const children = Array.from(protectionManager.children);
|
||||
children.forEach((child, index) => {
|
||||
if (index > 1) protectionManager.removeChild(child);
|
||||
});
|
||||
}
|
||||
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
|
||||
const selectedShortcutsContainer = document.createElement('div');
|
||||
selectedShortcutsContainer.className = 'selected-shortcuts-container';
|
||||
|
||||
const protectedShortcuts = currentShortcuts.filter(shortcut => shortcut.isPasswordProtected);
|
||||
|
||||
if (protectedShortcuts.length > 0) {
|
||||
protectedShortcuts.forEach((shortcut, index) => {
|
||||
const shortcutChip = document.createElement('div');
|
||||
shortcutChip.className = 'shortcut-chip';
|
||||
shortcutChip.dataset.index = currentShortcuts.indexOf(shortcut);
|
||||
|
||||
const shortcutIcon = document.createElement('img');
|
||||
shortcutIcon.src = `https://www.google.com/s2/favicons?domain=${shortcut.url}&sz=64`;
|
||||
shortcutIcon.alt = shortcut.name;
|
||||
|
||||
const shortcutName = document.createElement('span');
|
||||
shortcutName.textContent = shortcut.name;
|
||||
|
||||
const removeButton = document.createElement('button');
|
||||
removeButton.className = 'remove-chip-btn';
|
||||
removeButton.innerHTML = '×';
|
||||
removeButton.title = 'Remove protection';
|
||||
|
||||
shortcutChip.appendChild(shortcutIcon);
|
||||
shortcutChip.appendChild(shortcutName);
|
||||
shortcutChip.appendChild(removeButton);
|
||||
|
||||
removeButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
currentShortcuts[shortcutChip.dataset.index].isPasswordProtected = false;
|
||||
Storage.set('shortcuts', currentShortcuts);
|
||||
|
||||
shortcuts.render();
|
||||
shortcuts.createShortcutProtectionManager();
|
||||
|
||||
notifications.show(`Removed protection from: ${shortcut.name}`, 'info');
|
||||
});
|
||||
|
||||
selectedShortcutsContainer.appendChild(shortcutChip);
|
||||
});
|
||||
} else if (currentShortcuts.length > 0) {
|
||||
const emptyState = document.createElement('p');
|
||||
emptyState.className = 'empty-protection-state';
|
||||
emptyState.textContent = 'No protected shortcuts yet.';
|
||||
selectedShortcutsContainer.appendChild(emptyState);
|
||||
}
|
||||
|
||||
protectionManager.appendChild(selectedShortcutsContainer);
|
||||
|
||||
const selectorContainer = document.createElement('div');
|
||||
selectorContainer.className = 'shortcut-selector-container';
|
||||
|
||||
const unprotectedShortcuts = currentShortcuts.filter(shortcut => !shortcut.isPasswordProtected);
|
||||
|
||||
if (unprotectedShortcuts.length > 0) {
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.className = 'shortcut-dropdown';
|
||||
|
||||
const selected = document.createElement('div');
|
||||
selected.className = 'shortcut-dropdown-selected';
|
||||
selected.textContent = 'Select a shortcut to protect...';
|
||||
|
||||
const dropdownItems = document.createElement('div');
|
||||
dropdownItems.className = 'shortcut-dropdown-items';
|
||||
dropdownItems.classList.add('hidden');
|
||||
|
||||
unprotectedShortcuts.forEach(shortcut => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'shortcut-dropdown-item';
|
||||
item.dataset.index = currentShortcuts.indexOf(shortcut);
|
||||
|
||||
const icon = document.createElement('img');
|
||||
icon.src = `https://www.google.com/s2/favicons?domain=${shortcut.url}&sz=64`;
|
||||
icon.alt = shortcut.name;
|
||||
icon.style.width = '16px';
|
||||
icon.style.height = '16px';
|
||||
|
||||
const name = document.createElement('span');
|
||||
name.textContent = shortcut.name;
|
||||
|
||||
item.appendChild(icon);
|
||||
item.appendChild(name);
|
||||
|
||||
item.addEventListener('click', () => {
|
||||
currentShortcuts[item.dataset.index].isPasswordProtected = true;
|
||||
Storage.set('shortcuts', currentShortcuts);
|
||||
|
||||
shortcuts.render();
|
||||
shortcuts.createShortcutProtectionManager();
|
||||
|
||||
dropdownItems.classList.remove('active');
|
||||
selected.classList.remove('active');
|
||||
dropdownItems.classList.add('hidden');
|
||||
|
||||
notifications.show(`Protected shortcut: ${shortcut.name}`, 'success');
|
||||
});
|
||||
|
||||
dropdownItems.appendChild(item);
|
||||
});
|
||||
|
||||
selected.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
dropdownItems.classList.toggle('hidden');
|
||||
dropdownItems.classList.toggle('active');
|
||||
selected.classList.toggle('active');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!dropdown.contains(e.target)) {
|
||||
dropdownItems.classList.add('hidden');
|
||||
dropdownItems.classList.remove('active');
|
||||
selected.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
dropdown.appendChild(selected);
|
||||
dropdown.appendChild(dropdownItems);
|
||||
selectorContainer.appendChild(dropdown);
|
||||
} else if (currentShortcuts.length === 0) {
|
||||
const noShortcutsMessage = document.createElement('p');
|
||||
noShortcutsMessage.className = 'no-shortcuts-message';
|
||||
noShortcutsMessage.textContent = 'Add shortcuts to protect them with a password.';
|
||||
selectorContainer.appendChild(noShortcutsMessage);
|
||||
} else {
|
||||
const allProtectedMessage = document.createElement('p');
|
||||
allProtectedMessage.className = 'empty-protection-state';
|
||||
allProtectedMessage.textContent = 'All shortcuts are password protected.';
|
||||
selectorContainer.appendChild(allProtectedMessage);
|
||||
}
|
||||
|
||||
protectionManager.appendChild(selectorContainer);
|
||||
},
|
||||
|
||||
updateAddShortcutModal: () => {
|
||||
const modal = document.getElementById('add-shortcut-modal');
|
||||
if (!modal) return;
|
||||
|
||||
const modalContent = modal.querySelector('.modal-content');
|
||||
if (!modalContent) return;
|
||||
|
||||
const existingCheckbox = document.getElementById('protect-shortcut-container');
|
||||
if (existingCheckbox) return;
|
||||
|
||||
const isPasswordProtectionEnabled = Storage.get('passwordProtectionEnabled') || false;
|
||||
if (!isPasswordProtectionEnabled) return;
|
||||
|
||||
const checkboxContainer = document.createElement('div');
|
||||
checkboxContainer.id = 'protect-shortcut-container';
|
||||
checkboxContainer.className = 'checkbox-container';
|
||||
checkboxContainer.innerHTML = `
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="protect-shortcut">
|
||||
<span>Password protect this shortcut</span>
|
||||
</label>
|
||||
`;
|
||||
|
||||
const saveButton = modal.querySelector('#save-shortcut');
|
||||
if (saveButton) {
|
||||
modalContent.insertBefore(checkboxContainer, saveButton);
|
||||
} else {
|
||||
modalContent.appendChild(checkboxContainer);
|
||||
}
|
||||
|
||||
const editModal = document.getElementById('edit-shortcut-modal');
|
||||
if (editModal) {
|
||||
const existingEditCheckbox = document.getElementById('protect-shortcut-edit-container');
|
||||
if (!existingEditCheckbox) {
|
||||
const editModalContent = editModal.querySelector('.modal-content');
|
||||
const editCheckboxContainer = document.createElement('div');
|
||||
editCheckboxContainer.id = 'protect-shortcut-edit-container';
|
||||
editCheckboxContainer.className = 'checkbox-container';
|
||||
editCheckboxContainer.innerHTML = `
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="protect-shortcut-edit">
|
||||
<span>Password protect this shortcut</span>
|
||||
</label>
|
||||
`;
|
||||
|
||||
const modalActions = editModal.querySelector('.modal-actions');
|
||||
if (modalActions) {
|
||||
editModalContent.insertBefore(editCheckboxContainer, modalActions);
|
||||
} else {
|
||||
editModalContent.appendChild(editCheckboxContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toggleAnonymousMode: () => {
|
||||
const isAnonymous = Storage.get('anonymousMode') || false;
|
||||
Storage.set('anonymousMode', !isAnonymous);
|
||||
|
||||
if (!isAnonymous) {
|
||||
const randomName = anonymousNames.generate();
|
||||
Storage.set('anonymousName', randomName);
|
||||
notifications.show('Anonymous mode enabled!', 'info');
|
||||
} else {
|
||||
Storage.remove('anonymousName');
|
||||
notifications.show('Anonymous mode disabled!', 'info');
|
||||
}
|
||||
|
||||
shortcuts.render();
|
||||
updateGreeting();
|
||||
}
|
||||
};
|
|
@ -1,35 +1,36 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "JSTAR Tab",
|
||||
"version": "3.0.0",
|
||||
"description": "JSTAR Tab is a sleek, customizable new tab extension with personalized greetings, shortcuts, anonymous mode, search engine settings, themes, data management, and more, for an enhanced browsing experience.",
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "index.html"
|
||||
},
|
||||
"permissions": [
|
||||
"storage",
|
||||
"favicon"
|
||||
],
|
||||
"icons": {
|
||||
"16": "images/icon16.png",
|
||||
"48": "images/icon48.png",
|
||||
"128": "images/icon128.png"
|
||||
},
|
||||
"action": {
|
||||
"default_title": "New JSTAR Tab",
|
||||
"default_icon": {
|
||||
"16": "images/icon16.png",
|
||||
"48": "images/icon48.png",
|
||||
"128": "images/icon128.png"
|
||||
}
|
||||
},
|
||||
"author": "JSTAR",
|
||||
"homepage_url": "https://github.com/DevJSTAR/JSTAR-Tab",
|
||||
"web_accessible_resources": [{
|
||||
"resources": [
|
||||
"fonts/*",
|
||||
"images/*"
|
||||
],
|
||||
"matches": ["<all_urls>"]
|
||||
}]
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "JSTAR Tab",
|
||||
"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.",
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "index.html"
|
||||
},
|
||||
"permissions": [
|
||||
"storage",
|
||||
"favicon",
|
||||
"history"
|
||||
],
|
||||
"icons": {
|
||||
"16": "images/icon16.png",
|
||||
"48": "images/icon48.png",
|
||||
"128": "images/icon128.png"
|
||||
},
|
||||
"action": {
|
||||
"default_title": "New JSTAR Tab",
|
||||
"default_icon": {
|
||||
"16": "images/icon16.png",
|
||||
"48": "images/icon48.png",
|
||||
"128": "images/icon128.png"
|
||||
}
|
||||
},
|
||||
"author": "JSTAR",
|
||||
"homepage_url": "https://github.com/DevJSTAR/JSTAR-Tab",
|
||||
"web_accessible_resources": [{
|
||||
"resources": [
|
||||
"fonts/*",
|
||||
"images/*"
|
||||
],
|
||||
"matches": ["<all_urls>"]
|
||||
}]
|
||||
}
|
Loading…
Reference in New Issue
Block a user