@rashidazarang/airtable-mcp 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +1 -1
  2. package/airtable_simple_production.js +387 -5
  3. package/examples/claude_simple_config.json +0 -9
  4. package/package.json +10 -1
  5. package/.github/ISSUE_TEMPLATE/bug-report.yml +0 -173
  6. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  7. package/.github/ISSUE_TEMPLATE/custom.md +0 -10
  8. package/.github/ISSUE_TEMPLATE/feature-request.yml +0 -209
  9. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  10. package/.github/ISSUE_TEMPLATE/security-report.yml +0 -216
  11. package/.github/pull_request_template.md +0 -245
  12. package/.github/workflows/ci-cd.yml +0 -408
  13. package/.github/workflows/security-audit.yml +0 -316
  14. package/API_DOCUMENTATION.md +0 -897
  15. package/CAPABILITY_REPORT.md +0 -118
  16. package/CLAUDE_INTEGRATION.md +0 -96
  17. package/CODE_OF_CONDUCT.md +0 -181
  18. package/CONTRIBUTING.md +0 -81
  19. package/DEVELOPMENT.md +0 -190
  20. package/Dockerfile +0 -39
  21. package/Dockerfile.node +0 -20
  22. package/Dockerfile.production +0 -127
  23. package/IMPROVEMENT_PROPOSAL.md +0 -371
  24. package/INSTALLATION.md +0 -183
  25. package/ISSUE_RESPONSES.md +0 -171
  26. package/MCP_REVIEW_SUMMARY.md +0 -142
  27. package/QUICK_START.md +0 -60
  28. package/RELEASE_NOTES_v1.2.0.md +0 -50
  29. package/RELEASE_NOTES_v1.2.1.md +0 -40
  30. package/RELEASE_NOTES_v1.2.2.md +0 -48
  31. package/RELEASE_NOTES_v1.2.3.md +0 -105
  32. package/RELEASE_NOTES_v1.2.4.md +0 -60
  33. package/RELEASE_NOTES_v1.4.0.md +0 -104
  34. package/RELEASE_NOTES_v1.5.0.md +0 -185
  35. package/RELEASE_NOTES_v1.6.0.md +0 -248
  36. package/SECURITY_NOTICE.md +0 -40
  37. package/airtable-clipper/CHANGELOG.md +0 -198
  38. package/airtable-clipper/CHROME_STORE_SUBMISSION.md +0 -343
  39. package/airtable-clipper/LAUNCH_STRATEGY.md +0 -495
  40. package/airtable-clipper/LICENSE +0 -21
  41. package/airtable-clipper/OAUTH_SETUP.md +0 -51
  42. package/airtable-clipper/PRIVACY_POLICY.md +0 -187
  43. package/airtable-clipper/README.md +0 -575
  44. package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +0 -273
  45. package/airtable-clipper/build.sh +0 -85
  46. package/airtable-clipper/docs/QUICK_START.md +0 -99
  47. package/airtable-clipper/docs/SETUP.md +0 -291
  48. package/airtable-clipper/extension/background.js +0 -337
  49. package/airtable-clipper/extension/base-setup.html +0 -324
  50. package/airtable-clipper/extension/base-setup.js +0 -471
  51. package/airtable-clipper/extension/content.js +0 -771
  52. package/airtable-clipper/extension/icons/README.md +0 -69
  53. package/airtable-clipper/extension/icons/icon-16.png +0 -3
  54. package/airtable-clipper/extension/manifest.json +0 -73
  55. package/airtable-clipper/extension/popup.html +0 -144
  56. package/airtable-clipper/extension/popup.js +0 -475
  57. package/airtable-clipper/extension/styles/content.css +0 -229
  58. package/airtable-clipper/extension/styles/popup.css +0 -477
  59. package/airtable-clipper/privacy-policy.md +0 -63
  60. package/airtable-clipper/releases/v1.0.0/background.js +0 -337
  61. package/airtable-clipper/releases/v1.0.0/base-setup.html +0 -324
  62. package/airtable-clipper/releases/v1.0.0/base-setup.js +0 -471
  63. package/airtable-clipper/releases/v1.0.0/content.js +0 -771
  64. package/airtable-clipper/releases/v1.0.0/icons/README.md +0 -69
  65. package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +0 -2
  66. package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +0 -3
  67. package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +0 -2
  68. package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +0 -2
  69. package/airtable-clipper/releases/v1.0.0/manifest.json +0 -73
  70. package/airtable-clipper/releases/v1.0.0/popup.html +0 -144
  71. package/airtable-clipper/releases/v1.0.0/popup.js +0 -475
  72. package/airtable-clipper/releases/v1.0.0/sidepanel.html +0 -25
  73. package/airtable-clipper/releases/v1.0.0/styles/content.css +0 -229
  74. package/airtable-clipper/releases/v1.0.0/styles/popup.css +0 -477
  75. package/airtable-clipper/releases/v1.0.1/background.js +0 -337
  76. package/airtable-clipper/releases/v1.0.1/base-setup.html +0 -324
  77. package/airtable-clipper/releases/v1.0.1/base-setup.js +0 -471
  78. package/airtable-clipper/releases/v1.0.1/content.js +0 -771
  79. package/airtable-clipper/releases/v1.0.1/icons/README.md +0 -69
  80. package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +0 -2
  81. package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +0 -3
  82. package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +0 -2
  83. package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +0 -2
  84. package/airtable-clipper/releases/v1.0.1/manifest.json +0 -70
  85. package/airtable-clipper/releases/v1.0.1/popup.html +0 -157
  86. package/airtable-clipper/releases/v1.0.1/popup.js +0 -562
  87. package/airtable-clipper/releases/v1.0.1/sidepanel.html +0 -25
  88. package/airtable-clipper/releases/v1.0.1/styles/content.css +0 -229
  89. package/airtable-clipper/releases/v1.0.1/styles/popup.css +0 -647
  90. package/airtable-clipper/releases/v1.0.2/background.js +0 -337
  91. package/airtable-clipper/releases/v1.0.2/base-setup.html +0 -324
  92. package/airtable-clipper/releases/v1.0.2/base-setup.js +0 -471
  93. package/airtable-clipper/releases/v1.0.2/content.js +0 -771
  94. package/airtable-clipper/releases/v1.0.2/icons/README.md +0 -69
  95. package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +0 -2
  96. package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +0 -3
  97. package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +0 -2
  98. package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +0 -2
  99. package/airtable-clipper/releases/v1.0.2/manifest.json +0 -62
  100. package/airtable-clipper/releases/v1.0.2/popup.html +0 -157
  101. package/airtable-clipper/releases/v1.0.2/popup.js +0 -567
  102. package/airtable-clipper/releases/v1.0.2/sidepanel.html +0 -25
  103. package/airtable-clipper/releases/v1.0.2/styles/content.css +0 -229
  104. package/airtable-clipper/releases/v1.0.2/styles/popup.css +0 -647
  105. package/airtable-clipper/terms-of-service.md +0 -124
  106. package/airtable-clipper/test-credentials.md +0 -61
  107. package/airtable-clipper/test-extension/background.js +0 -337
  108. package/airtable-clipper/test-extension/base-setup.html +0 -324
  109. package/airtable-clipper/test-extension/base-setup.js +0 -471
  110. package/airtable-clipper/test-extension/content.js +0 -873
  111. package/airtable-clipper/test-extension/icons/README.md +0 -69
  112. package/airtable-clipper/test-extension/icons/icon-128.png +0 -2
  113. package/airtable-clipper/test-extension/icons/icon-16.png +0 -3
  114. package/airtable-clipper/test-extension/icons/icon-32.png +0 -2
  115. package/airtable-clipper/test-extension/icons/icon-48.png +0 -2
  116. package/airtable-clipper/test-extension/manifest.json +0 -72
  117. package/airtable-clipper/test-extension/popup.html +0 -274
  118. package/airtable-clipper/test-extension/popup.js +0 -729
  119. package/airtable-clipper/test-extension/sidepanel.html +0 -25
  120. package/airtable-clipper/test-extension/styles/content.css +0 -229
  121. package/airtable-clipper/test-extension/styles/popup.css +0 -794
  122. package/airtable_mcp/__init__.py +0 -5
  123. package/airtable_mcp/src/server.py +0 -329
  124. package/airtable_mcp_v2.js +0 -1505
  125. package/airtable_mcp_v2_oauth.js +0 -1048
  126. package/airtable_mcp_v3_advanced.js +0 -1161
  127. package/cleanup.sh +0 -71
  128. package/docker-compose.production.yml +0 -366
  129. package/helm/airtable-mcp/Chart.yaml +0 -122
  130. package/helm/airtable-mcp/values.yaml +0 -538
  131. package/index.js +0 -179
  132. package/inspector.py +0 -148
  133. package/inspector_server.py +0 -337
  134. package/k8s/deployment.yaml +0 -402
  135. package/k8s/namespace.yaml +0 -108
  136. package/k8s/service.yaml +0 -194
  137. package/monitoring/alerts.yml +0 -289
  138. package/monitoring/prometheus.yml +0 -224
  139. package/publish-steps.txt +0 -27
  140. package/quick_test.sh +0 -30
  141. package/requirements.txt +0 -10
  142. package/setup.py +0 -29
  143. package/simple_airtable_server.py +0 -151
  144. package/smithery.yaml +0 -45
  145. package/test_all_features.sh +0 -146
  146. package/test_all_operations.sh +0 -120
  147. package/test_client.py +0 -70
  148. package/test_enhanced_features.js +0 -389
  149. package/test_mcp_comprehensive.js +0 -163
  150. package/test_mock_server.js +0 -180
  151. package/test_v1.4.0_final.sh +0 -131
  152. package/test_v1.5.0_comprehensive.sh +0 -96
  153. package/test_v1.5.0_final.sh +0 -224
  154. package/test_v1.6.0_comprehensive.sh +0 -187
  155. package/test_webhooks.sh +0 -105
@@ -1,562 +0,0 @@
1
- // Airtable Clipper - Simple Popup Controller
2
- import { AirtableClient, AirtableError } from './lib/airtable-client.js';
3
-
4
- class PopupController {
5
- constructor() {
6
- this.client = null;
7
- this.currentTab = null;
8
- this.isConnected = false;
9
- this.init();
10
- }
11
-
12
- async init() {
13
- try {
14
- // Get current tab
15
- const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
16
- this.currentTab = tabs[0];
17
-
18
- // Check connection status
19
- await this.checkConnection();
20
-
21
- // Update UI for current page
22
- this.updateUIForCurrentPage();
23
-
24
- // Bind all event listeners
25
- this.bindEvents();
26
- } catch (error) {
27
- console.error('Init failed:', error);
28
- this.showToast('Failed to initialize extension', 'error');
29
- }
30
- }
31
-
32
- async checkConnection() {
33
- try {
34
- const result = await chrome.storage.local.get(['airtableToken', 'baseId']);
35
-
36
- if (result.airtableToken && result.baseId) {
37
- // Test the connection
38
- this.client = new AirtableClient(result.airtableToken, result.baseId);
39
- const testResult = await this.client.testConnection();
40
-
41
- if (testResult.success) {
42
- this.isConnected = true;
43
- this.showConnectedView();
44
- } else {
45
- this.showSetupView();
46
- }
47
- } else {
48
- this.showSetupView();
49
- }
50
- } catch (error) {
51
- console.error('Connection check failed:', error);
52
- this.showSetupView();
53
- }
54
- }
55
-
56
- showSetupView() {
57
- document.getElementById('setupView').style.display = 'block';
58
- document.getElementById('connectedView').style.display = 'none';
59
- this.isConnected = false;
60
- }
61
-
62
- showConnectedView() {
63
- document.getElementById('setupView').style.display = 'none';
64
- document.getElementById('connectedView').style.display = 'block';
65
- this.isConnected = true;
66
- }
67
-
68
- updateUIForCurrentPage() {
69
- if (!this.currentTab || !this.isConnected) return;
70
-
71
- try {
72
- const url = new URL(this.currentTab.url);
73
- const domain = url.hostname.replace('www.', '');
74
-
75
- // Update site info
76
- document.getElementById('siteName').textContent = this.currentTab.title || 'Current Page';
77
- document.getElementById('siteUrl').textContent = domain;
78
-
79
- // Set site icon based on domain
80
- let icon = '🌐';
81
- if (domain.includes('linkedin.com')) {
82
- icon = '💼';
83
- document.getElementById('linkedinBtn').style.display = 'flex';
84
- document.getElementById('saveSubtitle').textContent = 'to Contacts';
85
- } else {
86
- document.getElementById('linkedinBtn').style.display = 'none';
87
- document.getElementById('saveSubtitle').textContent = 'to Web Clips';
88
- }
89
-
90
- document.getElementById('siteIcon').textContent = icon;
91
- } catch (error) {
92
- console.error('Failed to update UI for page:', error);
93
- }
94
- }
95
-
96
- bindEvents() {
97
- // Setup view events
98
- document.getElementById('connectBtn')?.addEventListener('click', () => this.handleConnect());
99
- document.getElementById('setupBtn')?.addEventListener('click', () => this.handleSetupDatabase());
100
-
101
- // Connected view events
102
- document.getElementById('quickSaveBtn')?.addEventListener('click', () => this.handleQuickSave());
103
- document.getElementById('linkedinBtn')?.addEventListener('click', () => this.handleLinkedInSave());
104
- document.getElementById('bulkBtn')?.addEventListener('click', () => this.handleBulkMode());
105
- document.getElementById('settingsBtn')?.addEventListener('click', () => this.showSettings());
106
-
107
- // Settings events
108
- document.getElementById('closeSettings')?.addEventListener('click', () => this.hideSettings());
109
- document.getElementById('disconnectBtn')?.addEventListener('click', () => this.handleDisconnect());
110
-
111
- // Auto-save settings
112
- document.getElementById('autoSave')?.addEventListener('change', () => this.saveSettings());
113
- document.getElementById('notifications')?.addEventListener('change', () => this.saveSettings());
114
- }
115
-
116
- async handleConnect() {
117
- const connectBtn = document.getElementById('connectBtn');
118
- const btnText = connectBtn.querySelector('.btn-text');
119
- const btnSpinner = document.getElementById('connectSpinner');
120
-
121
- const token = document.getElementById('airtableToken').value.trim();
122
- const baseId = document.getElementById('baseId').value.trim();
123
-
124
- if (!token || !baseId) {
125
- this.showToast('Please enter both token and base ID', 'error');
126
- return;
127
- }
128
-
129
- // Show loading state
130
- connectBtn.disabled = true;
131
- btnText.textContent = 'Connecting...';
132
- btnSpinner.style.display = 'block';
133
-
134
- try {
135
- // Test connection
136
- const testClient = new AirtableClient(token, baseId);
137
- const result = await testClient.testConnection();
138
-
139
- if (result.success) {
140
- // Save credentials
141
- await chrome.storage.local.set({
142
- airtableToken: token,
143
- baseId: baseId
144
- });
145
-
146
- this.client = testClient;
147
- this.isConnected = true;
148
- this.showConnectedView();
149
- this.updateUIForCurrentPage();
150
- this.showToast('Connected successfully!', 'success');
151
- } else {
152
- throw new Error(result.error || 'Connection test failed');
153
- }
154
- } catch (error) {
155
- console.error('Connection failed:', error);
156
- let errorMessage = 'Connection failed';
157
-
158
- if (error instanceof AirtableError) {
159
- if (error.message.includes('401')) {
160
- errorMessage = 'Invalid token. Please check your Personal Access Token.';
161
- } else if (error.message.includes('404')) {
162
- errorMessage = 'Base not found. Please check your Base ID.';
163
- } else {
164
- errorMessage = error.message;
165
- }
166
- } else if (error.message) {
167
- errorMessage = error.message;
168
- }
169
-
170
- this.showToast(errorMessage, 'error');
171
- } finally {
172
- // Reset button state
173
- connectBtn.disabled = false;
174
- btnText.textContent = 'Connect to Airtable';
175
- btnSpinner.style.display = 'none';
176
- }
177
- }
178
-
179
- async handleOAuthConnect() {
180
- const connectBtn = document.getElementById('oauthConnectBtn');
181
- const btnText = connectBtn.querySelector('.btn-text');
182
- const btnSpinner = document.getElementById('connectSpinner');
183
-
184
- // Show loading state
185
- connectBtn.disabled = true;
186
- btnText.textContent = 'Connecting to Airtable...';
187
- btnSpinner.style.display = 'block';
188
-
189
- try {
190
- // Initialize OAuth
191
- const { AirtableOAuth } = await import('./lib/airtable-oauth.js');
192
- const oauth = new AirtableOAuth();
193
-
194
- // Start OAuth flow
195
- await oauth.authenticate();
196
-
197
- // Get user's bases
198
- const bases = await oauth.getUserBases();
199
-
200
- if (bases && bases.length > 0) {
201
- // Populate base selection
202
- this.populateBaseOptions(bases);
203
-
204
- // Hide OAuth button, show base selection
205
- document.querySelector('.oauth-connect').style.display = 'none';
206
- document.getElementById('baseSelection').style.display = 'block';
207
-
208
- this.showToast('Connected to Airtable! Please select a base.', 'success');
209
- } else {
210
- throw new Error('No accessible bases found in your account');
211
- }
212
- } catch (error) {
213
- console.error('OAuth failed:', error);
214
- let errorMessage = 'Connection failed';
215
-
216
- if (error.message.includes('cancelled')) {
217
- errorMessage = 'Sign-in was cancelled';
218
- } else if (error.message.includes('access_denied')) {
219
- errorMessage = 'Access denied. Please try again and grant permissions.';
220
- } else if (error.message) {
221
- errorMessage = error.message;
222
- }
223
-
224
- this.showToast(errorMessage, 'error');
225
- } finally {
226
- // Reset button state
227
- connectBtn.disabled = false;
228
- btnText.textContent = 'Connect to Airtable';
229
- btnSpinner.style.display = 'none';
230
- }
231
- }
232
-
233
- populateBaseOptions(bases) {
234
- const baseSelect = document.getElementById('baseSelect');
235
- baseSelect.innerHTML = '<option value="">Choose a base...</option>';
236
-
237
- bases.forEach(base => {
238
- const option = document.createElement('option');
239
- option.value = base.id;
240
- option.textContent = base.name;
241
- baseSelect.appendChild(option);
242
- });
243
-
244
- // Store bases for later use
245
- this.availableBases = bases;
246
- }
247
-
248
- async handleConfirmBase() {
249
- const baseSelect = document.getElementById('baseSelect');
250
- const selectedBaseId = baseSelect.value;
251
-
252
- if (!selectedBaseId) {
253
- this.showToast('Please select a base', 'error');
254
- return;
255
- }
256
-
257
- const selectedBase = this.availableBases.find(base => base.id === selectedBaseId);
258
-
259
- try {
260
- // Save base selection
261
- await chrome.storage.local.set({
262
- selectedBaseId: selectedBaseId,
263
- selectedBaseName: selectedBase.name,
264
- useOAuth: true
265
- });
266
-
267
- // Create client with OAuth token
268
- const { AirtableOAuth } = await import('./lib/airtable-oauth.js');
269
- const oauth = new AirtableOAuth();
270
- const token = await oauth.getAccessToken();
271
- this.client = new AirtableClient(token, selectedBaseId);
272
-
273
- // Switch to connected view
274
- this.isConnected = true;
275
- this.showConnectedView();
276
- this.updateUIForCurrentPage();
277
- this.showToast(`Connected to ${selectedBase.name}!`, 'success');
278
- } catch (error) {
279
- console.error('Base confirmation failed:', error);
280
- this.showToast(`Failed to connect: ${error.message}`, 'error');
281
- }
282
- }
283
-
284
- async handleManualConnect() {
285
- const connectBtn = document.getElementById('manualConnectBtn');
286
- const btnText = connectBtn.querySelector('.btn-text');
287
-
288
- const token = document.getElementById('airtableToken').value.trim();
289
- const baseId = document.getElementById('baseId').value.trim();
290
-
291
- if (!token || !baseId) {
292
- this.showToast('Please enter both token and base ID', 'error');
293
- return;
294
- }
295
-
296
- // Show loading state
297
- connectBtn.disabled = true;
298
- btnText.textContent = 'Connecting...';
299
- if (btnSpinner) {
300
- btnSpinner.style.display = 'block';
301
- }
302
-
303
- try {
304
- // Test connection
305
- const testClient = new AirtableClient(token, baseId);
306
- const result = await testClient.testConnection();
307
-
308
- if (result.success) {
309
- // Save credentials
310
- await chrome.storage.local.set({
311
- airtableToken: token,
312
- baseId: baseId
313
- });
314
-
315
- this.client = testClient;
316
- this.isConnected = true;
317
- this.showConnectedView();
318
- this.updateUIForCurrentPage();
319
- this.showToast('Connected successfully!', 'success');
320
- } else {
321
- throw new Error(result.error || 'Connection test failed');
322
- }
323
- } catch (error) {
324
- console.error('Connection failed:', error);
325
- let errorMessage = 'Connection failed';
326
-
327
- if (error instanceof AirtableError) {
328
- if (error.message.includes('401')) {
329
- errorMessage = 'Invalid token. Please check your Personal Access Token.';
330
- } else if (error.message.includes('404')) {
331
- errorMessage = 'Base not found. Please check your Base ID.';
332
- } else {
333
- errorMessage = error.message;
334
- }
335
- } else if (error.message) {
336
- errorMessage = error.message;
337
- }
338
-
339
- this.showToast(errorMessage, 'error');
340
- } finally {
341
- // Reset button state
342
- connectBtn.disabled = false;
343
- btnText.textContent = 'Connect Manually';
344
- }
345
- }
346
-
347
- async handleQuickSave() {
348
- if (!this.isConnected || !this.currentTab) {
349
- this.showToast('Not connected to Airtable', 'error');
350
- return;
351
- }
352
-
353
- const saveBtn = document.getElementById('quickSaveBtn');
354
- const originalText = saveBtn.querySelector('.save-text').textContent;
355
-
356
- saveBtn.disabled = true;
357
- saveBtn.querySelector('.save-text').textContent = 'Saving...';
358
-
359
- try {
360
- // Extract page data
361
- const pageData = {
362
- 'Title': this.currentTab.title || 'Untitled',
363
- 'URL': this.currentTab.url,
364
- 'Domain': new URL(this.currentTab.url).hostname,
365
- 'Saved At': new Date().toISOString()
366
- };
367
-
368
- // Determine table based on site
369
- const tableName = this.currentTab.url.includes('linkedin.com') ? 'Contacts' : 'Web Clips';
370
-
371
- // Save to Airtable
372
- const result = await this.client.createRecord(tableName, pageData);
373
-
374
- if (result.success) {
375
- this.showToast(`Saved to ${tableName}!`, 'success');
376
- } else {
377
- throw new Error(result.error || 'Failed to save');
378
- }
379
- } catch (error) {
380
- console.error('Quick save failed:', error);
381
- this.showToast(`Failed to save: ${error.message}`, 'error');
382
- } finally {
383
- saveBtn.disabled = false;
384
- saveBtn.querySelector('.save-text').textContent = originalText;
385
- }
386
- }
387
-
388
- async handleLinkedInSave() {
389
- if (!this.isConnected || !this.currentTab) {
390
- this.showToast('Not connected to Airtable', 'error');
391
- return;
392
- }
393
-
394
- if (!this.currentTab.url.includes('linkedin.com')) {
395
- this.showToast('This only works on LinkedIn profiles', 'error');
396
- return;
397
- }
398
-
399
- const linkedinBtn = document.getElementById('linkedinBtn');
400
- const originalText = linkedinBtn.querySelector('.action-text').textContent;
401
-
402
- linkedinBtn.disabled = true;
403
- linkedinBtn.querySelector('.action-text').textContent = 'Saving...';
404
-
405
- try {
406
- // Inject content script to extract profile data
407
- const [result] = await chrome.scripting.executeScript({
408
- target: { tabId: this.currentTab.id },
409
- files: ['lib/linkedin-scraper.js']
410
- });
411
-
412
- if (result && result.result) {
413
- const profileData = result.result;
414
-
415
- // Save to Contacts table
416
- const saveResult = await this.client.createRecord('Contacts', profileData);
417
-
418
- if (saveResult.success) {
419
- this.showToast('LinkedIn profile saved!', 'success');
420
- } else {
421
- throw new Error(saveResult.error || 'Failed to save profile');
422
- }
423
- } else {
424
- throw new Error('Failed to extract profile data');
425
- }
426
- } catch (error) {
427
- console.error('LinkedIn save failed:', error);
428
- this.showToast(`Failed to save profile: ${error.message}`, 'error');
429
- } finally {
430
- linkedinBtn.disabled = false;
431
- linkedinBtn.querySelector('.action-text').textContent = originalText;
432
- }
433
- }
434
-
435
- async handleBulkMode() {
436
- if (!this.isConnected || !this.currentTab) {
437
- this.showToast('Not connected to Airtable', 'error');
438
- return;
439
- }
440
-
441
- try {
442
- // Inject content script for bulk selection
443
- await chrome.scripting.executeScript({
444
- target: { tabId: this.currentTab.id },
445
- files: ['content.js']
446
- });
447
-
448
- // Send message to activate bulk mode
449
- await chrome.tabs.sendMessage(this.currentTab.id, {
450
- action: 'activateBulkMode'
451
- });
452
-
453
- this.showToast('Bulk mode activated! Select elements on the page.', 'success');
454
- window.close(); // Close popup so user can interact with page
455
- } catch (error) {
456
- console.error('Bulk mode failed:', error);
457
- this.showToast(`Failed to activate bulk mode: ${error.message}`, 'error');
458
- }
459
- }
460
-
461
- async handleSetupDatabase() {
462
- try {
463
- const result = await chrome.runtime.sendMessage({
464
- action: 'openDatabaseSetup'
465
- });
466
-
467
- if (result.success) {
468
- this.showToast('Database setup opened!', 'success');
469
- window.close();
470
- } else {
471
- throw new Error('Failed to open setup');
472
- }
473
- } catch (error) {
474
- console.error('Database setup failed:', error);
475
- this.showToast('Database setup will be available soon', 'info');
476
- }
477
- }
478
-
479
- showSettings() {
480
- document.getElementById('settingsPanel').style.display = 'block';
481
- }
482
-
483
- hideSettings() {
484
- document.getElementById('settingsPanel').style.display = 'none';
485
- }
486
-
487
- async handleDisconnect() {
488
- try {
489
- // Clear all possible authentication data
490
- await chrome.storage.local.remove([
491
- // Manual token data
492
- 'airtableToken', 'baseId',
493
- // OAuth data
494
- 'selectedBaseId', 'selectedBaseName', 'useOAuth',
495
- 'airtable_access_token', 'airtable_refresh_token',
496
- 'airtable_token_expires', 'airtable_token_type', 'airtable_scope'
497
- ]);
498
-
499
- this.client = null;
500
- this.isConnected = false;
501
- this.showSetupView();
502
- this.showToast('Disconnected successfully', 'success');
503
- } catch (error) {
504
- console.error('Disconnect failed:', error);
505
- this.showToast('Failed to disconnect', 'error');
506
- }
507
- }
508
-
509
- async saveSettings() {
510
- try {
511
- const settings = {
512
- autoSave: document.getElementById('autoSave').checked,
513
- notifications: document.getElementById('notifications').checked,
514
- defaultTable: document.getElementById('defaultTable').value,
515
- linkedinTable: document.getElementById('linkedinTable').value
516
- };
517
-
518
- await chrome.storage.local.set({ settings });
519
- this.showToast('Settings saved', 'success');
520
- } catch (error) {
521
- console.error('Save settings failed:', error);
522
- this.showToast('Failed to save settings', 'error');
523
- }
524
- }
525
-
526
- showToast(message, type = 'success') {
527
- const toast = document.getElementById('toast');
528
- const toastIcon = document.getElementById('toastIcon');
529
- const toastMessage = document.getElementById('toastMessage');
530
-
531
- // Set icon based on type
532
- const icons = {
533
- success: '✅',
534
- error: '❌',
535
- warning: '⚠️',
536
- info: 'ℹ️'
537
- };
538
-
539
- toastIcon.textContent = icons[type] || icons.success;
540
- toastMessage.textContent = message;
541
-
542
- // Show toast
543
- toast.classList.add('show');
544
-
545
- // Hide after 3 seconds
546
- setTimeout(() => {
547
- toast.classList.remove('show');
548
- }, 3000);
549
- }
550
- }
551
-
552
- // Initialize when DOM is ready
553
- document.addEventListener('DOMContentLoaded', () => {
554
- new PopupController();
555
- });
556
-
557
- // Also initialize immediately in case DOMContentLoaded already fired
558
- if (document.readyState === 'loading') {
559
- document.addEventListener('DOMContentLoaded', () => new PopupController());
560
- } else {
561
- new PopupController();
562
- }
@@ -1,25 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Airtable Clipper Dashboard</title>
7
- <link rel="stylesheet" href="styles/popup.css">
8
- </head>
9
- <body>
10
- <div class="header">
11
- <h1>📎 Airtable Clipper</h1>
12
- <p>Dashboard & Bulk Operations</p>
13
- </div>
14
-
15
- <div style="padding: 20px; text-align: center;">
16
- <h2>🚀 Coming Soon!</h2>
17
- <p>The dashboard panel is under development.</p>
18
- <p>For now, use the main extension popup to save content to Airtable.</p>
19
-
20
- <button onclick="chrome.action.openPopup();" style="margin-top: 20px; padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer;">
21
- Open Main Panel
22
- </button>
23
- </div>
24
- </body>
25
- </html>