diff --git a/Javascript/URL-Shortener/README.md b/Javascript/URL-Shortener/README.md new file mode 100644 index 0000000..d0e5cd6 --- /dev/null +++ b/Javascript/URL-Shortener/README.md @@ -0,0 +1,117 @@ +# 🔗 URL Shortener + +A simple, client-side URL shortener built with vanilla JavaScript, HTML, and CSS. No server required! + +## ✨ Features + +- **URL Validation** - Validates URLs before shortening +- **Short Code Generation** - Creates unique 6-character codes +- **Local Storage** - Persists URLs in browser storage +- **Click Tracking** - Counts URL access statistics +- **Copy to Clipboard** - One-click copying functionality +- **Recent URLs Management** - View and manage your shortened URLs +- **Automatic Redirection** - Seamlessly redirects to original URLs +- **Responsive Design** - Works on all devices +- **Modern UI** - Beautiful gradient design with smooth animations + +## 🚀 Quick Start + +1. **Clone or download** this repository +2. **Open** `index.html` in your web browser +3. **Start shortening URLs!** + +### Local Development Server + +For the best experience, serve the files using a local server: + +```bash +# Using Python +python3 -m http.server 8000 + +# Using Node.js (if you have http-server installed) +npx http-server + +# Using PHP +php -S localhost:8000 +``` + +Then open `http://localhost:8000` in your browser. + +## 📖 How It Works + +1. **Enter a long URL** in the input field +2. **Click "Shorten URL"** or press Enter +3. **Copy the shortened URL** and share it +4. **Access the short URL** to automatically redirect to the original + +### URL Format +- Short URLs follow the pattern: `your-domain.com#AbC123` +- The hash fragment (`#AbC123`) contains the unique short code +- Original URLs are stored in browser's localStorage + +## 🛠️ Technical Details + +- **Pure JavaScript** - No frameworks or dependencies +- **localStorage** - Client-side data persistence +- **Hash-based routing** - Uses URL fragments for redirection +- **Responsive CSS** - Mobile-first design approach +- **Modern ES6+** - Uses classes, arrow functions, and modern JavaScript features + +## 📱 Browser Support + +- Chrome 60+ +- Firefox 55+ +- Safari 12+ +- Edge 79+ + +## 🔧 Customization + +### Changing the Base URL +Update the `baseUrl` property in `script.js`: + +```javascript +this.baseUrl = 'https://your-domain.com'; +``` + +### Modifying the Short Code Length +Change the loop in the `generateShortCode()` method: + +```javascript +for (let i = 0; i < 8; i++) { // Change 6 to desired length +``` + +### Styling +All styles are in `style.css`. The design uses CSS custom properties and modern layout techniques. + +## 📊 Data Storage + +URLs are stored in browser's localStorage with the following structure: + +```javascript +{ + "AbC123": { + "originalUrl": "https://example.com/very/long/url", + "createdAt": "2024-01-01T00:00:00.000Z", + "clickCount": 5 + } +} +``` + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature-name` +3. Make your changes +4. Commit your changes: `git commit -m 'Add some feature'` +5. Push to the branch: `git push origin feature-name` +6. Submit a pull request + +## 📝 License + +This project is open source and available under the [MIT License](LICENSE). + +## 🙏 Acknowledgments + +- Built with vanilla JavaScript for maximum compatibility +- Inspired by popular URL shorteners like bit.ly and tinyurl.com +- Uses modern web standards for optimal performance diff --git a/Javascript/URL-Shortener/index.html b/Javascript/URL-Shortener/index.html new file mode 100644 index 0000000..7d2144c --- /dev/null +++ b/Javascript/URL-Shortener/index.html @@ -0,0 +1,48 @@ + + + + + + URL Shortener + + + +
+
+

🔗 URL Shortener

+

Make your long URLs short and shareable

+
+ +
+
+ + +
+ + + + +
+ +
+

Recent Shortened URLs

+
+ +
+
+
+ + + + diff --git a/Javascript/URL-Shortener/script.js b/Javascript/URL-Shortener/script.js new file mode 100644 index 0000000..a6e81be --- /dev/null +++ b/Javascript/URL-Shortener/script.js @@ -0,0 +1,206 @@ +class URLShortener { + constructor() { + this.urlStorage = this.loadFromStorage(); + this.baseUrl = window.location.origin + window.location.pathname; + this.initializeElements(); + this.bindEvents(); + this.displayRecentUrls(); + } + + initializeElements() { + this.urlInput = document.getElementById('urlInput'); + this.shortenBtn = document.getElementById('shortenBtn'); + this.result = document.getElementById('result'); + this.shortUrl = document.getElementById('shortUrl'); + this.originalUrl = document.getElementById('originalUrl'); + this.copyBtn = document.getElementById('copyBtn'); + this.error = document.getElementById('error'); + this.urlList = document.getElementById('urlList'); + } + + bindEvents() { + this.shortenBtn.addEventListener('click', () => this.shortenUrl()); + this.urlInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') this.shortenUrl(); + }); + this.copyBtn.addEventListener('click', () => this.copyToClipboard()); + } + + isValidUrl(string) { + try { + new URL(string); + return true; + } catch (_) { + return false; + } + } + + generateShortCode() { + // Generate a random 6-character code + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < 6; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; + } + + shortenUrl() { + const longUrl = this.urlInput.value.trim(); + + // Hide previous results + this.result.style.display = 'none'; + this.error.style.display = 'none'; + + if (!longUrl) { + this.showError('Please enter a URL'); + return; + } + + if (!this.isValidUrl(longUrl)) { + this.showError('Please enter a valid URL'); + return; + } + + // Check if URL already exists + const existingEntry = this.findExistingUrl(longUrl); + if (existingEntry) { + this.displayResult(existingEntry.shortCode, longUrl); + return; + } + + // Generate new short code + let shortCode; + do { + shortCode = this.generateShortCode(); + } while (this.urlStorage[shortCode]); // Ensure uniqueness + + // Store the URL + this.urlStorage[shortCode] = { + originalUrl: longUrl, + createdAt: new Date().toISOString(), + clickCount: 0 + }; + + this.saveToStorage(); + this.displayResult(shortCode, longUrl); + this.displayRecentUrls(); + } + + findExistingUrl(url) { + for (const [code, data] of Object.entries(this.urlStorage)) { + if (data.originalUrl === url) { + return { shortCode: code, ...data }; + } + } + return null; + } + + displayResult(shortCode, originalUrl) { + const shortUrl = `${this.baseUrl}#${shortCode}`; + this.shortUrl.value = shortUrl; + this.originalUrl.textContent = originalUrl; + this.result.style.display = 'block'; + this.urlInput.value = ''; + } + + showError(message) { + this.error.querySelector('p').textContent = message; + this.error.style.display = 'block'; + } + + copyToClipboard() { + this.shortUrl.select(); + this.shortUrl.setSelectionRange(0, 99999); // For mobile devices + document.execCommand('copy'); + + // Visual feedback + const originalText = this.copyBtn.textContent; + this.copyBtn.textContent = 'Copied!'; + this.copyBtn.style.backgroundColor = '#28a745'; + + setTimeout(() => { + this.copyBtn.textContent = originalText; + this.copyBtn.style.backgroundColor = ''; + }, 2000); + } + + displayRecentUrls() { + const entries = Object.entries(this.urlStorage) + .sort((a, b) => new Date(b[1].createdAt) - new Date(a[1].createdAt)) + .slice(0, 10); // Show only last 10 + + if (entries.length === 0) { + this.urlList.innerHTML = '

No URLs shortened yet

'; + return; + } + + this.urlList.innerHTML = entries.map(([code, data]) => ` +
+
+ +
${data.originalUrl}
+
+ Created: ${new Date(data.createdAt).toLocaleDateString()} | + Clicks: ${data.clickCount} +
+
+ +
+ `).join(''); + } + + deleteUrl(shortCode) { + if (confirm('Are you sure you want to delete this URL?')) { + delete this.urlStorage[shortCode]; + this.saveToStorage(); + this.displayRecentUrls(); + } + } + + loadFromStorage() { + try { + const stored = localStorage.getItem('urlShortener'); + return stored ? JSON.parse(stored) : {}; + } catch (e) { + console.error('Error loading from storage:', e); + return {}; + } + } + + saveToStorage() { + try { + localStorage.setItem('urlShortener', JSON.stringify(this.urlStorage)); + } catch (e) { + console.error('Error saving to storage:', e); + } + } + + // Handle URL redirection + handleRedirect() { + const hash = window.location.hash.substring(1); + if (hash && this.urlStorage[hash]) { + // Increment click count + this.urlStorage[hash].clickCount++; + this.saveToStorage(); + + // Redirect to original URL + window.location.href = this.urlStorage[hash].originalUrl; + } + } +} + +// Initialize the URL shortener +const urlShortener = new URLShortener(); + +// Handle page load for redirects +window.addEventListener('load', () => { + urlShortener.handleRedirect(); +}); + +// Handle hash changes +window.addEventListener('hashchange', () => { + urlShortener.handleRedirect(); +}); diff --git a/Javascript/URL-Shortener/style.css b/Javascript/URL-Shortener/style.css new file mode 100644 index 0000000..be3543d --- /dev/null +++ b/Javascript/URL-Shortener/style.css @@ -0,0 +1,297 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + padding: 20px; +} + +.container { + max-width: 800px; + margin: 0 auto; + background: white; + border-radius: 20px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + overflow: hidden; +} + +header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + text-align: center; + padding: 40px 20px; +} + +header h1 { + font-size: 2.5rem; + margin-bottom: 10px; + font-weight: 700; +} + +header p { + font-size: 1.1rem; + opacity: 0.9; +} + +main { + padding: 40px; +} + +.input-section { + display: flex; + gap: 15px; + margin-bottom: 30px; + flex-wrap: wrap; +} + +#urlInput { + flex: 1; + min-width: 300px; + padding: 15px 20px; + border: 2px solid #e1e5e9; + border-radius: 10px; + font-size: 16px; + transition: all 0.3s ease; +} + +#urlInput:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +#shortenBtn { + padding: 15px 30px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 10px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + white-space: nowrap; +} + +#shortenBtn:hover { + transform: translateY(-2px); + box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3); +} + +#shortenBtn:active { + transform: translateY(0); +} + +.result-section { + background: #f8f9fa; + border-radius: 15px; + padding: 25px; + margin-bottom: 30px; + border-left: 5px solid #28a745; +} + +.result-section h3 { + color: #28a745; + margin-bottom: 15px; + font-size: 1.2rem; +} + +.short-url-container { + display: flex; + gap: 10px; + margin-bottom: 15px; + flex-wrap: wrap; +} + +#shortUrl { + flex: 1; + min-width: 200px; + padding: 12px 15px; + border: 2px solid #e1e5e9; + border-radius: 8px; + background: white; + font-family: 'Courier New', monospace; + font-size: 14px; +} + +#copyBtn { + padding: 12px 20px; + background: #28a745; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: all 0.3s ease; + white-space: nowrap; +} + +#copyBtn:hover { + background: #218838; + transform: translateY(-1px); +} + +.original-url { + color: #6c757d; + font-size: 14px; + word-break: break-all; +} + +.error-message { + background: #f8d7da; + color: #721c24; + padding: 15px 20px; + border-radius: 10px; + border-left: 5px solid #dc3545; + margin-bottom: 20px; +} + +.recent-urls { + background: #f8f9fa; + padding: 30px; + border-top: 1px solid #e9ecef; +} + +.recent-urls h3 { + color: #495057; + margin-bottom: 20px; + font-size: 1.3rem; +} + +.url-list { + display: flex; + flex-direction: column; + gap: 15px; +} + +.url-item { + background: white; + border-radius: 10px; + padding: 20px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 15px; +} + +.url-info { + flex: 1; + min-width: 300px; +} + +.short-url a { + color: #667eea; + text-decoration: none; + font-weight: 600; + font-family: 'Courier New', monospace; + font-size: 14px; +} + +.short-url a:hover { + text-decoration: underline; +} + +.original-url { + color: #6c757d; + font-size: 14px; + margin: 8px 0; + word-break: break-all; +} + +.url-meta { + color: #adb5bd; + font-size: 12px; +} + +.delete-btn { + padding: 8px 16px; + background: #dc3545; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition: all 0.3s ease; +} + +.delete-btn:hover { + background: #c82333; + transform: translateY(-1px); +} + +.no-urls { + text-align: center; + color: #6c757d; + font-style: italic; + padding: 20px; +} + +/* Responsive Design */ +@media (max-width: 768px) { + body { + padding: 10px; + } + + .container { + border-radius: 15px; + } + + header { + padding: 30px 20px; + } + + header h1 { + font-size: 2rem; + } + + main { + padding: 30px 20px; + } + + .input-section { + flex-direction: column; + } + + #urlInput { + min-width: auto; + } + + .short-url-container { + flex-direction: column; + } + + #shortUrl { + min-width: auto; + } + + .url-item { + flex-direction: column; + align-items: stretch; + } + + .url-info { + min-width: auto; + } +} + +@media (max-width: 480px) { + header h1 { + font-size: 1.8rem; + } + + main { + padding: 20px 15px; + } + + .recent-urls { + padding: 20px 15px; + } +}