|
<!DOCTYPE html> |
|
<html lang="en" class="dark"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Notion Lite - Local 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', |
|
}, |
|
dark: { |
|
800: '#1e293b', |
|
900: '#0f172a', |
|
} |
|
}, |
|
animation: { |
|
'fade-in': 'fadeIn 0.3s ease-in-out', |
|
'slide-in': 'slideIn 0.2s ease-out', |
|
}, |
|
keyframes: { |
|
fadeIn: { |
|
'0%': { opacity: '0' }, |
|
'100%': { opacity: '1' }, |
|
}, |
|
slideIn: { |
|
'0%': { transform: 'translateY(-10px)', opacity: '0' }, |
|
'100%': { transform: 'translateY(0)', opacity: '1' }, |
|
} |
|
} |
|
} |
|
} |
|
} |
|
</script> |
|
<style> |
|
.prose { |
|
max-width: 100%; |
|
} |
|
.prose :where(h1):not(:where([class~="not-prose"] *)) { |
|
font-size: 2rem; |
|
margin-top: 0; |
|
margin-bottom: 1rem; |
|
font-weight: 700; |
|
line-height: 1.2; |
|
} |
|
.prose :where(h2):not(:where([class~="not-prose"] *)) { |
|
font-size: 1.5rem; |
|
margin-top: 1.5rem; |
|
margin-bottom: 1rem; |
|
font-weight: 600; |
|
line-height: 1.3; |
|
} |
|
.prose :where(h3):not(:where([class~="not-prose"] *)) { |
|
font-size: 1.25rem; |
|
margin-top: 1.25rem; |
|
margin-bottom: 0.75rem; |
|
font-weight: 600; |
|
line-height: 1.4; |
|
} |
|
.prose :where(p):not(:where([class~="not-prose"] *)) { |
|
margin-top: 1rem; |
|
margin-bottom: 1rem; |
|
} |
|
.prose :where(ul):not(:where([class~="not-prose"] *)) { |
|
list-style-type: disc; |
|
padding-left: 1.5rem; |
|
margin-top: 1rem; |
|
margin-bottom: 1rem; |
|
} |
|
.prose :where(ol):not(:where([class~="not-prose"] *)) { |
|
list-style-type: decimal; |
|
padding-left: 1.5rem; |
|
margin-top: 1rem; |
|
margin-bottom: 1rem; |
|
} |
|
.prose :where(blockquote):not(:where([class~="not-prose"] *)) { |
|
border-left: 4px solid #e5e7eb; |
|
padding-left: 1rem; |
|
margin-left: 0; |
|
margin-top: 1rem; |
|
margin-bottom: 1rem; |
|
color: #6b7280; |
|
} |
|
.prose :where(code):not(:where([class~="not-prose"] *)) { |
|
background-color: rgba(55, 65, 81, 0.1); |
|
border-radius: 0.25rem; |
|
padding: 0.2rem 0.4rem; |
|
font-family: monospace; |
|
} |
|
.prose :where(pre):not(:where([class~="not-prose"] *)) { |
|
background-color: rgba(55, 65, 81, 0.1); |
|
border-radius: 0.5rem; |
|
padding: 1rem; |
|
overflow-x: auto; |
|
margin-top: 1rem; |
|
margin-bottom: 1rem; |
|
} |
|
.prose :where(a):not(:where([class~="not-prose"] *)) { |
|
color: #3b82f6; |
|
text-decoration: underline; |
|
} |
|
.dark .prose :where(code):not(:where([class~="not-prose"] *)) { |
|
background-color: rgba(209, 213, 219, 0.1); |
|
} |
|
.dark .prose :where(pre):not(:where([class~="not-prose"] *)) { |
|
background-color: rgba(209, 213, 219, 0.1); |
|
} |
|
.dark .prose :where(blockquote):not(:where([class~="not-prose"] *)) { |
|
border-left-color: #374151; |
|
color: #9ca3af; |
|
} |
|
.editor-content { |
|
min-height: calc(100vh - 200px); |
|
} |
|
.sidebar { |
|
transition: transform 0.2s ease-in-out; |
|
} |
|
@media (max-width: 768px) { |
|
.sidebar { |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
bottom: 0; |
|
transform: translateX(-100%); |
|
z-index: 40; |
|
} |
|
.sidebar-open { |
|
transform: translateX(0); |
|
} |
|
} |
|
.table-wrapper { |
|
overflow-x: auto; |
|
} |
|
table { |
|
border-collapse: collapse; |
|
width: 100%; |
|
} |
|
th, td { |
|
border: 1px solid #e5e7eb; |
|
padding: 0.5rem; |
|
text-align: left; |
|
} |
|
.dark th, .dark td { |
|
border-color: #374151; |
|
} |
|
.to-do-item { |
|
display: flex; |
|
align-items: flex-start; |
|
gap: 0.5rem; |
|
margin-bottom: 0.5rem; |
|
} |
|
.to-do-checkbox { |
|
margin-top: 0.25rem; |
|
flex-shrink: 0; |
|
} |
|
.to-do-content { |
|
flex-grow: 1; |
|
} |
|
.to-do-checked { |
|
opacity: 0.6; |
|
text-decoration: line-through; |
|
} |
|
.image-upload-wrapper { |
|
position: relative; |
|
margin: 1rem 0; |
|
} |
|
.image-upload { |
|
width: 100%; |
|
max-width: 100%; |
|
border-radius: 0.5rem; |
|
display: block; |
|
} |
|
.image-upload-actions { |
|
position: absolute; |
|
top: 0.5rem; |
|
right: 0.5rem; |
|
display: flex; |
|
gap: 0.5rem; |
|
opacity: 0; |
|
transition: opacity 0.2s; |
|
} |
|
.image-upload-wrapper:hover .image-upload-actions { |
|
opacity: 1; |
|
} |
|
.tooltip { |
|
position: relative; |
|
} |
|
.tooltip-text { |
|
visibility: hidden; |
|
width: 120px; |
|
background-color: #1e293b; |
|
color: #fff; |
|
text-align: center; |
|
border-radius: 6px; |
|
padding: 5px; |
|
position: absolute; |
|
z-index: 1; |
|
bottom: 125%; |
|
left: 50%; |
|
transform: translateX(-50%); |
|
opacity: 0; |
|
transition: opacity 0.3s; |
|
} |
|
.tooltip:hover .tooltip-text { |
|
visibility: visible; |
|
opacity: 1; |
|
} |
|
.dark .tooltip-text { |
|
background-color: #f8fafc; |
|
color: #0f172a; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-50 dark:bg-dark-900 text-gray-800 dark:text-gray-200 transition-colors duration-200"> |
|
<div class="flex h-screen overflow-hidden"> |
|
|
|
<div class="sidebar w-64 bg-white dark:bg-dark-800 border-r border-gray-200 dark:border-gray-700 flex flex-col"> |
|
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between"> |
|
<div class="flex items-center gap-2"> |
|
<div class="w-8 h-8 rounded-md bg-primary-500 flex items-center justify-center text-white"> |
|
<i class="fas fa-book"></i> |
|
</div> |
|
<h1 class="font-bold text-lg">Notion Lite</h1> |
|
</div> |
|
<button id="sidebar-close" class="md:hidden p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<div class="flex-1 overflow-y-auto p-2"> |
|
<div class="mb-4"> |
|
<div class="flex items-center justify-between px-2 py-1"> |
|
<span class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase">Workspace</span> |
|
<button id="add-page" class="text-gray-500 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400"> |
|
<i class="fas fa-plus"></i> |
|
</button> |
|
</div> |
|
<div id="page-list" class="mt-1 space-y-1"> |
|
|
|
</div> |
|
</div> |
|
<div class="mb-4"> |
|
<div class="flex items-center justify-between px-2 py-1"> |
|
<span class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase">Quick Links</span> |
|
</div> |
|
<div class="mt-1 space-y-1"> |
|
<a href="#" class="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"> |
|
<i class="fas fa-inbox text-gray-500 dark:text-gray-400"></i> |
|
<span>Inbox</span> |
|
</a> |
|
<a href="#" class="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"> |
|
<i class="fas fa-star text-gray-500 dark:text-gray-400"></i> |
|
<span>Favorites</span> |
|
</a> |
|
<a href="#" class="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"> |
|
<i class="fas fa-trash text-gray-500 dark:text-gray-400"></i> |
|
<span>Trash</span> |
|
</a> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="p-3 border-t border-gray-200 dark:border-gray-700"> |
|
<div class="flex items-center gap-3"> |
|
<div class="w-8 h-8 rounded-full bg-primary-100 dark:bg-primary-900 flex items-center justify-center text-primary-600 dark:text-primary-300"> |
|
<i class="fas fa-user"></i> |
|
</div> |
|
<div class="flex-1"> |
|
<div class="text-sm font-medium">Guest User</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">Free Plan</div> |
|
</div> |
|
<button id="theme-toggle" class="p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-moon dark:hidden"></i> |
|
<i class="fas fa-sun hidden dark:block"></i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="flex-1 flex flex-col overflow-hidden"> |
|
|
|
<div class="bg-white dark:bg-dark-800 border-b border-gray-200 dark:border-gray-700 p-3 flex items-center justify-between"> |
|
<div class="flex items-center gap-2"> |
|
<button id="sidebar-toggle" class="md:hidden p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-bars"></i> |
|
</button> |
|
<div id="breadcrumbs" class="flex items-center gap-1 text-sm"> |
|
|
|
</div> |
|
</div> |
|
<div class="flex items-center gap-2"> |
|
<button id="search-button" class="p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-search"></i> |
|
</button> |
|
<button id="share-button" class="p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-share-alt"></i> |
|
</button> |
|
<button id="more-options" class="p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-ellipsis-h"></i> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="flex-1 overflow-auto p-4 md:p-6"> |
|
<div id="editor" class="max-w-4xl mx-auto prose dark:prose-invert"> |
|
<div id="editor-content" class="editor-content" contenteditable="true"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="fixed bottom-6 right-6 flex flex-col gap-3"> |
|
<div class="tooltip"> |
|
<button id="add-block" class="w-12 h-12 rounded-full bg-primary-500 text-white flex items-center justify-center shadow-lg hover:bg-primary-600 transition-colors"> |
|
<i class="fas fa-plus"></i> |
|
</button> |
|
<span class="tooltip-text">Add block</span> |
|
</div> |
|
<div class="tooltip"> |
|
<button id="save-button" class="w-12 h-12 rounded-full bg-green-500 text-white flex items-center justify-center shadow-lg hover:bg-green-600 transition-colors"> |
|
<i class="fas fa-save"></i> |
|
</button> |
|
<span class="tooltip-text">Save changes</span> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="block-menu" class="hidden fixed z-50 bg-white dark:bg-dark-800 rounded-lg shadow-xl w-64 border border-gray-200 dark:border-gray-700 overflow-hidden"> |
|
<div class="p-2 border-b border-gray-200 dark:border-gray-700"> |
|
<div class="relative"> |
|
<input type="text" placeholder="Search blocks..." class="w-full px-3 py-2 bg-gray-50 dark:bg-gray-700 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"> |
|
<i class="fas fa-search absolute right-3 top-2.5 text-gray-400"></i> |
|
</div> |
|
</div> |
|
<div class="overflow-y-auto max-h-96"> |
|
<div class="p-1"> |
|
<div class="text-xs font-semibold text-gray-500 dark:text-gray-400 px-2 py-1">Basic Blocks</div> |
|
<button class="block-menu-item" data-type="text"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-align-left text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Text</span> |
|
</button> |
|
<button class="block-menu-item" data-type="heading1"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-heading text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Heading 1</span> |
|
</button> |
|
<button class="block-menu-item" data-type="heading2"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-heading text-gray-500 dark:text-gray-400" style="font-size: 0.8em;"></i> |
|
</div> |
|
<span>Heading 2</span> |
|
</button> |
|
<button class="block-menu-item" data-type="heading3"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-heading text-gray-500 dark:text-gray-400" style="font-size: 0.6em;"></i> |
|
</div> |
|
<span>Heading 3</span> |
|
</button> |
|
<button class="block-menu-item" data-type="bullet-list"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-list-ul text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Bulleted list</span> |
|
</button> |
|
<button class="block-menu-item" data-type="number-list"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-list-ol text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Numbered list</span> |
|
</button> |
|
<button class="block-menu-item" data-type="to-do"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-check-square text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>To-do list</span> |
|
</button> |
|
<button class="block-menu-item" data-type="quote"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-quote-right text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Quote</span> |
|
</button> |
|
<button class="block-menu-item" data-type="divider"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-minus text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Divider</span> |
|
</button> |
|
</div> |
|
<div class="p-1"> |
|
<div class="text-xs font-semibold text-gray-500 dark:text-gray-400 px-2 py-1">Media</div> |
|
<button class="block-menu-item" data-type="image"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-image text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Image</span> |
|
</button> |
|
<button class="block-menu-item" data-type="video"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-video text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Video</span> |
|
</button> |
|
<button class="block-menu-item" data-type="file"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-file text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>File</span> |
|
</button> |
|
</div> |
|
<div class="p-1"> |
|
<div class="text-xs font-semibold text-gray-500 dark:text-gray-400 px-2 py-1">Advanced</div> |
|
<button class="block-menu-item" data-type="code"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-code text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Code</span> |
|
</button> |
|
<button class="block-menu-item" data-type="table"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-table text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Table</span> |
|
</button> |
|
<button class="block-menu-item" data-type="page"> |
|
<div class="w-6 h-6 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-file-alt text-gray-500 dark:text-gray-400"></i> |
|
</div> |
|
<span>Page</span> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="formatting-toolbar" class="hidden fixed z-40 bg-white dark:bg-dark-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 p-1 flex items-center gap-1"> |
|
<button class="format-button" data-command="bold" title="Bold (Ctrl+B)"> |
|
<i class="fas fa-bold"></i> |
|
</button> |
|
<button class="format-button" data-command="italic" title="Italic (Ctrl+I)"> |
|
<i class="fas fa-italic"></i> |
|
</button> |
|
<button class="format-button" data-command="underline" title="Underline (Ctrl+U)"> |
|
<i class="fas fa-underline"></i> |
|
</button> |
|
<button class="format-button" data-command="strikeThrough" title="Strikethrough (Ctrl+Shift+X)"> |
|
<i class="fas fa-strikethrough"></i> |
|
</button> |
|
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div> |
|
<button class="format-button" data-command="justifyLeft" title="Align left"> |
|
<i class="fas fa-align-left"></i> |
|
</button> |
|
<button class="format-button" data-command="justifyCenter" title="Align center"> |
|
<i class="fas fa-align-center"></i> |
|
</button> |
|
<button class="format-button" data-command="justifyRight" title="Align right"> |
|
<i class="fas fa-align-right"></i> |
|
</button> |
|
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div> |
|
<button class="format-button" data-command="insertUnorderedList" title="Bullet list"> |
|
<i class="fas fa-list-ul"></i> |
|
</button> |
|
<button class="format-button" data-command="insertOrderedList" title="Numbered list"> |
|
<i class="fas fa-list-ol"></i> |
|
</button> |
|
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div> |
|
<button class="format-button" data-command="createLink" title="Create link"> |
|
<i class="fas fa-link"></i> |
|
</button> |
|
<button class="format-button" data-command="unlink" title="Remove link"> |
|
<i class="fas fa-unlink"></i> |
|
</button> |
|
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div> |
|
<button class="format-button" data-command="undo" title="Undo (Ctrl+Z)"> |
|
<i class="fas fa-undo"></i> |
|
</button> |
|
<button class="format-button" data-command="redo" title="Redo (Ctrl+Y)"> |
|
<i class="fas fa-redo"></i> |
|
</button> |
|
</div> |
|
|
|
|
|
<div id="image-upload-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"> |
|
<div class="bg-white dark:bg-dark-800 rounded-lg shadow-xl w-full max-w-md p-4"> |
|
<div class="flex items-center justify-between mb-4"> |
|
<h3 class="text-lg font-medium">Upload Image</h3> |
|
<button id="close-image-modal" class="p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<div class="mb-4"> |
|
<div class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-4 text-center"> |
|
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-2"></i> |
|
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Drag & drop your image here or click to browse</p> |
|
<input type="file" id="image-input" accept="image/*" class="hidden"> |
|
<button id="browse-button" class="px-4 py-2 bg-primary-500 text-white rounded-md hover:bg-primary-600"> |
|
Browse Files |
|
</button> |
|
</div> |
|
</div> |
|
<div class="mb-4"> |
|
<label for="image-url" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Or enter image URL</label> |
|
<input type="text" id="image-url" placeholder="https://example.com/image.jpg" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-700"> |
|
</div> |
|
<div class="flex justify-end gap-2"> |
|
<button id="cancel-image-upload" class="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md"> |
|
Cancel |
|
</button> |
|
<button id="confirm-image-upload" class="px-4 py-2 bg-primary-500 text-white rounded-md hover:bg-primary-600"> |
|
Insert Image |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="add-page-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"> |
|
<div class="bg-white dark:bg-dark-800 rounded-lg shadow-xl w-full max-w-md p-4"> |
|
<div class="flex items-center justify-between mb-4"> |
|
<h3 class="text-lg font-medium">Create New Page</h3> |
|
<button id="close-page-modal" class="p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<div class="mb-4"> |
|
<label for="page-title" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Page Title</label> |
|
<input type="text" id="page-title" placeholder="Untitled" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-700"> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Parent Page</label> |
|
<select id="parent-page" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-700"> |
|
<option value="">No parent (top level)</option> |
|
|
|
</select> |
|
</div> |
|
<div class="flex justify-end gap-2"> |
|
<button id="cancel-page-add" class="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md"> |
|
Cancel |
|
</button> |
|
<button id="confirm-page-add" class="px-4 py-2 bg-primary-500 text-white rounded-md hover:bg-primary-600"> |
|
Create Page |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const state = { |
|
currentPageId: null, |
|
pages: [], |
|
showFormattingToolbar: false, |
|
toolbarPosition: { x: 0, y: 0 }, |
|
blockMenuPosition: { x: 0, y: 0 }, |
|
lastSelection: null |
|
}; |
|
|
|
|
|
const elements = { |
|
editorContent: document.getElementById('editor-content'), |
|
pageList: document.getElementById('page-list'), |
|
breadcrumbs: document.getElementById('breadcrumbs'), |
|
sidebar: document.querySelector('.sidebar'), |
|
sidebarToggle: document.getElementById('sidebar-toggle'), |
|
sidebarClose: document.getElementById('sidebar-close'), |
|
themeToggle: document.getElementById('theme-toggle'), |
|
addBlock: document.getElementById('add-block'), |
|
blockMenu: document.getElementById('block-menu'), |
|
formattingToolbar: document.getElementById('formatting-toolbar'), |
|
formatButtons: document.querySelectorAll('.format-button'), |
|
blockMenuItems: document.querySelectorAll('.block-menu-item'), |
|
imageUploadModal: document.getElementById('image-upload-modal'), |
|
imageInput: document.getElementById('image-input'), |
|
browseButton: document.getElementById('browse-button'), |
|
imageUrl: document.getElementById('image-url'), |
|
confirmImageUpload: document.getElementById('confirm-image-upload'), |
|
cancelImageUpload: document.getElementById('cancel-image-upload'), |
|
closeImageModal: document.getElementById('close-image-modal'), |
|
addPageButton: document.getElementById('add-page'), |
|
addPageModal: document.getElementById('add-page-modal'), |
|
pageTitle: document.getElementById('page-title'), |
|
parentPage: document.getElementById('parent-page'), |
|
confirmPageAdd: document.getElementById('confirm-page-add'), |
|
cancelPageAdd: document.getElementById('cancel-page-add'), |
|
closePageModal: document.getElementById('close-page-modal'), |
|
saveButton: document.getElementById('save-button') |
|
}; |
|
|
|
|
|
function init() { |
|
loadPages(); |
|
setupEventListeners(); |
|
updateUI(); |
|
|
|
|
|
if (state.pages.length === 0) { |
|
createPage('Welcome to Notion Lite', ''); |
|
} else { |
|
loadPage(state.pages[0].id); |
|
} |
|
} |
|
|
|
|
|
function loadPages() { |
|
const savedPages = localStorage.getItem('notion-lite-pages'); |
|
if (savedPages) { |
|
state.pages = JSON.parse(savedPages); |
|
renderPageList(); |
|
} |
|
} |
|
|
|
|
|
function savePages() { |
|
localStorage.setItem('notion-lite-pages', JSON.stringify(state.pages)); |
|
} |
|
|
|
|
|
function createPage(title, parentId) { |
|
const newPage = { |
|
id: Date.now().toString(), |
|
title: title, |
|
parentId: parentId || null, |
|
content: '<h1>' + title + '</h1><p>Start writing here...</p>', |
|
createdAt: new Date().toISOString(), |
|
updatedAt: new Date().toISOString() |
|
}; |
|
|
|
state.pages.push(newPage); |
|
savePages(); |
|
renderPageList(); |
|
|
|
if (!parentId) { |
|
loadPage(newPage.id); |
|
} |
|
|
|
return newPage; |
|
} |
|
|
|
|
|
function loadPage(pageId) { |
|
const page = state.pages.find(p => p.id === pageId); |
|
if (!page) return; |
|
|
|
state.currentPageId = pageId; |
|
elements.editorContent.innerHTML = page.content; |
|
|
|
|
|
updateBreadcrumbs(pageId); |
|
|
|
|
|
updateUI(); |
|
} |
|
|
|
|
|
function saveCurrentPage() { |
|
if (!state.currentPageId) return; |
|
|
|
const pageIndex = state.pages.findIndex(p => p.id === state.currentPageId); |
|
if (pageIndex === -1) return; |
|
|
|
state.pages[pageIndex].content = elements.editorContent.innerHTML; |
|
state.pages[pageIndex].updatedAt = new Date().toISOString(); |
|
savePages(); |
|
|
|
|
|
const saveButton = elements.saveButton; |
|
saveButton.innerHTML = '<i class="fas fa-check"></i>'; |
|
saveButton.classList.remove('bg-green-500', 'hover:bg-green-600'); |
|
saveButton.classList.add('bg-green-600'); |
|
|
|
setTimeout(() => { |
|
saveButton.innerHTML = '<i class="fas fa-save"></i>'; |
|
saveButton.classList.remove('bg-green-600'); |
|
saveButton.classList.add('bg-green-500', 'hover:bg-green-600'); |
|
}, 2000); |
|
} |
|
|
|
|
|
function updateBreadcrumbs(pageId) { |
|
const page = state.pages.find(p => p.id === pageId); |
|
if (!page) return; |
|
|
|
let breadcrumbs = []; |
|
let currentPage = page; |
|
|
|
while (currentPage) { |
|
breadcrumbs.unshift(currentPage); |
|
currentPage = currentPage.parentId ? state.pages.find(p => p.id === currentPage.parentId) : null; |
|
} |
|
|
|
elements.breadcrumbs.innerHTML = ''; |
|
|
|
breadcrumbs.forEach((crumb, index) => { |
|
const link = document.createElement('a'); |
|
link.href = '#'; |
|
link.className = 'hover:text-primary-500 dark:hover:text-primary-400'; |
|
link.textContent = crumb.title; |
|
link.addEventListener('click', (e) => { |
|
e.preventDefault(); |
|
loadPage(crumb.id); |
|
}); |
|
|
|
elements.breadcrumbs.appendChild(link); |
|
|
|
if (index < breadcrumbs.length - 1) { |
|
const separator = document.createElement('span'); |
|
separator.className = 'text-gray-400'; |
|
separator.textContent = ' / '; |
|
elements.breadcrumbs.appendChild(separator); |
|
} |
|
}); |
|
} |
|
|
|
|
|
function renderPageList() { |
|
elements.pageList.innerHTML = ''; |
|
|
|
|
|
const topLevelPages = state.pages.filter(page => !page.parentId); |
|
|
|
topLevelPages.forEach(page => { |
|
renderPageListItem(page); |
|
}); |
|
} |
|
|
|
|
|
function renderPageListItem(page, depth = 0) { |
|
const item = document.createElement('div'); |
|
item.className = `group flex items-center justify-between rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 ${state.currentPageId === page.id ? 'bg-gray-100 dark:bg-gray-700' : ''}`; |
|
|
|
const leftSide = document.createElement('div'); |
|
leftSide.className = 'flex items-center flex-1 min-w-0'; |
|
leftSide.style.paddingLeft = `${depth * 12}px`; |
|
|
|
const link = document.createElement('a'); |
|
link.href = '#'; |
|
link.className = 'flex items-center gap-2 py-1.5 px-2 flex-1 min-w-0'; |
|
link.innerHTML = ` |
|
<i class="fas fa-file-alt text-gray-500 dark:text-gray-400"></i> |
|
<span class="truncate">${page.title}</span> |
|
`; |
|
link.addEventListener('click', (e) => { |
|
e.preventDefault(); |
|
loadPage(page.id); |
|
}); |
|
|
|
const rightSide = document.createElement('div'); |
|
rightSide.className = 'opacity-0 group-hover:opacity-100 pr-2'; |
|
|
|
const addButton = document.createElement('button'); |
|
addButton.className = 'p-1 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400'; |
|
addButton.innerHTML = '<i class="fas fa-plus text-xs"></i>'; |
|
addButton.addEventListener('click', (e) => { |
|
e.stopPropagation(); |
|
showAddPageModal(page.id); |
|
}); |
|
|
|
rightSide.appendChild(addButton); |
|
|
|
leftSide.appendChild(link); |
|
item.appendChild(leftSide); |
|
item.appendChild(rightSide); |
|
elements.pageList.appendChild(item); |
|
|
|
|
|
const childPages = state.pages.filter(p => p.parentId === page.id); |
|
childPages.forEach(child => { |
|
renderPageListItem(child, depth + 1); |
|
}); |
|
} |
|
|
|
|
|
function updateUI() { |
|
|
|
document.querySelectorAll('#page-list a').forEach(link => { |
|
link.parentElement.parentElement.classList.remove('bg-gray-100', 'dark:bg-gray-700'); |
|
}); |
|
|
|
const currentLink = document.querySelector(`#page-list a[href="#"]`); |
|
if (currentLink) { |
|
currentLink.parentElement.parentElement.classList.add('bg-gray-100', 'dark:bg-gray-700'); |
|
} |
|
} |
|
|
|
|
|
function showBlockMenu(x, y) { |
|
state.blockMenuPosition = { x, y }; |
|
elements.blockMenu.style.left = `${x}px`; |
|
elements.blockMenu.style.top = `${y}px`; |
|
elements.blockMenu.classList.remove('hidden'); |
|
|
|
|
|
const searchInput = elements.blockMenu.querySelector('input'); |
|
if (searchInput) { |
|
searchInput.focus(); |
|
} |
|
} |
|
|
|
|
|
function hideBlockMenu() { |
|
elements.blockMenu.classList.add('hidden'); |
|
} |
|
|
|
|
|
function showFormattingToolbar(x, y) { |
|
state.showFormattingToolbar = true; |
|
state.toolbarPosition = { x, y }; |
|
elements.formattingToolbar.style.left = `${x}px`; |
|
elements.formattingToolbar.style.top = `${y}px`; |
|
elements.formattingToolbar.classList.remove('hidden'); |
|
} |
|
|
|
|
|
function hideFormattingToolbar() { |
|
state.showFormattingToolbar = false; |
|
elements.formattingToolbar.classList.add('hidden'); |
|
} |
|
|
|
|
|
function showImageUploadModal() { |
|
elements.imageUploadModal.classList.remove('hidden'); |
|
} |
|
|
|
|
|
function hideImageUploadModal() { |
|
elements.imageUploadModal.classList.add('hidden'); |
|
elements.imageInput.value = ''; |
|
elements.imageUrl.value = ''; |
|
} |
|
|
|
|
|
function showAddPageModal(parentId = null) { |
|
elements.addPageModal.classList.remove('hidden'); |
|
elements.parentPage.value = parentId || ''; |
|
} |
|
|
|
|
|
function hideAddPageModal() { |
|
elements.addPageModal.classList.add('hidden'); |
|
elements.pageTitle.value = ''; |
|
elements.parentPage.value = ''; |
|
} |
|
|
|
|
|
function insertBlock(type) { |
|
const selection = window.getSelection(); |
|
if (!selection.rangeCount) return; |
|
|
|
const range = selection.getRangeAt(0); |
|
const block = document.createElement('div'); |
|
block.className = 'mb-4'; |
|
|
|
switch (type) { |
|
case 'heading1': |
|
block.innerHTML = '<h1>Heading 1</h1>'; |
|
break; |
|
case 'heading2': |
|
block.innerHTML = '<h2>Heading 2</h2>'; |
|
break; |
|
case 'heading3': |
|
block.innerHTML = '<h3>Heading 3</h3>'; |
|
break; |
|
case 'bullet-list': |
|
block.innerHTML = '<ul><li>List item</li></ul>'; |
|
break; |
|
case 'number-list': |
|
block.innerHTML = '<ol><li>List item</li></ol>'; |
|
break; |
|
case 'to-do': |
|
block.innerHTML = ` |
|
<div class="to-do-item"> |
|
<input type="checkbox" class="to-do-checkbox"> |
|
<div class="to-do-content" contenteditable="true">Todo item</div> |
|
</div> |
|
`; |
|
break; |
|
case 'quote': |
|
block.innerHTML = '<blockquote>Quote</blockquote>'; |
|
break; |
|
case 'divider': |
|
block.innerHTML = '<hr class="border-t border-gray-300 dark:border-gray-600 my-4">'; |
|
break; |
|
case 'image': |
|
block.innerHTML = ` |
|
<div class="image-upload-wrapper"> |
|
<img src="https://via.placeholder.com/600x400?text=Click+to+upload+image" alt="Placeholder image" class="image-upload"> |
|
<div class="image-upload-actions"> |
|
<button class="p-1 bg-white dark:bg-gray-700 rounded-md shadow hover:bg-gray-100 dark:hover:bg-gray-600"> |
|
<i class="fas fa-edit text-gray-700 dark:text-gray-300"></i> |
|
</button> |
|
<button class="p-1 bg-white dark:bg-gray-700 rounded-md shadow hover:bg-gray-100 dark:hover:bg-gray-600"> |
|
<i class="fas fa-trash text-gray-700 dark:text-gray-300"></i> |
|
</button> |
|
</div> |
|
</div> |
|
`; |
|
break; |
|
case 'code': |
|
block.innerHTML = '<pre><code>// Your code here</code></pre>'; |
|
break; |
|
case 'table': |
|
block.innerHTML = ` |
|
<div class="table-wrapper"> |
|
<table> |
|
<thead> |
|
<tr> |
|
<th>Header 1</th> |
|
<th>Header 2</th> |
|
<th>Header 3</th> |
|
</tr> |
|
</thead> |
|
<tbody> |
|
<tr> |
|
<td>Cell 1</td> |
|
<td>Cell 2</td> |
|
<td>Cell 3</td> |
|
</tr> |
|
<tr> |
|
<td>Cell 4</td> |
|
<td>Cell 5</td> |
|
<td>Cell 6</td> |
|
</tr> |
|
</tbody> |
|
</table> |
|
</div> |
|
`; |
|
break; |
|
default: |
|
block.innerHTML = '<p>Text block</p>'; |
|
} |
|
|
|
range.insertNode(block); |
|
|
|
|
|
const editableElement = block.querySelector('[contenteditable="true"]') || block; |
|
const newRange = document.createRange(); |
|
newRange.selectNodeContents(editableElement); |
|
newRange.collapse(false); |
|
selection.removeAllRanges(); |
|
selection.addRange(newRange); |
|
editableElement.focus(); |
|
|
|
hideBlockMenu(); |
|
} |
|
|
|
|
|
function executeFormatCommand(command, value = null) { |
|
document.execCommand(command, false, value); |
|
elements.editorContent.focus(); |
|
} |
|
|
|
|
|
function setupEventListeners() { |
|
|
|
elements.editorContent.addEventListener('input', () => { |
|
|
|
clearTimeout(window.saveTimeout); |
|
window.saveTimeout = setTimeout(saveCurrentPage, 1000); |
|
}); |
|
|
|
elements.editorContent.addEventListener('click', () => { |
|
elements.editorContent.focus(); |
|
}); |
|
|
|
elements.editorContent.addEventListener('keydown', (e) => { |
|
|
|
if (e.ctrlKey && e.key === 's') { |
|
e.preventDefault(); |
|
saveCurrentPage(); |
|
} |
|
|
|
|
|
if (e.key === '/' && elements.editorContent === document.activeElement) { |
|
e.preventDefault(); |
|
const range = window.getSelection().getRangeAt(0); |
|
const rect = range.getBoundingClientRect(); |
|
showBlockMenu(rect.left, rect.bottom); |
|
} |
|
}); |
|
|
|
elements.editorContent.addEventListener('mouseup', (e) => { |
|
const selection = window.getSelection(); |
|
if (selection.toString().length > 0) { |
|
const range = selection.getRangeAt(0); |
|
const rect = range.getBoundingClientRect(); |
|
showFormattingToolbar(rect.left, rect.top - 40); |
|
state.lastSelection = selection; |
|
} else { |
|
hideFormattingToolbar(); |
|
} |
|
}); |
|
|
|
|
|
elements.sidebarToggle.addEventListener('click', () => { |
|
elements.sidebar.classList.add('sidebar-open'); |
|
}); |
|
|
|
elements.sidebarClose.addEventListener('click', () => { |
|
elements.sidebar.classList.remove('sidebar-open'); |
|
}); |
|
|
|
|
|
elements.themeToggle.addEventListener('click', () => { |
|
document.documentElement.classList.toggle('dark'); |
|
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark')); |
|
}); |
|
|
|
|
|
elements.addBlock.addEventListener('click', () => { |
|
const rect = elements.addBlock.getBoundingClientRect(); |
|
showBlockMenu(rect.left - 200, rect.top - 10); |
|
}); |
|
|
|
|
|
elements.blockMenuItems.forEach(item => { |
|
item.addEventListener('click', (e) => { |
|
e.preventDefault(); |
|
const type = item.dataset.type; |
|
insertBlock(type); |
|
}); |
|
}); |
|
|
|
|
|
elements.formatButtons.forEach(button => { |
|
button.addEventListener('click', () => { |
|
executeFormatCommand(button.dataset.command); |
|
}); |
|
}); |
|
|
|
|
|
document.addEventListener('click', (e) => { |
|
if (!elements.blockMenu.contains(e.target) && e.target !== elements.addBlock) { |
|
hideBlockMenu(); |
|
} |
|
|
|
if (!elements.formattingToolbar.contains(e.target) && |
|
!(state.lastSelection && state.lastSelection.toString().length > 0)) { |
|
hideFormattingToolbar(); |
|
} |
|
}); |
|
|
|
|
|
elements.browseButton.addEventListener('click', () => { |
|
elements.imageInput.click(); |
|
}); |
|
|
|
elements.imageInput.addEventListener('change', (e) => { |
|
const file = e.target.files[0]; |
|
if (file) { |
|
const reader = new FileReader(); |
|
reader.onload = (event) => { |
|
elements.imageUrl.value = event.target.result; |
|
}; |
|
reader.readAsDataURL(file); |
|
} |
|
}); |
|
|
|
elements.confirmImageUpload.addEventListener('click', () => { |
|
const imageUrl = elements.imageUrl.value.trim(); |
|
if (imageUrl) { |
|
const selection = window.getSelection(); |
|
if (selection.rangeCount) { |
|
const range = selection.getRangeAt(0); |
|
const img = document.createElement('img'); |
|
img.src = imageUrl; |
|
img.className = 'rounded-md max-w-full h-auto'; |
|
range.insertNode(img); |
|
hideImageUploadModal(); |
|
} |
|
} |
|
}); |
|
|
|
elements.cancelImageUpload.addEventListener('click', hideImageUploadModal); |
|
elements.closeImageModal.addEventListener('click', hideImageUploadModal); |
|
|
|
|
|
elements.addPageButton.addEventListener('click', showAddPageModal); |
|
|
|
elements.confirmPageAdd.addEventListener('click', () => { |
|
const title = elements.pageTitle.value.trim() || 'Untitled'; |
|
const parentId = elements.parentPage.value || null; |
|
createPage(title, parentId); |
|
hideAddPageModal(); |
|
}); |
|
|
|
elements.cancelPageAdd.addEventListener('click', hideAddPageModal); |
|
elements.closePageModal.addEventListener('click', hideAddPageModal); |
|
|
|
|
|
elements.saveButton.addEventListener('click', saveCurrentPage); |
|
|
|
|
|
if (localStorage.getItem('darkMode') === 'true') { |
|
document.documentElement.classList.add('dark'); |
|
} else if (localStorage.getItem('darkMode') === 'false') { |
|
document.documentElement.classList.remove('dark'); |
|
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { |
|
document.documentElement.classList.add('dark'); |
|
} |
|
} |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', init); |
|
</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=Harry00/notion" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
|
</html> |