import React, { useState } from 'react'; import { useEditor, EditorContent } from '@tiptap/react'; import { StarterKit } from '@tiptap/starter-kit'; import { Table } from '@tiptap/extension-table'; import { TableRow } from '@tiptap/extension-table-row'; import { TableCell } from '@tiptap/extension-table-cell'; import { TableHeader } from '@tiptap/extension-table-header'; import { Link } from '@tiptap/extension-link'; import { Image } from '@tiptap/extension-image'; import { Mathematics } from '@tiptap/extension-mathematics'; import { Underline } from '@tiptap/extension-underline'; import { CharacterCount } from '@tiptap/extension-character-count'; import MathField from './MathField'; import 'katex/dist/katex.min.css'; const TextEditor = () => { const editor = useEditor({ extensions: [ StarterKit, Underline, Table.configure({ resizable: true }), TableRow, TableHeader, TableCell, Link.configure({ openOnClick: false, HTMLAttributes: { class: 'editor-link' } }), Image.extend({ addAttributes() { return { ...this.parent?.(), width: { default: '100%', renderHTML: attributes => ({ width: attributes.width }), parseHTML: element => element.getAttribute('width'), }, }; }, }), Mathematics, CharacterCount, ], editorProps: { attributes: { class: 'tiptap-editor', style: 'outline: none; padding: 20px; min-height: 500px;', }, // Obsługa klawisza Tab handleKeyDown: (view, event) => { if (event.key === 'Tab') { if (editor.isActive('table')) return false; // Pozwalamy domyślnej obsłudze Tab w tabeli event.preventDefault(); const { state, dispatch } = view; const { selection, tr } = state; const { $from } = selection; // Wyciągamy pozycję kursora jako objekt ResolvedPos if (event.shiftKey) { // Obsługa Shift+Tab const textBefore = $from.parent.textBetween(0, $from.parentOffset); const match = textBefore.match(/ +$/); if (match) { const spacesFound = match[0].length; const amountToRemove = Math.min(spacesFound, 4); const transaction = tr.delete($from.pos - amountToRemove, $from.pos); dispatch(transaction); } } else { // Dla samego Tab wstawiamy 4 spacje const transaction = tr.insertText(' '); dispatch(transaction); } return true; } return false; }, }, content: `

Nowy Dokument

Zacznij pisać tutaj...

`, }); // Logika dodawania obrazu const addImage = () => { // Jeżeli kursor jest już na obrazie, pobierz jego aktualne atrybuty const existingAttrs = editor.getAttributes('image'); const previousUrl = existingAttrs.src || ""; const previousWidth = existingAttrs.width || "100%"; // Jeśli obrazek już istnieje, możemy najpierw zapytać o nową szerokość const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; // Wczywanie/zmiana rozmiaru obrazu if (previousUrl) { const width = prompt("Szerokość obrazu (np. 50% lub 300px):", previousWidth); editor.chain() .focus() .setImage({ src: previousUrl, width: width || '100%' }) .run(); } else { input.onchange = (event) => { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => { const width = prompt("Szerokość obrazu (np. 50% lub 300px):"); editor.chain() .focus() .setImage({ src: e.target.result, width: width || '100%' }) .run(); }; reader.readAsDataURL(file); } }; input.click();} }; // Logika ustawiania linku const setLink = () => { const previousUrl = editor.getAttributes('link').href; const url = window.prompt('URL:', previousUrl); if (url === null) return; if (url === '') { editor.chain().focus().extendMarkRange('link').unsetLink().run(); return; } editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run(); }; // Logika wstawiania formuły matematycznej const [isMathModalOpen, setMathModalOpen] = useState(false); const [tempLatex, setTempLatex] = useState(""); const insertMath = () => { const existing = editor.getAttributes('inlineMath').latex || ''; setTempLatex(existing); setMathModalOpen(true); }; const handleMathConfirm = () => { if (editor.isActive('inlineMath')) { editor.chain().focus().updateAttributes('inlineMath', { latex: tempLatex }).run(); } else { editor.chain().focus().insertContent({ type: 'inlineMath', attrs: { latex: tempLatex } }).run(); } setMathModalOpen(false); }; // Logika dodawania tabeli const addTable = () => { const rows = window.prompt("Liczba wierszy:", "3"); if (rows === null) return; // Anulowanie const cols = window.prompt("Liczba kolumn:", "3"); if (cols === null) return; // Anulowanie editor.chain().focus().insertTable({ rows: parseInt(rows) || 3, cols: parseInt(cols) || 3, withHeaderRow: true }).run(); }; // Logika zapisu dokumentu const handleSave = async () => { const defaultTitle = "Dokument Archivium_" + new Date().toLocaleDateString(); const userTitle = window.prompt("Podaj nazwę pliku:", defaultTitle); if (userTitle === null) return; // Anulowanie const finalTitle = userTitle.trim() || defaultTitle; const docData = { title: finalTitle, content: editor.getJSON(), }; try { const response = await fetch('http://127.0.0.1:8000/save-document', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(docData), }); const result = await response.json(); if (result.status === "success") { alert(`Zapisano pomyślnie jako: ${finalTitle}`); } } catch (error) { alert("Błąd połączenia z modułem zapisu!"); } }; // Logika wczytywania dokumentu const handleLoad = async () => { const fileName = window.prompt("Podaj nazwę dokumentu do wczytania:", ""); if (fileName === null) return; // Anulowanie try { let url = 'http://127.0.0.1:8000/load-document'; if (fileName.trim() !== "") { // Dodaj nazwę pliku jako parametr zapytania, jeśli została podana url += `?title=${encodeURIComponent(fileName.trim())}`; } const response = await fetch(url); const data = await response.json(); if (data.content) { editor.commands.setContent(data.content); alert(`Wczytano: ${data.title || fileName}`); } else { alert("Błąd wczytywania: " + (data.error || "Nie znaleziono pliku o tej nazwie.")); } } catch (error) { alert("Błąd połączenia z modułem wczytywania!"); } }; // Nie renderuj edytora, jeśli nie jest gotowy if (!editor) return null; return (
{/* Pasek narzędzi formatowania */}
{/* Grupa nagłówków */}
{/* Grupa formatowania tekstu */}
{/* Grupa list */}
{/* Grupa wstawiania elementów */}
{/* Pasek narzędzi tabeli */} {editor.isActive('table') && (
Tabela:
)} {/* Obszar edycji */}
{/* Pasek statusu */}
Znaki: {editor.storage.characterCount.characters()} | Słowa: {editor.storage.characterCount.words()}
{/* Okno Modala dla edytora funkcji matematycznych */} {isMathModalOpen && ( <> {/* Tło (Overlay) */}
{/* Okno Modala */}

Wizualny Edytor Równań

)}
); }; export default TextEditor;