markdown-defter/index.html

690 lines
19 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Markdown Not Defteri</title>
<style>
:root {
--bg: #f4f5f7;
--panel: #ffffff;
--border: #e2e4e8;
--text: #1f2328;
--muted: #6b7280;
--accent: #3b82f6;
--accent-soft: #eff6ff;
--danger: #dc2626;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
overflow-y: auto;
}
.app {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 1400px;
margin: 0 auto;
}
/* Üst bar */
.topbar {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--panel);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.topbar h1 {
font-size: 17px;
font-weight: 600;
flex-shrink: 0;
}
.topbar .spacer { flex: 1; }
.btn {
border: 1px solid var(--border);
background: var(--panel);
color: var(--text);
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
white-space: nowrap;
}
.btn:hover { background: var(--accent-soft); border-color: var(--accent); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.primary:hover { filter: brightness(1.05); }
.btn.danger:hover { color: var(--danger); border-color: var(--danger); background: #fef2f2; }
/* Ana bölüm */
.main {
display: flex;
flex: 1;
overflow: hidden;
}
/* Sol: not listesi */
.sidebar {
width: 260px;
min-width: 200px;
background: var(--panel);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow: hidden;
}
.searchbox {
margin: 12px;
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 13px;
outline: none;
}
.searchbox:focus { border-color: var(--accent); }
.note-list {
flex: 1;
overflow-y: auto;
padding: 0 8px 8px;
}
.note-item {
padding: 10px 12px;
border-radius: 6px;
cursor: pointer;
margin-bottom: 4px;
border: 1px solid transparent;
}
.note-item:hover { background: var(--bg); }
.note-item.active {
background: var(--accent-soft);
border-color: var(--accent);
}
.note-item .title {
font-size: 14px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.note-item .preview {
font-size: 12px;
color: var(--muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 2px;
}
.note-item .meta {
font-size: 11px;
color: var(--muted);
margin-top: 4px;
}
/* Sağ: editör */
.editor {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.editor-top {
display: flex;
gap: 8px;
padding: 10px 16px;
align-items: center;
flex-wrap: wrap;
}
.editor-top .notetitle-input {
flex: 1;
min-width: 160px;
font-size: 18px;
font-weight: 600;
border: 1px solid transparent;
background: transparent;
padding: 6px;
border-radius: 6px;
outline: none;
}
.editor-top .notetitle-input:hover { border-color: var(--border); }
.editor-top .notetitle-input:focus { border-color: var(--accent); background: var(--panel); }
.tabs {
display: flex;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 6px;
overflow: hidden;
}
.tab {
padding: 6px 14px;
font-size: 13px;
cursor: pointer;
border: none;
background: transparent;
color: var(--muted);
border-right: 1px solid var(--border);
}
.tab:last-child { border-right: none; }
.tab.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.pane {
flex: 1;
overflow-y: auto;
padding: 16px;
background: var(--panel);
margin: 0 16px 16px;
border: 1px solid var(--border);
border-radius: 8px;
}
textarea.markdown-input {
width: 100%;
height: 100%;
border: none;
resize: none;
outline: none;
font-family: 'Cascadia Code', 'Consolas', monospace;
font-size: 14px;
line-height: 1.6;
background: transparent;
}
/* Önizleme */
.markdown-preview h1, .markdown-preview h2, .markdown-preview h3,
.markdown-preview h4, .markdown-preview h5, .markdown-preview h6 {
margin: 1em 0 0.5em;
line-height: 1.3;
}
.markdown-preview h1 { font-size: 26px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
.markdown-preview h2 { font-size: 22px; }
.markdown-preview h3 { font-size: 18px; }
.markdown-preview p { margin: 0.6em 0; line-height: 1.6; }
.markdown-preview ul, .markdown-preview ol { margin: 0.6em 0 0.6em 1.6em; line-height: 1.6; }
.markdown-preview li { margin: 0.2em 0; }
.markdown-preview code {
background: var(--bg);
padding: 2px 5px;
border-radius: 4px;
font-family: 'Cascadia Code', 'Consolas', monospace;
font-size: 0.9em;
}
.markdown-preview pre {
background: #282c34;
color: #e5e7eb;
padding: 12px;
border-radius: 8px;
overflow-x: auto;
margin: 0.8em 0;
}
.markdown-preview pre code { background: transparent; padding: 0; color: inherit; }
.markdown-preview blockquote {
border-left: 4px solid var(--accent);
margin: 0.8em 0;
padding: 4px 16px;
color: var(--muted);
background: var(--bg);
border-radius: 0 6px 6px 0;
}
.markdown-preview img { max-width: 100%; border-radius: 6px; }
.markdown-preview a { color: var(--accent); }
.markdown-preview table { border-collapse: collapse; margin: 0.8em 0; }
.markdown-preview th, .markdown-preview td {
border: 1px solid var(--border);
padding: 6px 12px;
}
.markdown-preview th { background: var(--bg); }
.empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: var(--muted);
font-size: 15px;
text-align: center;
padding: 20px;
}
/* Responsive */
@media (max-width: 720px) {
.main { flex-direction: column; }
.sidebar { width: 100%; border-right: none; border-bottom: 1px solid var(--border); max-height: 40vh; }
.pane { margin: 0 8px 8px; padding: 12px; }
.editor-top { padding: 8px; }
.topbar { padding: 10px 8px; flex-wrap: wrap; }
}
</style>
</head>
<body>
<div class="app">
<div class="topbar">
<h1>📝 Markdown Not Defteri</h1>
<div class="spacer"></div>
<button class="btn primary" id="btn-yeni">+ Yeni Not</button>
</div>
<div class="main">
<aside class="sidebar">
<input class="searchbox" id="arama" type="text" placeholder="Notlarda ara...">
<div class="note-list" id="note-list"></div>
</aside>
<section class="editor" id="editor">
<div class="editor-top">
<input class="notetitle-input" id="not-baslik" type="text" placeholder="Başlık...">
<div class="tabs">
<button class="tab active" id="tab-duzenle">Düzenle</button>
<button class="tab" id="tab-onizle">Önizleme</button>
</div>
<button class="btn danger" id="btn-sil" title="Notu sil">🗑 Sil</button>
</div>
<div class="pane" id="duzenleme-pane">
<textarea class="markdown-input" id="markdown-input" placeholder="Markdown yazmaya başla..."></textarea>
</div>
<div class="pane" id="onizleme-pane" style="display:none;">
<div class="markdown-preview" id="markdown-preview"></div>
</div>
</section>
</div>
</div>
<script>
(function () {
'use strict';
var STORAGE_KEY = 'markdown-notes';
var notes = load();
/* ---------- Basit Markdown parser (elle yazıldı) ---------- */
function escapeHtml(str) {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function inline(text) {
// kod
text = text.replace(/`([^`]+)`/g, function (_, code) {
return '<code>' + escapeHtml(code) + '</code>';
});
// kalın
text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
// italik
text = text.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1<em>$2</em>');
// link
text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
'<a href="$2" target="_blank" rel="noopener">$1</a>');
return text;
}
function parseMarkdown(src) {
var lines = src.split('\n');
var html = '';
var i = 0;
var inList = null; // 'ul' ya da 'ol'
var inPre = false;
var intoQuote = false;
function closeList() {
if (inList) { html += '</' + inList + '>'; inList = null; }
}
function closeQuote() {
if (intoQuote) { html += '</blockquote>'; intoQuote = false; }
}
for (i = 0; i < lines.length; i++) {
var line = lines[i];
// Koda boş satır gibi davranma (code block)
if (inPre) {
if (/^```/.test(line.trim())) {
html += '</code></pre>';
inPre = false;
} else {
html += escapeHtml(line) + '\n';
}
continue;
}
if (/^```/.test(line.trim())) {
closeList(); closeQuote();
html += '<pre><code>';
inPre = true;
continue;
}
if (/^#\s/.test(line)) {
closeList(); closeQuote();
html += '<h1>' + inline(escapeHtml(line.replace(/^#\s+/, ''))) + '</h1>';
} else if (/^##\s/.test(line)) {
closeList(); closeQuote();
html += '<h2>' + inline(escapeHtml(line.replace(/^##\s+/, ''))) + '</h2>';
} else if (/^###\s/.test(line)) {
closeList(); closeQuote();
html += '<h3>' + inline(escapeHtml(line.replace(/^###\s+/, ''))) + '</h3>';
} else if (/^####\s/.test(line)) {
closeList(); closeQuote();
html += '<h4>' + inline(escapeHtml(line.replace(/^####\s+/, ''))) + '</h4>';
} else if (/^#####\s/.test(line)) {
closeList(); closeQuote();
html += '<h5>' + inline(escapeHtml(line.replace(/^#####\s+/, ''))) + '</h5>';
} else if (/^######\s/.test(line)) {
closeList(); closeQuote();
html += '<h6>' + inline(escapeHtml(line.replace(/^######\s+/, ''))) + '</h6>';
} else if (/^\s*>\s?/.test(line)) {
closeList();
if (!intoQuote) { html += '<blockquote>'; intoQuote = true; }
html += inline(escapeHtml(line.replace(/^\s*>\s?/, ''))) + '<br>';
} else if (/^\s*[-*+]\s+/.test(line)) {
closeQuote();
if (inList !== 'ul') { closeList(); html += '<ul>'; inList = 'ul'; }
html += '<li>' + inline(escapeHtml(line.replace(/^\s*[-*+]\s+/, ''))) + '</li>';
} else if (/^\s*\d+[.)]\s+/.test(line)) {
closeQuote();
if (inList !== 'ol') { closeList(); html += '<ol>'; inList = 'ol'; }
html += '<li>' + inline(escapeHtml(line.replace(/^\s*\d+[.)]\s+/, ''))) + '</li>';
} else if (/^\s*$/.test(line)) {
closeList(); closeQuote();
} else {
closeList(); closeQuote();
html += '<p>' + inline(escapeHtml(line)) + '</p>';
}
}
closeList(); closeQuote();
if (inPre) { html += '</code></pre>'; }
return html;
}
/* ---------- Depolama ---------- */
function load() {
try {
var raw = localStorage.getItem(STORAGE_KEY);
var parsed = raw ? JSON.parse(raw) : [];
if (!Array.isArray(parsed)) return [];
return parsed.map(function (n) {
return { id: n.id, title: n.title, body: n.body, updatedAt: n.updatedAt };
});
} catch (e) {
return [];
}
}
function save() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
}
function uid() {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}
/* ---------- Durum ---------- */
var currentId = null;
var activeView = 'duzenle'; // 'duzenle' | 'onizle'
var searchTerm = '';
/* ---------- DOM ---------- */
var noteList = document.getElementById('note-list');
var searchBox = document.getElementById('arama');
var editor = document.getElementById('editor');
var notBaslik = document.getElementById('not-baslik');
var markdownInput = document.getElementById('markdown-input');
var markdownPreview = document.getElementById('markdown-preview');
var duzenlemePane = document.getElementById('duzenleme-pane');
var onizlemePane = document.getElementById('onizleme-pane');
var tabDuzenle = document.getElementById('tab-duzenle');
var tabOnizle = document.getElementById('tab-onizle');
var btnYeni = document.getElementById('btn-yeni');
var btnSil = document.getElementById('btn-sil');
/* ---------- Yardımcılar ---------- */
function currentNote() {
return notes.find(function (n) { return n.id === currentId; }) || null;
}
function refreshList() {
var term = searchTerm.toLowerCase();
// sıralama: güncellenme tarihine göre, yenisi üstte
var sorted = notes.slice().sort(function (a, b) {
return (b.updatedAt || 0) - (a.updatedAt || 0);
});
if (sorted.length === 0) {
noteList.innerHTML = '<div class="empty">Henüz not yok.<br>Yukarıdan yeni not ekle.</div>';
return;
}
var html = '';
sorted.forEach(function (n) {
var title = n.title || 'Başlıksız Not';
var excerpt = plainText(n.body) || 'Boş not';
if (term && title.toLowerCase().indexOf(term) === -1 && excerpt.toLowerCase().indexOf(term) === -1) {
return;
}
html +=
'<div class="note-item' + (n.id === currentId ? ' active' : '') + '" data-id="' + n.id + '">' +
'<div class="title">' + escapeHtml(title) + '</div>' +
'<div class="preview">' + escapeHtml(excerpt) + '</div>' +
'<div class="meta">' + formatDate(n.updatedAt) + '</div>' +
'</div>';
});
if (!html) {
html = '<div class="empty">Arama sonucu yok.</div>';
}
noteList.innerHTML = html;
// tıklama
Array.prototype.forEach.call(noteList.querySelectorAll('.note-item'), function (el) {
el.addEventListener('click', function () {
selectNote(el.getAttribute('data-id'));
});
});
}
function plainText(md) {
return md
.replace(/```[\s\S]*?```/g, ' ')
.replace(/`([^`]+)`/g, '$1')
.replace(/^#+\s+/gm, '')
.replace(/[*_>\-]/g, ' ')
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
.replace(/\s+/g, ' ')
.trim();
}
function formatDate(ts) {
if (!ts) return '';
var d = new Date(ts);
var now = new Date();
var isToday = d.toDateString() === now.toDateString();
var time = pad(d.getHours()) + ':' + pad(d.getMinutes());
if (isToday) return 'Bugün ' + time;
var y = d.getFullYear();
var m = pad(d.getMonth() + 1);
var g = pad(d.getDate());
if (y === now.getFullYear()) return g + '.' + m + ' ' + time;
return g + '.' + m + '.' + y;
}
function pad(n) { return n < 10 ? '0' + n : '' + n; }
function selectNote(id) {
currentId = id;
var n = currentNote();
if (!n) { renderEmpty(); return; }
notBaslik.value = n.title;
markdownInput.value = n.body;
refreshList();
renderView();
}
function renderEmpty() {
// Editör elemanlarını DOM'da tut, sadece gizle (referanslar bozulmasın)
[duzenlemePane, onizlemePane].forEach(function (p) { p.style.display = 'none'; });
if (!editor.querySelector('.empty-placeholder')) {
var d = document.createElement('div');
d.className = 'empty empty-placeholder';
d.textContent = 'Bir not seç veya yeni not oluştur. 💡 İlk notunu yazmaya hazır mısın?';
editor.appendChild(d);
}
editor.querySelector('.empty-placeholder').style.display = 'flex';
}
function renderView() {
var ph = editor.querySelector('.empty-placeholder');
if (ph) ph.style.display = 'none';
if (currentNote()) {
if (activeView === 'onizle') {
onizlemePane.style.display = 'block';
duzenlemePane.style.display = 'none';
markdownPreview.innerHTML = parseMarkdown(markdownInput.value);
} else {
duzenlemePane.style.display = 'block';
onizlemePane.style.display = 'none';
}
}
}
function updateCurrent() {
var n = currentNote();
if (!n) return;
n.title = notBaslik.value;
n.body = markdownInput.value;
n.updatedAt = Date.now();
save();
refreshList(); // başlık/liste güncellensin ancak seçimi koru
}
function updateNoteTitle() {
var n = currentNote();
if (!n) return;
n.title = notBaslik.value;
n.updatedAt = Date.now();
save();
refreshList();
}
function createNote() {
var n = {
id: uid(),
title: 'Yeni Not',
body: '',
updatedAt: Date.now()
};
notes.unshift(n);
save();
currentId = n.id;
activeView = 'duzenle';
notBaslik.value = n.title;
markdownInput.value = '';
refreshList();
renderView();
markdownInput.focus();
}
function deleteCurrent() {
if (!currentId) return;
var n = currentNote();
var label = n && n.title ? n.title : 'bu not';
if (!confirm('"' + label + '" silinsin mi?')) return;
notes = notes.filter(function (x) { return x.id !== currentId; });
save();
currentId = null;
notBaslik.value = '';
markdownInput.value = '';
if (notes.length > 0) {
selectNote(notes[0].id);
} else {
renderEmpty();
refreshList();
}
}
/* ---------- Etkinlikler ---------- */
btnYeni.addEventListener('click', createNote);
btnSil.addEventListener('click', deleteCurrent);
notBaslik.addEventListener('input', updateNoteTitle);
// yazma → önizleme güncelle + kaydet
var saveTimer = null;
markdownInput.addEventListener('input', function () {
updateCurrent();
if (activeView === 'onizle') {
markdownPreview.innerHTML = parseMarkdown(markdownInput.value);
}
});
tabDuzenle.addEventListener('click', function () {
activeView = 'duzenle';
tabDuzenle.classList.add('active');
tabOnizle.classList.remove('active');
renderView();
});
tabOnizle.addEventListener('click', function () {
activeView = 'onizle';
tabOnizle.classList.add('active');
tabDuzenle.classList.remove('active');
renderView();
});
searchBox.addEventListener('input', function () {
searchTerm = searchBox.value;
refreshList();
});
// Ctrl+S kaydet (önemsiz, zaten anlık)
document.addEventListener('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
updateCurrent();
}
});
/* ---------- Başlat ---------- */
if (notes.length > 0) {
selectNote(notes[0].id);
} else {
renderEmpty();
refreshList();
}
})();
</script>
</body>
</html>