@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.
- package/README.md +1 -1
- package/airtable_simple_production.js +387 -5
- package/examples/claude_simple_config.json +0 -9
- package/package.json +10 -1
- package/.github/ISSUE_TEMPLATE/bug-report.yml +0 -173
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
- package/.github/ISSUE_TEMPLATE/custom.md +0 -10
- package/.github/ISSUE_TEMPLATE/feature-request.yml +0 -209
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.github/ISSUE_TEMPLATE/security-report.yml +0 -216
- package/.github/pull_request_template.md +0 -245
- package/.github/workflows/ci-cd.yml +0 -408
- package/.github/workflows/security-audit.yml +0 -316
- package/API_DOCUMENTATION.md +0 -897
- package/CAPABILITY_REPORT.md +0 -118
- package/CLAUDE_INTEGRATION.md +0 -96
- package/CODE_OF_CONDUCT.md +0 -181
- package/CONTRIBUTING.md +0 -81
- package/DEVELOPMENT.md +0 -190
- package/Dockerfile +0 -39
- package/Dockerfile.node +0 -20
- package/Dockerfile.production +0 -127
- package/IMPROVEMENT_PROPOSAL.md +0 -371
- package/INSTALLATION.md +0 -183
- package/ISSUE_RESPONSES.md +0 -171
- package/MCP_REVIEW_SUMMARY.md +0 -142
- package/QUICK_START.md +0 -60
- package/RELEASE_NOTES_v1.2.0.md +0 -50
- package/RELEASE_NOTES_v1.2.1.md +0 -40
- package/RELEASE_NOTES_v1.2.2.md +0 -48
- package/RELEASE_NOTES_v1.2.3.md +0 -105
- package/RELEASE_NOTES_v1.2.4.md +0 -60
- package/RELEASE_NOTES_v1.4.0.md +0 -104
- package/RELEASE_NOTES_v1.5.0.md +0 -185
- package/RELEASE_NOTES_v1.6.0.md +0 -248
- package/SECURITY_NOTICE.md +0 -40
- package/airtable-clipper/CHANGELOG.md +0 -198
- package/airtable-clipper/CHROME_STORE_SUBMISSION.md +0 -343
- package/airtable-clipper/LAUNCH_STRATEGY.md +0 -495
- package/airtable-clipper/LICENSE +0 -21
- package/airtable-clipper/OAUTH_SETUP.md +0 -51
- package/airtable-clipper/PRIVACY_POLICY.md +0 -187
- package/airtable-clipper/README.md +0 -575
- package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +0 -273
- package/airtable-clipper/build.sh +0 -85
- package/airtable-clipper/docs/QUICK_START.md +0 -99
- package/airtable-clipper/docs/SETUP.md +0 -291
- package/airtable-clipper/extension/background.js +0 -337
- package/airtable-clipper/extension/base-setup.html +0 -324
- package/airtable-clipper/extension/base-setup.js +0 -471
- package/airtable-clipper/extension/content.js +0 -771
- package/airtable-clipper/extension/icons/README.md +0 -69
- package/airtable-clipper/extension/icons/icon-16.png +0 -3
- package/airtable-clipper/extension/manifest.json +0 -73
- package/airtable-clipper/extension/popup.html +0 -144
- package/airtable-clipper/extension/popup.js +0 -475
- package/airtable-clipper/extension/styles/content.css +0 -229
- package/airtable-clipper/extension/styles/popup.css +0 -477
- package/airtable-clipper/privacy-policy.md +0 -63
- package/airtable-clipper/releases/v1.0.0/background.js +0 -337
- package/airtable-clipper/releases/v1.0.0/base-setup.html +0 -324
- package/airtable-clipper/releases/v1.0.0/base-setup.js +0 -471
- package/airtable-clipper/releases/v1.0.0/content.js +0 -771
- package/airtable-clipper/releases/v1.0.0/icons/README.md +0 -69
- package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +0 -2
- package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +0 -3
- package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +0 -2
- package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +0 -2
- package/airtable-clipper/releases/v1.0.0/manifest.json +0 -73
- package/airtable-clipper/releases/v1.0.0/popup.html +0 -144
- package/airtable-clipper/releases/v1.0.0/popup.js +0 -475
- package/airtable-clipper/releases/v1.0.0/sidepanel.html +0 -25
- package/airtable-clipper/releases/v1.0.0/styles/content.css +0 -229
- package/airtable-clipper/releases/v1.0.0/styles/popup.css +0 -477
- package/airtable-clipper/releases/v1.0.1/background.js +0 -337
- package/airtable-clipper/releases/v1.0.1/base-setup.html +0 -324
- package/airtable-clipper/releases/v1.0.1/base-setup.js +0 -471
- package/airtable-clipper/releases/v1.0.1/content.js +0 -771
- package/airtable-clipper/releases/v1.0.1/icons/README.md +0 -69
- package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +0 -2
- package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +0 -3
- package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +0 -2
- package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +0 -2
- package/airtable-clipper/releases/v1.0.1/manifest.json +0 -70
- package/airtable-clipper/releases/v1.0.1/popup.html +0 -157
- package/airtable-clipper/releases/v1.0.1/popup.js +0 -562
- package/airtable-clipper/releases/v1.0.1/sidepanel.html +0 -25
- package/airtable-clipper/releases/v1.0.1/styles/content.css +0 -229
- package/airtable-clipper/releases/v1.0.1/styles/popup.css +0 -647
- package/airtable-clipper/releases/v1.0.2/background.js +0 -337
- package/airtable-clipper/releases/v1.0.2/base-setup.html +0 -324
- package/airtable-clipper/releases/v1.0.2/base-setup.js +0 -471
- package/airtable-clipper/releases/v1.0.2/content.js +0 -771
- package/airtable-clipper/releases/v1.0.2/icons/README.md +0 -69
- package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +0 -2
- package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +0 -3
- package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +0 -2
- package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +0 -2
- package/airtable-clipper/releases/v1.0.2/manifest.json +0 -62
- package/airtable-clipper/releases/v1.0.2/popup.html +0 -157
- package/airtable-clipper/releases/v1.0.2/popup.js +0 -567
- package/airtable-clipper/releases/v1.0.2/sidepanel.html +0 -25
- package/airtable-clipper/releases/v1.0.2/styles/content.css +0 -229
- package/airtable-clipper/releases/v1.0.2/styles/popup.css +0 -647
- package/airtable-clipper/terms-of-service.md +0 -124
- package/airtable-clipper/test-credentials.md +0 -61
- package/airtable-clipper/test-extension/background.js +0 -337
- package/airtable-clipper/test-extension/base-setup.html +0 -324
- package/airtable-clipper/test-extension/base-setup.js +0 -471
- package/airtable-clipper/test-extension/content.js +0 -873
- package/airtable-clipper/test-extension/icons/README.md +0 -69
- package/airtable-clipper/test-extension/icons/icon-128.png +0 -2
- package/airtable-clipper/test-extension/icons/icon-16.png +0 -3
- package/airtable-clipper/test-extension/icons/icon-32.png +0 -2
- package/airtable-clipper/test-extension/icons/icon-48.png +0 -2
- package/airtable-clipper/test-extension/manifest.json +0 -72
- package/airtable-clipper/test-extension/popup.html +0 -274
- package/airtable-clipper/test-extension/popup.js +0 -729
- package/airtable-clipper/test-extension/sidepanel.html +0 -25
- package/airtable-clipper/test-extension/styles/content.css +0 -229
- package/airtable-clipper/test-extension/styles/popup.css +0 -794
- package/airtable_mcp/__init__.py +0 -5
- package/airtable_mcp/src/server.py +0 -329
- package/airtable_mcp_v2.js +0 -1505
- package/airtable_mcp_v2_oauth.js +0 -1048
- package/airtable_mcp_v3_advanced.js +0 -1161
- package/cleanup.sh +0 -71
- package/docker-compose.production.yml +0 -366
- package/helm/airtable-mcp/Chart.yaml +0 -122
- package/helm/airtable-mcp/values.yaml +0 -538
- package/index.js +0 -179
- package/inspector.py +0 -148
- package/inspector_server.py +0 -337
- package/k8s/deployment.yaml +0 -402
- package/k8s/namespace.yaml +0 -108
- package/k8s/service.yaml +0 -194
- package/monitoring/alerts.yml +0 -289
- package/monitoring/prometheus.yml +0 -224
- package/publish-steps.txt +0 -27
- package/quick_test.sh +0 -30
- package/requirements.txt +0 -10
- package/setup.py +0 -29
- package/simple_airtable_server.py +0 -151
- package/smithery.yaml +0 -45
- package/test_all_features.sh +0 -146
- package/test_all_operations.sh +0 -120
- package/test_client.py +0 -70
- package/test_enhanced_features.js +0 -389
- package/test_mcp_comprehensive.js +0 -163
- package/test_mock_server.js +0 -180
- package/test_v1.4.0_final.sh +0 -131
- package/test_v1.5.0_comprehensive.sh +0 -96
- package/test_v1.5.0_final.sh +0 -224
- package/test_v1.6.0_comprehensive.sh +0 -187
- 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>
|