Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion manifest-chromium.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

"permissions": [
"tabs",
"storage"
"storage",
"contextMenus"
],

"host_permissions": [
Expand Down
3 changes: 2 additions & 1 deletion manifest-firefox.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

"permissions": [
"tabs",
"storage"
"storage",
"contextMenus"
],

"host_permissions": [
Expand Down
6 changes: 4 additions & 2 deletions src/background/modules/sync-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,8 @@ export class SyncEngine {
const browserTabs = await tabManager.getSyncableTabs();
if (browserTabs.length > 0) {
const browserIdentity = await browserStorage.getBrowserIdentity();
await tabManager.syncMultipleTabs(browserTabs, apiClient, contextId, browserIdentity);
const syncSettings = await browserStorage.getSyncSettings();
await tabManager.syncMultipleTabs(browserTabs, apiClient, contextId, browserIdentity, syncSettings);
}
}

Expand All @@ -839,9 +840,10 @@ export class SyncEngine {
const browserTabs = await tabManager.getSyncableTabs();
if (browserTabs.length > 0) {
const browserIdentity = await browserStorage.getBrowserIdentity();
const syncSettings = await browserStorage.getSyncSettings();
const wsId = workspace?.name || workspace?.id;
if (wsId) {
const docs = browserTabs.map(tab => tabManager.convertTabToDocument(tab, browserIdentity));
const docs = browserTabs.map(tab => tabManager.convertTabToDocument(tab, browserIdentity, syncSettings));
await apiClient.insertWorkspaceDocuments(wsId, docs, workspacePath || '/', docs[0]?.featureArray || []);
}
}
Expand Down
24 changes: 17 additions & 7 deletions src/background/modules/tab-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class TabManager {
}

// Convert browser tab to Canvas document format (based on PAYLOAD.md format)
convertTabToDocument(tab, browserIdentity) {
convertTabToDocument(tab, browserIdentity, syncSettings = {}) {
const document = {
schema: 'data/abstraction/tab',
schemaVersion: '2.0',
Expand All @@ -30,7 +30,7 @@ export class TabManager {
favIconUrl: tab.favIconUrl,
timestamp: new Date().toISOString()
},
featureArray: this.generateFeatureArray(browserIdentity),
featureArray: this.generateFeatureArray(browserIdentity, syncSettings),
metadata: {
contentType: 'application/json',
contentEncoding: 'utf8',
Expand Down Expand Up @@ -61,7 +61,7 @@ export class TabManager {
}

// Generate feature array for tab document
generateFeatureArray(browserIdentity) {
generateFeatureArray(browserIdentity, syncSettings = {}) {
const features = ['data/abstraction/tab'];

// Add browser type
Expand All @@ -76,10 +76,20 @@ export class TabManager {
features.push('client/app/safari');
}

// Add browser identity string if "sync only current browser" is enabled
if (syncSettings.syncOnlyCurrentBrowser) {
features.push(`client/app/browser-identity-string`);
}

// Add instance identifier
features.push(`tag/${browserIdentity}`);
}

// Add custom tag if "sync only tagged tabs" is enabled and tag is specified
if (syncSettings.syncOnlyTaggedTabs && syncSettings.syncTagFilter) {
features.push(`custom/tag/${syncSettings.syncTagFilter}`);
}

return features;
}

Expand Down Expand Up @@ -433,7 +443,7 @@ export class TabManager {
}

// Sync a browser tab to Canvas
async syncTabToCanvas(tab, apiClient, contextId, browserIdentity) {
async syncTabToCanvas(tab, apiClient, contextId, browserIdentity, syncSettings = {}) {
try {
console.log(`🔧 TabManager.syncTabToCanvas: Starting sync for tab: ${tab.title} (${tab.url})`);

Expand All @@ -445,7 +455,7 @@ export class TabManager {
console.log(`🔧 TabManager.syncTabToCanvas: Tab is syncable, proceeding with sync`);

// Convert tab to Canvas document format
const document = this.convertTabToDocument(tab, browserIdentity);
const document = this.convertTabToDocument(tab, browserIdentity, syncSettings);
console.log(`🔧 TabManager.syncTabToCanvas: Converted to document:`, {
schema: document.schema,
url: document.data.url,
Expand Down Expand Up @@ -565,7 +575,7 @@ export class TabManager {
}

// Bulk sync multiple tabs
async syncMultipleTabs(tabs, apiClient, contextId, browserIdentity) {
async syncMultipleTabs(tabs, apiClient, contextId, browserIdentity, syncSettings = {}) {
console.log(`🔧 TabManager.syncMultipleTabs: Starting batch sync of ${tabs.length} tabs to context ${contextId}`);
console.log('🔧 TabManager.syncMultipleTabs: API client status:', {
hasApiClient: !!apiClient,
Expand Down Expand Up @@ -597,7 +607,7 @@ export class TabManager {
console.log(`🔧 TabManager.syncMultipleTabs: Converting ${syncableTabs.length} syncable tabs to documents`);

// Convert all tabs to documents
const documents = syncableTabs.map(tab => this.convertTabToDocument(tab, browserIdentity));
const documents = syncableTabs.map(tab => this.convertTabToDocument(tab, browserIdentity, syncSettings));

// Use batch API for better performance
console.log(`🔧 TabManager.syncMultipleTabs: Making batch API call with ${documents.length} documents`);
Expand Down
113 changes: 107 additions & 6 deletions src/background/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ chrome.runtime.onInstalled.addListener(async (details) => {
}
}

// Setup context menus
await setupContextMenus();

// Open settings page on first install
if (details.reason === 'install') {
await openSettingsPage();
Expand All @@ -33,6 +36,7 @@ chrome.runtime.onInstalled.addListener(async (details) => {
chrome.runtime.onStartup.addListener(async () => {
console.log('Browser startup - initializing Canvas Extension');
await initializeExtension();
await setupContextMenus();
});

// Initialize extension on service worker startup
Expand Down Expand Up @@ -322,10 +326,10 @@ chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
try {
let syncResult;
if (mode === 'context') {
syncResult = await tabManager.syncTabToCanvas(tab, apiClient, currentContext.id, browserIdentity);
syncResult = await tabManager.syncTabToCanvas(tab, apiClient, currentContext.id, browserIdentity, syncSettings);
} else {
// Workspace mode: insert document into workspace with contextSpec
const document = tabManager.convertTabToDocument(tab, browserIdentity);
const document = tabManager.convertTabToDocument(tab, browserIdentity, syncSettings);
const wsId = currentWorkspace.name || currentWorkspace.id;
const response = await apiClient.insertWorkspaceDocument(wsId, document, workspacePath || '/', document.featureArray);
if (response.status === 'success') {
Expand Down Expand Up @@ -1073,6 +1077,9 @@ async function handleSetModeAndSelection(data, sendResponse) {
}
}

// Update context menus after mode/selection change
await setupContextMenus();

sendResponse({ success: true });
} catch (error) {
console.error('Failed to set mode/selection:', error);
Expand Down Expand Up @@ -1393,6 +1400,9 @@ async function handleSaveSettings(data, sendResponse) {
context: verifyContext
});

// Update context menus after settings change
await setupContextMenus();

sendResponse({
success: true,
message: 'All settings saved successfully',
Expand Down Expand Up @@ -1587,6 +1597,9 @@ async function handleSyncTab(data, sendResponse) {
const connectionSettings = await browserStorage.getConnectionSettings();
console.log('Sync Tab: connection settings check:', connectionSettings);

// Get sync settings
const syncSettings = await browserStorage.getSyncSettings();

// Check if we have essential connection info (prioritize API token over connected flag)
if (!connectionSettings.apiToken || !connectionSettings.serverUrl) {
console.error('Sync Tab: Missing API token or server URL');
Expand Down Expand Up @@ -1614,12 +1627,12 @@ async function handleSyncTab(data, sendResponse) {
if (mode === 'context') {
const targetContextId = contextId || currentContext?.id;
if (!targetContextId) throw new Error('No context selected');
result = await tabManager.syncTabToCanvas(tab, apiClient, targetContextId, browserIdentity);
result = await tabManager.syncTabToCanvas(tab, apiClient, targetContextId, browserIdentity, syncSettings);
} else {
// Workspace mode
const wsId = currentWorkspace?.name || currentWorkspace?.id;
if (!wsId) throw new Error('No workspace selected');
const document = tabManager.convertTabToDocument(tab, browserIdentity);
const document = tabManager.convertTabToDocument(tab, browserIdentity, syncSettings);
const resp = await apiClient.insertWorkspaceDocument(wsId, document, contextSpec || workspacePath || '/', document.featureArray);
if (resp.status === 'success') {
const docId = Array.isArray(resp.payload) ? resp.payload[0]?.id : resp.payload?.id;
Expand Down Expand Up @@ -1695,6 +1708,9 @@ async function handleSyncMultipleTabs(data, sendResponse) {
const connectionSettings = await browserStorage.getConnectionSettings();
console.log('🔧 Connection settings:', connectionSettings);

// Get sync settings
const syncSettings = await browserStorage.getSyncSettings();

if (!connectionSettings.connected || !connectionSettings.apiToken) {
console.error('❌ Not connected to Canvas server:', {
connected: connectionSettings.connected,
Expand Down Expand Up @@ -1722,12 +1738,12 @@ async function handleSyncMultipleTabs(data, sendResponse) {
console.log('🔧 Calling tabManager.syncMultipleTabs (context mode)...');
const targetContextId = contextId || currentContext?.id;
if (!targetContextId) throw new Error('No context selected');
result = await tabManager.syncMultipleTabs(tabs, apiClient, targetContextId, browserIdentity);
result = await tabManager.syncMultipleTabs(tabs, apiClient, targetContextId, browserIdentity, syncSettings);
} else {
console.log('🔧 Syncing multiple tabs to workspace (explorer mode)...');
const wsId = currentWorkspace?.name || currentWorkspace?.id;
if (!wsId) throw new Error('No workspace selected');
const docs = tabs.map(tab => tabManager.convertTabToDocument(tab, browserIdentity));
const docs = tabs.map(tab => tabManager.convertTabToDocument(tab, browserIdentity, syncSettings));
const resp = await apiClient.insertWorkspaceDocuments(wsId, docs, contextSpec || workspacePath || '/', docs[0]?.featureArray || []);
if (resp.status === 'success') {
result = { success: true, total: tabs.length, successful: tabs.length, failed: 0 };
Expand Down Expand Up @@ -1899,5 +1915,90 @@ async function handleUpdateContextUrl(message, sendResponse) {
}
}

// Context Menu functionality
async function setupContextMenus() {
try {
// Remove existing context menus first
await chrome.contextMenus.removeAll();

// Check if we're connected and have context
const connectionSettings = await browserStorage.getConnectionSettings();
const mode = await browserStorage.getSyncMode();
const currentContext = await browserStorage.getCurrentContext();
const currentWorkspace = await browserStorage.getCurrentWorkspace();

if (!connectionSettings.connected) {
console.log('Not connected - skipping context menu setup');
return;
}

// Create main "Insert to Canvas" menu item
chrome.contextMenus.create({
id: 'insert-to-canvas',
title: 'Insert to Canvas',
contexts: ['tab'],
visible: true
});

if (mode === 'context' && currentContext?.id) {
// Context mode - direct insert
chrome.contextMenus.create({
id: 'insert-to-current-context',
parentId: 'insert-to-canvas',
title: `Insert to Context: ${currentContext.id}`,
contexts: ['tab']
});
} else if (mode === 'explorer' && currentWorkspace) {
// Explorer mode - show workspace tree
const workspacePath = await browserStorage.getWorkspacePath();
chrome.contextMenus.create({
id: 'insert-to-current-workspace',
parentId: 'insert-to-canvas',
title: `Insert to Workspace: ${currentWorkspace.name || currentWorkspace.id}${workspacePath || '/'}`,
contexts: ['tab']
});

// Add option to browse for path
chrome.contextMenus.create({
id: 'browse-workspace-path',
parentId: 'insert-to-canvas',
title: 'Browse workspace path...',
contexts: ['tab']
});
}

console.log('Context menus set up successfully');
} catch (error) {
console.error('Failed to setup context menus:', error);
}
}

// Handle context menu clicks
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
try {
console.log('Context menu clicked:', info.menuItemId, 'for tab:', tab.id);

if (info.menuItemId === 'insert-to-current-context') {
// Sync tab to current context
const currentContext = await browserStorage.getCurrentContext();
const response = await handleSyncTab({ tab, contextId: currentContext.id });
if (response.success) {
console.log('Tab synced to context via context menu');
}
} else if (info.menuItemId === 'insert-to-current-workspace') {
// Sync tab to current workspace
const response = await handleSyncTab({ tab });
if (response.success) {
console.log('Tab synced to workspace via context menu');
}
} else if (info.menuItemId === 'browse-workspace-path') {
// Open popup to browse workspace path
console.log('Browse workspace path not yet implemented');
}
} catch (error) {
console.error('Context menu action failed:', error);
}
});

// Initialize extension on service worker startup
initializeExtension();
58 changes: 58 additions & 0 deletions src/popup/popup.css
Original file line number Diff line number Diff line change
Expand Up @@ -964,3 +964,61 @@ body {
.logo:hover {
opacity: 0.8;
}

/* Context Menu Styles */
.context-menu {
position: fixed;
background: #ffffff;
border: 1px solid #dee2e6;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 4px 0;
z-index: 1000;
min-width: 150px;
display: none;
}

.context-menu-item {
padding: 8px 12px;
font-size: 13px;
cursor: pointer;
border: none;
background: none;
width: 100%;
text-align: left;
color: #333;
transition: background-color 0.1s ease;
}

.context-menu-item:hover {
background-color: #f8f9fa;
}

.context-menu-item:disabled {
color: #6c757d;
cursor: not-allowed;
}

.context-menu-item:disabled:hover {
background-color: transparent;
}

.context-menu-separator {
height: 1px;
background-color: #dee2e6;
margin: 4px 0;
}

.context-menu-submenu {
position: relative;
}

.context-menu-submenu::after {
content: '▶';
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
font-size: 10px;
color: #6c757d;
}
6 changes: 6 additions & 0 deletions src/popup/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ <h3>Switch Sync Mode</h3>

<div class="toast" id="toast" style="display: none;"></div>

<!-- Context Menu -->
<div id="contextMenu" class="context-menu">
<button class="context-menu-item" id="contextMenuSync">Insert to Current Context</button>
<button class="context-menu-item" id="contextMenuBrowse">Browse Contexts...</button>
</div>

<script type="module" src="popup.js"></script>
</body>
</html>
Loading