|
<!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</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: 'translateX(20px)', opacity: '0' }, |
|
'100%': { transform: 'translateX(0)', opacity: '1' }, |
|
} |
|
} |
|
} |
|
} |
|
} |
|
</script> |
|
<style> |
|
.ProseMirror { |
|
min-height: 100px; |
|
padding: 10px; |
|
outline: none; |
|
} |
|
.ProseMirror:focus { |
|
outline: none; |
|
} |
|
.ProseMirror p { |
|
margin-bottom: 1rem; |
|
} |
|
.ProseMirror h1 { |
|
font-size: 2rem; |
|
font-weight: bold; |
|
margin: 1rem 0; |
|
} |
|
.ProseMirror h2 { |
|
font-size: 1.5rem; |
|
font-weight: bold; |
|
margin: 0.75rem 0; |
|
} |
|
.ProseMirror h3 { |
|
font-size: 1.25rem; |
|
font-weight: bold; |
|
margin: 0.5rem 0; |
|
} |
|
.ProseMirror ul, .ProseMirror ol { |
|
padding-left: 1.5rem; |
|
margin-bottom: 1rem; |
|
} |
|
.ProseMirror ul { |
|
list-style-type: disc; |
|
} |
|
.ProseMirror ol { |
|
list-style-type: decimal; |
|
} |
|
.ProseMirror blockquote { |
|
border-left: 3px solid #94a3b8; |
|
padding-left: 1rem; |
|
margin: 1rem 0; |
|
color: #64748b; |
|
} |
|
.ProseMirror pre { |
|
background: #1e293b; |
|
color: #f8fafc; |
|
padding: 1rem; |
|
border-radius: 0.5rem; |
|
margin: 1rem 0; |
|
overflow-x: auto; |
|
} |
|
.ProseMirror code { |
|
background: #e2e8f0; |
|
padding: 0.2rem 0.4rem; |
|
border-radius: 0.25rem; |
|
font-family: monospace; |
|
} |
|
.ProseMirror table { |
|
border-collapse: collapse; |
|
margin: 1rem 0; |
|
width: 100%; |
|
} |
|
.ProseMirror th, .ProseMirror td { |
|
border: 1px solid #cbd5e1; |
|
padding: 0.5rem; |
|
} |
|
.ProseMirror th { |
|
background-color: #f1f5f9; |
|
} |
|
.ProseMirror img { |
|
max-width: 100%; |
|
height: auto; |
|
margin: 1rem 0; |
|
border-radius: 0.5rem; |
|
} |
|
.ProseMirror a { |
|
color: #3b82f6; |
|
text-decoration: underline; |
|
} |
|
.ProseMirror .task-item { |
|
display: flex; |
|
align-items: center; |
|
margin-bottom: 0.5rem; |
|
} |
|
.ProseMirror .task-item input[type="checkbox"] { |
|
margin-right: 0.5rem; |
|
} |
|
.ProseMirror .task-item.checked { |
|
color: #64748b; |
|
text-decoration: line-through; |
|
} |
|
.custom-scrollbar::-webkit-scrollbar { |
|
width: 6px; |
|
height: 6px; |
|
} |
|
.custom-scrollbar::-webkit-scrollbar-track { |
|
background: #f1f5f9; |
|
} |
|
.custom-scrollbar::-webkit-scrollbar-thumb { |
|
background: #cbd5e1; |
|
border-radius: 3px; |
|
} |
|
.dark .custom-scrollbar::-webkit-scrollbar-track { |
|
background: #1e293b; |
|
} |
|
.dark .custom-scrollbar::-webkit-scrollbar-thumb { |
|
background: #475569; |
|
} |
|
.resize-handle { |
|
position: absolute; |
|
right: 0; |
|
top: 0; |
|
bottom: 0; |
|
width: 4px; |
|
background: #cbd5e1; |
|
cursor: col-resize; |
|
transition: background 0.2s; |
|
} |
|
.resize-handle:hover { |
|
background: #94a3b8; |
|
} |
|
.dark .resize-handle { |
|
background: #475569; |
|
} |
|
.dark .resize-handle:hover { |
|
background: #64748b; |
|
} |
|
.floating-toolbar { |
|
opacity: 0; |
|
transform: translateY(10px); |
|
transition: opacity 0.2s, transform 0.2s; |
|
} |
|
.floating-toolbar.visible { |
|
opacity: 1; |
|
transform: translateY(0); |
|
} |
|
.slate-insert-menu { |
|
opacity: 0; |
|
transform: translateY(10px); |
|
transition: opacity 0.2s, transform 0.2s; |
|
pointer-events: none; |
|
} |
|
.slate-insert-menu.visible { |
|
opacity: 1; |
|
transform: translateY(0); |
|
pointer-events: all; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-50 dark:bg-dark-900 text-gray-800 dark:text-gray-200 transition-colors duration-200 min-h-screen flex"> |
|
|
|
<div class="w-64 bg-white dark:bg-dark-800 border-r border-gray-200 dark:border-gray-700 flex flex-col h-screen sticky top-0"> |
|
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between"> |
|
<div class="flex items-center space-x-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> |
|
<span class="font-semibold">Notion Lite</span> |
|
</div> |
|
<button id="toggle-dark" class="p-2 rounded-full 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 class="flex-1 overflow-y-auto custom-scrollbar p-2"> |
|
<div class="space-y-1"> |
|
<button id="new-page" class="w-full flex items-center space-x-2 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-left"> |
|
<i class="fas fa-plus text-gray-500 dark:text-gray-400"></i> |
|
<span>New page</span> |
|
</button> |
|
<div class="px-2 py-1 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Pages</div> |
|
<div id="pages-list" class="space-y-1"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
<div class="p-4 border-t border-gray-200 dark:border-gray-700"> |
|
<div class="flex items-center space-x-2"> |
|
<div class="w-8 h-8 rounded-full bg-primary-500 flex items-center justify-center text-white"> |
|
<span class="text-sm">U</span> |
|
</div> |
|
<span class="text-sm font-medium">User</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="flex-1 flex flex-col h-screen overflow-hidden"> |
|
|
|
<div class="bg-white dark:bg-dark-800 border-b border-gray-200 dark:border-gray-700 p-2 flex items-center justify-between"> |
|
<div class="flex items-center space-x-2"> |
|
<button id="toggle-sidebar" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 md:hidden"> |
|
<i class="fas fa-bars"></i> |
|
</button> |
|
<div id="breadcrumbs" class="flex items-center space-x-1 text-sm"> |
|
|
|
</div> |
|
</div> |
|
<div class="flex items-center space-x-2"> |
|
<button id="save-button" class="px-3 py-1 rounded-lg bg-primary-500 text-white hover:bg-primary-600 flex items-center space-x-1"> |
|
<i class="fas fa-save"></i> |
|
<span class="hidden md:inline">Save</span> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="flex-1 overflow-auto custom-scrollbar"> |
|
<div id="editor-container" class="max-w-4xl mx-auto p-4"> |
|
<div id="editor" class="ProseMirror"></div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="floating-toolbar" class="floating-toolbar fixed bg-white dark:bg-dark-800 shadow-lg rounded-lg p-1 border border-gray-200 dark:border-gray-700 z-50 flex items-center space-x-1"> |
|
<button data-command="bold" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-bold"></i> |
|
</button> |
|
<button data-command="italic" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-italic"></i> |
|
</button> |
|
<button data-command="underline" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-underline"></i> |
|
</button> |
|
<button data-command="strike" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-strikethrough"></i> |
|
</button> |
|
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div> |
|
<button data-command="heading1" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<span class="font-bold">H1</span> |
|
</button> |
|
<button data-command="heading2" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<span class="font-bold">H2</span> |
|
</button> |
|
<button data-command="heading3" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<span class="font-bold">H3</span> |
|
</button> |
|
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div> |
|
<button data-command="bulletList" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-list-ul"></i> |
|
</button> |
|
<button data-command="orderedList" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-list-ol"></i> |
|
</button> |
|
<button data-command="taskList" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-tasks"></i> |
|
</button> |
|
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div> |
|
<button data-command="blockquote" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-quote-right"></i> |
|
</button> |
|
<button data-command="codeBlock" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-code"></i> |
|
</button> |
|
<button data-command="horizontalRule" class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"> |
|
<i class="fas fa-minus"></i> |
|
</button> |
|
</div> |
|
|
|
|
|
<div id="slate-insert-menu" class="slate-insert-menu fixed bg-white dark:bg-dark-800 shadow-lg rounded-lg p-2 border border-gray-200 dark:border-gray-700 z-50 w-64"> |
|
<div class="text-xs font-semibold text-gray-500 dark:text-gray-400 px-2 py-1">Insert</div> |
|
<div class="space-y-1"> |
|
<button data-insert="heading1" class="w-full flex items-center space-x-2 p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-left"> |
|
<div class="w-8 h-8 rounded bg-blue-100 dark:bg-blue-900 flex items-center justify-center"> |
|
<span class="font-bold text-blue-600 dark:text-blue-300">H1</span> |
|
</div> |
|
<div> |
|
<div class="font-medium">Heading 1</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">Large section heading</div> |
|
</div> |
|
</button> |
|
<button data-insert="heading2" class="w-full flex items-center space-x-2 p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-left"> |
|
<div class="w-8 h-8 rounded bg-blue-100 dark:bg-blue-900 flex items-center justify-center"> |
|
<span class="font-bold text-blue-600 dark:text-blue-300">H2</span> |
|
</div> |
|
<div> |
|
<div class="font-medium">Heading 2</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">Medium section heading</div> |
|
</div> |
|
</button> |
|
<button data-insert="bulletList" class="w-full flex items-center space-x-2 p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-left"> |
|
<div class="w-8 h-8 rounded bg-purple-100 dark:bg-purple-900 flex items-center justify-center"> |
|
<i class="fas fa-list-ul text-purple-600 dark:text-purple-300"></i> |
|
</div> |
|
<div> |
|
<div class="font-medium">Bulleted list</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">Create a simple bulleted list</div> |
|
</div> |
|
</button> |
|
<button data-insert="orderedList" class="w-full flex items-center space-x-2 p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-left"> |
|
<div class="w-8 h-8 rounded bg-green-100 dark:bg-green-900 flex items-center justify-center"> |
|
<i class="fas fa-list-ol text-green-600 dark:text-green-300"></i> |
|
</div> |
|
<div> |
|
<div class="font-medium">Numbered list</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">Create a list with numbering</div> |
|
</div> |
|
</button> |
|
<button data-insert="taskList" class="w-full flex items-center space-x-2 p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-left"> |
|
<div class="w-8 h-8 rounded bg-yellow-100 dark:bg-yellow-900 flex items-center justify-center"> |
|
<i class="fas fa-tasks text-yellow-600 dark:text-yellow-300"></i> |
|
</div> |
|
<div> |
|
<div class="font-medium">Task list</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">Track tasks with checkboxes</div> |
|
</div> |
|
</button> |
|
<button data-insert="table" class="w-full flex items-center space-x-2 p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-left"> |
|
<div class="w-8 h-8 rounded bg-red-100 dark:bg-red-900 flex items-center justify-center"> |
|
<i class="fas fa-table text-red-600 dark:text-red-300"></i> |
|
</div> |
|
<div> |
|
<div class="font-medium">Table</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">Insert a table</div> |
|
</div> |
|
</button> |
|
<button data-insert="codeBlock" class="w-full flex items-center space-x-2 p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-left"> |
|
<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center"> |
|
<i class="fas fa-code text-gray-600 dark:text-gray-300"></i> |
|
</div> |
|
<div> |
|
<div class="font-medium">Code block</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">Insert a block of code</div> |
|
</div> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
class NotionLite { |
|
constructor() { |
|
this.pages = []; |
|
this.currentPage = null; |
|
this.editor = null; |
|
this.initElements(); |
|
this.initEventListeners(); |
|
this.loadPages(); |
|
this.initEditor(); |
|
this.updateUI(); |
|
} |
|
|
|
initElements() { |
|
this.elements = { |
|
sidebar: document.querySelector('.w-64'), |
|
toggleSidebar: document.getElementById('toggle-sidebar'), |
|
toggleDark: document.getElementById('toggle-dark'), |
|
newPage: document.getElementById('new-page'), |
|
pagesList: document.getElementById('pages-list'), |
|
breadcrumbs: document.getElementById('breadcrumbs'), |
|
saveButton: document.getElementById('save-button'), |
|
editor: document.getElementById('editor'), |
|
editorContainer: document.getElementById('editor-container'), |
|
floatingToolbar: document.getElementById('floating-toolbar'), |
|
slateInsertMenu: document.getElementById('slate-insert-menu'), |
|
}; |
|
} |
|
|
|
initEventListeners() { |
|
|
|
this.elements.toggleSidebar.addEventListener('click', () => { |
|
this.elements.sidebar.classList.toggle('hidden'); |
|
}); |
|
|
|
|
|
this.elements.toggleDark.addEventListener('click', () => { |
|
document.documentElement.classList.toggle('dark'); |
|
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark')); |
|
}); |
|
|
|
|
|
this.elements.newPage.addEventListener('click', () => this.createNewPage()); |
|
|
|
|
|
this.elements.saveButton.addEventListener('click', () => this.saveCurrentPage()); |
|
|
|
|
|
document.querySelectorAll('[data-command]').forEach(button => { |
|
button.addEventListener('click', (e) => { |
|
const command = e.currentTarget.getAttribute('data-command'); |
|
this.executeCommand(command); |
|
}); |
|
}); |
|
|
|
|
|
document.querySelectorAll('[data-insert]').forEach(button => { |
|
button.addEventListener('click', (e) => { |
|
const command = e.currentTarget.getAttribute('data-insert'); |
|
this.executeCommand(command); |
|
this.hideInsertMenu(); |
|
}); |
|
}); |
|
|
|
|
|
this.elements.editor.addEventListener('click', () => this.updateFloatingToolbar()); |
|
this.elements.editor.addEventListener('keyup', () => this.updateFloatingToolbar()); |
|
this.elements.editor.addEventListener('input', () => this.saveCurrentPage()); |
|
|
|
|
|
this.elements.editor.addEventListener('keydown', (e) => { |
|
if (e.key === '/' && this.getCurrentTextBeforeCursor() === '') { |
|
this.showInsertMenu(); |
|
e.preventDefault(); |
|
} else if (e.key === 'Escape') { |
|
this.hideInsertMenu(); |
|
} |
|
}); |
|
|
|
|
|
document.addEventListener('click', (e) => { |
|
if (!this.elements.slateInsertMenu.contains(e.target) && |
|
!this.elements.editor.contains(e.target)) { |
|
this.hideInsertMenu(); |
|
} |
|
}); |
|
|
|
|
|
setInterval(() => this.saveCurrentPage(), 10000); |
|
} |
|
|
|
initEditor() { |
|
|
|
this.elements.editor.contentEditable = true; |
|
|
|
|
|
if (this.pages.length > 0) { |
|
this.loadPage(this.pages[0].id); |
|
} else { |
|
this.createNewPage(); |
|
} |
|
} |
|
|
|
executeCommand(command) { |
|
document.execCommand('styleWithCSS', false, true); |
|
|
|
switch (command) { |
|
case 'bold': |
|
document.execCommand('bold', false, null); |
|
break; |
|
case 'italic': |
|
document.execCommand('italic', false, null); |
|
break; |
|
case 'underline': |
|
document.execCommand('underline', false, null); |
|
break; |
|
case 'strike': |
|
document.execCommand('strikeThrough', false, null); |
|
break; |
|
case 'heading1': |
|
document.execCommand('formatBlock', false, '<h1>'); |
|
break; |
|
case 'heading2': |
|
document.execCommand('formatBlock', false, '<h2>'); |
|
break; |
|
case 'heading3': |
|
document.execCommand('formatBlock', false, '<h3>'); |
|
break; |
|
case 'bulletList': |
|
document.execCommand('insertUnorderedList', false, null); |
|
break; |
|
case 'orderedList': |
|
document.execCommand('insertOrderedList', false, null); |
|
break; |
|
case 'taskList': |
|
this.insertTaskItem(); |
|
break; |
|
case 'blockquote': |
|
document.execCommand('formatBlock', false, '<blockquote>'); |
|
break; |
|
case 'codeBlock': |
|
this.insertCodeBlock(); |
|
break; |
|
case 'horizontalRule': |
|
document.execCommand('insertHorizontalRule', false, null); |
|
break; |
|
case 'table': |
|
this.insertTable(); |
|
break; |
|
} |
|
|
|
this.elements.editor.focus(); |
|
this.saveCurrentPage(); |
|
} |
|
|
|
insertTaskItem() { |
|
const selection = window.getSelection(); |
|
const range = selection.getRangeAt(0); |
|
const container = document.createElement('div'); |
|
container.className = 'task-item'; |
|
|
|
const checkbox = document.createElement('input'); |
|
checkbox.type = 'checkbox'; |
|
checkbox.className = 'task-checkbox'; |
|
checkbox.addEventListener('change', function() { |
|
if (this.checked) { |
|
container.classList.add('checked'); |
|
} else { |
|
container.classList.remove('checked'); |
|
} |
|
}); |
|
|
|
const text = document.createElement('span'); |
|
text.className = 'task-text'; |
|
text.contentEditable = true; |
|
text.textContent = 'New task'; |
|
|
|
container.appendChild(checkbox); |
|
container.appendChild(text); |
|
|
|
range.deleteContents(); |
|
range.insertNode(container); |
|
|
|
|
|
const newRange = document.createRange(); |
|
newRange.selectNodeContents(text); |
|
newRange.collapse(false); |
|
selection.removeAllRanges(); |
|
selection.addRange(newRange); |
|
|
|
text.focus(); |
|
} |
|
|
|
insertCodeBlock() { |
|
const selection = window.getSelection(); |
|
const range = selection.getRangeAt(0); |
|
const pre = document.createElement('pre'); |
|
const code = document.createElement('code'); |
|
code.textContent = '// Your code here'; |
|
pre.appendChild(code); |
|
|
|
range.deleteContents(); |
|
range.insertNode(pre); |
|
|
|
|
|
const newRange = document.createRange(); |
|
newRange.selectNodeContents(code); |
|
newRange.collapse(false); |
|
selection.removeAllRanges(); |
|
selection.addRange(newRange); |
|
|
|
code.focus(); |
|
} |
|
|
|
insertTable() { |
|
const selection = window.getSelection(); |
|
const range = selection.getRangeAt(0); |
|
|
|
const table = document.createElement('table'); |
|
const thead = document.createElement('thead'); |
|
const tbody = document.createElement('tbody'); |
|
const headerRow = document.createElement('tr'); |
|
|
|
|
|
for (let i = 0; i < 3; i++) { |
|
const th = document.createElement('th'); |
|
th.textContent = i === 0 ? 'Header 1' : i === 1 ? 'Header 2' : 'Header 3'; |
|
headerRow.appendChild(th); |
|
} |
|
|
|
|
|
for (let i = 0; i < 3; i++) { |
|
const row = document.createElement('tr'); |
|
for (let j = 0; j < 3; j++) { |
|
const td = document.createElement('td'); |
|
td.textContent = `Row ${i+1}, Col ${j+1}`; |
|
row.appendChild(td); |
|
} |
|
tbody.appendChild(row); |
|
} |
|
|
|
thead.appendChild(headerRow); |
|
table.appendChild(thead); |
|
table.appendChild(tbody); |
|
|
|
range.deleteContents(); |
|
range.insertNode(table); |
|
|
|
|
|
const newRange = document.createRange(); |
|
newRange.setStartAfter(table); |
|
newRange.collapse(true); |
|
selection.removeAllRanges(); |
|
selection.addRange(newRange); |
|
|
|
this.elements.editor.focus(); |
|
} |
|
|
|
updateFloatingToolbar() { |
|
const selection = window.getSelection(); |
|
if (!selection.rangeCount) return; |
|
|
|
const range = selection.getRangeAt(0); |
|
const rect = range.getBoundingClientRect(); |
|
|
|
|
|
this.elements.floatingToolbar.style.top = `${rect.top + window.scrollY - 40}px`; |
|
this.elements.floatingToolbar.style.left = `${rect.left + window.scrollX - 10}px`; |
|
|
|
|
|
if (!selection.isCollapsed) { |
|
this.elements.floatingToolbar.classList.add('visible'); |
|
} else { |
|
this.elements.floatingToolbar.classList.remove('visible'); |
|
} |
|
} |
|
|
|
showInsertMenu() { |
|
const selection = window.getSelection(); |
|
if (!selection.rangeCount) return; |
|
|
|
const range = selection.getRangeAt(0); |
|
const rect = range.getBoundingClientRect(); |
|
|
|
|
|
this.elements.slateInsertMenu.style.top = `${rect.bottom + window.scrollY + 5}px`; |
|
this.elements.slateInsertMenu.style.left = `${rect.left + window.scrollX}px`; |
|
this.elements.slateInsertMenu.classList.add('visible'); |
|
} |
|
|
|
hideInsertMenu() { |
|
this.elements.slateInsertMenu.classList.remove('visible'); |
|
} |
|
|
|
getCurrentTextBeforeCursor() { |
|
const selection = window.getSelection(); |
|
if (!selection.rangeCount) return ''; |
|
|
|
const range = selection.getRangeAt(0); |
|
const node = range.startContainer; |
|
const offset = range.startOffset; |
|
|
|
if (node.nodeType === Node.TEXT_NODE) { |
|
return node.textContent.substring(0, offset); |
|
} |
|
|
|
return ''; |
|
} |
|
|
|
createNewPage() { |
|
const newPage = { |
|
id: Date.now().toString(), |
|
title: 'Untitled', |
|
content: '', |
|
createdAt: new Date().toISOString(), |
|
updatedAt: new Date().toISOString(), |
|
}; |
|
|
|
this.pages.push(newPage); |
|
this.currentPage = newPage; |
|
this.savePages(); |
|
this.updateUI(); |
|
|
|
|
|
setTimeout(() => { |
|
this.elements.editor.focus(); |
|
}, 100); |
|
} |
|
|
|
loadPage(pageId) { |
|
const page = this.pages.find(p => p.id === pageId); |
|
if (!page) return; |
|
|
|
this.currentPage = page; |
|
this.elements.editor.innerHTML = page.content || ''; |
|
this.updateUI(); |
|
|
|
|
|
setTimeout(() => { |
|
this.elements.editor.focus(); |
|
}, 100); |
|
} |
|
|
|
saveCurrentPage() { |
|
if (!this.currentPage) return; |
|
|
|
this.currentPage.content = this.elements.editor.innerHTML; |
|
this.currentPage.updatedAt = new Date().toISOString(); |
|
|
|
|
|
const tempDiv = document.createElement('div'); |
|
tempDiv.innerHTML = this.currentPage.content; |
|
|
|
const heading = tempDiv.querySelector('h1, h2, h3'); |
|
if (heading) { |
|
this.currentPage.title = heading.textContent.trim(); |
|
} else { |
|
const firstText = tempDiv.textContent.trim().split('\n')[0]; |
|
this.currentPage.title = firstText || 'Untitled'; |
|
} |
|
|
|
this.savePages(); |
|
this.updateUI(); |
|
} |
|
|
|
loadPages() { |
|
const savedPages = localStorage.getItem('notion-lite-pages'); |
|
if (savedPages) { |
|
this.pages = JSON.parse(savedPages); |
|
} |
|
|
|
|
|
const darkMode = localStorage.getItem('darkMode') === 'true'; |
|
if (darkMode) { |
|
document.documentElement.classList.add('dark'); |
|
} else { |
|
document.documentElement.classList.remove('dark'); |
|
} |
|
} |
|
|
|
savePages() { |
|
localStorage.setItem('notion-lite-pages', JSON.stringify(this.pages)); |
|
} |
|
|
|
updateUI() { |
|
|
|
this.elements.pagesList.innerHTML = ''; |
|
|
|
this.pages.forEach(page => { |
|
const pageElement = document.createElement('button'); |
|
pageElement.className = `w-full flex items-center space-x-2 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-left ${this.currentPage?.id === page.id ? 'bg-gray-100 dark:bg-gray-700' : ''}`; |
|
pageElement.innerHTML = ` |
|
<i class="fas fa-file-alt text-gray-500 dark:text-gray-400"></i> |
|
<span class="truncate">${page.title}</span> |
|
`; |
|
pageElement.addEventListener('click', () => this.loadPage(page.id)); |
|
this.elements.pagesList.appendChild(pageElement); |
|
}); |
|
|
|
|
|
if (this.currentPage) { |
|
this.elements.breadcrumbs.innerHTML = ` |
|
<span class="text-gray-500 dark:text-gray-400">Pages</span> |
|
<span class="text-gray-500 dark:text-gray-400">/</span> |
|
<span class="truncate max-w-xs">${this.currentPage.title}</span> |
|
`; |
|
} |
|
} |
|
} |
|
|
|
|
|
const app = new NotionLite(); |
|
}); |
|
</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> |