dhishooooom commited on
Commit
4b1a094
·
verified ·
1 Parent(s): ee6ac19

Make something people will use just because it exists. Something so useful that they never even felt the absence of any it - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +6 -4
  2. index.html +379 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Clipstack
3
- emoji: 🌍
4
  colorFrom: red
5
- colorTo: pink
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: clipstack
3
+ emoji: 🐳
4
  colorFrom: red
5
+ colorTo: green
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,379 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>ClipStack - Never Lose Copied Text Again</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .clipboard-item {
11
+ transition: all 0.2s ease;
12
+ }
13
+ .clipboard-item:hover {
14
+ transform: translateY(-2px);
15
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
16
+ }
17
+ .fade-in {
18
+ animation: fadeIn 0.3s ease-in;
19
+ }
20
+ @keyframes fadeIn {
21
+ from { opacity: 0; transform: translateY(10px); }
22
+ to { opacity: 1; transform: translateY(0); }
23
+ }
24
+ .text-ellipsis {
25
+ display: -webkit-box;
26
+ -webkit-line-clamp: 2;
27
+ -webkit-box-orient: vertical;
28
+ overflow: hidden;
29
+ text-overflow: ellipsis;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body class="bg-gray-50 min-h-screen">
34
+ <div class="container mx-auto px-4 py-8 max-w-4xl">
35
+ <!-- Header -->
36
+ <header class="mb-8 text-center">
37
+ <h1 class="text-4xl font-bold text-indigo-600 mb-2">ClipStack</h1>
38
+ <p class="text-gray-600">Your personal clipboard history manager</p>
39
+ <div class="mt-4 flex justify-center space-x-2">
40
+ <button id="clearAllBtn" class="px-4 py-2 bg-red-100 text-red-600 rounded-lg hover:bg-red-200 transition">
41
+ <i class="fas fa-trash mr-2"></i>Clear All
42
+ </button>
43
+ <button id="toggleAutoSave" class="px-4 py-2 bg-green-100 text-green-600 rounded-lg hover:bg-green-200 transition">
44
+ <i class="fas fa-save mr-2"></i>Auto-save: ON
45
+ </button>
46
+ </div>
47
+ </header>
48
+
49
+ <!-- Main Content -->
50
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden">
51
+ <!-- Search Bar -->
52
+ <div class="p-4 bg-indigo-50">
53
+ <div class="relative">
54
+ <input type="text" id="searchInput" placeholder="Search your clipboard history..."
55
+ class="w-full px-4 py-3 rounded-lg border border-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-400">
56
+ <i class="fas fa-search absolute right-4 top-3.5 text-indigo-400"></i>
57
+ </div>
58
+ </div>
59
+
60
+ <!-- Clipboard Items -->
61
+ <div id="clipboardItems" class="divide-y divide-gray-100 max-h-[60vh] overflow-y-auto">
62
+ <!-- Items will be added here dynamically -->
63
+ <div class="text-center py-12 text-gray-400" id="emptyState">
64
+ <i class="fas fa-clipboard text-4xl mb-4"></i>
65
+ <p>Your clipboard history will appear here</p>
66
+ <p class="text-sm mt-2">Try copying some text and it will automatically appear</p>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Current Clipboard -->
71
+ <div class="p-4 border-t border-gray-200 bg-gray-50">
72
+ <h3 class="font-medium text-gray-700 mb-2">Current Clipboard</h3>
73
+ <div class="flex items-start">
74
+ <textarea id="currentClipboard" rows="3"
75
+ class="flex-1 px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-400 resize-none"
76
+ placeholder="Paste here or it will update automatically when you copy"></textarea>
77
+ <button id="copyCurrentBtn" class="ml-2 px-4 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition">
78
+ <i class="fas fa-copy"></i>
79
+ </button>
80
+ </div>
81
+ </div>
82
+ </div>
83
+
84
+ <!-- Stats Footer -->
85
+ <footer class="mt-6 text-center text-sm text-gray-500">
86
+ <div class="inline-flex items-center space-x-4">
87
+ <span id="itemCount">0 items saved</span>
88
+ <span id="storageStatus">Using 0KB of storage</span>
89
+ </div>
90
+ </footer>
91
+ </div>
92
+
93
+ <!-- Toast Notification -->
94
+ <div id="toast" class="fixed bottom-4 right-4 px-6 py-3 bg-green-500 text-white rounded-lg shadow-lg transform translate-y-10 opacity-0 transition-all duration-300">
95
+ <i class="fas fa-check-circle mr-2"></i>
96
+ <span id="toastMessage">Copied to clipboard!</span>
97
+ </div>
98
+
99
+ <script>
100
+ document.addEventListener('DOMContentLoaded', function() {
101
+ // Elements
102
+ const clipboardItems = document.getElementById('clipboardItems');
103
+ const currentClipboard = document.getElementById('currentClipboard');
104
+ const copyCurrentBtn = document.getElementById('copyCurrentBtn');
105
+ const clearAllBtn = document.getElementById('clearAllBtn');
106
+ const toggleAutoSave = document.getElementById('toggleAutoSave');
107
+ const searchInput = document.getElementById('searchInput');
108
+ const emptyState = document.getElementById('emptyState');
109
+ const itemCount = document.getElementById('itemCount');
110
+ const storageStatus = document.getElementById('storageStatus');
111
+ const toast = document.getElementById('toast');
112
+
113
+ // State
114
+ let autoSave = true;
115
+ let items = JSON.parse(localStorage.getItem('clipboardItems') || '[]');
116
+
117
+ // Initialize
118
+ renderItems();
119
+ updateStats();
120
+ checkCurrentClipboard();
121
+
122
+ // Clipboard monitoring
123
+ setInterval(checkCurrentClipboard, 1000);
124
+
125
+ // Event Listeners
126
+ copyCurrentBtn.addEventListener('click', copyCurrentToClipboard);
127
+ clearAllBtn.addEventListener('click', clearAllItems);
128
+ toggleAutoSave.addEventListener('click', toggleAutoSaveFeature);
129
+ searchInput.addEventListener('input', filterItems);
130
+ currentClipboard.addEventListener('input', handleManualInput);
131
+
132
+ // Functions
133
+ function checkCurrentClipboard() {
134
+ navigator.clipboard.readText().then(text => {
135
+ if (text && text !== currentClipboard.value) {
136
+ currentClipboard.value = text;
137
+ if (autoSave && text.trim() && !items.some(item => item.text === text)) {
138
+ addNewItem(text);
139
+ }
140
+ }
141
+ }).catch(err => {
142
+ console.log('Clipboard access denied', err);
143
+ });
144
+ }
145
+
146
+ function addNewItem(text) {
147
+ const newItem = {
148
+ id: Date.now(),
149
+ text: text,
150
+ timestamp: new Date().toISOString(),
151
+ pinned: false
152
+ };
153
+
154
+ items.unshift(newItem);
155
+ saveItems();
156
+ renderItems();
157
+ showToast('Text saved to history!');
158
+ }
159
+
160
+ function renderItems(filteredItems = null) {
161
+ const itemsToRender = filteredItems || items;
162
+
163
+ if (itemsToRender.length === 0) {
164
+ emptyState.style.display = 'block';
165
+ return;
166
+ }
167
+
168
+ emptyState.style.display = 'none';
169
+ clipboardItems.innerHTML = '';
170
+
171
+ itemsToRender.forEach(item => {
172
+ const itemElement = document.createElement('div');
173
+ itemElement.className = `clipboard-item p-4 hover:bg-gray-50 cursor-pointer fade-in ${item.pinned ? 'bg-yellow-50' : ''}`;
174
+ itemElement.innerHTML = `
175
+ <div class="flex justify-between items-start">
176
+ <div class="flex-1 min-w-0">
177
+ <div class="text-ellipsis text-gray-800">${escapeHtml(item.text)}</div>
178
+ <div class="text-xs text-gray-500 mt-1">${formatDate(item.timestamp)}</div>
179
+ </div>
180
+ <div class="flex space-x-2 ml-3">
181
+ <button class="p-2 text-gray-400 hover:text-indigo-600 copy-btn" data-id="${item.id}">
182
+ <i class="fas fa-copy"></i>
183
+ </button>
184
+ <button class="p-2 text-gray-400 hover:text-yellow-600 pin-btn" data-id="${item.id}">
185
+ <i class="fas ${item.pinned ? 'fa-thumbtack' : 'fa-thumbtack rotate-45'}"></i>
186
+ </button>
187
+ <button class="p-2 text-gray-400 hover:text-red-600 delete-btn" data-id="${item.id}">
188
+ <i class="fas fa-trash"></i>
189
+ </button>
190
+ </div>
191
+ </div>
192
+ `;
193
+
194
+ clipboardItems.appendChild(itemElement);
195
+ });
196
+
197
+ // Add event listeners to new buttons
198
+ document.querySelectorAll('.copy-btn').forEach(btn => {
199
+ btn.addEventListener('click', (e) => {
200
+ e.stopPropagation();
201
+ const id = parseInt(btn.dataset.id);
202
+ copyItemToClipboard(id);
203
+ });
204
+ });
205
+
206
+ document.querySelectorAll('.pin-btn').forEach(btn => {
207
+ btn.addEventListener('click', (e) => {
208
+ e.stopPropagation();
209
+ const id = parseInt(btn.dataset.id);
210
+ togglePinItem(id);
211
+ });
212
+ });
213
+
214
+ document.querySelectorAll('.delete-btn').forEach(btn => {
215
+ btn.addEventListener('click', (e) => {
216
+ e.stopPropagation();
217
+ const id = parseInt(btn.dataset.id);
218
+ deleteItem(id);
219
+ });
220
+ });
221
+
222
+ // Click on item to expand
223
+ document.querySelectorAll('.clipboard-item').forEach(item => {
224
+ item.addEventListener('click', (e) => {
225
+ if (e.target.tagName !== 'BUTTON') {
226
+ const id = parseInt(item.querySelector('.copy-btn').dataset.id);
227
+ const clickedItem = items.find(i => i.id === id);
228
+ if (clickedItem) {
229
+ currentClipboard.value = clickedItem.text;
230
+ copyTextToClipboard(clickedItem.text);
231
+ }
232
+ }
233
+ });
234
+ });
235
+ }
236
+
237
+ function copyCurrentToClipboard() {
238
+ const text = currentClipboard.value.trim();
239
+ if (text) {
240
+ copyTextToClipboard(text);
241
+ if (autoSave && !items.some(item => item.text === text)) {
242
+ addNewItem(text);
243
+ }
244
+ }
245
+ }
246
+
247
+ function copyItemToClipboard(id) {
248
+ const item = items.find(item => item.id === id);
249
+ if (item) {
250
+ copyTextToClipboard(item.text);
251
+ currentClipboard.value = item.text;
252
+ }
253
+ }
254
+
255
+ function copyTextToClipboard(text) {
256
+ navigator.clipboard.writeText(text).then(() => {
257
+ showToast('Copied to clipboard!');
258
+ }).catch(err => {
259
+ console.error('Failed to copy text: ', err);
260
+ });
261
+ }
262
+
263
+ function togglePinItem(id) {
264
+ const itemIndex = items.findIndex(item => item.id === id);
265
+ if (itemIndex !== -1) {
266
+ items[itemIndex].pinned = !items[itemIndex].pinned;
267
+
268
+ // Move pinned items to top
269
+ if (items[itemIndex].pinned) {
270
+ const [pinnedItem] = items.splice(itemIndex, 1);
271
+ items.unshift(pinnedItem);
272
+ }
273
+
274
+ saveItems();
275
+ renderItems();
276
+ }
277
+ }
278
+
279
+ function deleteItem(id) {
280
+ items = items.filter(item => item.id !== id);
281
+ saveItems();
282
+ renderItems();
283
+ showToast('Item deleted');
284
+ }
285
+
286
+ function clearAllItems() {
287
+ if (confirm('Are you sure you want to delete all clipboard items?')) {
288
+ items = [];
289
+ saveItems();
290
+ renderItems();
291
+ showToast('All items cleared');
292
+ }
293
+ }
294
+
295
+ function toggleAutoSaveFeature() {
296
+ autoSave = !autoSave;
297
+ toggleAutoSave.innerHTML = autoSave
298
+ ? '<i class="fas fa-save mr-2"></i>Auto-save: ON'
299
+ : '<i class="fas fa-save mr-2"></i>Auto-save: OFF';
300
+ toggleAutoSave.className = autoSave
301
+ ? 'px-4 py-2 bg-green-100 text-green-600 rounded-lg hover:bg-green-200 transition'
302
+ : 'px-4 py-2 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 transition';
303
+
304
+ localStorage.setItem('clipboardAutoSave', autoSave);
305
+ showToast(`Auto-save ${autoSave ? 'enabled' : 'disabled'}`);
306
+ }
307
+
308
+ function filterItems() {
309
+ const searchTerm = searchInput.value.toLowerCase();
310
+ if (!searchTerm) {
311
+ renderItems();
312
+ return;
313
+ }
314
+
315
+ const filtered = items.filter(item =>
316
+ item.text.toLowerCase().includes(searchTerm)
317
+ );
318
+ renderItems(filtered);
319
+ }
320
+
321
+ function handleManualInput() {
322
+ // Optional: You could add auto-save when user types manually
323
+ }
324
+
325
+ function saveItems() {
326
+ localStorage.setItem('clipboardItems', JSON.stringify(items));
327
+ updateStats();
328
+ }
329
+
330
+ function updateStats() {
331
+ itemCount.textContent = `${items.length} ${items.length === 1 ? 'item' : 'items'} saved`;
332
+
333
+ // Calculate approximate storage usage
334
+ const dataSize = JSON.stringify(items).length;
335
+ const sizeInKB = Math.round(dataSize / 1024);
336
+ storageStatus.textContent = `Using ${sizeInKB}KB of storage`;
337
+ }
338
+
339
+ function showToast(message) {
340
+ document.getElementById('toastMessage').textContent = message;
341
+ toast.style.transform = 'translateY(0)';
342
+ toast.style.opacity = '1';
343
+
344
+ setTimeout(() => {
345
+ toast.style.transform = 'translateY(10px)';
346
+ toast.style.opacity = '0';
347
+ }, 3000);
348
+ }
349
+
350
+ function formatDate(isoString) {
351
+ const date = new Date(isoString);
352
+ return date.toLocaleString();
353
+ }
354
+
355
+ function escapeHtml(unsafe) {
356
+ return unsafe
357
+ .replace(/&/g, "&amp;")
358
+ .replace(/</g, "&lt;")
359
+ .replace(/>/g, "&gt;")
360
+ .replace(/"/g, "&quot;")
361
+ .replace(/'/g, "&#039;")
362
+ .replace(/\n/g, '<br>');
363
+ }
364
+
365
+ // Load auto-save preference
366
+ const savedAutoSave = localStorage.getItem('clipboardAutoSave');
367
+ if (savedAutoSave !== null) {
368
+ autoSave = savedAutoSave === 'true';
369
+ toggleAutoSave.innerHTML = autoSave
370
+ ? '<i class="fas fa-save mr-2"></i>Auto-save: ON'
371
+ : '<i class="fas fa-save mr-2"></i>Auto-save: OFF';
372
+ toggleAutoSave.className = autoSave
373
+ ? 'px-4 py-2 bg-green-100 text-green-600 rounded-lg hover:bg-green-200 transition'
374
+ : 'px-4 py-2 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 transition';
375
+ }
376
+ });
377
+ </script>
378
+ <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=dhishooooom/clipstack" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
379
+ </html>