/* global chrome, browser */
// Browser compatibility polyfill
if (typeof browser === 'undefined' && typeof chrome !== 'undefined') {
browser = chrome;
}
// DOM Elements
const statusIndicatorEl = document.getElementById('statusIndicator');
const statusTextEl = document.getElementById('statusText');
const statusMessageEl = document.getElementById('statusMessage');
const openOptionsBtn = document.getElementById('openOptions');
// Tab elements
const tabBtns = document.querySelectorAll('.tab');
const tabContents = document.querySelectorAll('.tab-content');
// Bookmark elements
const bookmarkTitleInput = document.getElementById('bookmarkTitle');
const bookmarkUrlInput = document.getElementById('bookmarkUrl');
const bookmarkDescriptionInput = document.getElementById('bookmarkDescription');
const bookmarkTagsInput = document.getElementById('bookmarkTags');
const bookmarkPublicInput = document.getElementById('bookmarkPublic');
const saveBookmarkBtn = document.getElementById('saveBookmarkBtn');
// File elements
const fileInput = document.getElementById('fileInput');
const fileDescriptionInput = document.getElementById('fileDescription');
const uploadFileBtn = document.getElementById('uploadFileBtn');
// Smart suggestion elements
const suggestedTagsContainer = document.getElementById('suggestedTags');
const contentTypeIndicator = document.getElementById('contentTypeIndicator');
const quickSaveBtn = document.getElementById('quickSaveBtn');
let trackeepConfig = {
apiBaseUrl: '',
authToken: ''
};
let smartData = null;
let isQuickSaveMode = false;
// Tab switching functionality
function initTabs() {
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
const targetTab = btn.dataset.tab;
// Update button states
tabBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Update content visibility
tabContents.forEach(content => {
content.classList.remove('active');
if (content.id === `${targetTab}-tab`) {
content.classList.add('active');
}
});
});
});
}
// Status management
function updateStatus(text, type = 'info') {
statusTextEl.textContent = text;
statusIndicatorEl.className = 'status-indicator';
if (type === 'success') {
statusIndicatorEl.classList.add('connected');
} else if (type === 'error') {
statusIndicatorEl.classList.add('error');
}
}
function showMessage(message, type = 'info', duration = 5000) {
statusMessageEl.textContent = message;
statusMessageEl.className = `status-message ${type}`;
statusMessageEl.style.display = 'flex';
// Auto-hide after duration
if (duration > 0) {
setTimeout(() => {
statusMessageEl.style.display = 'none';
}, duration);
}
}
function hideMessage() {
statusMessageEl.style.display = 'none';
}
// Loading states
function setButtonLoading(button, loading = true) {
if (loading) {
button.disabled = true;
const originalContent = button.innerHTML;
button.dataset.originalContent = originalContent;
button.innerHTML = `
Processing...
`;
} else {
button.disabled = false;
if (button.dataset.originalContent) {
button.innerHTML = button.dataset.originalContent;
delete button.dataset.originalContent;
}
}
}
function disableForms(disabled) {
const elements = [
bookmarkTitleInput, bookmarkUrlInput, bookmarkDescriptionInput,
bookmarkTagsInput, bookmarkPublicInput, saveBookmarkBtn,
fileInput, fileDescriptionInput, uploadFileBtn
];
elements.forEach(el => {
if (el) el.disabled = disabled;
});
}
function loadConfig(callback) {
browser.storage.sync.get(['trackeepApiBaseUrl', 'trackeepAuthToken'], (items) => {
const apiBaseUrl = (items.trackeepApiBaseUrl || '').trim();
const authToken = (items.trackeepAuthToken || '').trim();
trackeepConfig = { apiBaseUrl, authToken };
if (!apiBaseUrl || !authToken) {
updateStatus('Configuration required', 'error');
showMessage('Configure API URL and token in Options to enable saving.', 'error');
disableForms(true);
} else {
updateStatus(`Connected to ${apiBaseUrl}`, 'success');
hideMessage();
disableForms(false);
}
if (typeof callback === 'function') {
callback();
}
});
}
function detectTrackeepDomain(callback) {
browser.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const tab = tabs && tabs[0];
if (!tab || !tab.url) {
if (callback) callback();
return;
}
try {
const url = new URL(tab.url);
const isTrackeepDomain = url.hostname.includes('trackeep') || url.hostname === 'localhost';
if (isTrackeepDomain && url.protocol === 'https:') {
const candidate = `${url.origin}/api/v1`;
browser.storage.sync.get(['trackeepApiBaseUrl'], (items) => {
if (!items.trackeepApiBaseUrl) {
browser.storage.sync.set({ trackeepApiBaseUrl: candidate }, () => {
console.log('Auto-detected Trackeep API URL:', candidate);
if (callback) callback();
});
} else {
if (callback) callback();
}
});
} else {
if (callback) callback();
}
} catch (e) {
if (callback) callback();
}
});
}
function initActiveTab() {
browser.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const tab = tabs && tabs[0];
if (!tab) return;
browser.storage.local.get(['contextMenuData'], (items) => {
const ctx = items.contextMenuData;
if (ctx && ctx.timestamp && Date.now() - ctx.timestamp < 5000) {
// Use context menu data
smartData = ctx.smartData || null;
isQuickSaveMode = ctx.isQuickSave || false;
if (ctx.url && !bookmarkUrlInput.value) {
bookmarkUrlInput.value = ctx.url;
}
if (ctx.title && !bookmarkTitleInput.value) {
bookmarkTitleInput.value = ctx.title;
}
if (ctx.selection && !bookmarkDescriptionInput.value) {
bookmarkDescriptionInput.value = ctx.selection;
}
// Apply smart suggestions
if (smartData) {
applySmartSuggestions(smartData);
}
// Handle quick save mode
if (isQuickSaveMode) {
handleQuickSave();
}
browser.storage.local.remove(['contextMenuData']);
} else {
// Regular tab detection
detectAndApplySmartData(tab);
if (tab.title && !bookmarkTitleInput.value) {
bookmarkTitleInput.value = tab.title;
}
if (tab.url && !bookmarkUrlInput.value) {
bookmarkUrlInput.value = tab.url;
}
}
});
});
}
// Smart data detection for regular tab
async function detectAndApplySmartData(tab) {
try {
const info = { linkUrl: tab.url, srcUrl: tab.url };
smartData = await detectContentType(info, tab);
if (smartData) {
applySmartSuggestions(smartData);
}
} catch (e) {
console.error('Smart detection failed:', e);
}
}
// Apply smart suggestions to UI
function applySmartSuggestions(data) {
// Show content type indicator
if (contentTypeIndicator) {
const typeColors = {
video: '#ff0000',
social: '#1da1f2',
code: '#0969da',
article: '#ff6900',
documentation: '#6f42c1',
news: '#ff4500',
shopping: '#ff9500',
general: '#6b7280'
};
const typeIcons = {
video: '🎥',
social: '💬',
code: '💻',
article: '📝',
documentation: '📚',
news: '📰',
shopping: '🛒',
general: '🔗'
};
contentTypeIndicator.innerHTML = `
${typeIcons[data.type] || typeIcons.general} ${data.type.charAt(0).toUpperCase() + data.type.slice(1)}
${data.platform ? `• ${data.platform}` : ''}
`;
contentTypeIndicator.style.display = 'inline-block';
}
// Show suggested tags
if (suggestedTagsContainer && data.suggestedTags) {
suggestedTagsContainer.innerHTML = '';
data.suggestedTags.forEach(tag => {
const tagEl = document.createElement('span');
tagEl.className = 'suggested-tag';
tagEl.textContent = tag;
tagEl.onclick = () => addSuggestedTag(tag);
suggestedTagsContainer.appendChild(tagEl);
});
suggestedTagsContainer.style.display = 'flex';
}
}
// Add suggested tag to input
function addSuggestedTag(tag) {
const currentTags = bookmarkTagsInput.value
.split(',')
.map(t => t.trim())
.filter(t => t);
if (!currentTags.includes(tag)) {
currentTags.push(tag);
bookmarkTagsInput.value = currentTags.join(', ');
}
}
// Handle quick save
function handleQuickSave() {
if (isQuickSaveMode && smartData) {
// Auto-fill with smart data and save immediately
if (smartData.suggestedTags && !bookmarkTagsInput.value) {
bookmarkTagsInput.value = smartData.suggestedTags.join(', ');
}
// Auto-save after a short delay
setTimeout(() => {
if (bookmarkUrlInput.value && bookmarkTitleInput.value) {
saveBookmark(new Event('submit'));
}
}, 500);
}
}
async function saveBookmark(event) {
event.preventDefault();
hideMessage();
const { apiBaseUrl, authToken } = trackeepConfig;
if (!apiBaseUrl || !authToken) {
showMessage('Missing API URL or auth token. Open options first.', 'error');
return;
}
const url = bookmarkUrlInput.value.trim();
if (!url) {
showMessage('URL is required.', 'error');
return;
}
const title = bookmarkTitleInput.value.trim() || url;
const description = bookmarkDescriptionInput.value.trim();
const tagsRaw = bookmarkTagsInput.value.trim();
const isPublic = !!bookmarkPublicInput.checked;
const tags = tagsRaw
? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean)
: [];
const payload = {
title,
url,
description,
tags,
is_public: isPublic
};
setButtonLoading(saveBookmarkBtn, true);
showMessage('Saving bookmark...', 'info', 0);
try {
const base = apiBaseUrl.replace(/\/$/, '');
const response = await fetch(`${base}/bookmarks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify(payload)
});
if (!response.ok) {
let errorMessage = `Failed to save bookmark (status ${response.status})`;
try {
const data = await response.json();
if (data && data.error) {
errorMessage = data.error;
}
} catch (_) {
// ignore JSON parse errors
}
throw new Error(errorMessage);
}
showMessage(`
Bookmark saved successfully!
`, 'success');
// Clear form after successful save
setTimeout(() => {
bookmarkDescriptionInput.value = '';
bookmarkTagsInput.value = '';
bookmarkPublicInput.checked = false;
}, 2000);
} catch (err) {
console.error('Error saving bookmark', err);
showMessage(err && err.message ? err.message : 'Failed to save bookmark.', 'error');
} finally {
setButtonLoading(saveBookmarkBtn, false);
}
}
async function uploadFile(event) {
event.preventDefault();
hideMessage();
const { apiBaseUrl, authToken } = trackeepConfig;
if (!apiBaseUrl || !authToken) {
showMessage('Missing API URL or auth token. Open options first.', 'error');
return;
}
const file = fileInput.files && fileInput.files[0];
if (!file) {
showMessage('Please choose a file to upload.', 'error');
return;
}
const description = fileDescriptionInput.value.trim();
const formData = new FormData();
formData.append('file', file, file.name);
if (description) {
formData.append('description', description);
}
setButtonLoading(uploadFileBtn, true);
showMessage('Uploading file...', 'info', 0);
try {
const base = apiBaseUrl.replace(/\/$/, '');
const response = await fetch(`${base}/files/upload`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
},
body: formData
});
if (!response.ok) {
let errorMessage = `Failed to upload file (status ${response.status})`;
try {
const data = await response.json();
if (data && data.error) {
errorMessage = data.error;
}
} catch (_) {
// ignore JSON parse errors
}
throw new Error(errorMessage);
}
showMessage(`
File uploaded successfully!
`, 'success');
// Clear form after successful upload
setTimeout(() => {
fileInput.value = '';
fileDescriptionInput.value = '';
}, 2000);
} catch (err) {
console.error('Error uploading file', err);
showMessage(err && err.message ? err.message : 'Failed to upload file.', 'error');
} finally {
setButtonLoading(uploadFileBtn, false);
}
}
function openOptions() {
if (browser.runtime.openOptionsPage) {
browser.runtime.openOptionsPage();
} else {
window.open(browser.runtime.getURL('options.html'));
}
}
// Initialize everything when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
// Initialize tabs
initTabs();
// Event listeners
openOptionsBtn.addEventListener('click', openOptions);
quickSaveBtn.addEventListener('click', handleQuickSave);
saveBookmarkBtn.addEventListener('click', (e) => {
e.preventDefault();
saveBookmark(e);
});
uploadFileBtn.addEventListener('click', (e) => {
e.preventDefault();
uploadFile(e);
});
// Keyboard shortcut for quick save
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
e.preventDefault();
handleQuickSave();
}
});
// Initialize configuration and active tab
detectTrackeepDomain(() => {
loadConfig(() => {
initActiveTab();
});
});
});