@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,771 +0,0 @@
|
|
|
1
|
-
// Airtable Clipper Content Script
|
|
2
|
-
// Runs on all web pages to provide extraction and interaction capabilities
|
|
3
|
-
|
|
4
|
-
import { LinkedInScraper } from './lib/linkedin-scraper.js';
|
|
5
|
-
|
|
6
|
-
class ContentScriptController {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.isLinkedInPage = window.location.hostname.includes('linkedin.com');
|
|
9
|
-
this.bulkModeActive = false;
|
|
10
|
-
this.selectedElements = new Set();
|
|
11
|
-
this.floatingButton = null;
|
|
12
|
-
this.init();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
init() {
|
|
16
|
-
// Wait for page to load
|
|
17
|
-
if (document.readyState === 'loading') {
|
|
18
|
-
document.addEventListener('DOMContentLoaded', () => this.setup());
|
|
19
|
-
} else {
|
|
20
|
-
this.setup();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
setup() {
|
|
25
|
-
// Listen for messages from popup and background
|
|
26
|
-
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
27
|
-
this.handleMessage(message, sender, sendResponse);
|
|
28
|
-
return true; // Keep message channel open for async responses
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Create floating action button if appropriate
|
|
32
|
-
if (this.shouldShowFloatingButton()) {
|
|
33
|
-
this.createFloatingButton();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// LinkedIn-specific setup
|
|
37
|
-
if (this.isLinkedInPage) {
|
|
38
|
-
this.setupLinkedInFeatures();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Setup general extraction features
|
|
42
|
-
this.setupGeneralFeatures();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async handleMessage(message, sender, sendResponse) {
|
|
46
|
-
try {
|
|
47
|
-
switch (message.action) {
|
|
48
|
-
case 'extractPageData':
|
|
49
|
-
const pageData = await this.extractPageData(message.type);
|
|
50
|
-
sendResponse({ success: true, data: pageData });
|
|
51
|
-
break;
|
|
52
|
-
|
|
53
|
-
case 'extractLinkedInProfile':
|
|
54
|
-
const profileData = await this.extractLinkedInProfile();
|
|
55
|
-
sendResponse({ success: true, data: profileData });
|
|
56
|
-
break;
|
|
57
|
-
|
|
58
|
-
case 'enterBulkMode':
|
|
59
|
-
this.enterBulkMode();
|
|
60
|
-
sendResponse({ success: true });
|
|
61
|
-
break;
|
|
62
|
-
|
|
63
|
-
case 'exitBulkMode':
|
|
64
|
-
this.exitBulkMode();
|
|
65
|
-
sendResponse({ success: true });
|
|
66
|
-
break;
|
|
67
|
-
|
|
68
|
-
case 'triggerSave':
|
|
69
|
-
await this.triggerSave(message.data);
|
|
70
|
-
sendResponse({ success: true });
|
|
71
|
-
break;
|
|
72
|
-
|
|
73
|
-
default:
|
|
74
|
-
sendResponse({ success: false, error: 'Unknown action' });
|
|
75
|
-
}
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error('Content script error:', error);
|
|
78
|
-
sendResponse({ success: false, error: error.message });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
shouldShowFloatingButton() {
|
|
83
|
-
// Show floating button on supported sites
|
|
84
|
-
const supportedSites = [
|
|
85
|
-
'linkedin.com',
|
|
86
|
-
'github.com',
|
|
87
|
-
'stackoverflow.com',
|
|
88
|
-
'medium.com',
|
|
89
|
-
'dev.to'
|
|
90
|
-
];
|
|
91
|
-
|
|
92
|
-
return supportedSites.some(site => window.location.hostname.includes(site));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
createFloatingButton() {
|
|
96
|
-
// Remove existing button if any
|
|
97
|
-
if (this.floatingButton) {
|
|
98
|
-
this.floatingButton.remove();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Create floating action button
|
|
102
|
-
this.floatingButton = document.createElement('div');
|
|
103
|
-
this.floatingButton.id = 'airtable-clipper-fab';
|
|
104
|
-
this.floatingButton.innerHTML = `
|
|
105
|
-
<div class="fab-main" title="Save to Airtable">
|
|
106
|
-
📎
|
|
107
|
-
</div>
|
|
108
|
-
`;
|
|
109
|
-
|
|
110
|
-
// Add styles
|
|
111
|
-
this.floatingButton.style.cssText = `
|
|
112
|
-
position: fixed;
|
|
113
|
-
bottom: 20px;
|
|
114
|
-
right: 20px;
|
|
115
|
-
z-index: 10000;
|
|
116
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
117
|
-
`;
|
|
118
|
-
|
|
119
|
-
// Style the main button
|
|
120
|
-
const fabMain = this.floatingButton.querySelector('.fab-main');
|
|
121
|
-
fabMain.style.cssText = `
|
|
122
|
-
width: 56px;
|
|
123
|
-
height: 56px;
|
|
124
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
125
|
-
border-radius: 50%;
|
|
126
|
-
display: flex;
|
|
127
|
-
align-items: center;
|
|
128
|
-
justify-content: center;
|
|
129
|
-
color: white;
|
|
130
|
-
font-size: 20px;
|
|
131
|
-
cursor: pointer;
|
|
132
|
-
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
133
|
-
transition: all 0.3s ease;
|
|
134
|
-
user-select: none;
|
|
135
|
-
`;
|
|
136
|
-
|
|
137
|
-
// Add hover effects
|
|
138
|
-
fabMain.addEventListener('mouseenter', () => {
|
|
139
|
-
fabMain.style.transform = 'scale(1.1)';
|
|
140
|
-
fabMain.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.6)';
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
fabMain.addEventListener('mouseleave', () => {
|
|
144
|
-
fabMain.style.transform = 'scale(1)';
|
|
145
|
-
fabMain.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.4)';
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Add click handler
|
|
149
|
-
fabMain.addEventListener('click', (e) => {
|
|
150
|
-
e.preventDefault();
|
|
151
|
-
e.stopPropagation();
|
|
152
|
-
this.handleFloatingButtonClick();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Add to page
|
|
156
|
-
document.body.appendChild(this.floatingButton);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async handleFloatingButtonClick() {
|
|
160
|
-
try {
|
|
161
|
-
if (this.isLinkedInPage && window.location.pathname.includes('/in/')) {
|
|
162
|
-
// LinkedIn profile page - extract profile
|
|
163
|
-
await this.triggerSave({ type: 'linkedin' });
|
|
164
|
-
} else {
|
|
165
|
-
// General page - extract page data
|
|
166
|
-
await this.triggerSave({ type: 'general' });
|
|
167
|
-
}
|
|
168
|
-
} catch (error) {
|
|
169
|
-
console.error('Floating button click failed:', error);
|
|
170
|
-
this.showNotification('Failed to save content', 'error');
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async triggerSave(data) {
|
|
175
|
-
try {
|
|
176
|
-
let extractedData;
|
|
177
|
-
|
|
178
|
-
if (data.type === 'linkedin') {
|
|
179
|
-
extractedData = await this.extractLinkedInProfile();
|
|
180
|
-
} else {
|
|
181
|
-
extractedData = await this.extractPageData(data.type);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Send to background script for saving
|
|
185
|
-
const response = await chrome.runtime.sendMessage({
|
|
186
|
-
action: 'saveToAirtable',
|
|
187
|
-
data: {
|
|
188
|
-
...data,
|
|
189
|
-
...extractedData,
|
|
190
|
-
timestamp: new Date().toISOString()
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
if (response && response.success) {
|
|
195
|
-
this.showNotification('Saved to Airtable!', 'success');
|
|
196
|
-
} else {
|
|
197
|
-
throw new Error(response?.error || 'Failed to save');
|
|
198
|
-
}
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.error('Save failed:', error);
|
|
201
|
-
this.showNotification(`Save failed: ${error.message}`, 'error');
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async extractPageData(type = 'general') {
|
|
206
|
-
const data = {
|
|
207
|
-
url: window.location.href,
|
|
208
|
-
title: document.title,
|
|
209
|
-
domain: window.location.hostname,
|
|
210
|
-
extractedAt: new Date().toISOString()
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// Extract meta information
|
|
214
|
-
const description = document.querySelector('meta[name="description"]');
|
|
215
|
-
if (description) {
|
|
216
|
-
data.description = description.content;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const keywords = document.querySelector('meta[name="keywords"]');
|
|
220
|
-
if (keywords) {
|
|
221
|
-
data.keywords = keywords.content;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Extract main content based on page type
|
|
225
|
-
if (type === 'selection' && window.getSelection().toString()) {
|
|
226
|
-
data.content = window.getSelection().toString();
|
|
227
|
-
data.type = 'Text Selection';
|
|
228
|
-
} else {
|
|
229
|
-
data.content = this.extractMainContent();
|
|
230
|
-
data.type = 'Web Page';
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Site-specific extractions
|
|
234
|
-
if (window.location.hostname.includes('github.com')) {
|
|
235
|
-
data.github = this.extractGitHubData();
|
|
236
|
-
} else if (window.location.hostname.includes('stackoverflow.com')) {
|
|
237
|
-
data.stackoverflow = this.extractStackOverflowData();
|
|
238
|
-
} else if (window.location.hostname.includes('medium.com')) {
|
|
239
|
-
data.medium = this.extractMediumData();
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return data;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
extractMainContent() {
|
|
246
|
-
// Try different selectors for main content
|
|
247
|
-
const contentSelectors = [
|
|
248
|
-
'main',
|
|
249
|
-
'article',
|
|
250
|
-
'[role="main"]',
|
|
251
|
-
'.content',
|
|
252
|
-
'.post-content',
|
|
253
|
-
'.entry-content',
|
|
254
|
-
'#content',
|
|
255
|
-
'.main-content'
|
|
256
|
-
];
|
|
257
|
-
|
|
258
|
-
for (const selector of contentSelectors) {
|
|
259
|
-
const element = document.querySelector(selector);
|
|
260
|
-
if (element) {
|
|
261
|
-
return this.getTextContent(element).substring(0, 5000); // Limit to 5000 chars
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Fallback: get text from body, excluding navigation and footer
|
|
266
|
-
const body = document.body.cloneNode(true);
|
|
267
|
-
|
|
268
|
-
// Remove common navigation and footer elements
|
|
269
|
-
const elementsToRemove = ['nav', 'header', 'footer', '.nav', '.navigation', '.menu', '.sidebar'];
|
|
270
|
-
elementsToRemove.forEach(selector => {
|
|
271
|
-
const elements = body.querySelectorAll(selector);
|
|
272
|
-
elements.forEach(el => el.remove());
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
return this.getTextContent(body).substring(0, 5000);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
getTextContent(element) {
|
|
279
|
-
// Get clean text content from element
|
|
280
|
-
const text = element.textContent || element.innerText || '';
|
|
281
|
-
return text.replace(/\s+/g, ' ').trim();
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async extractLinkedInProfile() {
|
|
285
|
-
if (!this.isLinkedInPage) {
|
|
286
|
-
throw new Error('Not on LinkedIn');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const scraper = new LinkedInScraper();
|
|
290
|
-
const profileData = await scraper.extractProfile();
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
type: 'linkedin',
|
|
294
|
-
...profileData
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
extractGitHubData() {
|
|
299
|
-
const data = {};
|
|
300
|
-
|
|
301
|
-
// Repository name and owner
|
|
302
|
-
const repoInfo = window.location.pathname.match(/^\/([^\/]+)\/([^\/]+)/);
|
|
303
|
-
if (repoInfo) {
|
|
304
|
-
data.owner = repoInfo[1];
|
|
305
|
-
data.repository = repoInfo[2];
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Stars and forks
|
|
309
|
-
const stars = document.querySelector('#repo-stars-counter-star');
|
|
310
|
-
if (stars) data.stars = stars.textContent.trim();
|
|
311
|
-
|
|
312
|
-
const forks = document.querySelector('#repo-network-counter');
|
|
313
|
-
if (forks) data.forks = forks.textContent.trim();
|
|
314
|
-
|
|
315
|
-
// Description
|
|
316
|
-
const description = document.querySelector('[data-pjax="#repo-content-pjax-container"] p');
|
|
317
|
-
if (description) data.description = description.textContent.trim();
|
|
318
|
-
|
|
319
|
-
// Language
|
|
320
|
-
const language = document.querySelector('[data-ga-click*="Language"]');
|
|
321
|
-
if (language) data.primaryLanguage = language.textContent.trim();
|
|
322
|
-
|
|
323
|
-
return data;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
extractStackOverflowData() {
|
|
327
|
-
const data = {};
|
|
328
|
-
|
|
329
|
-
// Question title
|
|
330
|
-
const title = document.querySelector('h1[itemprop="name"]');
|
|
331
|
-
if (title) data.questionTitle = title.textContent.trim();
|
|
332
|
-
|
|
333
|
-
// Question score
|
|
334
|
-
const score = document.querySelector('.js-vote-count');
|
|
335
|
-
if (score) data.score = score.textContent.trim();
|
|
336
|
-
|
|
337
|
-
// Tags
|
|
338
|
-
const tags = Array.from(document.querySelectorAll('.post-tag'))
|
|
339
|
-
.map(tag => tag.textContent.trim());
|
|
340
|
-
if (tags.length > 0) data.tags = tags;
|
|
341
|
-
|
|
342
|
-
// Views
|
|
343
|
-
const views = document.querySelector('[title*="viewed"]');
|
|
344
|
-
if (views) data.views = views.textContent.trim();
|
|
345
|
-
|
|
346
|
-
return data;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
extractMediumData() {
|
|
350
|
-
const data = {};
|
|
351
|
-
|
|
352
|
-
// Author
|
|
353
|
-
const author = document.querySelector('[data-testid="authorName"]');
|
|
354
|
-
if (author) data.author = author.textContent.trim();
|
|
355
|
-
|
|
356
|
-
// Publication date
|
|
357
|
-
const date = document.querySelector('[data-testid="storyPublishDate"]');
|
|
358
|
-
if (date) data.publishDate = date.textContent.trim();
|
|
359
|
-
|
|
360
|
-
// Reading time
|
|
361
|
-
const readTime = document.querySelector('[data-testid="storyReadTime"]');
|
|
362
|
-
if (readTime) data.readingTime = readTime.textContent.trim();
|
|
363
|
-
|
|
364
|
-
// Claps
|
|
365
|
-
const claps = document.querySelector('[data-testid="clapCount"]');
|
|
366
|
-
if (claps) data.claps = claps.textContent.trim();
|
|
367
|
-
|
|
368
|
-
return data;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
setupLinkedInFeatures() {
|
|
372
|
-
// Auto-save feature for LinkedIn profiles
|
|
373
|
-
if (window.location.pathname.includes('/in/')) {
|
|
374
|
-
this.checkAutoSaveSettings();
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
async checkAutoSaveSettings() {
|
|
379
|
-
try {
|
|
380
|
-
const response = await chrome.runtime.sendMessage({
|
|
381
|
-
action: 'getSettings'
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
if (response.success && response.settings.autoSave) {
|
|
385
|
-
// Auto-save after a delay to ensure page is loaded
|
|
386
|
-
setTimeout(() => {
|
|
387
|
-
this.triggerSave({ type: 'linkedin', auto: true });
|
|
388
|
-
}, 3000);
|
|
389
|
-
}
|
|
390
|
-
} catch (error) {
|
|
391
|
-
console.error('Failed to check auto-save settings:', error);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
setupGeneralFeatures() {
|
|
396
|
-
// Text selection features
|
|
397
|
-
document.addEventListener('mouseup', () => {
|
|
398
|
-
const selection = window.getSelection().toString().trim();
|
|
399
|
-
if (selection.length > 10) {
|
|
400
|
-
this.showSelectionToolbar(selection);
|
|
401
|
-
} else {
|
|
402
|
-
this.hideSelectionToolbar();
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
showSelectionToolbar(selectedText) {
|
|
408
|
-
// Remove existing toolbar
|
|
409
|
-
this.hideSelectionToolbar();
|
|
410
|
-
|
|
411
|
-
const selection = window.getSelection();
|
|
412
|
-
const range = selection.getRangeAt(0);
|
|
413
|
-
const rect = range.getBoundingClientRect();
|
|
414
|
-
|
|
415
|
-
// Create toolbar
|
|
416
|
-
const toolbar = document.createElement('div');
|
|
417
|
-
toolbar.id = 'airtable-clipper-selection-toolbar';
|
|
418
|
-
toolbar.innerHTML = `
|
|
419
|
-
<button class="selection-btn" data-action="save">
|
|
420
|
-
📎 Save to Airtable
|
|
421
|
-
</button>
|
|
422
|
-
`;
|
|
423
|
-
|
|
424
|
-
// Style toolbar
|
|
425
|
-
toolbar.style.cssText = `
|
|
426
|
-
position: fixed;
|
|
427
|
-
top: ${rect.top + window.scrollY - 40}px;
|
|
428
|
-
left: ${rect.left + window.scrollX}px;
|
|
429
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
430
|
-
color: white;
|
|
431
|
-
padding: 8px 12px;
|
|
432
|
-
border-radius: 6px;
|
|
433
|
-
font-size: 12px;
|
|
434
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
435
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
436
|
-
z-index: 10001;
|
|
437
|
-
user-select: none;
|
|
438
|
-
`;
|
|
439
|
-
|
|
440
|
-
// Style button
|
|
441
|
-
const button = toolbar.querySelector('.selection-btn');
|
|
442
|
-
button.style.cssText = `
|
|
443
|
-
background: none;
|
|
444
|
-
border: none;
|
|
445
|
-
color: white;
|
|
446
|
-
cursor: pointer;
|
|
447
|
-
font-size: 12px;
|
|
448
|
-
font-family: inherit;
|
|
449
|
-
`;
|
|
450
|
-
|
|
451
|
-
// Add click handler
|
|
452
|
-
button.addEventListener('click', () => {
|
|
453
|
-
this.triggerSave({ type: 'selection', text: selectedText });
|
|
454
|
-
this.hideSelectionToolbar();
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
document.body.appendChild(toolbar);
|
|
458
|
-
|
|
459
|
-
// Auto-hide after 5 seconds
|
|
460
|
-
setTimeout(() => this.hideSelectionToolbar(), 5000);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
hideSelectionToolbar() {
|
|
464
|
-
const toolbar = document.getElementById('airtable-clipper-selection-toolbar');
|
|
465
|
-
if (toolbar) {
|
|
466
|
-
toolbar.remove();
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
enterBulkMode() {
|
|
471
|
-
if (this.bulkModeActive) return;
|
|
472
|
-
|
|
473
|
-
this.bulkModeActive = true;
|
|
474
|
-
this.selectedElements.clear();
|
|
475
|
-
|
|
476
|
-
// Add bulk mode overlay
|
|
477
|
-
this.createBulkModeOverlay();
|
|
478
|
-
|
|
479
|
-
// Add click handlers to selectable elements
|
|
480
|
-
this.addBulkModeHandlers();
|
|
481
|
-
|
|
482
|
-
this.showNotification('Bulk mode activated. Click items to select them.', 'info');
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
createBulkModeOverlay() {
|
|
486
|
-
const overlay = document.createElement('div');
|
|
487
|
-
overlay.id = 'airtable-clipper-bulk-overlay';
|
|
488
|
-
overlay.innerHTML = `
|
|
489
|
-
<div class="bulk-header">
|
|
490
|
-
<span class="bulk-title">📋 Bulk Mode Active</span>
|
|
491
|
-
<span class="bulk-counter">0 items selected</span>
|
|
492
|
-
<div class="bulk-actions">
|
|
493
|
-
<button class="bulk-btn save-selected">Save Selected</button>
|
|
494
|
-
<button class="bulk-btn cancel-bulk">Cancel</button>
|
|
495
|
-
</div>
|
|
496
|
-
</div>
|
|
497
|
-
`;
|
|
498
|
-
|
|
499
|
-
// Style overlay
|
|
500
|
-
overlay.style.cssText = `
|
|
501
|
-
position: fixed;
|
|
502
|
-
top: 0;
|
|
503
|
-
left: 0;
|
|
504
|
-
right: 0;
|
|
505
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
506
|
-
color: white;
|
|
507
|
-
padding: 12px 20px;
|
|
508
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
509
|
-
font-size: 14px;
|
|
510
|
-
z-index: 10002;
|
|
511
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
512
|
-
`;
|
|
513
|
-
|
|
514
|
-
// Style header
|
|
515
|
-
const header = overlay.querySelector('.bulk-header');
|
|
516
|
-
header.style.cssText = `
|
|
517
|
-
display: flex;
|
|
518
|
-
align-items: center;
|
|
519
|
-
gap: 20px;
|
|
520
|
-
`;
|
|
521
|
-
|
|
522
|
-
// Style buttons
|
|
523
|
-
const buttons = overlay.querySelectorAll('.bulk-btn');
|
|
524
|
-
buttons.forEach(btn => {
|
|
525
|
-
btn.style.cssText = `
|
|
526
|
-
background: rgba(255,255,255,0.2);
|
|
527
|
-
border: 1px solid rgba(255,255,255,0.3);
|
|
528
|
-
color: white;
|
|
529
|
-
padding: 6px 12px;
|
|
530
|
-
border-radius: 4px;
|
|
531
|
-
cursor: pointer;
|
|
532
|
-
font-size: 12px;
|
|
533
|
-
margin-left: 8px;
|
|
534
|
-
`;
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
// Add event handlers
|
|
538
|
-
overlay.querySelector('.save-selected').addEventListener('click', () => {
|
|
539
|
-
this.saveBulkSelection();
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
overlay.querySelector('.cancel-bulk').addEventListener('click', () => {
|
|
543
|
-
this.exitBulkMode();
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
document.body.appendChild(overlay);
|
|
547
|
-
document.body.style.paddingTop = '60px'; // Make room for overlay
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
addBulkModeHandlers() {
|
|
551
|
-
// Add handlers for clickable elements based on current site
|
|
552
|
-
let selectors = [];
|
|
553
|
-
|
|
554
|
-
if (this.isLinkedInPage) {
|
|
555
|
-
selectors = [
|
|
556
|
-
'.search-result__wrapper',
|
|
557
|
-
'.entity-result',
|
|
558
|
-
'.reusable-search__result-container'
|
|
559
|
-
];
|
|
560
|
-
} else {
|
|
561
|
-
selectors = [
|
|
562
|
-
'article',
|
|
563
|
-
'.item',
|
|
564
|
-
'.card',
|
|
565
|
-
'.result',
|
|
566
|
-
'.post'
|
|
567
|
-
];
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
selectors.forEach(selector => {
|
|
571
|
-
const elements = document.querySelectorAll(selector);
|
|
572
|
-
elements.forEach(element => {
|
|
573
|
-
element.addEventListener('click', this.handleBulkElementClick.bind(this));
|
|
574
|
-
element.style.cursor = 'pointer';
|
|
575
|
-
element.style.transition = 'all 0.2s ease';
|
|
576
|
-
});
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
handleBulkElementClick(event) {
|
|
581
|
-
event.preventDefault();
|
|
582
|
-
event.stopPropagation();
|
|
583
|
-
|
|
584
|
-
const element = event.currentTarget;
|
|
585
|
-
const isSelected = this.selectedElements.has(element);
|
|
586
|
-
|
|
587
|
-
if (isSelected) {
|
|
588
|
-
this.selectedElements.delete(element);
|
|
589
|
-
element.style.background = '';
|
|
590
|
-
element.style.border = '';
|
|
591
|
-
} else {
|
|
592
|
-
this.selectedElements.add(element);
|
|
593
|
-
element.style.background = 'rgba(102, 126, 234, 0.1)';
|
|
594
|
-
element.style.border = '2px solid #667eea';
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
this.updateBulkCounter();
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
updateBulkCounter() {
|
|
601
|
-
const counter = document.querySelector('.bulk-counter');
|
|
602
|
-
if (counter) {
|
|
603
|
-
const count = this.selectedElements.size;
|
|
604
|
-
counter.textContent = `${count} item${count !== 1 ? 's' : ''} selected`;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
async saveBulkSelection() {
|
|
609
|
-
if (this.selectedElements.size === 0) {
|
|
610
|
-
this.showNotification('No items selected', 'error');
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
this.showNotification('Saving selected items...', 'info');
|
|
615
|
-
|
|
616
|
-
try {
|
|
617
|
-
const items = [];
|
|
618
|
-
|
|
619
|
-
for (const element of this.selectedElements) {
|
|
620
|
-
const itemData = await this.extractElementData(element);
|
|
621
|
-
if (itemData) {
|
|
622
|
-
items.push(itemData);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Save all items
|
|
627
|
-
const response = await chrome.runtime.sendMessage({
|
|
628
|
-
action: 'saveBulkToAirtable',
|
|
629
|
-
data: {
|
|
630
|
-
items: items,
|
|
631
|
-
type: 'bulk',
|
|
632
|
-
timestamp: new Date().toISOString()
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
if (response && response.success) {
|
|
637
|
-
this.showNotification(`Saved ${items.length} items to Airtable!`, 'success');
|
|
638
|
-
this.exitBulkMode();
|
|
639
|
-
} else {
|
|
640
|
-
throw new Error(response?.error || 'Failed to save bulk items');
|
|
641
|
-
}
|
|
642
|
-
} catch (error) {
|
|
643
|
-
console.error('Bulk save failed:', error);
|
|
644
|
-
this.showNotification(`Bulk save failed: ${error.message}`, 'error');
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
async extractElementData(element) {
|
|
649
|
-
// Extract data from individual element based on site type
|
|
650
|
-
const data = {
|
|
651
|
-
type: 'bulk_item',
|
|
652
|
-
url: window.location.href,
|
|
653
|
-
extractedAt: new Date().toISOString()
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
if (this.isLinkedInPage) {
|
|
657
|
-
// LinkedIn search result
|
|
658
|
-
const nameElement = element.querySelector('.entity-result__title-text a, .search-result__result-link');
|
|
659
|
-
const titleElement = element.querySelector('.entity-result__primary-subtitle, .subline-level-1');
|
|
660
|
-
const companyElement = element.querySelector('.entity-result__secondary-subtitle, .subline-level-2');
|
|
661
|
-
|
|
662
|
-
if (nameElement) {
|
|
663
|
-
data.name = nameElement.textContent.trim();
|
|
664
|
-
data.linkedinUrl = nameElement.href;
|
|
665
|
-
}
|
|
666
|
-
if (titleElement) data.title = titleElement.textContent.trim();
|
|
667
|
-
if (companyElement) data.company = companyElement.textContent.trim();
|
|
668
|
-
} else {
|
|
669
|
-
// General web page element
|
|
670
|
-
const title = element.querySelector('h1, h2, h3, .title, .headline');
|
|
671
|
-
const link = element.querySelector('a[href]');
|
|
672
|
-
|
|
673
|
-
if (title) data.title = title.textContent.trim();
|
|
674
|
-
if (link) data.url = link.href;
|
|
675
|
-
|
|
676
|
-
data.content = this.getTextContent(element).substring(0, 1000);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
return data;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
exitBulkMode() {
|
|
683
|
-
if (!this.bulkModeActive) return;
|
|
684
|
-
|
|
685
|
-
this.bulkModeActive = false;
|
|
686
|
-
|
|
687
|
-
// Remove overlay
|
|
688
|
-
const overlay = document.getElementById('airtable-clipper-bulk-overlay');
|
|
689
|
-
if (overlay) {
|
|
690
|
-
overlay.remove();
|
|
691
|
-
document.body.style.paddingTop = '';
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Reset selected elements
|
|
695
|
-
this.selectedElements.forEach(element => {
|
|
696
|
-
element.style.background = '';
|
|
697
|
-
element.style.border = '';
|
|
698
|
-
element.style.cursor = '';
|
|
699
|
-
});
|
|
700
|
-
this.selectedElements.clear();
|
|
701
|
-
|
|
702
|
-
this.showNotification('Bulk mode deactivated', 'info');
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
showNotification(message, type = 'info') {
|
|
706
|
-
// Remove existing notification
|
|
707
|
-
const existing = document.getElementById('airtable-clipper-notification');
|
|
708
|
-
if (existing) existing.remove();
|
|
709
|
-
|
|
710
|
-
// Create notification
|
|
711
|
-
const notification = document.createElement('div');
|
|
712
|
-
notification.id = 'airtable-clipper-notification';
|
|
713
|
-
notification.textContent = message;
|
|
714
|
-
|
|
715
|
-
// Style based on type
|
|
716
|
-
const colors = {
|
|
717
|
-
success: '#10b981',
|
|
718
|
-
error: '#ef4444',
|
|
719
|
-
info: '#3b82f6'
|
|
720
|
-
};
|
|
721
|
-
|
|
722
|
-
notification.style.cssText = `
|
|
723
|
-
position: fixed;
|
|
724
|
-
top: 20px;
|
|
725
|
-
right: 20px;
|
|
726
|
-
background: ${colors[type] || colors.info};
|
|
727
|
-
color: white;
|
|
728
|
-
padding: 12px 16px;
|
|
729
|
-
border-radius: 6px;
|
|
730
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
731
|
-
font-size: 14px;
|
|
732
|
-
font-weight: 500;
|
|
733
|
-
z-index: 10003;
|
|
734
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
735
|
-
max-width: 300px;
|
|
736
|
-
word-wrap: break-word;
|
|
737
|
-
animation: slideInRight 0.3s ease;
|
|
738
|
-
`;
|
|
739
|
-
|
|
740
|
-
// Add animation keyframes
|
|
741
|
-
if (!document.querySelector('#airtable-clipper-animations')) {
|
|
742
|
-
const style = document.createElement('style');
|
|
743
|
-
style.id = 'airtable-clipper-animations';
|
|
744
|
-
style.textContent = `
|
|
745
|
-
@keyframes slideInRight {
|
|
746
|
-
from {
|
|
747
|
-
transform: translateX(100%);
|
|
748
|
-
opacity: 0;
|
|
749
|
-
}
|
|
750
|
-
to {
|
|
751
|
-
transform: translateX(0);
|
|
752
|
-
opacity: 1;
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
`;
|
|
756
|
-
document.head.appendChild(style);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
document.body.appendChild(notification);
|
|
760
|
-
|
|
761
|
-
// Auto-hide after 3 seconds
|
|
762
|
-
setTimeout(() => {
|
|
763
|
-
if (notification && notification.parentNode) {
|
|
764
|
-
notification.remove();
|
|
765
|
-
}
|
|
766
|
-
}, 3000);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// Initialize content script
|
|
771
|
-
new ContentScriptController();
|