nexus-notes / index.html
yasarefe's picture
Add 2 files
4568a4e verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nexus Notes - Modern Note Taking</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
},
secondary: {
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a',
}
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['Fira Code', 'monospace']
}
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap');
body {
font-family: 'Inter', sans-serif;
}
.markdown-content h1 {
@apply text-2xl font-bold my-4 text-gray-900 dark:text-white;
}
.markdown-content h2 {
@apply text-xl font-bold my-3 text-gray-800 dark:text-gray-200;
}
.markdown-content h3 {
@apply text-lg font-bold my-2 text-gray-700 dark:text-gray-300;
}
.markdown-content p {
@apply my-2 text-gray-700 dark:text-gray-300;
}
.markdown-content ul {
@apply list-disc pl-5 my-2 text-gray-700 dark:text-gray-300;
}
.markdown-content ol {
@apply list-decimal pl-5 my-2 text-gray-700 dark:text-gray-300;
}
.markdown-content pre {
@apply bg-gray-100 dark:bg-gray-700 p-3 rounded my-2 overflow-x-auto text-gray-800 dark:text-gray-200 font-mono text-sm;
}
.markdown-content code {
@apply bg-gray-100 dark:bg-gray-700 px-1 rounded text-sm text-gray-800 dark:text-gray-200 font-mono;
}
.markdown-content blockquote {
@apply border-l-4 border-primary-500 pl-4 italic my-2 text-gray-600 dark:text-gray-400;
}
.markdown-content a {
@apply text-primary-600 dark:text-primary-400 hover:underline;
}
.markdown-content img {
@apply max-w-full rounded my-2;
}
.markdown-content table {
@apply w-full border-collapse my-3;
}
.markdown-content th,
.markdown-content td {
@apply border border-gray-300 dark:border-gray-600 px-3 py-2 text-left;
}
.markdown-content th {
@apply bg-gray-100 dark:bg-gray-700 font-medium;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
@apply bg-gray-100 dark:bg-gray-800;
}
::-webkit-scrollbar-thumb {
@apply bg-gray-300 dark:bg-gray-600 rounded;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-gray-400 dark:bg-gray-500;
}
/* Animation for note cards */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.note-card {
animation: fadeIn 0.3s ease-out forwards;
}
/* Mobile menu transition */
.mobile-menu {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.mobile-menu.hidden {
transform: translateX(-100%);
opacity: 0;
}
.mobile-menu.shown {
transform: translateX(0);
opacity: 1;
}
/* Editor height */
.editor-container {
height: calc(100vh - 180px);
}
@media (min-width: 768px) {
.editor-container {
height: calc(100vh - 140px);
}
}
/* Bottom navigation */
.bottom-nav {
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
@apply dark:shadow-none dark:border-t dark:border-gray-700;
}
/* Floating action button */
.fab {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
@apply transition-transform hover:scale-105 active:scale-95;
}
/* Note card hover effect */
.note-card:hover {
transform: translateY(-2px);
@apply transition-transform duration-200;
}
/* Active tab indicator */
.tab-indicator {
@apply absolute bottom-0 left-0 h-0.5 bg-primary-500 transition-all duration-300;
}
/* Markdown toolbar */
.markdown-toolbar {
@apply sticky top-0 bg-white dark:bg-gray-800 z-10 border-b border-gray-200 dark:border-gray-700 px-2 py-1 flex flex-wrap items-center gap-1;
}
.markdown-toolbar button {
@apply p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 hover:text-primary-500 dark:hover:text-primary-400;
min-width: 32px;
min-height: 32px;
}
.markdown-toolbar button.active {
@apply bg-primary-100 dark:bg-gray-700 text-primary-600 dark:text-primary-400;
}
/* Preview pane */
.preview-pane {
@apply border-l border-gray-200 dark:border-gray-700 h-full overflow-y-auto;
}
</style>
</head>
<body class="bg-gray-50 text-gray-800 dark:bg-gray-900 dark:text-gray-100 transition-colors duration-200">
<div class="flex flex-col min-h-screen">
<!-- Header -->
<header class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-4 shadow-sm sticky top-0 z-10">
<div class="container mx-auto flex justify-between items-center">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 rounded-md bg-primary-500 flex items-center justify-center">
<i class="fas fa-cube text-white"></i>
</div>
<h1 class="text-xl font-bold hidden sm:block">Nexus Notes</h1>
</div>
<div class="flex items-center space-x-4">
<button id="darkModeToggle" class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition">
<i class="fas fa-moon dark:hidden text-gray-600"></i>
<i class="fas fa-sun hidden dark:block text-yellow-400"></i>
</button>
<button id="newNoteBtn" class="hidden md:flex items-center space-x-2 bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-md font-medium transition">
<i class="fas fa-plus"></i>
<span>New Note</span>
</button>
</div>
</div>
</header>
<!-- Main Content - Split View -->
<div class="flex flex-1 overflow-hidden">
<!-- Left Pane - Note List -->
<div class="w-full md:w-1/3 lg:w-1/4 xl:w-1/5 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex-col overflow-y-auto">
<div class="p-4 sticky top-0 bg-white dark:bg-gray-800 z-10">
<div class="relative mb-4">
<input type="text" id="searchNotes" placeholder="Search notes..."
class="w-full p-2 pl-10 border border-gray-200 dark:border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
<div class="flex justify-between items-center">
<h2 class="font-semibold text-gray-700 dark:text-gray-300">My Notes</h2>
<div class="flex space-x-2">
<button id="sortDateBtn" class="p-1 text-primary-500 dark:text-primary-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" title="Sort by date">
<i class="fas fa-calendar"></i>
</button>
<button id="sortAlphaBtn" class="p-1 text-gray-500 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" title="Sort alphabetically">
<i class="fas fa-sort-alpha-down"></i>
</button>
</div>
</div>
</div>
<div id="noteList" class="divide-y divide-gray-100 dark:divide-gray-700 flex-1 overflow-y-auto">
<!-- Notes will be dynamically added here -->
</div>
</div>
<!-- Right Pane - Side by Side Editor/Preview -->
<div class="hidden md:flex flex-1 flex-col md:flex-row bg-white dark:bg-gray-800 overflow-hidden border-l border-gray-200 dark:border-gray-700">
<!-- Editor Pane -->
<div id="editorPane" class="flex-1 flex flex-col border-r border-gray-200 dark:border-gray-700">
<div class="p-4 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-800 z-10 flex justify-between items-center">
<input type="text" id="noteTitle" placeholder="Note title"
class="text-xl font-bold w-full focus:outline-none bg-transparent text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500">
<div class="flex space-x-2">
<button id="saveNoteBtn" class="p-2 text-gray-500 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition" title="Save">
<i class="fas fa-save"></i>
</button>
<button id="deleteNoteBtn" class="p-2 text-gray-500 dark:text-gray-400 hover:text-red-500 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition" title="Delete">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="markdown-toolbar">
<button class="format-btn" data-format="bold"><i class="fas fa-bold"></i></button>
<button class="format-btn" data-format="italic"><i class="fas fa-italic"></i></button>
<button class="format-btn" data-format="header1"><i class="fas fa-heading"><sub>1</sub></i></button>
<button class="format-btn" data-format="header2"><i class="fas fa-heading"><sub>2</sub></i></button>
<button class="format-btn" data-format="header3"><i class="fas fa-heading"><sub>3</sub></i></button>
<button class="format-btn" data-format="quote"><i class="fas fa-quote-right"></i></button>
<button class="format-btn" data-format="code"><i class="fas fa-code"></i></button>
<button class="format-btn" data-format="link"><i class="fas fa-link"></i></button>
<button class="format-btn" data-format="image"><i class="fas fa-image"></i></button>
<button class="format-btn" data-format="list"><i class="fas fa-list-ul"></i></button>
<button class="format-btn" data-format="olist"><i class="fas fa-list-ol"></i></button>
</div>
<div class="flex-1 overflow-auto">
<textarea id="noteContent" class="w-full h-full p-4 focus:outline-none resize-none font-mono bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500"
placeholder="Start writing your note here... Markdown is supported!"></textarea>
</div>
<div class="p-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700 text-sm text-gray-500 dark:text-gray-400 flex justify-between items-center">
<div id="wordCount" class="flex items-center space-x-2">
<i class="fas fa-font text-xs"></i>
<span>0 words</span>
</div>
<div id="lastSaved" class="text-xs flex items-center space-x-1">
<i class="fas fa-clock text-xs"></i>
<span>Not saved yet</span>
</div>
</div>
</div>
<!-- Preview Pane -->
<div id="previewPane" class="preview-pane hidden lg:block lg:w-1/2">
<div class="p-4 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-800 z-10">
<h2 class="text-xl font-bold text-gray-800 dark:text-white">Preview</h2>
</div>
<div id="notePreviewContent" class="p-4 markdown-content bg-white dark:bg-gray-800">
<!-- Preview content will be rendered here -->
</div>
</div>
</div>
<!-- Mobile View (Single Pane) -->
<div id="mobileEditor" class="md:hidden flex-1 flex flex-col bg-white dark:bg-gray-800 overflow-hidden">
<!-- Empty State -->
<div id="emptyState" class="h-full flex flex-col items-center justify-center text-gray-500 dark:text-gray-400 p-4">
<div class="w-16 h-16 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center mb-4">
<i class="fas fa-sticky-note text-2xl opacity-50"></i>
</div>
<h2 class="text-xl font-medium mb-2">No Note Selected</h2>
<p class="mb-6 text-center max-w-md">Select a note from the sidebar or create a new one to get started</p>
<button id="emptyNewNoteBtn" class="bg-primary-500 hover:bg-primary-600 text-white px-6 py-3 rounded-md font-medium transition flex items-center space-x-2">
<i class="fas fa-plus"></i>
<span>Create New Note</span>
</button>
</div>
<!-- Editor (Mobile) -->
<div id="mobileNoteEditor" class="h-full hidden flex-col">
<div class="p-4 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-800 z-10 flex justify-between items-center">
<input type="text" id="mobileNoteTitle" placeholder="Note title"
class="text-xl font-bold w-full focus:outline-none bg-transparent text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500">
<div class="flex space-x-2">
<button id="mobileSaveNoteBtn" class="p-2 text-gray-500 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition" title="Save">
<i class="fas fa-save"></i>
</button>
<button id="mobileDeleteNoteBtn" class="p-2 text-gray-500 dark:text-gray-400 hover:text-red-500 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition" title="Delete">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="flex border-b border-gray-200 dark:border-gray-700 relative">
<button id="writeTab" class="px-4 py-3 font-medium text-primary-500 dark:text-primary-400 relative">
<i class="fas fa-pen mr-2"></i>
<span>Write</span>
</button>
<button id="previewTab" class="px-4 py-3 font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-eye mr-2"></i>
<span>Preview</span>
</button>
<div id="tabIndicator" class="tab-indicator w-1/2"></div>
</div>
<div class="editor-container flex flex-col">
<div id="writePane" class="flex-1 overflow-auto">
<textarea id="mobileNoteContent" class="w-full h-full p-4 focus:outline-none resize-none font-mono bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500"
placeholder="Start writing your note here... Markdown is supported!"></textarea>
</div>
<div id="mobilePreviewPane" class="flex-1 overflow-auto p-4 hidden markdown-content bg-white dark:bg-gray-800">
<!-- Markdown preview will be rendered here -->
</div>
</div>
</div>
</div>
</div>
<!-- Bottom Navigation (Mobile) -->
<nav class="md:hidden bottom-nav bg-white dark:bg-gray-800 fixed bottom-0 left-0 right-0 z-50">
<div class="flex justify-around items-center p-2">
<button id="mobileNotesBtn" class="p-3 rounded-full text-primary-500 dark:text-primary-400 bg-primary-50 dark:bg-gray-700">
<i class="fas fa-sticky-note text-lg"></i>
</button>
<button id="mobileNewNoteBtn" class="fab bg-primary-500 text-white p-4 rounded-full -mt-6">
<i class="fas fa-plus text-xl"></i>
</button>
<button id="mobilePreviewToggle" class="p-3 rounded-full text-gray-500 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400">
<i class="fas fa-eye text-lg"></i>
</button>
</div>
</nav>
<!-- Modal for markdown elements -->
<div id="mdModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black bg-opacity-50">
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 w-full max-w-md mx-4">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800 dark:text-white" id="mdModalTitle">Add Element</h3>
<button id="closeMdModal" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
<div id="mdModalContent" class="mb-4">
<!-- Content will be dynamically added here -->
</div>
<div class="flex justify-end space-x-2">
<button id="cancelMdModal" class="px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
Cancel
</button>
<button id="insertMdElement" class="px-4 py-2 rounded-md bg-primary-500 text-white hover:bg-primary-600">
Insert
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const darkModeToggle = document.getElementById('darkModeToggle');
const newNoteBtn = document.getElementById('newNoteBtn');
const emptyNewNoteBtn = document.getElementById('emptyNewNoteBtn');
const mobileNewNoteBtn = document.getElementById('mobileNewNoteBtn');
const searchNotes = document.getElementById('searchNotes');
const sortDateBtn = document.getElementById('sortDateBtn');
const sortAlphaBtn = document.getElementById('sortAlphaBtn');
const noteList = document.getElementById('noteList');
const emptyState = document.getElementById('emptyState');
const mobileEditor = document.getElementById('mobileEditor');
const mobileNoteEditor = document.getElementById('mobileNoteEditor');
// Desktop Editor Elements
const editorPane = document.getElementById('editorPane');
const previewPane = document.getElementById('previewPane');
const noteTitle = document.getElementById('noteTitle');
const noteContent = document.getElementById('noteContent');
const saveNoteBtn = document.getElementById('saveNoteBtn');
const deleteNoteBtn = document.getElementById('deleteNoteBtn');
const wordCount = document.getElementById('wordCount');
const lastSaved = document.getElementById('lastSaved');
const notePreviewContent = document.getElementById('notePreviewContent');
// Mobile Editor Elements
const mobileNotesBtn = document.getElementById('mobileNotesBtn');
const mobilePreviewToggle = document.getElementById('mobilePreviewToggle');
const mobileNoteTitle = document.getElementById('mobileNoteTitle');
const mobileNoteContent = document.getElementById('mobileNoteContent');
const mobileSaveNoteBtn = document.getElementById('mobileSaveNoteBtn');
const mobileDeleteNoteBtn = document.getElementById('mobileDeleteNoteBtn');
const writeTab = document.getElementById('writeTab');
const previewTab = document.getElementById('previewTab');
const writePane = document.getElementById('writePane');
const mobilePreviewPane = document.getElementById('mobilePreviewPane');
const tabIndicator = document.getElementById('tabIndicator');
// Formatting Elements
const mdModal = document.getElementById('mdModal');
const mdModalTitle = document.getElementById('mdModalTitle');
const mdModalContent = document.getElementById('mdModalContent');
const cancelMdModal = document.getElementById('cancelMdModal');
const insertMdElement = document.getElementById('insertMdElement');
const closeMdModal = document.getElementById('closeMdModal');
const formatButtons = document.querySelectorAll('.format-btn');
// State
let notes = JSON.parse(localStorage.getItem('nexus-notes')) || [];
let currentNoteId = null;
let sortByDate = true;
let darkMode = localStorage.getItem('darkMode') === 'true';
let currentFormat = null;
let selectionStart = 0;
let selectionEnd = 0;
let isMobile = window.innerWidth < 768;
let previewVisible = false;
// Initialize
if (darkMode) {
document.documentElement.classList.add('dark');
}
renderNoteList();
updateSortButtons();
// Detect mobile/desktop on resize
window.addEventListener('resize', () => {
const newMobileState = window.innerWidth < 768;
if (newMobileState !== isMobile) {
isMobile = newMobileState;
if (currentNoteId) {
openNote(currentNoteId);
}
}
});
// Sample notes if empty (for demo purposes)
if (notes.length === 0) {
notes = [
{
id: '1',
title: 'Welcome to Nexus Notes',
content: '# Welcome to Nexus Notes\n\nNexus Notes is a **markdown** note taking app with **real-time preview**.\n\n## Features\n\n- 📝 **Rich Markdown Editing**\n- 👁️ **Live Preview**\n- 🎨 **Dark/Light Mode**\n- 🔍 **Search Notes**\n- 📱 **Mobile Friendly**\n\nTry creating a new note!',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
},
{
id: '2',
title: 'Markdown Cheatsheet',
content: '# Markdown Cheatsheet\n\n## Headers\n\n```\n# H1\n## H2\n### H3\n```\n\n## Text Styles\n\n```\n**bold**\n*italic*\n~~strikethrough~~\n```\n\n## Lists\n\n```\n- Unordered list\n - Sub item\n\n1. Ordered list\n 1. Sub item\n```',
createdAt: new Date(Date.now() - 86400000).toISOString(),
updatedAt: new Date(Date.now() - 86400000).toISOString()
}
];
saveNotes();
renderNoteList();
}
// Event Listeners
darkModeToggle.addEventListener('click', toggleDarkMode);
newNoteBtn.addEventListener('click', createNewNote);
emptyNewNoteBtn.addEventListener('click', createNewNote);
mobileNewNoteBtn.addEventListener('click', createNewNote);
searchNotes.addEventListener('input', filterNotes);
sortDateBtn.addEventListener('click', () => {
sortByDate = true;
renderNoteList();
updateSortButtons();
});
sortAlphaBtn.addEventListener('click', () => {
sortByDate = false;
renderNoteList();
updateSortButtons();
});
// Desktop Editor Events
saveNoteBtn.addEventListener('click', saveCurrentNote);
deleteNoteBtn.addEventListener('click', deleteCurrentNote);
// Mobile Editor Events
mobileSaveNoteBtn.addEventListener('click', saveCurrentNote);
mobileDeleteNoteBtn.addEventListener('click', deleteCurrentNote);
mobileNotesBtn.addEventListener('click', () => {
if (currentNoteId) {
// If already viewing a note, close it to show the list
closeCurrentNote();
} else {
// Show mobile sidebar (in a real app you'd implement this)
alert('Show mobile sidebar with notes list');
}
});
mobilePreviewToggle.addEventListener('click', togglePreviewPane);
writeTab.addEventListener('click', showWritePane);
previewTab.addEventListener('click', showPreviewPane);
// Format buttons
formatButtons.forEach(button => {
button.addEventListener('click', function() {
currentFormat = this.dataset.format;
showFormatModal();
});
});
// Markdown modal events
cancelMdModal.addEventListener('click', hideMdModal);
closeMdModal.addEventListener('click', hideMdModal);
insertMdElement.addEventListener('click', insertFormatting);
// Auto-save when typing
let saveTimeout;
function setupAutoSave(element) {
element.addEventListener('input', () => {
updateWordCount();
updatePreview();
clearTimeout(saveTimeout);
saveTimeout = setTimeout(saveCurrentNote, 2000);
});
}
setupAutoSave(noteContent);
setupAutoSave(mobileNoteContent);
// Save selection
noteContent.addEventListener('click', saveSelection);
noteContent.addEventListener('keyup', saveSelection);
mobileNoteContent.addEventListener('click', saveSelection);
mobileNoteContent.addEventListener('keyup', saveSelection);
// Functions
function toggleDarkMode() {
darkMode = !darkMode;
localStorage.setItem('darkMode', darkMode);
if (darkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
function updateSortButtons() {
if (sortByDate) {
sortDateBtn.classList.remove('text-gray-500', 'dark:text-gray-400');
sortDateBtn.classList.add('text-primary-500', 'dark:text-primary-400');
sortAlphaBtn.classList.remove('text-primary-500', 'dark:text-primary-400');
sortAlphaBtn.classList.add('text-gray-500', 'dark:text-gray-400');
} else {
sortAlphaBtn.classList.remove('text-gray-500', 'dark:text-gray-400');
sortAlphaBtn.classList.add('text-primary-500', 'dark:text-primary-400');
sortDateBtn.classList.remove('text-primary-500', 'dark:text-primary-400');
sortDateBtn.classList.add('text-gray-500', 'dark:text-gray-400');
}
}
function createNewNote() {
const newNote = {
id: Date.now().toString(),
title: 'Untitled Note',
content: '',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
notes.unshift(newNote);
saveNotes();
renderNoteList();
// Open the new note
openNote(newNote.id);
// Focus on title field
setTimeout(() => {
if (isMobile) {
mobileNoteTitle.focus();
mobileNoteTitle.select();
} else {
noteTitle.focus();
noteTitle.select();
}
}, 100);
}
function saveNotes() {
localStorage.setItem('nexus-notes', JSON.stringify(notes));
}
function renderNoteList() {
let filteredNotes = getFilteredNotes();
if (filteredNotes.length === 0) {
noteList.innerHTML = `
<div class="p-4 text-center text-gray-500 dark:text-gray-400">
${searchNotes.value ? 'No notes found matching your search.' : 'No notes yet. Create your first note!'}
</div>
`;
return;
}
noteList.innerHTML = filteredNotes.map(note => createNoteCardHTML(note)).join('');
}
function getFilteredNotes() {
let filteredNotes = [...notes];
// Filter by search
const searchTerm = searchNotes.value.toLowerCase();
if (searchTerm) {
filteredNotes = filteredNotes.filter(note =>
note.title.toLowerCase().includes(searchTerm) ||
note.content.toLowerCase().includes(searchTerm)
);
}
// Sort
if (sortByDate) {
filteredNotes.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
} else {
filteredNotes.sort((a, b) => a.title.localeCompare(b.title));
}
return filteredNotes;
}
function createNoteCardHTML(note) {
const isActive = currentNoteId === note.id;
return `
<div class="note-card cursor-pointer rounded-lg transition ${isActive ? 'bg-primary-50 dark:bg-gray-700 border border-primary-200 dark:border-gray-600' : 'bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 border border-gray-100 dark:border-gray-700'}"
data-id="${note.id}" onclick="openNote('${note.id}')">
<div class="p-3">
<h3 class="font-medium truncate ${isActive ? 'text-primary-600 dark:text-primary-400' : 'text-gray-800 dark:text-gray-200'}">${note.title || 'Untitled Note'}</h3>
<p class="text-sm ${isActive ? 'text-primary-500 dark:text-primary-300' : 'text-gray-500 dark:text-gray-400'} truncate mt-1">${note.content.substring(0, 100)}${note.content.length > 100 ? '...' : ''}</p>
<div class="flex justify-between items-center mt-2 text-xs ${isActive ? 'text-primary-400 dark:text-primary-300' : 'text-gray-400 dark:text-gray-500'}">
<span>${formatDate(note.updatedAt)}</span>
<span>${countWords(note.content)} words</span>
</div>
</div>
</div>
`;
}
function openNote(id) {
const note = notes.find(n => n.id === id);
if (!note) return;
currentNoteId = id;
if (isMobile) {
// Mobile view - show editor in single pane mode
emptyState.classList.add('hidden');
mobileNoteEditor.classList.remove('hidden');
editorPane.classList.add('hidden');
previewPane.classList.add('hidden');
mobileNoteTitle.value = note.title;
mobileNoteContent.value = note.content;
updateWordCount();
updatePreview();
// Show write pane by default on mobile
showWritePane();
} else {
// Desktop view - show split pane editor/preview
emptyState.classList.add('hidden');
editorPane.classList.remove('hidden');
previewPane.classList.remove('hidden');
mobileNoteEditor.classList.add('hidden');
noteTitle.value = note.title;
noteContent.value = note.content;
updateWordCount();
updatePreview();
lastSaved.textContent = `Last saved: ${formatDate(note.updatedAt)}`;
}
// Highlight active note in list
highlightActiveNote();
}
function closeCurrentNote() {
currentNoteId = null;
if (isMobile) {
mobileNoteEditor.classList.add('hidden');
emptyState.classList.remove('hidden');
} else {
editorPane.classList.add('hidden');
previewPane.classList.add('hidden');
emptyState.classList.remove('hidden');
}
highlightActiveNote();
}
function highlightActiveNote() {
document.querySelectorAll('.note-card').forEach(card => {
if (card.dataset.id === currentNoteId) {
card.classList.add('bg-primary-50', 'dark:bg-gray-700', 'border-primary-200', 'dark:border-gray-600');
card.classList.remove('hover:bg-gray-50', 'dark:hover:bg-gray-700', 'border-gray-100', 'dark:border-gray-700');
card.querySelector('h3').classList.add('text-primary-600', 'dark:text-primary-400');
card.querySelector('p').classList.add('text-primary-500', 'dark:text-primary-300');
card.querySelector('div').classList.add('text-primary-400', 'dark:text-primary-300');
} else {
card.classList.remove('bg-primary-50', 'dark:bg-gray-700', 'border-primary-200', 'dark:border-gray-600');
card.classList.add('hover:bg-gray-50', 'dark:hover:bg-gray-700', 'border-gray-100', 'dark:border-gray-700');
card.querySelector('h3').classList.remove('text-primary-600', 'dark:text-primary-400');
card.querySelector('p').classList.remove('text-primary-500', 'dark:text-primary-300');
card.querySelector('div').classList.remove('text-primary-400', 'dark:text-primary-300');
}
});
}
function saveCurrentNote() {
if (!currentNoteId) return;
const noteIndex = notes.findIndex(n => n.id === currentNoteId);
if (noteIndex === -1) return;
// Get values from the appropriate editor based on view
const title = isMobile ? mobileNoteTitle.value : noteTitle.value;
const content = isMobile ? mobileNoteContent.value : noteContent.value;
notes[noteIndex] = {
...notes[noteIndex],
title: title,
content: content,
updatedAt: new Date().toISOString()
};
saveNotes();
renderNoteList();
if (isMobile) {
// Update mobile view
} else {
lastSaved.textContent = `Last saved: ${formatDate(new Date().toISOString())}`;
}
// Update preview
updatePreview();
}
function deleteCurrentNote() {
if (!currentNoteId) return;
if (confirm('Are you sure you want to delete this note?')) {
notes = notes.filter(note => note.id !== currentNoteId);
saveNotes();
renderNoteList();
closeCurrentNote();
}
}
function filterNotes() {
renderNoteList();
}
function updateWordCount() {
const text = isMobile ? mobileNoteContent.value : noteContent.value;
wordCount.querySelector('span').textContent = `${countWords(text)} words`;
}
function countWords(text) {
return text.trim() === '' ? 0 : text.trim().split(/\s+/).length;
}
function formatDate(dateString) {
const date = new Date(dateString);
const now = new Date();
if (date.toDateString() === now.toDateString()) {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
if (date.getFullYear() === now.getFullYear()) {
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
}
return date.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' });
}
function showWritePane() {
if (isMobile) {
writePane.classList.remove('hidden');
mobilePreviewPane.classList.add('hidden');
writeTab.classList.add('text-primary-500', 'dark:text-primary-400');
writeTab.classList.remove('text-gray-500', 'dark:text-gray-400');
previewTab.classList.remove('text-primary-500', 'dark:text-primary-400');
previewTab.classList.add('text-gray-500', 'dark:text-gray-400');
tabIndicator.style.transform = 'translateX(0)';
}
}
function showPreviewPane() {
if (isMobile) {
writePane.classList.add('hidden');
mobilePreviewPane.classList.remove('hidden');
previewTab.classList.add('text-primary-500', 'dark:text-primary-400');
previewTab.classList.remove('text-gray-500', 'dark:text-gray-400');
writeTab.classList.remove('text-primary-500', 'dark:text-primary-400');
writeTab.classList.add('text-gray-500', 'dark:text-gray-400');
tabIndicator.style.transform = 'translateX(100%)';
updatePreview();
}
}
function togglePreviewPane() {
previewVisible = !previewVisible;
if (previewVisible) {
previewPane.classList.remove('hidden');
mobilePreviewToggle.classList.add('text-primary-500', 'dark:text-primary-400');
} else {
previewPane.classList.add('hidden');
mobilePreviewToggle.classList.remove('text-primary-500', 'dark:text-primary-400');
}
}
function renderMarkdown(text) {
// Simple markdown rendering
return text
.replace(/^# (.*$)/gm, '<h1>$1</h1>')
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
.replace(/^### (.*$)/gm, '<h3>$1</h3>')
.replace(/^#### (.*$)/gm, '<h4>$1</h4>')
.replace(/^##### (.*$)/gm, '<h5>$1</h5>')
.replace(/^###### (.*$)/gm, '<h6>$1</h6>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/~~(.*?)~~/g, '<del>$1</del>')
.replace(/`{3}([^`]+)`{3}/g, '<pre><code>$1</code></pre>')
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/!\[(.*?)\]\((.*?)\)/g, '<img alt="$1" src="$2">')
.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>')
.replace(/^>\s(.*$)/gm, '<blockquote>$1</blockquote>')
.replace(/^\s*\n\*/gm, '<ul>\n*')
.replace(/^(\*.+)\s*\n([^\*])/gm, '$1\n</ul>\n\n$2')
.replace(/^\*(.+)/gm, '<li>$1</li>')
.replace(/^\s*\n\d+\./gm, '<ol>\n1.')
.replace(/^(\d\..+)\s*\n([^\d\.])/gm, '$1\n</ol>\n\n$2')
.replace(/^\d\.(.+)/gm, '<li>$1</li>')
.replace(/\n/g, '<br>');
}
function updatePreview() {
const content = isMobile ? mobileNoteContent.value : noteContent.value;
if (isMobile && !mobilePreviewPane.classList.contains('hidden')) {
mobilePreviewPane.innerHTML = renderMarkdown(content);
}
if (!isMobile) {
notePreviewContent.innerHTML = renderMarkdown(content);
}
}
function showFormatModal() {
// Save current selection
saveSelection();
mdModalTitle.textContent = getModalTitle();
mdModalContent.innerHTML = getModalContent();
mdModal.classList.remove('hidden');
// Focus on first input if exists
const firstInput = mdModalContent.querySelector('input');
if (firstInput) {
firstInput.focus();
}
}
function hideMdModal() {
mdModal.classList.add('hidden');
}
function getModalTitle() {
switch(currentFormat) {
case 'bold': return 'Insert Bold Text';
case 'italic': return 'Insert Italic Text';
case 'header1': return 'Insert Heading 1';
case 'header2': return 'Insert Heading 2';
case 'header3': return 'Insert Heading 3';
case 'quote': return 'Insert Blockquote';
case 'code': return 'Insert Code';
case 'link': return 'Insert Link';
case 'image': return 'Insert Image';
case 'list': return 'Insert List';
case 'olist': return 'Insert Ordered List';
default: return 'Insert Element';
}
}
function getModalContent() {
switch(currentFormat) {
case 'bold':
case 'italic':
case 'header1':
case 'header2':
case 'header3':
case 'quote':
return '<input type="text" id="mdInputText" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Enter text" value="' + getSelectedText() + '">';
case 'code':
return `
<div class="space-y-2">
<input type="text" id="mdInputCode" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Enter code">
<div class="flex items-center">
<input type="checkbox" id="mdInlineCode" class="mr-2">
<label for="mdInlineCode" class="text-sm text-gray-700 dark:text-gray-300">Inline code</label>
</div>
</div>
`;
case 'link':
return `
<div class="space-y-2">
<input type="text" id="mdLinkText" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Link text" value="${getSelectedText()}">
<input type="text" id="mdLinkUrl" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="URL (https://)">
</div>
`;
case 'image':
return `
<div class="space-y-2">
<input type="text" id="mdImageAlt" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Alternative text">
<input type="text" id="mdImageUrl" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Image URL (https://)">
</div>
`;
case 'list':
case 'olist':
return `
<div class="space-y-2">
<textarea id="mdListItems" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" rows="4" placeholder="Enter list items, one per line"></textarea>
</div>
`;
default:
return '<p>Select a formatting option</p>';
}
}
function insertFormatting() {
const activeTextarea = isMobile ? mobileNoteContent : noteContent;
let content = activeTextarea.value;
let textBefore = content.substring(0, selectionStart);
let textAfter = content.substring(selectionEnd);
let selectedText = content.substring(selectionStart, selectionEnd);
let insertText = '';
switch(currentFormat) {
case 'bold':
const boldText = document.getElementById('mdInputText').value;
insertText = `**${boldText}**`;
break;
case 'italic':
const italicText = document.getElementById('mdInputText').value;
insertText = `*${italicText}*`;
break;
case 'header1':
const header1Text = document.getElementById('mdInputText').value;
insertText = `# ${header1Text}`;
break;
case 'header2':
const header2Text = document.getElementById('mdInputText').value;
insertText = `## ${header2Text}`;
break;
case 'header3':
const header3Text = document.getElementById('mdInputText').value;
insertText = `### ${header3Text}`;
break;
case 'quote':
const quoteText = document.getElementById('mdInputText').value;
insertText = `> ${quoteText}`;
break;
case 'code':
const codeText = document.getElementById('mdInputCode').value;
const isInline = document.getElementById('mdInlineCode').checked;
if (isInline) {
insertText = `\`${codeText}\``;
} else {
insertText = `\`\`\`\n${codeText}\n\`\`\``;
}
break;
case 'link':
const linkText = document.getElementById('mdLinkText').value;
const linkUrl = document.getElementById('mdLinkUrl').value;
insertText = `[${linkText}](${linkUrl})`;
break;
case 'image':
const altText = document.getElementById('mdImageAlt').value;
const imageUrl = document.getElementById('mdImageUrl').value;
insertText = `![${altText}](${imageUrl})`;
break;
case 'list':
const listItems = document.getElementById('mdListItems').value.split('\n');
insertText = listItems.filter(item => item.trim()).map(item => `- ${item}`).join('\n');
break;
case 'olist':
const olistItems = document.getElementById('mdListItems').value.split('\n');
insertText = olistItems.filter((item, index) => item.trim()).map((item, index) => `${index + 1}. ${item}`).join('\n');
break;
}
// Update content
activeTextarea.value = textBefore + insertText + textAfter;
// Set cursor position after inserted text
const newCursorPos = selectionStart + insertText.length;
activeTextarea.setSelectionRange(newCursorPos, newCursorPos);
activeTextarea.focus();
// Update preview and save
updatePreview();
// Auto-save after formatting
saveCurrentNote();
hideMdModal();
}
function saveSelection() {
const activeTextarea = isMobile ? mobileNoteContent : noteContent;
selectionStart = activeTextarea.selectionStart;
selectionEnd = activeTextarea.selectionEnd;
}
function getSelectedText() {
const activeTextarea = isMobile ? mobileNoteContent : noteContent;
return activeTextarea.value.substring(selectionStart, selectionEnd);
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=yasarefe/nexus-notes" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>