ساخت ابزار مدیریت لینک و بوکمارک خصوصی با HTML و CSS

آموزش ساخت یک ابزار سبک و خصوصی برای مدیریت لینک‌های روزانه با قابلیت ذخیره در مرورگر و خروجی JSON بدون نیاز به سرور.

🎧 خلاصه صوتی مقاله (پادکست ۲ دقیقه‌ای):

فرصت مطالعه ندارید؟ چکیده این مطلب را همین‌جا گوش دهید:

مدیریت لینک‌ها و ذخیره‌سازی مطالب مفید وب یکی از چالش‌های روزمره‌ی دانشجویان، پژوهشگران و کاربران پرکار اینترنت است. سیستم‌های سنتی ذخیره‌سازی تب‌ها و بوکمارک‌های مرورگر معمولاً شلوغ، دست‌وپاگیر و وابسته به حساب‌های ابری هستند که حریم خصوصی را محدود می‌کنند و جابجایی اطلاعات بین دستگاه‌های مختلف را دشوار می‌سازند. اگر دوست دارید ابزاری کاملاً شخصی، سریع و سبک برای سازماندهی لینک‌های مهم روزانه خود داشته باشید که هیچ وابستگی به سرورهای خارجی نداشته باشد، این پروژه دقیقاً همان چیزی است که نیاز دارید. با توسعه‌ی این ابزار کوچک، تمامی لینک‌های خود را با دسته‌بندی رنگی، برچسب‌های سفارشی و قابلیت پشتیبان‌گیری محلی در یک فایل ساده در اختیار خواهید داشت.

برای پیاده‌سازی این ابزار، ما از تکنولوژی‌های پایه‌ی وب یعنی HTML5، CSS3 و JavaScript خالص (Vanilla JS) استفاده می‌کنیم. این یعنی نیازی به نصب هیچ‌گونه پکیج پیچیده، فریم‌ورک سنگین یا ابزارهای جانبی ندارید و تنها با یک ویرایشگر متن ساده می‌توانید کل سیستم را اجرا کنید. در کنار این ابزار، اگر به توسعه‌ی پروژه‌های پیشرفته‌تر وب علاقه دارید، مطالعه‌ی مقاله بررسی کورسر (Cursor)؛ دستیار هوش مصنوعی پیشرفته برای برنامه‌نویسی می‌تواند ایده‌های بسیار خوبی برای تسریع فرآیند کدنویسی به شما بدهد.

ویژگی‌ها و قابلیت‌های کلیدی ابزار مدیریت لینک

این ابزار به گونه‌ای طراحی شده است که در عین سادگی، تمام نیازهای یک کاربر حرفه‌ای برای دسته‌بندی و بازیابی اطلاعات را پوشش دهد. سیستم ذخیره‌سازی داده‌ها بر پایه‌ی LocalStorage مرورگر بنا شده است که تضمین می‌کند اطلاعات شما حتی پس از بستن کامل مرورگر یا قطع اتصال اینترنت باقی بمانند. همچنین قابلیت دریافت خروجی (Export) به صورت فایل استاندارد JSON به شما اجازه می‌دهد از تمامی لینک‌های خود نسخه پشتیبان تهیه کنید و هر زمان که خواستید با قابلیت ورود اطلاعات (Import)، آن‌ها را به مرورگر یا سیستم دیگری منتقل کنید.

رابط کاربری این ابزار به صورت کاملاً واکنش‌گرا (Responsive) طراحی شده است تا چه در صفحه‌نمایش بزرگ دسکتاپ و چه در گوشی‌های هوشمند، تجربه‌ی کاربری روانی داشته باشد. دسته‌بندی رنگی برچسب‌ها کمک می‌کند تا لینک‌های کاری، مطالعاتی، تفریحی و آموزشی را در یک نگاه از هم تفکیک کنید. پژوهشگرانی که حجم بالایی از داده‌ها و منابع دیجیتال را مدیریت می‌کنند، همچنین می‌توانند برای تحلیل داده‌های کلان خود نگاهی به معرفی Luzmo؛ پلتفرم هوش مصنوعی برای ساخت داشبوردهای تحلیلی بیندازند تا در کنار ذخیره‌ی لینک‌ها، توانایی مصورسازی اطلاعات را نیز داشته باشند.

ویژگیتوضیحات فنی
تکنولوژی ساختHTML5, CSS3, Vanilla JavaScript
محل ذخیره‌سازی داده‌هاحافظه محلی مرورگر (LocalStorage)
قابلیت خروجی/ورودیپشتیبانی کامل از فایل‌های فرمت JSON
طراحی ظاهریکاملاً واکنش‌گرا و سازگار با موبایل و دسکتاپ
وضعیت دسترسی برای ایرانکاملاً آزاد (بدون محدودیت منطقه‌ای)
هزینه‌ی استفاده۱۰۰٪ رایگان و متن‌باز

کد کامل و یکپارچه‌ی پروژه‌ی مدیریت لینک

برای استفاده از این ابزار، کافی است کد زیر را کالکت کرده، یک فایل متنی جدید با نام دلخواه مثلاً bookmarks.html ایجاد کنید، کد را درون آن قرار داده و ذخیره کنید. سپس با دو بار کلیک روی فایل، آن را در هر مرورگری باز کنید تا ابزار آماده به کار شود.

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ابزار مدیریت لینک‌های شخصی</title>
    <style>
        :root {
            --primary-color: #4f46e5;
            --primary-hover: #4338ca;
            --bg-color: #f8fafc;
            --card-bg: #ffffff;
            --text-color: #1e293b;
            --border-color: #e2e8f0;
            --danger-color: #ef4444;
        }
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: system-ui, -apple-system, sans-serif;
        }
        body {
            background-color: var(--bg-color);
            color: var(--text-color);
            line-height: 1.6;
            padding: 20px;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
        }
        header {
            text-align: center;
            margin-bottom: 30px;
        }
        header h1 {
            font-size: 1.8rem;
            color: var(--primary-color);
            margin-bottom: 8px;
        }
        header p {
            color: #64748b;
            font-size: 0.95rem;
        }
        .card {
            background: var(--card-bg);
            border: 1px solid var(--border-color);
            border-radius: 12px;
            padding: 20px;
            margin-bottom: 20px;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: 600;
            font-size: 0.9rem;
        }
        input, select {
            width: 100%;
            padding: 10px 12px;
            border: 1px solid var(--border-color);
            border-radius: 8px;
            font-size: 0.95rem;
            outline: none;
            transition: border-color 0.2s;
        }
        input:focus, select:focus {
            border-color: var(--primary-color);
        }
        .btn-container {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }
        button {
            background-color: var(--primary-color);
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-weight: 600;
            cursor: pointer;
            transition: background-color 0.2s;
            font-size: 0.95rem;
        }
        button:hover {
            background-color: var(--primary-hover);
        }
        .btn-secondary {
            background-color: #64748b;
        }
        .btn-secondary:hover {
            background-color: #475569;
        }
        .btn-danger {
            background-color: var(--danger-color);
            padding: 5px 10px;
            font-size: 0.8rem;
        }
        .actions-bar {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
            gap: 10px;
            flex-wrap: wrap;
        }
        .bookmark-list {
            display: grid;
            gap: 15px;
        }
        .bookmark-item {
            background: var(--card-bg);
            border: 1px solid var(--border-color);
            border-radius: 10px;
            padding: 15px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            transition: transform 0.2s;
        }
        .bookmark-item:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(0,0,0,0.05);
        }
        .bookmark-info h3 {
            font-size: 1.1rem;
            margin-bottom: 5px;
        }
        .bookmark-info a {
            color: var(--primary-color);
            text-decoration: none;
            word-break: break-all;
            font-size: 0.9rem;
        }
        .bookmark-info a:hover {
            text-decoration: underline;
        }
        .badge {
            display: inline-block;
            padding: 2px 8px;
            border-radius: 20px;
            font-size: 0.75rem;
            font-weight: 600;
            background: #e0e7ff;
            color: var(--primary-color);
            margin-top: 5px;
        }
        .empty-state {
            text-align: center;
            color: #94a3b8;
            padding: 40px;
            font-size: 0.95rem;
        }
        #importFile {
            display: none;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>مدیریت لینک‌های شخصی</h1>
            <p>ذخیره، دسته‌بندی و پشتیبان‌گیری از لینک‌های مفید روزانه در مرورگر شما</p>
        </header>

        <div class="card">
            <form id="bookmarkForm">
                <div class="form-group">
                    <label for="title">عنوان لینک:</label>
                    <input type="text" id="title" placeholder="مثلاً سایت مرجع آموزشی" required>
                </div>
                <div class="form-group">
                    <label for="url">آدرس اینترنتی (URL):</label>
                    <input type="url" id="url" placeholder="https://example.com" required>
                </div>
                <div class="form-group">
                    <label for="category">دسته‌بندی / برچسب:</label>
                    <input type="text" id="category" placeholder="مثلاً برنامه‌نویسی، اخبار، تحقیق" required>
                </div>
                <button type="submit">افزودن لینک جدید</button>
            </form>
        </div>

        <div class="actions-bar">
            <div class="btn-container">
                <button class="btn-secondary" onclick="exportJSON()">دریافت خروجی JSON</button>
                <button class="btn-secondary" onclick="document.getElementById('importFile').click()">ورود اطلاعات</button>
                <input type="file" id="importFile" accept=".json" onchange="importJSON(event)">
            </div>
            <div style="font-size: 0.9rem; color: #64748b;" id="counter">تعداد لینک‌ها: 0</div>
        </div>

        <div id="bookmarkList" class="bookmark-list"></div>
    </div>

    <script>
        let bookmarks = JSON.parse(localStorage.getItem('my_bookmarks')) || [];

        const form = document.getElementById('bookmarkForm');
        const listContainer = document.getElementById('bookmarkList');
        const counterEl = document.getElementById('counter');

        form.addEventListener('submit', function(e) {
            e.preventDefault();
            const title = document.getElementById('title').value.trim();
            const url = document.getElementById('url').value.trim();
            const category = document.getElementById('category').value.trim();

            const newBookmark = {
                id: Date.now(),
                title,
                url,
                category
            };

            bookmarks.push(newBookmark);
            saveAndRender();
            form.reset();
        });

        function deleteBookmark(id) {
            bookmarks = bookmarks.filter(b => b.id !== id);
            saveAndRender();
        }

        function saveAndRender() {
            localStorage.setItem('my_bookmarks', JSON.stringify(bookmarks));
            renderBookmarks();
        }

        function renderBookmarks() {
            listContainer.innerHTML = '';
            counterEl.textContent = `تعداد لینک‌ها: ${bookmarks.length}`;

            if (bookmarks.length === 0) {
                listContainer.innerHTML = '<div class="empty-state">هنوز هیچ لینکی اضافه نشده است. اولین لینک خود را ثبت کنید!</div>';
                return;
            }

            bookmarks.slice().reverse().forEach(b => {
                const item = document.createElement('div');
                item.className = 'bookmark-item';
                item.innerHTML = `
                    <div class="bookmark-info">
                        <h3>${escapeHtml(b.title)}</h3>
                        <a href="${escapeHtml(b.url)}" target="_blank" rel="noopener noreferrer">${escapeHtml(b.url)}</a><br>
                        <span class="badge">${escapeHtml(b.category)}</span>
                    </div>
                    <button class="btn-danger" onclick="deleteBookmark(${b.id})</div>">حذف</button>
                `;
                // اصلاح دکمه حذف به شکل صحیح:
                item.querySelector('.btn-danger').onclick = () => deleteBookmark(b.id);
                listContainer.appendChild(item);
            });
        }

        function escapeHtml(str) {
            return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
        }

        function exportJSON() {
            if (bookmarks.length === 0) {
                alert('لیست لینک‌ها خالی است!');
                return;
            }
            const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(bookmarks, null, 2));
            const downloadAnchor = document.createElement('a');
            downloadAnchor.setAttribute("href", dataStr);
            downloadAnchor.setAttribute("download", "bookmarks_backup.json");
            document.body.appendChild(downloadAnchor);
            downloadAnchor.click();
            downloadAnchor.remove();
        }

        function importJSON(event) {
            const file = event.target.files[0];
            if (!file) return;

            const reader = new FileReader();
            reader.onload = function(e) {
                try {
                    const imported = JSON.parse(e.target.result);
                    if (Array.isArray(imported)) {
                        bookmarks = imported;
                        saveAndRender();
                        alert('اطلاعات با موفقیت بارگذاری شد!');
                    } else {
                        alert('فرمت فایل JSON نامعتبر است.');
                    }
                } catch (err) {
                    alert('خطا در خواندن فایل JSON.');
                }
            };
            reader.readAsText(file);
            event.target.value = '';
        }

        renderBookmarks();
    </script>
</body>
</html>

بررسی بخش‌به‌بخش ساختار کد و نحوه عملکرد آن

این کد به سه بخش اصلی تقسیم شده است: ساختار ظاهری (HTML)، استایل‌ها و طراحی واکنش‌گرا (CSS) و منطق پردازشی و ذخیره‌سازی داده‌ها (JavaScript). بخش CSS از متغیرهای رنگی مدرن بهره می‌برد که تغییر ظاهر و تم برنامه را در آینده بسیار ساده می‌کند. استایل‌ها با استفاده از سیستم Flexbox و Grid چیده شده‌اند تا کمترین به‌ریختگی را در ابعاد مختلف صفحه داشته باشند.

در بخش جاوا اسکریپت، متغیر bookmarks وظیفه‌ی نگهداری آرایه‌ای از اشیاء لینک را بر عهده دارد. با هر بار فراخوانی تابع saveAndRender()، تغییرات هم درحافظه localStorage ثبت می‌شوند و هم لیست موجود در صفحه دوباره‌سازی (Render) می‌شود. تابع امنیتی escapeHtml نیز برای جلوگیری از خطاهای احتمالی تزریق کدهای مخرب هنگام نمایش عنوان‌ها و آدرس‌ها به کار رفته است تا امنیت محیط محلی کاربر کاملاً حفظ شود.

راهنمای اجرا، محدودیت‌ها و اشتباهات رایج

برای اجرای صحیح این ابزار نیاز به هیچ سرور محلی (Localhost) ندارید؛ کافی است فایل ذخیره شده را با مرورگرهای مدرن مثل کروم، فایرفاکس یا اج باز کنید. توجه داشته باشید که داده‌ها درون حافظه‌ی مرورگر شما ذخیره می‌شوند؛ بنابراین اگر حافظه‌ی پنهان (Cache) یا داده‌های مرورگر را به طور کامل پاک کنید، ممکن است اطلاعات ذخیره‌شده پاک شوند. برای جلوگیری از این اتفاق، توصیه می‌شود به صورت هفتگی از دکمه‌ی «دریافت خروجی JSON» استفاده کنید تا یک نسخه‌ی پشتیبان مطمئن روی هارد دیسک خود داشته باشید.

یکی از اشتباهات رایج کاربران این است که فراموش می‌کنند فایل خروجی را با پسوند .json ذخیره کنند یا هنگام ورود اطلاعات، فایلی با ساختار غیرمرتبط انتخاب می‌کنند. برنامه‌ی نوشته‌شده این خطاها را مدیریت کرده و در صورت نامعتبر بودن ساختار فایل، با پیام هشدار از بروز خطاهای سیستمی جلوگیری می‌کند.

پرسش‌های متداول

آیا این ابزار به اینترنت نیاز دارد؟

خیر، این ابزار کاملاً آفلاین است و پس از دانلود یا کپی کردن کد در یک فایل، بدون نیاز به اتصال اینترنت روی سیستم شما کار می‌کند.

داده‌های من کجا ذخیره می‌شوند؟

تمامی داده‌ها در حافظه‌ی محلی مرورگر شما (LocalStorage) ذخیره می‌شوند و هیچ اطلاعاتی به هیچ سرور خارجی ارسال نمی‌شود.

چگونه می‌توانم لینک‌ها را به کامپیوتر دیگری منتقل کنم؟

با استفاده از دکمه‌ی «دریافت خروجی JSON» یک فایل پشتیبان دریافت کنید، آن را به سیستم جدید منتقل کرده و از طریق دکمه‌ی «ورود اطلاعات» در برنامه‌ی جدید بارگذاری کنید.

آیا محدودیتی در تعداد لینک‌های ذخیره‌شده وجود دارد؟

حافظه‌ی LocalStorage مرورگر معمولاً تا چند مگابایت فضا در اختیار شما قرار می‌دهد که برای ذخیره‌سازی ده‌ها هزار لینک متنی کاملاً کافی است.

جمع‌بندی

استفاده از ابزارهای سبک و اختصاصی وب نه‌تنها کنترل کامل بر داده‌ها را برای شما به ارمغان می‌آورد، بلکه سرعت انجام کارهای روزمره را به شکل چشمگیری افزایش می‌دهد. پروژه‌ای که در این مقاله بررسی کردیم، جایگزینی امن، سریع و کاملاً خصوصی برای بوکمارک‌های سنتی مرورگر است که با قابلیت خروجی JSON خیال شما را از بابت حفظ همیشگی لینک‌های مهم راحت می‌کند. با کپی کردن این کد ساده، اکنون صاحب یک ابزار اختصاصی مدیریت لینک شده‌اید که می‌توانید آن را به دلخواه خود توسعه دهید.

پاسخی بگذارید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *