@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,475 +0,0 @@
1
- // Airtable Clipper Popup Script
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.settings = {};
9
- this.init();
10
- }
11
-
12
- async init() {
13
- // Get current tab
14
- const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
15
- this.currentTab = tabs[0];
16
-
17
- // Load settings and initialize UI
18
- await this.loadSettings();
19
- this.initializeUI();
20
- this.bindEvents();
21
-
22
- // Check connection status
23
- await this.checkConnection();
24
-
25
- // Update UI based on current page
26
- this.updateUIForCurrentPage();
27
- }
28
-
29
- async loadSettings() {
30
- try {
31
- const response = await chrome.runtime.sendMessage({
32
- action: 'getSettings'
33
- });
34
-
35
- if (response.success) {
36
- this.settings = response.settings;
37
- this.populateSettingsUI();
38
-
39
- if (this.settings.airtableToken && this.settings.baseId) {
40
- this.client = new AirtableClient(this.settings.airtableToken, this.settings.baseId);
41
- }
42
- }
43
- } catch (error) {
44
- console.error('Failed to load settings:', error);
45
- }
46
- }
47
-
48
- populateSettingsUI() {
49
- // Populate configuration form
50
- const tokenInput = document.getElementById('airtableToken');
51
- const baseIdInput = document.getElementById('baseId');
52
-
53
- if (tokenInput) tokenInput.value = this.settings.airtableToken || '';
54
- if (baseIdInput) baseIdInput.value = this.settings.baseId || '';
55
-
56
- // Populate settings
57
- const defaultTableSelect = document.getElementById('defaultTable');
58
- const linkedinTableSelect = document.getElementById('linkedinTable');
59
- const autoSaveCheckbox = document.getElementById('autoSave');
60
- const notificationsCheckbox = document.getElementById('notifications');
61
-
62
- if (defaultTableSelect) defaultTableSelect.value = this.settings.defaultTable || 'Clips';
63
- if (linkedinTableSelect) linkedinTableSelect.value = this.settings.linkedinTable || 'Contacts';
64
- if (autoSaveCheckbox) autoSaveCheckbox.checked = this.settings.autoSave || false;
65
- if (notificationsCheckbox) notificationsCheckbox.checked = this.settings.notifications !== false;
66
- }
67
-
68
- initializeUI() {
69
- const isConnected = this.client !== null;
70
-
71
- // Show/hide sections based on connection status
72
- document.getElementById('configuration').style.display = isConnected ? 'none' : 'block';
73
- document.getElementById('quickActions').style.display = isConnected ? 'block' : 'none';
74
- document.getElementById('settings').style.display = 'none'; // Hidden by default
75
-
76
- // Update connection status
77
- this.updateConnectionStatus(isConnected);
78
-
79
- // Load usage stats if connected
80
- if (isConnected) {
81
- this.loadUsageStats();
82
- }
83
- }
84
-
85
- bindEvents() {
86
- // Configuration events
87
- document.getElementById('connectBtn')?.addEventListener('click', () => this.handleConnect());
88
-
89
- // Quick action events
90
- document.getElementById('quickSaveBtn')?.addEventListener('click', () => this.handleQuickSave());
91
- document.getElementById('linkedinSaveBtn')?.addEventListener('click', () => this.handleLinkedInSave());
92
- document.getElementById('bulkModeBtn')?.addEventListener('click', () => this.handleBulkMode());
93
-
94
- // Footer events
95
- document.getElementById('settingsToggle')?.addEventListener('click', () => this.toggleSettings());
96
- document.getElementById('helpBtn')?.addEventListener('click', () => this.openHelp());
97
- document.getElementById('sidePanelBtn')?.addEventListener('click', () => this.openSidePanel());
98
-
99
- // Setup database button
100
- document.getElementById('setupDatabaseBtn')?.addEventListener('click', () => this.openDatabaseSetup());
101
-
102
- // Settings events
103
- document.getElementById('autoSave')?.addEventListener('change', () => this.saveSettings());
104
- document.getElementById('notifications')?.addEventListener('change', () => this.saveSettings());
105
- document.getElementById('defaultTable')?.addEventListener('change', () => this.saveSettings());
106
- document.getElementById('linkedinTable')?.addEventListener('change', () => this.saveSettings());
107
-
108
- // Status message close
109
- document.getElementById('statusClose')?.addEventListener('click', () => this.hideStatusMessage());
110
- }
111
-
112
- async handleConnect() {
113
- const connectBtn = document.getElementById('connectBtn');
114
- const connectText = document.getElementById('connectText');
115
- const connectLoading = document.getElementById('connectLoading');
116
-
117
- const token = document.getElementById('airtableToken').value.trim();
118
- const baseId = document.getElementById('baseId').value.trim();
119
-
120
- if (!token || !baseId) {
121
- this.showStatusMessage('Please enter both token and base ID', 'error');
122
- return;
123
- }
124
-
125
- // Show loading state
126
- connectBtn.disabled = true;
127
- connectText.textContent = 'Connecting...';
128
- connectLoading.style.display = 'inline';
129
-
130
- try {
131
- // Test connection
132
- const testClient = new AirtableClient(token, baseId);
133
- const result = await testClient.testConnection();
134
-
135
- if (result.success) {
136
- // Save settings
137
- await this.saveSettings({ airtableToken: token, baseId: baseId });
138
-
139
- // Update client
140
- this.client = testClient;
141
-
142
- // Load table options
143
- await this.loadTableOptions();
144
-
145
- // Update UI
146
- this.initializeUI();
147
- this.showStatusMessage('Connected successfully!', 'success');
148
- } else {
149
- throw new Error(result.message);
150
- }
151
- } catch (error) {
152
- console.error('Connection failed:', error);
153
- this.showStatusMessage(`Connection failed: ${error.message}`, 'error');
154
- } finally {
155
- // Reset button state
156
- connectBtn.disabled = false;
157
- connectText.textContent = 'Connect to Airtable';
158
- connectLoading.style.display = 'none';
159
- }
160
- }
161
-
162
- async handleQuickSave() {
163
- if (!this.client) return;
164
-
165
- this.showLoading('Saving to Airtable...');
166
-
167
- try {
168
- // Send message to content script to extract page data
169
- const response = await chrome.tabs.sendMessage(this.currentTab.id, {
170
- action: 'extractPageData',
171
- type: 'general'
172
- });
173
-
174
- if (response && response.success) {
175
- // Save to Airtable via background script
176
- const saveResponse = await chrome.runtime.sendMessage({
177
- action: 'saveToAirtable',
178
- data: {
179
- type: 'general',
180
- title: response.data.title,
181
- url: response.data.url,
182
- content: response.data.content,
183
- timestamp: new Date().toISOString()
184
- }
185
- });
186
-
187
- if (saveResponse.success) {
188
- this.showStatusMessage('Saved to Airtable!', 'success');
189
- this.updateUsageStats();
190
- } else {
191
- throw new Error(saveResponse.error);
192
- }
193
- } else {
194
- // Fallback: save basic page info
195
- await this.saveBasicPageInfo();
196
- }
197
- } catch (error) {
198
- console.error('Quick save failed:', error);
199
- this.showStatusMessage(`Save failed: ${error.message}`, 'error');
200
- } finally {
201
- this.hideLoading();
202
- }
203
- }
204
-
205
- async handleLinkedInSave() {
206
- if (!this.client) return;
207
-
208
- this.showLoading('Extracting LinkedIn profile...');
209
-
210
- try {
211
- const response = await chrome.tabs.sendMessage(this.currentTab.id, {
212
- action: 'extractLinkedInProfile'
213
- });
214
-
215
- if (response && response.success) {
216
- const saveResponse = await chrome.runtime.sendMessage({
217
- action: 'saveToAirtable',
218
- data: {
219
- type: 'linkedin',
220
- ...response.data,
221
- timestamp: new Date().toISOString()
222
- }
223
- });
224
-
225
- if (saveResponse.success) {
226
- this.showStatusMessage('LinkedIn profile saved!', 'success');
227
- this.updateUsageStats();
228
- } else {
229
- throw new Error(saveResponse.error);
230
- }
231
- } else {
232
- throw new Error('Failed to extract LinkedIn profile');
233
- }
234
- } catch (error) {
235
- console.error('LinkedIn save failed:', error);
236
- this.showStatusMessage(`LinkedIn save failed: ${error.message}`, 'error');
237
- } finally {
238
- this.hideLoading();
239
- }
240
- }
241
-
242
- async handleBulkMode() {
243
- try {
244
- // Send message to content script to enter bulk mode
245
- await chrome.tabs.sendMessage(this.currentTab.id, {
246
- action: 'enterBulkMode'
247
- });
248
-
249
- // Close popup
250
- window.close();
251
- } catch (error) {
252
- console.error('Bulk mode failed:', error);
253
- this.showStatusMessage('Bulk mode not available on this page', 'error');
254
- }
255
- }
256
-
257
- async saveBasicPageInfo() {
258
- const saveResponse = await chrome.runtime.sendMessage({
259
- action: 'saveToAirtable',
260
- data: {
261
- type: 'general',
262
- title: this.currentTab.title,
263
- url: this.currentTab.url,
264
- timestamp: new Date().toISOString()
265
- }
266
- });
267
-
268
- if (saveResponse.success) {
269
- this.showStatusMessage('Page saved to Airtable!', 'success');
270
- this.updateUsageStats();
271
- } else {
272
- throw new Error(saveResponse.error);
273
- }
274
- }
275
-
276
- async loadTableOptions() {
277
- if (!this.client) return;
278
-
279
- try {
280
- const tables = await this.client.listTables();
281
-
282
- const defaultTableSelect = document.getElementById('defaultTable');
283
- const linkedinTableSelect = document.getElementById('linkedinTable');
284
-
285
- // Clear existing options
286
- defaultTableSelect.innerHTML = '';
287
- linkedinTableSelect.innerHTML = '';
288
-
289
- // Add table options
290
- tables.forEach(table => {
291
- const option1 = new Option(table.name, table.name);
292
- const option2 = new Option(table.name, table.name);
293
-
294
- defaultTableSelect.add(option1);
295
- linkedinTableSelect.add(option2);
296
- });
297
-
298
- // Set current values
299
- defaultTableSelect.value = this.settings.defaultTable || (tables.length > 0 ? tables[0].name : '');
300
- linkedinTableSelect.value = this.settings.linkedinTable || 'Contacts';
301
-
302
- } catch (error) {
303
- console.error('Failed to load tables:', error);
304
- }
305
- }
306
-
307
- async checkConnection() {
308
- if (!this.client) return false;
309
-
310
- try {
311
- const result = await this.client.testConnection();
312
- this.updateConnectionStatus(result.success);
313
- return result.success;
314
- } catch (error) {
315
- this.updateConnectionStatus(false);
316
- return false;
317
- }
318
- }
319
-
320
- updateConnectionStatus(isConnected) {
321
- const statusDot = document.querySelector('.status-dot');
322
- const statusText = document.querySelector('.status-text');
323
-
324
- if (isConnected) {
325
- statusDot.className = 'status-dot connected';
326
- statusText.textContent = 'Connected';
327
- } else {
328
- statusDot.className = 'status-dot disconnected';
329
- statusText.textContent = 'Not Connected';
330
- }
331
- }
332
-
333
- updateUIForCurrentPage() {
334
- if (!this.currentTab) return;
335
-
336
- const url = this.currentTab.url;
337
- const quickSaveBtn = document.getElementById('quickSaveBtn');
338
- const quickSaveSubtext = document.getElementById('quickSaveSubtext');
339
- const linkedinSaveBtn = document.getElementById('linkedinSaveBtn');
340
-
341
- // Update quick save button text based on current page
342
- if (url.includes('linkedin.com/in/')) {
343
- quickSaveSubtext.textContent = 'Save LinkedIn profile';
344
- linkedinSaveBtn.style.display = 'block';
345
- } else if (url.includes('github.com')) {
346
- quickSaveSubtext.textContent = 'Save GitHub repository';
347
- } else if (url.includes('twitter.com') || url.includes('x.com')) {
348
- quickSaveSubtext.textContent = 'Save tweet';
349
- } else {
350
- quickSaveSubtext.textContent = 'Save this page';
351
- }
352
-
353
- // Hide LinkedIn button if not on LinkedIn
354
- if (!url.includes('linkedin.com')) {
355
- linkedinSaveBtn.style.display = 'none';
356
- }
357
- }
358
-
359
- async saveSettings(newSettings = null) {
360
- try {
361
- const settingsToSave = newSettings || {
362
- defaultTable: document.getElementById('defaultTable')?.value || this.settings.defaultTable,
363
- linkedinTable: document.getElementById('linkedinTable')?.value || this.settings.linkedinTable,
364
- autoSave: document.getElementById('autoSave')?.checked || this.settings.autoSave,
365
- notifications: document.getElementById('notifications')?.checked !== false,
366
- ...this.settings
367
- };
368
-
369
- if (newSettings) {
370
- Object.assign(settingsToSave, newSettings);
371
- }
372
-
373
- const response = await chrome.runtime.sendMessage({
374
- action: 'saveSettings',
375
- settings: settingsToSave
376
- });
377
-
378
- if (response.success) {
379
- this.settings = settingsToSave;
380
- }
381
- } catch (error) {
382
- console.error('Failed to save settings:', error);
383
- }
384
- }
385
-
386
- async loadUsageStats() {
387
- try {
388
- const result = await chrome.storage.local.get(['usageStats']);
389
- const stats = result.usageStats || { weeklyClips: 0, totalClips: 0 };
390
-
391
- document.getElementById('weeklyClips').textContent = stats.weeklyClips;
392
- document.getElementById('usageInfo').style.display = 'block';
393
- } catch (error) {
394
- console.error('Failed to load usage stats:', error);
395
- }
396
- }
397
-
398
- async updateUsageStats() {
399
- // Reload usage stats after saving
400
- setTimeout(() => this.loadUsageStats(), 500);
401
- }
402
-
403
- toggleSettings() {
404
- const settings = document.getElementById('settings');
405
- const isVisible = settings.style.display !== 'none';
406
-
407
- settings.style.display = isVisible ? 'none' : 'block';
408
-
409
- const toggleBtn = document.getElementById('settingsToggle');
410
- toggleBtn.textContent = isVisible ? 'Settings' : 'Hide Settings';
411
- }
412
-
413
- openHelp() {
414
- chrome.tabs.create({
415
- url: 'https://github.com/rashidazarang/airtable-clipper#readme'
416
- });
417
- }
418
-
419
- async openSidePanel() {
420
- try {
421
- await chrome.runtime.sendMessage({
422
- action: 'openSidePanel'
423
- });
424
- window.close();
425
- } catch (error) {
426
- console.error('Failed to open side panel:', error);
427
- }
428
- }
429
-
430
- openDatabaseSetup() {
431
- // Open the database setup wizard in a new window
432
- chrome.tabs.create({
433
- url: chrome.runtime.getURL('base-setup.html'),
434
- active: true
435
- });
436
- window.close();
437
- }
438
-
439
- showStatusMessage(message, type = 'success') {
440
- const statusMessage = document.getElementById('statusMessage');
441
- const statusIcon = document.getElementById('statusIcon');
442
- const statusText = document.getElementById('statusText');
443
-
444
- statusIcon.textContent = type === 'success' ? '✅' : '❌';
445
- statusText.textContent = message;
446
- statusMessage.className = `status-message ${type}`;
447
- statusMessage.style.display = 'block';
448
-
449
- // Auto-hide after 3 seconds
450
- setTimeout(() => this.hideStatusMessage(), 3000);
451
- }
452
-
453
- hideStatusMessage() {
454
- const statusMessage = document.getElementById('statusMessage');
455
- statusMessage.style.display = 'none';
456
- }
457
-
458
- showLoading(text = 'Processing...') {
459
- const loadingOverlay = document.getElementById('loadingOverlay');
460
- const loadingText = document.getElementById('loadingText');
461
-
462
- loadingText.textContent = text;
463
- loadingOverlay.style.display = 'flex';
464
- }
465
-
466
- hideLoading() {
467
- const loadingOverlay = document.getElementById('loadingOverlay');
468
- loadingOverlay.style.display = 'none';
469
- }
470
- }
471
-
472
- // Initialize popup when DOM is loaded
473
- document.addEventListener('DOMContentLoaded', () => {
474
- new PopupController();
475
- });
@@ -1,229 +0,0 @@
1
- /* Airtable Clipper Content Script Styles */
2
-
3
- /* Floating Action Button */
4
- #airtable-clipper-fab {
5
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
6
- }
7
-
8
- #airtable-clipper-fab .fab-main {
9
- transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
10
- }
11
-
12
- #airtable-clipper-fab .fab-main:hover {
13
- transform: scale(1.1) rotate(5deg);
14
- }
15
-
16
- #airtable-clipper-fab .fab-main:active {
17
- transform: scale(0.95);
18
- }
19
-
20
- /* Selection Toolbar */
21
- #airtable-clipper-selection-toolbar {
22
- animation: fadeInUp 0.2s ease;
23
- }
24
-
25
- #airtable-clipper-selection-toolbar .selection-btn:hover {
26
- background: rgba(255, 255, 255, 0.1);
27
- }
28
-
29
- /* Bulk Mode Overlay */
30
- #airtable-clipper-bulk-overlay {
31
- animation: slideDown 0.3s ease;
32
- }
33
-
34
- #airtable-clipper-bulk-overlay .bulk-btn:hover {
35
- background: rgba(255, 255, 255, 0.3);
36
- transform: translateY(-1px);
37
- }
38
-
39
- /* Bulk Selection Highlights */
40
- .airtable-clipper-selected {
41
- background: rgba(102, 126, 234, 0.1) !important;
42
- border: 2px solid #667eea !important;
43
- border-radius: 4px;
44
- transition: all 0.2s ease;
45
- }
46
-
47
- .airtable-clipper-selectable:hover {
48
- background: rgba(102, 126, 234, 0.05) !important;
49
- cursor: pointer;
50
- }
51
-
52
- /* Notifications */
53
- #airtable-clipper-notification {
54
- animation: slideInRight 0.3s ease;
55
- backdrop-filter: blur(10px);
56
- }
57
-
58
- /* Animations */
59
- @keyframes fadeInUp {
60
- from {
61
- opacity: 0;
62
- transform: translateY(10px);
63
- }
64
- to {
65
- opacity: 1;
66
- transform: translateY(0);
67
- }
68
- }
69
-
70
- @keyframes slideDown {
71
- from {
72
- transform: translateY(-100%);
73
- }
74
- to {
75
- transform: translateY(0);
76
- }
77
- }
78
-
79
- @keyframes slideInRight {
80
- from {
81
- transform: translateX(100%);
82
- opacity: 0;
83
- }
84
- to {
85
- transform: translateX(0);
86
- opacity: 1;
87
- }
88
- }
89
-
90
- @keyframes pulse {
91
- 0%, 100% {
92
- opacity: 1;
93
- }
94
- 50% {
95
- opacity: 0.7;
96
- }
97
- }
98
-
99
- /* Loading States */
100
- .airtable-clipper-loading {
101
- position: relative;
102
- opacity: 0.7;
103
- }
104
-
105
- .airtable-clipper-loading::after {
106
- content: '';
107
- position: absolute;
108
- top: 50%;
109
- left: 50%;
110
- width: 20px;
111
- height: 20px;
112
- margin: -10px 0 0 -10px;
113
- border: 2px solid #667eea;
114
- border-top: 2px solid transparent;
115
- border-radius: 50%;
116
- animation: spin 1s linear infinite;
117
- }
118
-
119
- @keyframes spin {
120
- 0% { transform: rotate(0deg); }
121
- 100% { transform: rotate(360deg); }
122
- }
123
-
124
- /* Site-specific enhancements */
125
-
126
- /* LinkedIn */
127
- body[class*="linkedin"] .airtable-clipper-selected,
128
- [data-test-id*="search-result"] .airtable-clipper-selected {
129
- box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
130
- }
131
-
132
- /* GitHub */
133
- .repository-content .airtable-clipper-selected,
134
- .Box .airtable-clipper-selected {
135
- border-radius: 6px;
136
- }
137
-
138
- /* Stack Overflow */
139
- .question-summary .airtable-clipper-selected,
140
- .answer .airtable-clipper-selected {
141
- border-left: 4px solid #667eea;
142
- padding-left: 8px;
143
- }
144
-
145
- /* Medium */
146
- article .airtable-clipper-selected {
147
- border-radius: 8px;
148
- padding: 8px;
149
- margin: 4px 0;
150
- }
151
-
152
- /* Responsive Design */
153
- @media (max-width: 768px) {
154
- #airtable-clipper-fab {
155
- bottom: 80px; /* Above mobile browser UI */
156
- right: 16px;
157
- z-index: 10000;
158
- }
159
-
160
- #airtable-clipper-fab .fab-main {
161
- width: 48px;
162
- height: 48px;
163
- font-size: 18px;
164
- }
165
-
166
- #airtable-clipper-bulk-overlay {
167
- font-size: 12px;
168
- padding: 8px 12px;
169
- }
170
-
171
- #airtable-clipper-bulk-overlay .bulk-header {
172
- flex-direction: column;
173
- gap: 8px;
174
- align-items: flex-start;
175
- }
176
-
177
- #airtable-clipper-bulk-overlay .bulk-actions {
178
- display: flex;
179
- gap: 8px;
180
- }
181
-
182
- #airtable-clipper-notification {
183
- top: 10px;
184
- right: 10px;
185
- left: 10px;
186
- max-width: none;
187
- font-size: 13px;
188
- }
189
- }
190
-
191
- /* Dark Mode Support */
192
- @media (prefers-color-scheme: dark) {
193
- .airtable-clipper-selected {
194
- background: rgba(102, 126, 234, 0.2) !important;
195
- border-color: #818cf8 !important;
196
- }
197
-
198
- .airtable-clipper-selectable:hover {
199
- background: rgba(102, 126, 234, 0.1) !important;
200
- }
201
- }
202
-
203
- /* High Contrast Mode */
204
- @media (prefers-contrast: high) {
205
- #airtable-clipper-fab .fab-main {
206
- border: 2px solid #000;
207
- }
208
-
209
- .airtable-clipper-selected {
210
- border-width: 3px !important;
211
- background: rgba(102, 126, 234, 0.3) !important;
212
- }
213
- }
214
-
215
- /* Reduced Motion */
216
- @media (prefers-reduced-motion: reduce) {
217
- #airtable-clipper-fab .fab-main,
218
- #airtable-clipper-notification,
219
- #airtable-clipper-selection-toolbar,
220
- #airtable-clipper-bulk-overlay,
221
- .airtable-clipper-selected {
222
- animation: none !important;
223
- transition: none !important;
224
- }
225
-
226
- #airtable-clipper-fab .fab-main:hover {
227
- transform: none !important;
228
- }
229
- }