mirror of
https://github.com/DevJSTAR/JSTAR-Tab.git
synced 2025-04-18 17:35:26 +00:00
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>
|
||||
</div>
|
||||
<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">
|
||||
<input type="text" id="user-name" placeholder="What's your name?">
|
||||
<input type="text" id="user-name" placeholder="Enter your name">
|
||||
</div>
|
||||
<button id="next-step-btn" class="btn-primary">Continue</button>
|
||||
</div>
|
||||
<div class="step hidden" data-step="2">
|
||||
<div class="step hidden" data-step="3">
|
||||
<h2>Choose Your Search Engine</h2>
|
||||
<div class="search-engine-options">
|
||||
<!-- Search engine options -->
|
||||
|
@ -148,7 +161,7 @@
|
|||
<div class="setting-item">
|
||||
<div class="setting-label">Custom Greeting</div>
|
||||
<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 class="setting-item horizontal">
|
||||
|
@ -176,6 +189,47 @@
|
|||
</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 -->
|
||||
<div class="settings-section">
|
||||
<h3>Data Management</h3>
|
||||
|
@ -197,12 +251,11 @@
|
|||
<div class="settings-section">
|
||||
<h3>About</h3>
|
||||
<div class="about-content">
|
||||
<p>JSTAR Tab v2.5.0</p>
|
||||
<p>Author: JSTAR</p>
|
||||
<p>JSTAR Tab v2.6.0</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>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>
|
||||
|
@ -273,5 +326,6 @@
|
|||
<script src="js/shortcuts.js"></script>
|
||||
<script src="js/settings.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
<script src="js/keybinds.js"></script>
|
||||
</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() {
|
||||
const greeting = document.getElementById('greeting');
|
||||
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 isAnonymous = Storage.get('anonymousMode') || false;
|
||||
const userName = isAnonymous ?
|
||||
|
@ -35,7 +35,7 @@ async function updateGreeting() {
|
|||
}, 100);
|
||||
}
|
||||
|
||||
// Set up event listeners for modal interactions
|
||||
// Modal handling
|
||||
function initModalHandlers() {
|
||||
const modals = document.querySelectorAll('.modal');
|
||||
|
||||
|
@ -52,10 +52,18 @@ function initModalHandlers() {
|
|||
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) {
|
||||
if (!modal) return;
|
||||
modal.classList.remove('hidden');
|
||||
|
@ -64,7 +72,6 @@ function openModal(modal) {
|
|||
});
|
||||
}
|
||||
|
||||
// Close modal with animation
|
||||
function closeModal(modal) {
|
||||
if (!modal) return;
|
||||
modal.classList.remove('active');
|
||||
|
@ -73,9 +80,8 @@ function closeModal(modal) {
|
|||
}, 300);
|
||||
}
|
||||
|
||||
// Initialize application
|
||||
// Application initialization
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Apply visibility settings
|
||||
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
||||
const isVisible = Storage.get(`show_${element}`);
|
||||
if (isVisible === false) {
|
||||
|
@ -84,28 +90,39 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
}
|
||||
});
|
||||
|
||||
// Start onboarding or show main content
|
||||
if (!Storage.get('onboardingComplete')) {
|
||||
onboarding.start();
|
||||
} else {
|
||||
document.getElementById('main-content').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Initialize features
|
||||
search.init();
|
||||
shortcuts.init();
|
||||
settings.init();
|
||||
initModalHandlers();
|
||||
|
||||
// Set up greeting
|
||||
updateGreeting();
|
||||
setInterval(updateGreeting, 60000);
|
||||
|
||||
// Settings button handler
|
||||
const settingsButton = document.getElementById('settings-button');
|
||||
const settingsModal = document.getElementById('settings-modal');
|
||||
|
||||
settingsButton.addEventListener('click', () => {
|
||||
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
|
||||
const onboarding = {
|
||||
// Check if onboarding is complete
|
||||
// Core functions
|
||||
isComplete: () => {
|
||||
return Storage.get('onboardingComplete') === true;
|
||||
},
|
||||
|
||||
// Start the onboarding process
|
||||
start: () => {
|
||||
const modal = document.getElementById('onboarding-modal');
|
||||
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()) {
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('active');
|
||||
mainContent.classList.add('hidden');
|
||||
|
||||
document.getElementById('next-step-btn').addEventListener('click', () => onboarding.nextStep(1));
|
||||
document.getElementById('complete-setup-btn').addEventListener('click', onboarding.complete);
|
||||
startFreshBtn.addEventListener('click', () => {
|
||||
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');
|
||||
engines.forEach(engine => {
|
||||
engine.addEventListener('click', () => {
|
||||
|
@ -26,31 +86,34 @@ const onboarding = {
|
|||
engine.classList.add('selected');
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('complete-setup-btn').addEventListener('click', onboarding.complete);
|
||||
} else {
|
||||
modal.classList.add('hidden');
|
||||
mainContent.classList.remove('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
// Move to the next step in onboarding
|
||||
// Onboarding step navigation
|
||||
nextStep: (currentStep) => {
|
||||
const currentStepEl = document.querySelector(`[data-step="${currentStep}"]`);
|
||||
const nextStepEl = document.querySelector(`[data-step="${currentStep + 1}"]`);
|
||||
const name = document.getElementById('user-name').value.trim();
|
||||
|
||||
if (!name) {
|
||||
notifications.show('Please enter your name!', 'error');
|
||||
return;
|
||||
if (currentStep === 2) {
|
||||
const name = document.getElementById('user-name').value.trim();
|
||||
if (!name) {
|
||||
notifications.show('Please enter your name!', 'error');
|
||||
return;
|
||||
}
|
||||
Storage.set('userName', name);
|
||||
}
|
||||
|
||||
Storage.set('userName', name);
|
||||
|
||||
currentStepEl.classList.add('hidden');
|
||||
nextStepEl.classList.remove('hidden');
|
||||
nextStepEl.classList.add('visible');
|
||||
},
|
||||
|
||||
// Complete the onboarding process
|
||||
// Finalize onboarding
|
||||
complete: () => {
|
||||
const selectedEngine = document.querySelector('.search-engine-option.selected');
|
||||
if (!selectedEngine) {
|
||||
|
|
159
js/settings.js
159
js/settings.js
|
@ -10,7 +10,6 @@ const anonymousNames = {
|
|||
}
|
||||
};
|
||||
|
||||
// Define getTimeBasedGreeting function
|
||||
const getTimeBasedGreeting = () => {
|
||||
const hour = new Date().getHours();
|
||||
if (hour < 12) return 'Good Morning';
|
||||
|
@ -21,7 +20,8 @@ const getTimeBasedGreeting = () => {
|
|||
|
||||
// Main settings object
|
||||
const settings = {
|
||||
// Toggle between light and dark themes
|
||||
GREETING_MAX_LENGTH: 60,
|
||||
|
||||
toggleTheme: () => {
|
||||
const currentTheme = document.body.getAttribute('data-theme');
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
|
@ -32,7 +32,6 @@ const settings = {
|
|||
themeIcon.className = `fas fa-${newTheme === 'dark' ? 'sun' : 'moon'}`;
|
||||
},
|
||||
|
||||
// Toggle anonymous mode
|
||||
toggleAnonymousMode: () => {
|
||||
const isAnonymous = Storage.get('anonymousMode') || false;
|
||||
Storage.set('anonymousMode', !isAnonymous);
|
||||
|
@ -50,7 +49,6 @@ const settings = {
|
|||
updateGreeting();
|
||||
},
|
||||
|
||||
// Update the search engine
|
||||
updateSearchEngine: (engine) => {
|
||||
Storage.set('searchEngine', engine);
|
||||
notifications.show('Search engine updated successfully!', 'success');
|
||||
|
@ -198,8 +196,25 @@ const settings = {
|
|||
settings.updateVisibility();
|
||||
|
||||
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) => {
|
||||
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 === '') {
|
||||
Storage.remove('customGreeting');
|
||||
notifications.show('Using default greeting format', 'info');
|
||||
|
@ -239,15 +254,32 @@ const settings = {
|
|||
try {
|
||||
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 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') ?
|
||||
(Storage.get('anonymousName') || anonymousNames.generate()) :
|
||||
(Storage.get('userName') || 'Friend');
|
||||
|
||||
const formats = {
|
||||
name: userName,
|
||||
greeting: format.startsWith('{greeting}') ?
|
||||
timeGreeting.charAt(0).toUpperCase() + timeGreeting.slice(1) :
|
||||
timeGreeting,
|
||||
time: now.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }),
|
||||
date: now.toLocaleDateString(),
|
||||
date: now.getDate().toString(),
|
||||
day: now.toLocaleDateString([], { weekday: 'long' }),
|
||||
month: now.toLocaleDateString([], { month: 'long' }),
|
||||
year: now.getFullYear()
|
||||
|
@ -267,38 +299,113 @@ const settings = {
|
|||
|
||||
return formattedGreeting;
|
||||
} catch (e) {
|
||||
Storage.remove('customGreeting');
|
||||
notifications.show('Error in greeting format. Using default.', 'error');
|
||||
notifications.show('Invalid greeting format. Using default.', 'error');
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// Data management functions
|
||||
exportData: () => {
|
||||
const data = {
|
||||
settings: {
|
||||
theme: document.body.getAttribute('data-theme'),
|
||||
theme: Storage.get('theme'),
|
||||
userName: Storage.get('userName'),
|
||||
anonymousMode: Storage.get('anonymousMode'),
|
||||
anonymousName: Storage.get('anonymousName'),
|
||||
searchEngine: Storage.get('searchEngine'),
|
||||
customGreeting: Storage.get('customGreeting'),
|
||||
show_greeting: Storage.get('show_greeting'),
|
||||
show_search: Storage.get('show_search'),
|
||||
show_shortcuts: Storage.get('show_shortcuts'),
|
||||
show_addShortcut: Storage.get('show_addShortcut'),
|
||||
customGreeting: Storage.get('customGreeting')
|
||||
show_addShortcut: Storage.get('show_addShortcut')
|
||||
},
|
||||
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 url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'jstar-tab-settings.json';
|
||||
a.download = 'jstar-tab-backup.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
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 fileInput = document.getElementById('import-file');
|
||||
|
||||
// Export Data
|
||||
exportBtn.addEventListener('click', () => {
|
||||
exportBtn.replaceWith(exportBtn.cloneNode(true));
|
||||
const newExportBtn = document.getElementById('export-data');
|
||||
|
||||
newExportBtn.addEventListener('click', () => {
|
||||
try {
|
||||
const data = {
|
||||
settings: {
|
||||
|
@ -325,7 +434,8 @@ settings.initDataManagement = () => {
|
|||
show_addShortcut: Storage.get('show_addShortcut'),
|
||||
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' });
|
||||
|
@ -345,7 +455,6 @@ settings.initDataManagement = () => {
|
|||
}
|
||||
});
|
||||
|
||||
// Import Data
|
||||
importBtn.addEventListener('click', () => fileInput.click());
|
||||
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
|
@ -357,7 +466,7 @@ settings.initDataManagement = () => {
|
|||
try {
|
||||
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');
|
||||
}
|
||||
|
||||
|
@ -365,6 +474,19 @@ settings.initDataManagement = () => {
|
|||
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);
|
||||
|
||||
fileInput.value = '';
|
||||
|
@ -454,7 +576,6 @@ settings.initDataManagement = () => {
|
|||
reader.readAsText(file);
|
||||
});
|
||||
|
||||
// Reset Data
|
||||
resetBtn.addEventListener('click', () => {
|
||||
const confirmReset = confirm('Are you sure you want to reset all data? This action cannot be undone.');
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const shortcuts = {
|
||||
MAX_SHORTCUTS: 12,
|
||||
|
||||
// Validate and format URL
|
||||
// URL Validation
|
||||
validateAndFormatUrl: (url) => {
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
url = 'https://' + url;
|
||||
|
@ -15,7 +15,7 @@ const shortcuts = {
|
|||
}
|
||||
},
|
||||
|
||||
// Add new shortcut
|
||||
// Shortcut Management
|
||||
add: (url, name) => {
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) {
|
||||
|
@ -34,7 +34,6 @@ const shortcuts = {
|
|||
shortcuts.render();
|
||||
},
|
||||
|
||||
// Remove shortcut
|
||||
remove: (index) => {
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
currentShortcuts.splice(index, 1);
|
||||
|
@ -43,7 +42,6 @@ const shortcuts = {
|
|||
notifications.show('Shortcut removed!', 'success');
|
||||
},
|
||||
|
||||
// Edit existing shortcut
|
||||
edit: (index, newUrl, newName) => {
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
currentShortcuts[index] = { url: newUrl, name: newName };
|
||||
|
@ -52,7 +50,7 @@ const shortcuts = {
|
|||
notifications.show('Shortcut updated!', 'success');
|
||||
},
|
||||
|
||||
// Show context menu for shortcut
|
||||
// UI Interactions
|
||||
showContextMenu: (e, index) => {
|
||||
e.preventDefault();
|
||||
const menu = document.getElementById('context-menu');
|
||||
|
@ -75,7 +73,7 @@ const shortcuts = {
|
|||
}, 0);
|
||||
},
|
||||
|
||||
// Render shortcuts grid
|
||||
// Rendering
|
||||
render: () => {
|
||||
const grid = document.getElementById('shortcuts-grid');
|
||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||
|
@ -130,9 +128,23 @@ const shortcuts = {
|
|||
});
|
||||
},
|
||||
|
||||
// Initialize shortcuts functionality
|
||||
// Initialization
|
||||
init: () => {
|
||||
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) {
|
||||
addShortcutButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
@ -143,7 +155,6 @@ const shortcuts = {
|
|||
return;
|
||||
}
|
||||
|
||||
const modal = document.getElementById('add-shortcut-modal');
|
||||
if (modal) {
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('active');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"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.",
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "index.html"
|
||||
|
|
75
style.css
75
style.css
|
@ -416,7 +416,7 @@ input[type="text"]:focus {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2rem;
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
|
@ -445,6 +445,7 @@ input[type="text"]:focus {
|
|||
|
||||
.settings-section:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.settings-section h3 {
|
||||
|
@ -518,6 +519,7 @@ input[type="text"]:focus {
|
|||
bottom: 3px;
|
||||
background-color: var(--background);
|
||||
border-radius: 50%;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider {
|
||||
|
@ -528,6 +530,35 @@ input:checked + .toggle-slider:before {
|
|||
transform: translateX(24px);
|
||||
}
|
||||
|
||||
#keybind-url-combo {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
#keybind-url {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.keybind-container {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.clear-keybind {
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: var(--accent);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.clear-keybind:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.format-hint {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
|
@ -916,4 +947,46 @@ input:checked + .toggle-slider:before {
|
|||
padding: 0.875rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
.import-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.or-divider {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.or-divider::before,
|
||||
.or-divider::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: calc(50% - 24px);
|
||||
height: 1px;
|
||||
background-color: var(--border);
|
||||
}
|
||||
|
||||
.or-divider::before {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.or-divider::after {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.or-divider span {
|
||||
background-color: var(--background);
|
||||
padding: 0 8px;
|
||||
color: var(--text);
|
||||
font-size: 0.9rem;
|
||||
}
|
Loading…
Reference in New Issue
Block a user