Создание Сервиса Ведения Заметок в Стиле Zettelkasten

В этой статье мы рассмотрим процесс создания сервиса ведения заметок в стиле Зеттелькастен, который позволяет пользователям эффективно организовывать и связывать свои идеи и мысли. Мы будем использовать HTML, CSS и PHP для создания динамического и интерактивного веб-приложения.

Зеттелькастен (Zettelkasten) – это методология ведения заметок, разработанная немецким социологом Никласом Луманом, которая позволяет создавать сеть знаний путем связи отдельных заметок. В нашем сервисе мы реализуем основные принципы Зеттелькастена, позволяя пользователям создавать заметки, связывать их друг с другом, и эффективно искать и просматривать свою базу знаний.

Структура Файлов

Наш сервис состоит из двух основных файлов: index.html и save_notes.php.

  • index.html – это HTML-страница, которая содержит интерфейс пользователя, включая боковое меню для поиска и списка заметок, а также область для просмотра и редактирования заметок.
  • save_notes.php – это PHP-скрипт, ответственный за сохранение заметок в файл notes.json.

HTML и CSS

В HTML-разметке мы используем фреймворк Bootstrap для создания адаптивного и привлекательного дизайна. Разделяем страницу на две основные области: боковое меню и область заметок.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Zettelkasten Notes</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            display: flex;
            height: 100vh;
            margin: 0;
            font-family: Arial, sans-serif;
        }
        #notes-list {
            width: 25%;
            border-right: 1px solid #ddd;
            overflow-y: auto;
            padding: 15px;
        }
        #note-content {
            flex: 1;
            padding: 15px;
        }
        .note-item {
            cursor: pointer;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            margin-bottom: 10px;
        }
        .note-item:hover {
            background-color: #f8f9fa;
        }
        .btn {
            margin-top: 10px;
        }
        #note-title {
            border: none;
            font-size: 1.5rem;
            font-weight: bold;
            width: 100%;
        }
    </style>
</head>
<body>

<div id="notes-list">
    <input type="text" id="search-bar" class="form-control mb-3" placeholder="Search notes..." oninput="searchNotes()">
    <button class="btn btn-primary w-100 mb-3" onclick="createNote()">New Note</button>
    <h4>Notes</h4>
    <div id="notes"></div>
</div>

<div id="note-content">
    <input id="note-title" placeholder="Title" />
    <textarea id="note-text" class="form-control mt-2" rows="10" placeholder="Type your note here..."></textarea>
    <input type="text" id="note-hashtag" class="form-control mt-2" placeholder="#Hashtag">
    <button class="btn btn-success mt-3" onclick="saveNote()">Save</button>
    <button class="btn btn-danger mt-3" onclick="deleteNote()">Delete</button>
    <button class="btn btn-secondary mt-3" onclick="linkNote()">Link Note</button>
    <div id="linked-notes" class="mt-3"></div>
</div>

<script>
    let notes = [];
    let currentNoteId = null;

    function loadNotes() {
        fetch('notes.json')
            .then(response => response.json())
            .then(data => {
                notes = data;
                renderNotesList();
            })
            .catch(() => {
                notes = [];
            });
    }

    function renderNotesList() {
        const notesDiv = document.getElementById('notes');
        notesDiv.innerHTML = '';
        notes.forEach((note, index) => {
            const noteDiv = document.createElement('div');
            noteDiv.className = 'note-item';
            noteDiv.textContent = `ID: ${index} - ${note.title || 'Untitled'}`;
            noteDiv.onclick = () => loadNoteContent(index);
            notesDiv.appendChild(noteDiv);
        });
    }

    function loadNoteContent(index) {
        currentNoteId = index;
        const note = notes[index];
        document.getElementById('note-title').value = note.title;
        document.getElementById('note-text').value = note.content;
        document.getElementById('note-hashtag').value = note.hashtag;
        renderLinkedNotes(note.linkedNotes || []);
    }

    function createNote() {
        const timestamp = new Date().toLocaleString();
        const newNote = { title: 'New Note', content: '', hashtag: '', createdAt: timestamp, linkedNotes: [] };
        notes.push(newNote);
        currentNoteId = notes.length - 1;
        renderNotesList();
        loadNoteContent(currentNoteId);
    }

    function saveNote() {
        if (currentNoteId !== null) {
            const note = notes[currentNoteId];
            note.title = document.getElementById('note-title').value;
            note.content = document.getElementById('note-text').value;
            note.hashtag = document.getElementById('note-hashtag').value;
            saveNotesToFile();
            renderNotesList();
        }
    }

    function deleteNote() {
        if (currentNoteId !== null) {
            notes.forEach(note => {
                if (note.linkedNotes) {
                    note.linkedNotes = note.linkedNotes.filter(link => link !== currentNoteId);
                }
            });
            notes.splice(currentNoteId, 1);
            currentNoteId = null;
            document.getElementById('note-title').value = '';
            document.getElementById('note-text').value = '';
            document.getElementById('note-hashtag').value = '';
            saveNotesToFile();
            renderNotesList();
        }
    }

    function linkNote() {
        const linkIndex = prompt('Enter the ID of the note to link:');
        if (linkIndex !== null && linkIndex >= 0 && linkIndex < notes.length && linkIndex != currentNoteId) {
            notes[currentNoteId].linkedNotes = notes[currentNoteId].linkedNotes || [];
            notes[currentNoteId].linkedNotes.push(parseInt(linkIndex));

            notes[linkIndex].linkedNotes = notes[linkIndex].linkedNotes || [];
            if (!notes[linkIndex].linkedNotes.includes(currentNoteId)) {
                notes[linkIndex].linkedNotes.push(currentNoteId);
            }

            renderLinkedNotes(notes[currentNoteId].linkedNotes);
            saveNotesToFile();
        } else {
            alert('Invalid note ID.');
        }
    }

    function renderLinkedNotes(linkedNotes) {
        const linkedNotesDiv = document.getElementById('linked-notes');
        linkedNotesDiv.innerHTML = '<h5>Linked Notes:</h5>';
        linkedNotes.forEach(index => {
            if (notes[index]) {
                const note = notes[index];
                const link = document.createElement('div');
                link.textContent = `ID: ${index} - ${note.title || 'Untitled'}`;
                link.className = 'note-item';
                link.onclick = () => loadNoteContent(index);
                linkedNotesDiv.appendChild(link);
            }
        });
    }

    function searchNotes() {
        const query = document.getElementById('search-bar').value.toLowerCase();
        const filteredNotes = notes.filter(note =>
            (note.title && note.title.toLowerCase().includes(query)) ||
            (note.content && note.content.toLowerCase().includes(query)) ||
            (note.hashtag && note.hashtag.toLowerCase().includes(query))
        );
        const notesDiv = document.getElementById('notes');
        notesDiv.innerHTML = '';
        filteredNotes.forEach((note, index) => {
            const noteDiv = document.createElement('div');
            noteDiv.className = 'note-item';
            noteDiv.textContent = `ID: ${index} - ${note.title || 'Untitled'}`;
            noteDiv.onclick = () => loadNoteContent(index);
            notesDiv.appendChild(noteDiv);
        });
    }

    function saveNotesToFile() {
        fetch('save_notes.php', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(notes)
        }).then(response => {
            if (!response.ok) {
                alert('Failed to save notes!');
            }
        });
    }

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

PHP (save_notes.php)

PHP-скрипт save_notes.php обрабатывает сохранение заметок в файл notes.json.

<?php
$data = file_get_contents('php://input');
if ($data) {
    $notes = json_decode($data, true);
    if (json_last_error() === JSON_ERROR_NONE) {
        file_put_contents('notes.json', json_encode($notes, JSON_PRETTY_PRINT));
        http_response_code(200);
        echo json_encode(["status" => "success"]);
    } else {
        http_response_code(400);
        echo json_encode(["status" => "error", "message" => "Invalid JSON"]);
    }
} else {
    http_response_code(400);
    echo json_encode(["status" => "error", "message" => "No data received"]);
}
?>

Созданный нами сервис ведения заметок в стиле Зеттелькастен позволяет пользователям эффективно организовывать свои мысли и идеи. С помощью HTML, CSS, JavaScript, и PHP мы создали динамичный и интерактивный интерфейс, который обеспечивает удобное ведение заметок и управление связями между ними.

Пользователи могут создавать заметки, редактировать их, связывать с другими заметками, и легко искать необходимую информацию. Этот сервис может стать мощным инструментом для тех, кто стремится развивать свои идеи и строить собственную базу знаний.