В этой статье мы рассмотрим процесс создания сервиса ведения заметок в стиле Зеттелькастен, который позволяет пользователям эффективно организовывать и связывать свои идеи и мысли. Мы будем использовать 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 мы создали динамичный и интерактивный интерфейс, который обеспечивает удобное ведение заметок и управление связями между ними.
Пользователи могут создавать заметки, редактировать их, связывать с другими заметками, и легко искать необходимую информацию. Этот сервис может стать мощным инструментом для тех, кто стремится развивать свои идеи и строить собственную базу знаний.