forked from xXx_M0mMy_xXx/JSTAR-Tab
Add files via upload
This commit is contained in:
parent
5a8ed20b50
commit
49f25d3405
70
index.html
70
index.html
|
@ -19,13 +19,26 @@
|
||||||
<i class="fas fa-hand-wave"></i>
|
<i class="fas fa-hand-wave"></i>
|
||||||
</div>
|
</div>
|
||||||
<h2>Welcome to JSTAR Tab! 👋</h2>
|
<h2>Welcome to JSTAR Tab! 👋</h2>
|
||||||
<p>Let's personalize your experience</p>
|
<p>Let's get you started</p>
|
||||||
|
<div class="import-options">
|
||||||
|
<button id="start-fresh-btn" class="btn-primary">Start Fresh</button>
|
||||||
|
<div class="or-divider">
|
||||||
|
<span>OR</span>
|
||||||
|
</div>
|
||||||
|
<button id="import-data-btn" class="btn-primary">
|
||||||
|
<i class="fas fa-upload"></i> Import Data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="onboarding-import" accept=".json" style="display: none;">
|
||||||
|
</div>
|
||||||
|
<div class="step hidden" data-step="2">
|
||||||
|
<h2>What's your name?</h2>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" id="user-name" placeholder="What's your name?">
|
<input type="text" id="user-name" placeholder="Enter your name">
|
||||||
</div>
|
</div>
|
||||||
<button id="next-step-btn" class="btn-primary">Continue</button>
|
<button id="next-step-btn" class="btn-primary">Continue</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="step hidden" data-step="2">
|
<div class="step hidden" data-step="3">
|
||||||
<h2>Choose Your Search Engine</h2>
|
<h2>Choose Your Search Engine</h2>
|
||||||
<div class="search-engine-options">
|
<div class="search-engine-options">
|
||||||
<!-- Search engine options -->
|
<!-- Search engine options -->
|
||||||
|
@ -148,7 +161,7 @@
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-label">Custom Greeting</div>
|
<div class="setting-label">Custom Greeting</div>
|
||||||
<input type="text" id="custom-greeting" placeholder="Enter custom greeting">
|
<input type="text" id="custom-greeting" placeholder="Enter custom greeting">
|
||||||
<span class="format-hint">{name}, {time}, {date}, {day}, {month}, {year}</span>
|
<span class="format-hint">{name}, {greeting}, {time}, {date}, {day}, {month}, {year}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-item horizontal">
|
<div class="setting-item horizontal">
|
||||||
|
@ -176,6 +189,47 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Keyboard Shortcuts Settings -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Keyboard Shortcuts</h3>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Settings Menu</div>
|
||||||
|
<div class="keybind-container">
|
||||||
|
<input type="text" id="keybind-settings" placeholder="Press keys..." readonly>
|
||||||
|
<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">
|
||||||
|
<input type="text" id="keybind-anonymous" placeholder="Press keys..." readonly>
|
||||||
|
<button class="clear-keybind" data-for="anonymous">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Toggle Theme</div>
|
||||||
|
<div class="keybind-container">
|
||||||
|
<input type="text" id="keybind-theme" placeholder="Press keys..." readonly>
|
||||||
|
<button class="clear-keybind" data-for="theme">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Quick URL</div>
|
||||||
|
<div class="keybind-container">
|
||||||
|
<input type="text" id="keybind-url-combo" placeholder="Press keys..." readonly>
|
||||||
|
<button class="clear-keybind" data-for="url">×</button>
|
||||||
|
</div>
|
||||||
|
<input type="text" id="keybind-url" placeholder="Enter URL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Data Management -->
|
<!-- Data Management -->
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>Data Management</h3>
|
<h3>Data Management</h3>
|
||||||
|
@ -197,12 +251,11 @@
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>About</h3>
|
<h3>About</h3>
|
||||||
<div class="about-content">
|
<div class="about-content">
|
||||||
<p>JSTAR Tab v2.5.0</p>
|
<p>JSTAR Tab v2.6.0</p>
|
||||||
<p>Author: JSTAR</p>
|
|
||||||
<p>Homepage: <a href="https://github.com/DevJSTAR/JSTAR-Tab" target="_blank">GitHub Repository</a></p>
|
<p>Homepage: <a href="https://github.com/DevJSTAR/JSTAR-Tab" target="_blank">GitHub Repository</a></p>
|
||||||
<p>Latest Update: <a href="https://github.com/DevJSTAR/JSTAR-Tab/releases/latest" target="_blank">Check for updates</a></p>
|
<p>Latest Update: <a href="https://github.com/DevJSTAR/JSTAR-Tab/releases/latest" target="_blank">Check for updates</a></p>
|
||||||
<p>License: <a href="https://github.com/DevJSTAR/JSTAR-Tab/blob/main/LICENSE" target="_blank">MIT License</a></p>
|
<p>License: <a href="https://github.com/DevJSTAR/JSTAR-Tab/blob/main/LICENSE" target="_blank">MIT License</a></p>
|
||||||
<p class="made-with">Made with <i class="fas fa-heart"></i> by JSTAR</p>
|
<p class="made-with">Made with <i class="fas fa-heart"></i> by <a href="https://linktr.ee/jstarsdev" target="_blank">JSTAR</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -273,5 +326,6 @@
|
||||||
<script src="js/shortcuts.js"></script>
|
<script src="js/shortcuts.js"></script>
|
||||||
<script src="js/settings.js"></script>
|
<script src="js/settings.js"></script>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
|
<script src="js/keybinds.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
265
js/keybinds.js
Normal file
265
js/keybinds.js
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
// List of keys that cannot be used as keybinds
|
||||||
|
const FORBIDDEN_KEYS = [
|
||||||
|
'Tab', 'CapsLock', 'Meta', 'ContextMenu',
|
||||||
|
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
|
||||||
|
'Home', 'End', 'PageUp', 'PageDown', 'Insert', 'Delete', 'ScrollLock', 'Pause', 'NumLock'
|
||||||
|
];
|
||||||
|
|
||||||
|
const keybinds = {
|
||||||
|
bindings: {},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.bindings = Storage.get('keybinds') || {};
|
||||||
|
|
||||||
|
// URL keybind handling
|
||||||
|
const urlInput = document.getElementById('keybind-url');
|
||||||
|
const urlComboInput = document.getElementById('keybind-url-combo');
|
||||||
|
|
||||||
|
if (this.bindings.url) {
|
||||||
|
urlInput.value = this.bindings.url.url || '';
|
||||||
|
urlComboInput.value = this.bindings.url.keys || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastSavedUrl = urlInput.value;
|
||||||
|
|
||||||
|
function isValidUrl(string) {
|
||||||
|
try {
|
||||||
|
const urlString = string.match(/^https?:\/\//) ? string : `https://${string}`;
|
||||||
|
new URL(urlString);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urlInput.addEventListener('input', () => {
|
||||||
|
if (!this.bindings.url) {
|
||||||
|
this.bindings.url = { url: '', keys: '' };
|
||||||
|
}
|
||||||
|
this.bindings.url.url = urlInput.value;
|
||||||
|
Storage.set('keybinds', this.bindings);
|
||||||
|
});
|
||||||
|
|
||||||
|
urlInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
urlInput.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
urlInput.addEventListener('blur', () => {
|
||||||
|
const currentUrl = urlInput.value.trim();
|
||||||
|
|
||||||
|
if (currentUrl === lastSavedUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentUrl) {
|
||||||
|
if (isValidUrl(currentUrl)) {
|
||||||
|
lastSavedUrl = currentUrl;
|
||||||
|
notifications.show('URL saved.', 'success');
|
||||||
|
} else {
|
||||||
|
notifications.show('Please enter a valid URL.', 'error');
|
||||||
|
urlInput.value = lastSavedUrl;
|
||||||
|
this.bindings.url.url = lastSavedUrl;
|
||||||
|
Storage.set('keybinds', this.bindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keybind input handling
|
||||||
|
const keybindInputs = document.querySelectorAll('[id^="keybind-"]');
|
||||||
|
keybindInputs.forEach(input => {
|
||||||
|
if (input.id === 'keybind-url') {
|
||||||
|
const urlBinding = this.bindings['url'];
|
||||||
|
if (urlBinding && urlBinding.url) {
|
||||||
|
input.value = urlBinding.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
if (this.bindings['url']) {
|
||||||
|
this.bindings['url'].url = input.value;
|
||||||
|
Storage.set('keybinds', this.bindings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = input.id.replace('keybind-url-combo', 'url').replace('keybind-', '');
|
||||||
|
if (this.bindings[action]) {
|
||||||
|
input.value = this.bindings[action].keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentKeys = new Set();
|
||||||
|
let isProcessingKeybind = false;
|
||||||
|
|
||||||
|
input.addEventListener('keydown', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
input.blur();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
notifications.show('CTRL key combinations are not allowed.', 'error');
|
||||||
|
isProcessingKeybind = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FORBIDDEN_KEYS.includes(e.key)) {
|
||||||
|
notifications.show('This key cannot be used as a keybind.', 'error');
|
||||||
|
isProcessingKeybind = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isProcessingKeybind = false;
|
||||||
|
|
||||||
|
if (e.key !== 'Alt' && e.key !== 'Shift') {
|
||||||
|
currentKeys.add(e.key);
|
||||||
|
}
|
||||||
|
if (e.altKey) currentKeys.add('Alt');
|
||||||
|
if (e.shiftKey) currentKeys.add('Shift');
|
||||||
|
|
||||||
|
input.value = Array.from(currentKeys).join('+');
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('keyup', (e) => {
|
||||||
|
if (isProcessingKeybind) {
|
||||||
|
currentKeys.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Alt' || e.key === 'Shift') {
|
||||||
|
if (currentKeys.size === 1) {
|
||||||
|
notifications.show('Add another key with Alt or Shift.', 'error');
|
||||||
|
}
|
||||||
|
currentKeys.clear();
|
||||||
|
input.value = this.bindings[action]?.keys || '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const combo = Array.from(currentKeys).join('+');
|
||||||
|
|
||||||
|
if (!combo) return;
|
||||||
|
|
||||||
|
const duplicate = Object.entries(this.bindings).find(([key, value]) =>
|
||||||
|
value.keys === combo && key !== action
|
||||||
|
);
|
||||||
|
|
||||||
|
if (duplicate) {
|
||||||
|
notifications.show('This keybind is already in use.', 'error');
|
||||||
|
currentKeys.clear();
|
||||||
|
input.value = this.bindings[action]?.keys || '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bindings[action] = {
|
||||||
|
keys: combo,
|
||||||
|
url: action === 'url' ? document.getElementById('keybind-url').value : null
|
||||||
|
};
|
||||||
|
Storage.set('keybinds', this.bindings);
|
||||||
|
notifications.show('Keybind saved.', 'success');
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('blur', () => {
|
||||||
|
currentKeys.clear();
|
||||||
|
input.value = this.bindings[action]?.keys || '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear keybind button handling
|
||||||
|
document.querySelectorAll('.clear-keybind').forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const action = button.dataset.for;
|
||||||
|
const input = document.getElementById(`keybind-${action}-combo`) ||
|
||||||
|
document.getElementById(`keybind-${action}`);
|
||||||
|
|
||||||
|
input.value = '';
|
||||||
|
if (action === 'url') {
|
||||||
|
document.getElementById('keybind-url').value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this.bindings[action];
|
||||||
|
Storage.set('keybinds', this.bindings);
|
||||||
|
notifications.show('Keybind removed.', 'success');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global keybind listener
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.target.tagName === 'INPUT') return;
|
||||||
|
|
||||||
|
const keys = [];
|
||||||
|
if (e.altKey) keys.push('Alt');
|
||||||
|
if (e.shiftKey) keys.push('Shift');
|
||||||
|
if (e.key !== 'Alt' && e.key !== 'Shift') keys.push(e.key);
|
||||||
|
|
||||||
|
const combo = keys.join('+');
|
||||||
|
|
||||||
|
Object.entries(this.bindings).forEach(([action, binding]) => {
|
||||||
|
if (binding.keys === combo) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.executeAction(action, binding);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Execute the action associated with a keybind
|
||||||
|
executeAction(action, binding) {
|
||||||
|
const activeModal = document.querySelector('.modal.active');
|
||||||
|
if (activeModal) {
|
||||||
|
closeModal(activeModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'settings':
|
||||||
|
const settingsModal = document.getElementById('settings-modal');
|
||||||
|
if (settingsModal === activeModal) {
|
||||||
|
notifications.show('Settings closed.', 'info');
|
||||||
|
settings.updateSettingsUI();
|
||||||
|
} else {
|
||||||
|
notifications.show('Opening settings...', 'info');
|
||||||
|
settings.updateSettingsUI();
|
||||||
|
openModal(settingsModal);
|
||||||
|
}
|
||||||
|
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 closed.', 'info');
|
||||||
|
} else {
|
||||||
|
notifications.show('Opening add shortcut...', 'info');
|
||||||
|
openModal(shortcutModal);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'anonymous':
|
||||||
|
settings.toggleAnonymousMode();
|
||||||
|
break;
|
||||||
|
case 'theme':
|
||||||
|
settings.toggleTheme();
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
37
js/main.js
37
js/main.js
|
@ -1,3 +1,4 @@
|
||||||
|
// Greeting functionality
|
||||||
async function updateGreeting() {
|
async function updateGreeting() {
|
||||||
const greeting = document.getElementById('greeting');
|
const greeting = document.getElementById('greeting');
|
||||||
if (!greeting) return;
|
if (!greeting) return;
|
||||||
|
@ -15,7 +16,6 @@ async function updateGreeting() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to default greeting if custom format is not set or fails
|
|
||||||
const hour = new Date().getHours();
|
const hour = new Date().getHours();
|
||||||
const isAnonymous = Storage.get('anonymousMode') || false;
|
const isAnonymous = Storage.get('anonymousMode') || false;
|
||||||
const userName = isAnonymous ?
|
const userName = isAnonymous ?
|
||||||
|
@ -35,7 +35,7 @@ async function updateGreeting() {
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up event listeners for modal interactions
|
// Modal handling
|
||||||
function initModalHandlers() {
|
function initModalHandlers() {
|
||||||
const modals = document.querySelectorAll('.modal');
|
const modals = document.querySelectorAll('.modal');
|
||||||
|
|
||||||
|
@ -52,10 +52,18 @@ function initModalHandlers() {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.modal .close-button').forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const modal = button.closest('.modal');
|
||||||
|
if (modal) {
|
||||||
|
closeModal(modal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open modal with animation
|
|
||||||
function openModal(modal) {
|
function openModal(modal) {
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
|
@ -64,7 +72,6 @@ function openModal(modal) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close modal with animation
|
|
||||||
function closeModal(modal) {
|
function closeModal(modal) {
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
modal.classList.remove('active');
|
modal.classList.remove('active');
|
||||||
|
@ -73,9 +80,8 @@ function closeModal(modal) {
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize application
|
// Application initialization
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Apply visibility settings
|
|
||||||
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
||||||
const isVisible = Storage.get(`show_${element}`);
|
const isVisible = Storage.get(`show_${element}`);
|
||||||
if (isVisible === false) {
|
if (isVisible === false) {
|
||||||
|
@ -84,28 +90,39 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start onboarding or show main content
|
|
||||||
if (!Storage.get('onboardingComplete')) {
|
if (!Storage.get('onboardingComplete')) {
|
||||||
onboarding.start();
|
onboarding.start();
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('main-content').classList.remove('hidden');
|
document.getElementById('main-content').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize features
|
|
||||||
search.init();
|
search.init();
|
||||||
shortcuts.init();
|
shortcuts.init();
|
||||||
settings.init();
|
settings.init();
|
||||||
initModalHandlers();
|
initModalHandlers();
|
||||||
|
|
||||||
// Set up greeting
|
|
||||||
updateGreeting();
|
updateGreeting();
|
||||||
setInterval(updateGreeting, 60000);
|
setInterval(updateGreeting, 60000);
|
||||||
|
|
||||||
// Settings button handler
|
|
||||||
const settingsButton = document.getElementById('settings-button');
|
const settingsButton = document.getElementById('settings-button');
|
||||||
const settingsModal = document.getElementById('settings-modal');
|
const settingsModal = document.getElementById('settings-modal');
|
||||||
|
|
||||||
settingsButton.addEventListener('click', () => {
|
settingsButton.addEventListener('click', () => {
|
||||||
openModal(settingsModal);
|
openModal(settingsModal);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
keybinds.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global keydown event handler
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
const activeModal = document.querySelector('.modal.active');
|
||||||
|
if (activeModal && !activeModal.matches('#settings-modal')) {
|
||||||
|
const primaryButton = activeModal.querySelector('.btn-primary');
|
||||||
|
if (primaryButton) {
|
||||||
|
primaryButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
|
@ -1,24 +1,84 @@
|
||||||
// Onboarding module
|
// Onboarding module
|
||||||
const onboarding = {
|
const onboarding = {
|
||||||
// Check if onboarding is complete
|
// Core functions
|
||||||
isComplete: () => {
|
isComplete: () => {
|
||||||
return Storage.get('onboardingComplete') === true;
|
return Storage.get('onboardingComplete') === true;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Start the onboarding process
|
|
||||||
start: () => {
|
start: () => {
|
||||||
const modal = document.getElementById('onboarding-modal');
|
const modal = document.getElementById('onboarding-modal');
|
||||||
const mainContent = document.getElementById('main-content');
|
const mainContent = document.getElementById('main-content');
|
||||||
|
const importDataBtn = document.getElementById('import-data-btn');
|
||||||
|
const startFreshBtn = document.getElementById('start-fresh-btn');
|
||||||
|
const fileInput = document.getElementById('onboarding-import');
|
||||||
|
|
||||||
if (!onboarding.isComplete()) {
|
if (!onboarding.isComplete()) {
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
modal.classList.add('active');
|
modal.classList.add('active');
|
||||||
mainContent.classList.add('hidden');
|
mainContent.classList.add('hidden');
|
||||||
|
|
||||||
document.getElementById('next-step-btn').addEventListener('click', () => onboarding.nextStep(1));
|
startFreshBtn.addEventListener('click', () => {
|
||||||
document.getElementById('complete-setup-btn').addEventListener('click', onboarding.complete);
|
document.querySelector('[data-step="1"]').classList.add('hidden');
|
||||||
|
document.querySelector('[data-step="2"]').classList.remove('hidden');
|
||||||
|
document.getElementById('next-step-btn').addEventListener('click', () => onboarding.nextStep(2));
|
||||||
|
});
|
||||||
|
|
||||||
// Set up search engine selection
|
importDataBtn.addEventListener('click', () => fileInput.click());
|
||||||
|
|
||||||
|
// Data import handling
|
||||||
|
fileInput.addEventListener('change', async (e) => {
|
||||||
|
if (e.target.files.length > 0) {
|
||||||
|
try {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const text = await file.text();
|
||||||
|
const data = JSON.parse(text);
|
||||||
|
|
||||||
|
if (!data.settings || typeof data.settings !== 'object' ||
|
||||||
|
!Array.isArray(data.shortcuts)) {
|
||||||
|
throw new Error('Invalid data structure');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(data.settings).forEach(([key, value]) => {
|
||||||
|
Storage.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
Storage.set('shortcuts', data.shortcuts);
|
||||||
|
|
||||||
|
if (data.keybinds) {
|
||||||
|
Storage.set('keybinds', data.keybinds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize components
|
||||||
|
search.init();
|
||||||
|
shortcuts.init();
|
||||||
|
settings.init();
|
||||||
|
updateGreeting();
|
||||||
|
document.body.setAttribute('data-theme', data.settings.theme || 'light');
|
||||||
|
|
||||||
|
// Update UI visibility
|
||||||
|
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
||||||
|
const isVisible = data.settings[`show_${element}`];
|
||||||
|
const elementNode = document.getElementById(element === 'search' ? 'search-container' : element);
|
||||||
|
if (elementNode) {
|
||||||
|
elementNode.style.display = isVisible === false ? 'none' : 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Storage.set('onboardingComplete', true);
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
mainContent.classList.remove('hidden');
|
||||||
|
|
||||||
|
const userName = Storage.get('userName') || 'Guest';
|
||||||
|
notifications.show(`Welcome back, ${userName}! 👋`, 'success');
|
||||||
|
} catch (error) {
|
||||||
|
notifications.show('Failed to import data: Invalid file format!', 'error');
|
||||||
|
fileInput.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Search engine selection
|
||||||
const engines = document.querySelectorAll('.search-engine-option');
|
const engines = document.querySelectorAll('.search-engine-option');
|
||||||
engines.forEach(engine => {
|
engines.forEach(engine => {
|
||||||
engine.addEventListener('click', () => {
|
engine.addEventListener('click', () => {
|
||||||
|
@ -26,31 +86,34 @@ const onboarding = {
|
||||||
engine.classList.add('selected');
|
engine.classList.add('selected');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('complete-setup-btn').addEventListener('click', onboarding.complete);
|
||||||
} else {
|
} else {
|
||||||
modal.classList.add('hidden');
|
modal.classList.add('hidden');
|
||||||
mainContent.classList.remove('hidden');
|
mainContent.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Move to the next step in onboarding
|
// Onboarding step navigation
|
||||||
nextStep: (currentStep) => {
|
nextStep: (currentStep) => {
|
||||||
const currentStepEl = document.querySelector(`[data-step="${currentStep}"]`);
|
const currentStepEl = document.querySelector(`[data-step="${currentStep}"]`);
|
||||||
const nextStepEl = document.querySelector(`[data-step="${currentStep + 1}"]`);
|
const nextStepEl = document.querySelector(`[data-step="${currentStep + 1}"]`);
|
||||||
const name = document.getElementById('user-name').value.trim();
|
|
||||||
|
|
||||||
if (!name) {
|
if (currentStep === 2) {
|
||||||
notifications.show('Please enter your name!', 'error');
|
const name = document.getElementById('user-name').value.trim();
|
||||||
return;
|
if (!name) {
|
||||||
|
notifications.show('Please enter your name!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Storage.set('userName', name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage.set('userName', name);
|
|
||||||
|
|
||||||
currentStepEl.classList.add('hidden');
|
currentStepEl.classList.add('hidden');
|
||||||
nextStepEl.classList.remove('hidden');
|
nextStepEl.classList.remove('hidden');
|
||||||
nextStepEl.classList.add('visible');
|
nextStepEl.classList.add('visible');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Complete the onboarding process
|
// Finalize onboarding
|
||||||
complete: () => {
|
complete: () => {
|
||||||
const selectedEngine = document.querySelector('.search-engine-option.selected');
|
const selectedEngine = document.querySelector('.search-engine-option.selected');
|
||||||
if (!selectedEngine) {
|
if (!selectedEngine) {
|
||||||
|
|
159
js/settings.js
159
js/settings.js
|
@ -10,7 +10,6 @@ const anonymousNames = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define getTimeBasedGreeting function
|
|
||||||
const getTimeBasedGreeting = () => {
|
const getTimeBasedGreeting = () => {
|
||||||
const hour = new Date().getHours();
|
const hour = new Date().getHours();
|
||||||
if (hour < 12) return 'Good Morning';
|
if (hour < 12) return 'Good Morning';
|
||||||
|
@ -21,7 +20,8 @@ const getTimeBasedGreeting = () => {
|
||||||
|
|
||||||
// Main settings object
|
// Main settings object
|
||||||
const settings = {
|
const settings = {
|
||||||
// Toggle between light and dark themes
|
GREETING_MAX_LENGTH: 60,
|
||||||
|
|
||||||
toggleTheme: () => {
|
toggleTheme: () => {
|
||||||
const currentTheme = document.body.getAttribute('data-theme');
|
const currentTheme = document.body.getAttribute('data-theme');
|
||||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||||
|
@ -32,7 +32,6 @@ const settings = {
|
||||||
themeIcon.className = `fas fa-${newTheme === 'dark' ? 'sun' : 'moon'}`;
|
themeIcon.className = `fas fa-${newTheme === 'dark' ? 'sun' : 'moon'}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle anonymous mode
|
|
||||||
toggleAnonymousMode: () => {
|
toggleAnonymousMode: () => {
|
||||||
const isAnonymous = Storage.get('anonymousMode') || false;
|
const isAnonymous = Storage.get('anonymousMode') || false;
|
||||||
Storage.set('anonymousMode', !isAnonymous);
|
Storage.set('anonymousMode', !isAnonymous);
|
||||||
|
@ -50,7 +49,6 @@ const settings = {
|
||||||
updateGreeting();
|
updateGreeting();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update the search engine
|
|
||||||
updateSearchEngine: (engine) => {
|
updateSearchEngine: (engine) => {
|
||||||
Storage.set('searchEngine', engine);
|
Storage.set('searchEngine', engine);
|
||||||
notifications.show('Search engine updated successfully!', 'success');
|
notifications.show('Search engine updated successfully!', 'success');
|
||||||
|
@ -198,8 +196,25 @@ const settings = {
|
||||||
settings.updateVisibility();
|
settings.updateVisibility();
|
||||||
|
|
||||||
const customGreetingInput = document.getElementById('custom-greeting');
|
const customGreetingInput = document.getElementById('custom-greeting');
|
||||||
|
customGreetingInput.maxLength = settings.GREETING_MAX_LENGTH;
|
||||||
|
|
||||||
|
customGreetingInput.addEventListener('input', (e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
if (value.length > settings.GREETING_MAX_LENGTH) {
|
||||||
|
e.target.value = value.substring(0, settings.GREETING_MAX_LENGTH);
|
||||||
|
notifications.show(`Custom greeting must be less than ${settings.GREETING_MAX_LENGTH} characters`, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
customGreetingInput.addEventListener('change', (e) => {
|
customGreetingInput.addEventListener('change', (e) => {
|
||||||
const value = e.target.value.trim();
|
const value = e.target.value.trim();
|
||||||
|
|
||||||
|
if (value.length > settings.GREETING_MAX_LENGTH) {
|
||||||
|
e.target.value = value.substring(0, settings.GREETING_MAX_LENGTH);
|
||||||
|
notifications.show(`Custom greeting must be less than ${settings.GREETING_MAX_LENGTH} characters`, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
Storage.remove('customGreeting');
|
Storage.remove('customGreeting');
|
||||||
notifications.show('Using default greeting format', 'info');
|
notifications.show('Using default greeting format', 'info');
|
||||||
|
@ -239,15 +254,32 @@ const settings = {
|
||||||
try {
|
try {
|
||||||
if (!format) return null;
|
if (!format) return null;
|
||||||
|
|
||||||
|
const validTokens = ['{name}', '{greeting}', '{time}', '{date}', '{day}', '{month}', '{year}'];
|
||||||
|
const tokens = format.match(/{[^}]+}/g) || [];
|
||||||
|
|
||||||
|
if (!tokens.every(token => validTokens.includes(token))) {
|
||||||
|
notifications.show('Invalid greeting format: Unknown token used', 'error');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
const hour = now.getHours();
|
||||||
|
let timeGreeting = 'good night';
|
||||||
|
if (hour >= 5 && hour < 12) timeGreeting = 'good morning';
|
||||||
|
else if (hour >= 12 && hour < 17) timeGreeting = 'good afternoon';
|
||||||
|
else if (hour >= 17 && hour < 20) timeGreeting = 'good evening';
|
||||||
|
|
||||||
const userName = Storage.get('anonymousMode') ?
|
const userName = Storage.get('anonymousMode') ?
|
||||||
(Storage.get('anonymousName') || anonymousNames.generate()) :
|
(Storage.get('anonymousName') || anonymousNames.generate()) :
|
||||||
(Storage.get('userName') || 'Friend');
|
(Storage.get('userName') || 'Friend');
|
||||||
|
|
||||||
const formats = {
|
const formats = {
|
||||||
name: userName,
|
name: userName,
|
||||||
|
greeting: format.startsWith('{greeting}') ?
|
||||||
|
timeGreeting.charAt(0).toUpperCase() + timeGreeting.slice(1) :
|
||||||
|
timeGreeting,
|
||||||
time: now.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }),
|
time: now.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }),
|
||||||
date: now.toLocaleDateString(),
|
date: now.getDate().toString(),
|
||||||
day: now.toLocaleDateString([], { weekday: 'long' }),
|
day: now.toLocaleDateString([], { weekday: 'long' }),
|
||||||
month: now.toLocaleDateString([], { month: 'long' }),
|
month: now.toLocaleDateString([], { month: 'long' }),
|
||||||
year: now.getFullYear()
|
year: now.getFullYear()
|
||||||
|
@ -267,38 +299,113 @@ const settings = {
|
||||||
|
|
||||||
return formattedGreeting;
|
return formattedGreeting;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Storage.remove('customGreeting');
|
notifications.show('Invalid greeting format. Using default.', 'error');
|
||||||
notifications.show('Error in greeting format. Using default.', 'error');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Data management functions
|
||||||
exportData: () => {
|
exportData: () => {
|
||||||
const data = {
|
const data = {
|
||||||
settings: {
|
settings: {
|
||||||
theme: document.body.getAttribute('data-theme'),
|
theme: Storage.get('theme'),
|
||||||
userName: Storage.get('userName'),
|
userName: Storage.get('userName'),
|
||||||
anonymousMode: Storage.get('anonymousMode'),
|
anonymousMode: Storage.get('anonymousMode'),
|
||||||
anonymousName: Storage.get('anonymousName'),
|
anonymousName: Storage.get('anonymousName'),
|
||||||
searchEngine: Storage.get('searchEngine'),
|
searchEngine: Storage.get('searchEngine'),
|
||||||
|
customGreeting: Storage.get('customGreeting'),
|
||||||
show_greeting: Storage.get('show_greeting'),
|
show_greeting: Storage.get('show_greeting'),
|
||||||
show_search: Storage.get('show_search'),
|
show_search: Storage.get('show_search'),
|
||||||
show_shortcuts: Storage.get('show_shortcuts'),
|
show_shortcuts: Storage.get('show_shortcuts'),
|
||||||
show_addShortcut: Storage.get('show_addShortcut'),
|
show_addShortcut: Storage.get('show_addShortcut')
|
||||||
customGreeting: Storage.get('customGreeting')
|
|
||||||
},
|
},
|
||||||
shortcuts: Storage.get('shortcuts') || []
|
shortcuts: Storage.get('shortcuts') || [],
|
||||||
|
keybinds: Storage.get('keybinds') || {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!data.settings || !data.shortcuts || !data.keybinds) {
|
||||||
|
notifications.show('Failed to export data: Invalid data structure.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'jstar-tab-settings.json';
|
a.download = 'jstar-tab-backup.json';
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
|
},
|
||||||
|
|
||||||
|
importData: async (file) => {
|
||||||
|
const text = await file.text();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(text);
|
||||||
|
|
||||||
|
if (!data.settings || typeof data.settings !== 'object' ||
|
||||||
|
!Array.isArray(data.shortcuts) ||
|
||||||
|
!data.keybinds || typeof data.keybinds !== 'object') {
|
||||||
|
throw new Error('Invalid data structure');
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(data.settings).forEach(([key, value]) => {
|
||||||
|
Storage.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
Storage.set('shortcuts', data.shortcuts);
|
||||||
|
|
||||||
|
const validatedKeybinds = {};
|
||||||
|
Object.entries(data.keybinds).forEach(([action, binding]) => {
|
||||||
|
if (binding && typeof binding === 'object' &&
|
||||||
|
typeof binding.keys === 'string' &&
|
||||||
|
(!binding.url || typeof binding.url === 'string')) {
|
||||||
|
validatedKeybinds[action] = binding;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Storage.set('keybinds', validatedKeybinds);
|
||||||
|
|
||||||
|
settings.updateSettingsUI();
|
||||||
|
settings.updateVisibility();
|
||||||
|
shortcuts.render();
|
||||||
|
document.body.setAttribute('data-theme', data.settings.theme || 'light');
|
||||||
|
|
||||||
|
notifications.show('Data imported successfully!', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
notifications.show('Failed to import data: Invalid file format!', 'error');
|
||||||
|
console.error('Import error:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
resetData: () => {
|
||||||
|
const defaultSettings = {
|
||||||
|
theme: 'light',
|
||||||
|
userName: '',
|
||||||
|
anonymousMode: false,
|
||||||
|
searchEngine: 'Google',
|
||||||
|
show_greeting: true,
|
||||||
|
show_search: true,
|
||||||
|
show_shortcuts: true,
|
||||||
|
show_addShortcut: true
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(defaultSettings).forEach(([key, value]) => {
|
||||||
|
Storage.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
Storage.remove('shortcuts');
|
||||||
|
Storage.remove('keybinds');
|
||||||
|
Storage.remove('anonymousName');
|
||||||
|
Storage.remove('customGreeting');
|
||||||
|
|
||||||
|
settings.updateSettingsUI();
|
||||||
|
settings.updateVisibility();
|
||||||
|
shortcuts.render();
|
||||||
|
document.body.setAttribute('data-theme', 'light');
|
||||||
|
|
||||||
|
notifications.show('All data has been reset!', 'success');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -309,8 +416,10 @@ settings.initDataManagement = () => {
|
||||||
const resetBtn = document.getElementById('reset-data');
|
const resetBtn = document.getElementById('reset-data');
|
||||||
const fileInput = document.getElementById('import-file');
|
const fileInput = document.getElementById('import-file');
|
||||||
|
|
||||||
// Export Data
|
exportBtn.replaceWith(exportBtn.cloneNode(true));
|
||||||
exportBtn.addEventListener('click', () => {
|
const newExportBtn = document.getElementById('export-data');
|
||||||
|
|
||||||
|
newExportBtn.addEventListener('click', () => {
|
||||||
try {
|
try {
|
||||||
const data = {
|
const data = {
|
||||||
settings: {
|
settings: {
|
||||||
|
@ -325,7 +434,8 @@ settings.initDataManagement = () => {
|
||||||
show_addShortcut: Storage.get('show_addShortcut'),
|
show_addShortcut: Storage.get('show_addShortcut'),
|
||||||
customGreeting: Storage.get('customGreeting')
|
customGreeting: Storage.get('customGreeting')
|
||||||
},
|
},
|
||||||
shortcuts: Storage.get('shortcuts') || []
|
shortcuts: Storage.get('shortcuts') || [],
|
||||||
|
keybinds: Storage.get('keybinds') || {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||||
|
@ -345,7 +455,6 @@ settings.initDataManagement = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Import Data
|
|
||||||
importBtn.addEventListener('click', () => fileInput.click());
|
importBtn.addEventListener('click', () => fileInput.click());
|
||||||
|
|
||||||
fileInput.addEventListener('change', (e) => {
|
fileInput.addEventListener('change', (e) => {
|
||||||
|
@ -357,7 +466,7 @@ settings.initDataManagement = () => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.target.result);
|
const data = JSON.parse(event.target.result);
|
||||||
|
|
||||||
if (!data.shortcuts || !data.settings) {
|
if (!data.shortcuts || !data.settings || !data.keybinds) {
|
||||||
throw new Error('Invalid backup file format');
|
throw new Error('Invalid backup file format');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,6 +474,19 @@ settings.initDataManagement = () => {
|
||||||
Storage.set(key, value);
|
Storage.set(key, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (data.keybinds) {
|
||||||
|
const validatedKeybinds = {};
|
||||||
|
Object.entries(data.keybinds).forEach(([action, binding]) => {
|
||||||
|
if (binding && typeof binding === 'object' &&
|
||||||
|
typeof binding.keys === 'string' &&
|
||||||
|
(!binding.url || typeof binding.url === 'string')) {
|
||||||
|
validatedKeybinds[action] = binding;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Storage.set('keybinds', validatedKeybinds);
|
||||||
|
keybinds.init();
|
||||||
|
}
|
||||||
|
|
||||||
Storage.set('shortcuts', data.shortcuts);
|
Storage.set('shortcuts', data.shortcuts);
|
||||||
|
|
||||||
fileInput.value = '';
|
fileInput.value = '';
|
||||||
|
@ -454,7 +576,6 @@ settings.initDataManagement = () => {
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset Data
|
|
||||||
resetBtn.addEventListener('click', () => {
|
resetBtn.addEventListener('click', () => {
|
||||||
const confirmReset = confirm('Are you sure you want to reset all data? This action cannot be undone.');
|
const confirmReset = confirm('Are you sure you want to reset all data? This action cannot be undone.');
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const shortcuts = {
|
const shortcuts = {
|
||||||
MAX_SHORTCUTS: 12,
|
MAX_SHORTCUTS: 12,
|
||||||
|
|
||||||
// Validate and format URL
|
// URL Validation
|
||||||
validateAndFormatUrl: (url) => {
|
validateAndFormatUrl: (url) => {
|
||||||
if (!/^https?:\/\//i.test(url)) {
|
if (!/^https?:\/\//i.test(url)) {
|
||||||
url = 'https://' + url;
|
url = 'https://' + url;
|
||||||
|
@ -15,7 +15,7 @@ const shortcuts = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Add new shortcut
|
// Shortcut Management
|
||||||
add: (url, name) => {
|
add: (url, name) => {
|
||||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||||
if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) {
|
if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) {
|
||||||
|
@ -34,7 +34,6 @@ const shortcuts = {
|
||||||
shortcuts.render();
|
shortcuts.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Remove shortcut
|
|
||||||
remove: (index) => {
|
remove: (index) => {
|
||||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||||
currentShortcuts.splice(index, 1);
|
currentShortcuts.splice(index, 1);
|
||||||
|
@ -43,7 +42,6 @@ const shortcuts = {
|
||||||
notifications.show('Shortcut removed!', 'success');
|
notifications.show('Shortcut removed!', 'success');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Edit existing shortcut
|
|
||||||
edit: (index, newUrl, newName) => {
|
edit: (index, newUrl, newName) => {
|
||||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||||
currentShortcuts[index] = { url: newUrl, name: newName };
|
currentShortcuts[index] = { url: newUrl, name: newName };
|
||||||
|
@ -52,7 +50,7 @@ const shortcuts = {
|
||||||
notifications.show('Shortcut updated!', 'success');
|
notifications.show('Shortcut updated!', 'success');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Show context menu for shortcut
|
// UI Interactions
|
||||||
showContextMenu: (e, index) => {
|
showContextMenu: (e, index) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const menu = document.getElementById('context-menu');
|
const menu = document.getElementById('context-menu');
|
||||||
|
@ -75,7 +73,7 @@ const shortcuts = {
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Render shortcuts grid
|
// Rendering
|
||||||
render: () => {
|
render: () => {
|
||||||
const grid = document.getElementById('shortcuts-grid');
|
const grid = document.getElementById('shortcuts-grid');
|
||||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||||
|
@ -130,9 +128,23 @@ const shortcuts = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Initialize shortcuts functionality
|
// Initialization
|
||||||
init: () => {
|
init: () => {
|
||||||
const addShortcutButton = document.getElementById('add-shortcut');
|
const addShortcutButton = document.getElementById('add-shortcut');
|
||||||
|
const modal = document.getElementById('add-shortcut-modal');
|
||||||
|
const closeBtn = modal.querySelector('.close-modal');
|
||||||
|
|
||||||
|
if (closeBtn) {
|
||||||
|
closeBtn.addEventListener('click', () => {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.getElementById('shortcut-url').value = '';
|
||||||
|
document.getElementById('shortcut-name').value = '';
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (addShortcutButton) {
|
if (addShortcutButton) {
|
||||||
addShortcutButton.addEventListener('click', (e) => {
|
addShortcutButton.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -143,7 +155,6 @@ const shortcuts = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = document.getElementById('add-shortcut-modal');
|
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
modal.classList.add('active');
|
modal.classList.add('active');
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "JSTAR Tab",
|
"name": "JSTAR Tab",
|
||||||
"version": "2.5.0",
|
"version": "2.6.0",
|
||||||
"description": "JSTAR Tab is a sleek, customizable new tab extension with personalized greetings, shortcuts, anonymous mode, search engine settings, themes, data management, and more, for an enhanced browsing experience.",
|
"description": "JSTAR Tab is a sleek, customizable new tab extension with personalized greetings, shortcuts, anonymous mode, search engine settings, themes, data management, and more, for an enhanced browsing experience.",
|
||||||
"chrome_url_overrides": {
|
"chrome_url_overrides": {
|
||||||
"newtab": "index.html"
|
"newtab": "index.html"
|
||||||
|
|
75
style.css
75
style.css
|
@ -416,7 +416,7 @@ input[type="text"]:focus {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header h2 {
|
.modal-header h2 {
|
||||||
|
@ -445,6 +445,7 @@ input[type="text"]:focus {
|
||||||
|
|
||||||
.settings-section:last-child {
|
.settings-section:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-section h3 {
|
.settings-section h3 {
|
||||||
|
@ -518,6 +519,7 @@ input[type="text"]:focus {
|
||||||
bottom: 3px;
|
bottom: 3px;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:checked + .toggle-slider {
|
input:checked + .toggle-slider {
|
||||||
|
@ -528,6 +530,35 @@ input:checked + .toggle-slider:before {
|
||||||
transform: translateX(24px);
|
transform: translateX(24px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#keybind-url-combo {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keybind-url {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keybind-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-keybind {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
background: var(--accent);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-keybind:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.format-hint {
|
.format-hint {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
@ -916,4 +947,46 @@ input:checked + .toggle-slider:before {
|
||||||
padding: 0.875rem;
|
padding: 0.875rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider::before,
|
||||||
|
.or-divider::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
width: calc(50% - 24px);
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider::before {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider::after {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider span {
|
||||||
|
background-color: var(--background);
|
||||||
|
padding: 0 8px;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.9rem;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user