import { Editor } from '@tiptap/core'; import StarterKit from '@tiptap/starter-kit'; import { Markdown } from 'tiptap-markdown'; import Placeholder from '@tiptap/extension-placeholder'; import BubbleMenu from '@tiptap/extension-bubble-menu'; import './styles.css'; import 'remixicon/fonts/remixicon.css'; // DOM Elements const saveBtn = document.getElementById('save-btn'); const editorContainer = document.getElementById('editor-container'); const bubbleMenuEl = document.getElementById('bubble-menu'); // Editor Instance let editor; let currentFileId = null; // CSRF Helper function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } // Initialize Editor (only once) function initEditor() { // Bubble Menu Buttons const btnBold = document.getElementById('btn-bold'); const btnItalic = document.getElementById('btn-italic'); const btnStrike = document.getElementById('btn-strike'); const btnCode = document.getElementById('btn-code'); editor = new Editor({ element: document.querySelector('#editor'), extensions: [ StarterKit, Markdown, Placeholder.configure({ placeholder: "Type '/' for commands...", }), BubbleMenu.configure({ element: bubbleMenuEl, tippyOptions: { duration: 100 }, }), ], content: '', editorProps: { attributes: { class: 'prose prose-sm sm:prose lg:prose-lg max-w-none focus:outline-none', }, }, onSelectionUpdate({ editor }) { btnBold?.classList.toggle('is-active', editor.isActive('bold')); btnItalic?.classList.toggle('is-active', editor.isActive('italic')); btnStrike?.classList.toggle('is-active', editor.isActive('strike')); btnCode?.classList.toggle('is-active', editor.isActive('code')); } }); // Bubble Menu Listeners if (btnBold) btnBold.addEventListener('click', () => editor.chain().focus().toggleBold().run()); if (btnItalic) btnItalic.addEventListener('click', () => editor.chain().focus().toggleItalic().run()); if (btnStrike) btnStrike.addEventListener('click', () => editor.chain().focus().toggleStrike().run()); if (btnCode) btnCode.addEventListener('click', () => editor.chain().focus().toggleCode().run()); // Container Focus if (editorContainer) { editorContainer.addEventListener('click', () => { if (editor && !editor.isFocused) { editor.chain().focus().run(); } }); } // Expose to global scope for SPA usage window.editorInstance = editor; window.setEditorFileId = (fileId) => { currentFileId = fileId; }; } // Save Content if (saveBtn) { saveBtn.addEventListener('click', async () => { if (!editor || !currentFileId) return; saveBtn.disabled = true; saveBtn.textContent = 'Saving...'; const markdownContent = editor.storage.markdown.getMarkdown(); const saveUrl = `/file/content/${currentFileId}/save/`; try { const response = await fetch(saveUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify({ content: markdownContent }) }); if (response.ok) { saveBtn.textContent = 'Saved!'; setTimeout(() => { saveBtn.textContent = 'Save Changes'; saveBtn.disabled = false; }, 1000); } else { throw new Error('Save failed'); } } catch (err) { alert('Failed to save file'); saveBtn.textContent = 'Save Changes'; saveBtn.disabled = false; } }); } // Initialize editor on load if (document.querySelector('#editor')) { initEditor(); }