File size: 24,319 Bytes
db3676c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Smart Web Extractor</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        /* Custom CSS for things Tailwind can't handle */
        .dark-mode {
            background-color: #1a202c;
            color: #f7fafc;
        }
        .light-mode {
            background-color: #f7fafc;
            color: #1a202c;
        }
        .popup-container {
            width: 350px;
            min-height: 200px;
            max-height: 600px;
            overflow-y: auto;
        }
        .tab-active {
            border-bottom: 3px solid #4299e1;
        }
        .highlight {
            background-color: rgba(255, 255, 0, 0.4);
        }
        .domain-badge {
            font-size: 0.75rem;
            border-radius: 9999px;
        }
        .slide-fade-enter-active {
            transition: all 0.3s ease-out;
        }
        .slide-fade-leave-active {
            transition: all 0.3s ease-in;
        }
        .slide-fade-enter-from,
        .slide-fade-leave-to {
            transform: translateY(10px);
            opacity: 0;
        }
    </style>
</head>
<body class="font-sans text-sm">
    <div id="app" class="popup-container transition-colors duration-200" :class="isDarkMode ? 'dark-mode' : 'light-mode'">
        <!-- Header with logo and theme toggle -->
        <div class="flex justify-between items-center p-4 border-b">
            <div class="flex items-center">
                <i class="fas fa-globe-americas text-blue-500 text-xl mr-2"></i>
                <h1 class="font-bold text-lg">Smart Web Extractor</h1>
            </div>
            <button @click="toggleTheme" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
                <i :class="isDarkMode ? 'fas fa-sun text-yellow-300' : 'fas fa-moon text-gray-600'"></i>
            </button>
        </div>

        <!-- Main content area with tabs -->
        <div class="p-4">
            <!-- Tab navigation -->
            <div class="flex border-b mb-4">
                <button @click="activeTab = 'extract'" class="px-4 py-2 font-medium" :class="{'tab-active text-blue-500': activeTab === 'extract'}">
                    <i class="fas fa-download mr-1"></i> Extract
                </button>
                <button @click="activeTab = 'results'" class="px-4 py-2 font-medium" :class="{'tab-active text-blue-500': activeTab === 'results'}">
                    <i class="fas fa-list mr-1"></i> Results
                </button>
                <button @click="activeTab = 'settings'" class="px-4 py-2 font-medium" :class="{'tab-active text-blue-500': activeTab === 'settings'}">
                    <i class="fas fa-cog mr-1"></i> Settings
                </button>
            </div>

            <!-- Extract Tab -->
            <div v-if="activeTab === 'extract'" class="transition-all duration-300">
                <div class="mb-4">
                    <label class="block text-sm font-medium mb-1">Extraction Scope</label>
                    <div class="flex space-x-2">
                        <button @click="scope = 'visible'" class="flex-1 py-2 px-3 rounded border" :class="scope === 'visible' ? 'bg-blue-100 border-blue-300 dark:bg-blue-900 dark:border-blue-700' : 'hover:bg-gray-100 dark:hover:bg-gray-700'">
                            <i class="fas fa-eye mr-1"></i> Visible Content
                        </button>
                        <button @click="scope = 'full'" class="flex-1 py-2 px-3 rounded border" :class="scope === 'full' ? 'bg-blue-100 border-blue-300 dark:bg-blue-900 dark:border-blue-700' : 'hover:bg-gray-100 dark:hover:bg-gray-700'">
                            <i class="fas fa-file-alt mr-1"></i> Full Page
                        </button>
                        <button @click="scope = 'multi'" class="flex-1 py-2 px-3 rounded border" :class="scope === 'multi' ? 'bg-blue-100 border-blue-300 dark:bg-blue-900 dark:border-blue-700' : 'hover:bg-gray-100 dark:hover:bg-gray-700'">
                            <i class="fas fa-copy mr-1"></i> Multi-Page
                        </button>
                    </div>
                </div>

                <div class="mb-4">
                    <label class="block text-sm font-medium mb-1">Filter Options</label>
                    <input v-model="filterKeyword" type="text" placeholder="Keywords (comma separated)" class="w-full p-2 border rounded mb-2 dark:bg-gray-800 dark:border-gray-600">
                    <input v-model="filterRegex" type="text" placeholder="Regular expression" class="w-full p-2 border rounded dark:bg-gray-800 dark:border-gray-600">
                    <div class="flex items-center mt-2">
                        <input v-model="removeDuplicates" type="checkbox" id="removeDuplicates" class="mr-2">
                        <label for="removeDuplicates">Remove duplicates</label>
                    </div>
                    <div class="flex items-center mt-1">
                        <input v-model="groupByDomain" type="checkbox" id="groupByDomain" class="mr-2">
                        <label for="groupByDomain">Group by domain</label>
                    </div>
                </div>

                <div class="flex space-x-2">
                    <button @click="extractContent" class="flex-1 bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded flex items-center justify-center">
                        <i class="fas fa-play mr-2"></i> Extract Now
                    </button>
                    <button @click="showAdvanced = !showAdvanced" class="bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 py-2 px-3 rounded">
                        <i class="fas fa-sliders-h"></i>
                    </button>
                </div>

                <div v-if="showAdvanced" class="mt-4 p-3 bg-gray-100 dark:bg-gray-800 rounded">
                    <h3 class="font-medium mb-2">Advanced Options</h3>
                    <div class="mb-2">
                        <label class="block text-xs mb-1">Tab delay (ms)</label>
                        <input v-model="tabDelay" type="number" min="0" class="w-full p-2 border rounded dark:bg-gray-700 dark:border-gray-600">
                    </div>
                    <div class="flex items-center">
                        <input v-model="keepHighlights" type="checkbox" id="keepHighlights" class="mr-2">
                        <label for="keepHighlights">Keep highlights</label>
                    </div>
                </div>
            </div>

            <!-- Results Tab -->
            <div v-if="activeTab === 'results'" class="transition-all duration-300">
                <div class="flex justify-between items-center mb-3">
                    <h3 class="font-medium">Extracted Items ({{filteredResults.length}})</h3>
                    <div class="flex space-x-1">
                        <button @click="exportResults('csv')" class="p-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded" title="Export to CSV">
                            <i class="fas fa-file-csv text-green-500"></i>
                        </button>
                        <button @click="exportResults('excel')" class="p-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded" title="Export to Excel">
                            <i class="fas fa-file-excel text-green-600"></i>
                        </button>
                        <button @click="exportResults('text')" class="p-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded" title="Export to Text">
                            <i class="fas fa-file-alt text-blue-500"></i>
                        </button>
                        <button @click="exportResults('bookmarks')" class="p-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded" title="Save as Bookmarks">
                            <i class="fas fa-bookmark text-yellow-500"></i>
                        </button>
                        <button @click="copyToClipboard" class="p-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded" title="Copy to Clipboard">
                            <i class="fas fa-copy text-purple-500"></i>
                        </button>
                    </div>
                </div>

                <div v-if="filteredResults.length === 0" class="text-center py-8 text-gray-500">
                    <i class="fas fa-inbox text-3xl mb-2"></i>
                    <p>No extracted content yet</p>
                </div>

                <div v-else class="space-y-2 max-h-96 overflow-y-auto">
                    <div v-for="(result, index) in filteredResults" :key="index" class="p-3 border rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
                        <div class="flex justify-between items-start">
                            <div class="flex-1 min-w-0">
                                <p class="truncate font-medium">{{ result.title || 'Untitled' }}</p>
                                <p class="text-xs text-gray-500 dark:text-gray-400 truncate">{{ result.url }}</p>
                                <div v-if="result.domain" class="mt-1">
                                    <span class="domain-badge px-2 py-0.5 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300">{{ result.domain }}</span>
                                </div>
                            </div>
                            <button @click="openUrl(result.url)" class="ml-2 p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded" title="Open in new tab">
                                <i class="fas fa-external-link-alt text-xs"></i>
                            </button>
                        </div>
                        <div v-if="result.content" class="mt-2 text-sm text-gray-700 dark:text-gray-300">
                            <p class="line-clamp-2">{{ result.content }}</p>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Settings Tab -->
            <div v-if="activeTab === 'settings'" class="transition-all duration-300">
                <div class="mb-4">
                    <h3 class="font-medium mb-2">Keyboard Shortcuts</h3>
                    <div class="space-y-2">
                        <div v-for="(shortcut, name) in shortcuts" :key="name" class="flex items-center">
                            <label class="w-32 text-sm">{{ name.replace(/_/g, ' ') }}</label>
                            <input v-model="shortcuts[name]" type="text" class="flex-1 p-2 border rounded dark:bg-gray-800 dark:border-gray-600">
                        </div>
                    </div>
                </div>

                <div class="mb-4">
                    <h3 class="font-medium mb-2">Blacklisted Sites</h3>
                    <div class="flex mb-2">
                        <input v-model="newBlacklistItem" type="text" placeholder="example.com" class="flex-1 p-2 border rounded-l dark:bg-gray-800 dark:border-gray-600">
                        <button @click="addToBlacklist" class="bg-blue-500 hover:bg-blue-600 text-white px-3 rounded-r">
                            <i class="fas fa-plus"></i>
                        </button>
                    </div>
                    <div class="max-h-32 overflow-y-auto border rounded dark:border-gray-600">
                        <div v-for="(site, index) in blacklist" :key="index" class="flex items-center justify-between p-2 hover:bg-gray-100 dark:hover:bg-gray-800">
                            <span>{{ site }}</span>
                            <button @click="removeFromBlacklist(index)" class="text-red-500 hover:text-red-700">
                                <i class="fas fa-times"></i>
                            </button>
                        </div>
                    </div>
                </div>

                <div class="mb-4">
                    <h3 class="font-medium mb-2">Appearance</h3>
                    <div class="flex items-center">
                        <input v-model="autoTheme" type="checkbox" id="autoTheme" class="mr-2">
                        <label for="autoTheme">Auto light/dark mode</label>
                    </div>
                </div>

                <div class="flex justify-end">
                    <button @click="saveSettings" class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded">
                        Save Settings
                    </button>
                </div>
            </div>
        </div>

        <!-- Status bar -->
        <div class="p-2 border-t text-xs text-gray-500 dark:text-gray-400 flex justify-between">
            <span v-if="statusMessage">{{ statusMessage }}</span>
            <span v-else>Ready</span>
            <span v-if="isProcessing" class="text-blue-500">
                <i class="fas fa-spinner fa-spin mr-1"></i> Processing...
            </span>
        </div>
    </div>

    <script>
        // Vue.js-like simple reactive framework for the extension
        const app = {
            data() {
                return {
                    activeTab: 'extract',
                    isDarkMode: false,
                    autoTheme: true,
                    scope: 'visible',
                    filterKeyword: '',
                    filterRegex: '',
                    removeDuplicates: true,
                    groupByDomain: true,
                    showAdvanced: false,
                    tabDelay: 500,
                    keepHighlights: true,
                    isProcessing: false,
                    statusMessage: '',
                    results: [],
                    shortcuts: {
                        extract_content: 'Ctrl+Shift+E',
                        open_results: 'Ctrl+Shift+R',
                        toggle_theme: 'Ctrl+Shift+T'
                    },
                    blacklist: [],
                    newBlacklistItem: ''
                }
            },
            computed: {
                filteredResults() {
                    let results = [...this.results];
                    
                    // Filter by keyword if provided
                    if (this.filterKeyword) {
                        const keywords = this.filterKeyword.split(',').map(k => k.trim().toLowerCase());
                        results = results.filter(item => {
                            const content = (item.title + ' ' + item.content + ' ' + item.url).toLowerCase();
                            return keywords.some(k => content.includes(k));
                        });
                    }
                    
                    // Filter by regex if provided
                    if (this.filterRegex) {
                        try {
                            const regex = new RegExp(this.filterRegex, 'i');
                            results = results.filter(item => {
                                return regex.test(item.title) || regex.test(item.content) || regex.test(item.url);
                            });
                        } catch (e) {
                            this.statusMessage = 'Invalid regular expression';
                            setTimeout(() => this.statusMessage = '', 3000);
                        }
                    }
                    
                    // Remove duplicates if enabled
                    if (this.removeDuplicates) {
                        const uniqueUrls = new Set();
                        results = results.filter(item => {
                            if (uniqueUrls.has(item.url)) {
                                return false;
                            }
                            uniqueUrls.add(item.url);
                            return true;
                        });
                    }
                    
                    // Group by domain if enabled
                    if (this.groupByDomain) {
                        const domainMap = {};
                        results.forEach(item => {
                            const domain = this.extractDomain(item.url);
                            if (!domainMap[domain]) {
                                domainMap[domain] = [];
                            }
                            domainMap[domain].push(item);
                        });
                        
                        // Flatten back to array but with domain property
                        results = [];
                        for (const domain in domainMap) {
                            domainMap[domain].forEach(item => {
                                results.push({
                                    ...item,
                                    domain
                                });
                            });
                        }
                    }
                    
                    return results;
                }
            },
            methods: {
                toggleTheme() {
                    this.isDarkMode = !this.isDarkMode;
                    if (!this.autoTheme) {
                        this.saveSettings();
                    }
                },
                checkSystemTheme() {
                    if (this.autoTheme) {
                        const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
                        this.isDarkMode = darkModeMediaQuery.matches;
                    }
                },
                extractContent() {
                    this.isProcessing = true;
                    this.statusMessage = 'Extracting content...';
                    
                    // Simulate extraction (in a real extension, this would use chrome APIs)
                    setTimeout(() => {
                        // Mock data for demonstration
                        const mockResults = [
                            {
                                title: 'Example Page 1',
                                url: 'https://example.com/page1',
                                content: 'This is some example content from page 1 with relevant information.'
                            },
                            {
                                title: 'Example Page 2',
                                url: 'https://example.com/page2',
                                content: 'More example content here, this time from page 2 with different data.'
                            },
                            {
                                title: 'SEO Best Practices',
                                url: 'https://seo-example.org/best-practices',
                                content: 'Learn about the latest SEO techniques and how to implement them.'
                            },
                            {
                                title: 'Marketing Trends 2023',
                                url: 'https://marketing-site.com/trends',
                                content: 'The top marketing trends you need to know for 2023 and beyond.'
                            },
                            {
                                title: 'Academic Research Paper',
                                url: 'https://academic.edu/research/important-study',
                                content: 'A groundbreaking study in the field of computer science.'
                            }
                        ];
                        
                        // Add domain to each result
                        mockResults.forEach(item => {
                            item.domain = this.extractDomain(item.url);
                        });
                        
                        this.results = [...this.results, ...mockResults];
                        this.isProcessing = false;
                        this.statusMessage = `Extracted ${mockResults.length} items`;
                        this.activeTab = 'results';
                        
                        setTimeout(() => {
                            this.statusMessage = '';
                        }, 3000);
                    }, 1500);
                },
                extractDomain(url) {
                    try {
                        const domain = new URL(url).hostname.replace('www.', '');
                        return domain;
                    } catch {
                        return 'unknown';
                    }
                },
                openUrl(url) {
                    // In a real extension: chrome.tabs.create({ url });
                    this.statusMessage = `Opening: ${url}`;
                    setTimeout(() => {
                        this.statusMessage = '';
                    }, 2000);
                },
                exportResults(format) {
                    this.statusMessage = `Exporting to ${format}...`;
                    setTimeout(() => {
                        this.statusMessage = `Exported ${this.filteredResults.length} items to ${format}`;
                        setTimeout(() => {
                            this.statusMessage = '';
                        }, 3000);
                    }, 1000);
                },
                copyToClipboard() {
                    // In a real extension: navigator.clipboard.writeText()
                    this.statusMessage = 'Copied to clipboard';
                    setTimeout(() => {
                        this.statusMessage = '';
                    }, 2000);
                },
                addToBlacklist() {
                    if (this.newBlacklistItem.trim() && !this.blacklist.includes(this.newBlacklistItem.trim())) {
                        this.blacklist.push(this.newBlacklistItem.trim());
                        this.newBlacklistItem = '';
                    }
                },
                removeFromBlacklist(index) {
                    this.blacklist.splice(index, 1);
                },
                saveSettings() {
                    // In a real extension: chrome.storage.sync.set()
                    this.statusMessage = 'Settings saved';
                    setTimeout(() => {
                        this.statusMessage = '';
                    }, 2000);
                },
                loadSettings() {
                    // In a real extension: chrome.storage.sync.get()
                    // Mock loading settings
                    setTimeout(() => {
                        this.shortcuts = {
                            extract_content: 'Ctrl+Shift+E',
                            open_results: 'Ctrl+Shift+R',
                            toggle_theme: 'Ctrl+Shift+T'
                        };
                        this.blacklist = ['socialmedia.com', 'news.site'];
                        this.autoTheme = true;
                        this.checkSystemTheme();
                    }, 500);
                }
            },
            mounted() {
                this.loadSettings();
                
                // Listen for system theme changes
                window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
                    this.checkSystemTheme();
                });
                
                // Check theme on startup
                this.checkSystemTheme();
            }
        };

        // Initialize the app
        const { createApp } = Vue;
        createApp(app).mount('#app');
    </script>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MikePy/web-extractor" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>