Pasek narzędzi, podgląd na żywo i WYSIWYG w obszarze tekstowym, który już posiadasz
Ostatnia aktualizacja:
This translation is older than the English page and may be missing recent changes.
Read the English versionEdytor przecen, który ulepsza obszar tekstowy zamiast go zastępować, dzięki czemu przesyłanie formularzy, sprawdzanie poprawności i wymagane pola nadal działają. WYSIWYG i zwykłe tryby Markdown, podgląd na żywo, znajdź i zamień, obsługa RTL, tryb ciemny. Działa samodzielnie z Django, Laravel, Rails, Node.js, PHP i dowolnym stosem.
Nie jest wymagany Frutjam ani Tailwind. Style są w pakiecie, więc edytor może włączyć się do dowolnego projektu.
Instalacja
NPM (bundlery: Vite, webpack, Rollup itp.)
npm install markdown-text-editor |
1 2 | import MarkdownEditor from 'markdown-text-editor'; new MarkdownEditor('#markdown-editor'); |
Definicje TypeScript są dostarczane z pakietem, więc opcje, wpisy na pasku narzędzi i zmienne kształty są sprawdzane i automatycznie uzupełniane bez dodatkowej instalacji.
Typy można również zaimportować, co ma znaczenie, gdy opcje nie są zapisane bezpośrednio w witrynie wywołań. TypeScript sprawdza tylko nieznane klucze w literale obiektu przekazanym bezpośrednio, więc konfiguracja przechowywana we własnej zmiennej ma błędnie napisany klucz po cichu ignorowany. Dodanie adnotacji powoduje cofnięcie kontroli.
1 2 3 4 5 6 7 8 9 10 11 12 13 | import MarkdownEditor, { type MarkdownEditorOptions, type ToolbarEntry } from 'markdown-text-editor'; // Without the annotation this compiles, the editor renders with the default // toolbar, and nothing tells you why. const config: MarkdownEditorOptions = { placeholder: 'Write...', toolbr: ['bold', 'preview'], // Error: did you mean to write 'toolbar'? }; // Building a toolbar conditionally const toolbar: ToolbarEntry[] = ['bold', 'italic']; if (canUpload) toolbar.push({ image: { fileInput: { uploadUrl: '/api/upload' } } }); toolbar.push('preview'); |
To samo dotyczy komponentu opakowania, gdzie MarkdownEditorOptions to jest to, co przekazujesz zamiast any, więc ktokolwiek użyje Twojego opakowania, zachowa kontrolę.
1 2 3 4 | interface EditorProps { value: string; options?: MarkdownEditorOptions; } |
CDN: moduł ES
1 2 3 4 | <script type="module"> import MarkdownEditor from 'https://cdn.jsdelivr.net/npm/markdown-text-editor/+esm'; new MarkdownEditor('#markdown-editor'); </script> |
CDN: globalny znacznik skryptu (IIFE)
Nie ma potrzeby importowania: MarkdownEditor jest automatycznie dostępny jako zmienna globalna.
1 2 3 4 5 6 7 8 9 | <form action="/api/save" method="POST"> <textarea id="markdown-editor" name="content"># Hello World</textarea> <button type="submit">Save Content</button> </form> <script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script> <script> new MarkdownEditor('#markdown-editor'); </script> |
Wersja demonstracyjna edytora Markdown
Szybki start
Przekaż obiekt opcji, aby dostosować edytor. Wszystkie opcje są opcjonalne: pomiń dowolną, aby użyć wartości domyślnej.
1 2 3 4 | const editor = new MarkdownEditor('#markdown-editor', { placeholder: 'Write your markdown...', toolbar: ['heading', 'bold', 'italic', 'strikethrough', 'ul', 'ol', 'checklist', 'blockquote', 'link', 'preview'], }); |
Filozofia: „Najpierw rodzimy”
Większość redaktorów łamie standardowy przepływ pracy w sieci. MarkdownEditor to uwzględnia. Ponieważ znajduje się bezpośrednio na wierzchu <textarea>, nie musisz uczyć się nowego sposobu obsługi danych.
- Nie jest potrzebne wiązanie danych: Działa z
<form method="POST">po wyjęciu z pudełka - Dostęp standardowy: Użyj
document.getElementById('editor').valuetak jak normalne wejście. - Backend Agnostic: Działa z dowolnym backendem (Python, Node.js, PHP itp.), tak jak normalne pole formularza
MarkdownEditor kontra EasyMDE / SimpleMDE
Większość edytorów przecen JavaScript (EasyMDE, SimpleMDE, edytory oparte na CodeMirror) ukryj swoje <textarea> i edytuj kopię, zapisując treść ponownie po przesłaniu formularza. To działa, dopóki coś innego nie będzie potrzebowało tej wartości: a required pole, na którym przeglądarka nie może się skupić, całkowicie blokuje przesłanie, oraz .value Lub FormData read before Submit zwraca pusty ciąg znaków, który łamie zabezpieczenia htmx, Turbo, automatycznego zapisywania i niezapisanych zmian. MarkdownEditor jest inny. Stylizuje obszar tekstowy, który już posiadasz i pozostawia go jako pole, w którym piszesz, więc wartość jest zawsze poprawna.
| Funkcja | Edytor Markdown | EasyMDE / SimpleMDE |
|---|---|---|
| Zachowany natywny obszar tekstowy | ✅ | ❌ Ukryty, zredagowany jako kopia |
Współpracuje z required pola | ✅ | ❌ Przeglądarka blokuje przesyłanie |
.value popraw przed przesłaniem | ✅ | ❌ Puste do momentu wysłania formularza |
Serializuje z FormData, htmx, Turbo | ✅ | ❌ Wymaga własnego API edytora |
| Tryb hybrydowy WYSIWYG | ✅ | ❌ |
| Wbudowane narzędzie Znajdź i zamień | ✅ | ❌ |
| Wsparcie RTL | ✅ Wbudowany | Za pośrednictwem CodeMirror direction opcja |
| Wbudowane procedury obsługi zdarzeń (CSP) | ✅ Brak | Niektóre |
| Tryb ciemny / motyw | ✅ | Ograniczony |
| Rozmiar pakietu, zapinany na zamek | 53KB | 107 KB (JS + CSS) |
Integracja frameworków
Ponieważ MarkdownEditor zachowuje wersję natywną <textarea>integruje się z każdym frameworkiem backendowym bez dodatkowego kodu. Twój serwer otrzymuje treść przeceny dokładnie tak samo, jak z dowolnego standardowego pola formularza. W przypadku React i Vue potrzeba kilku linijek, które zostaną omówione na końcu tej sekcji.
Django
Dodaj class do widżetu obszaru tekstowego i zainicjuj edytor: request.POST['content'] działa bez dodatkowych kroków.
1 2 3 4 5 6 7 8 | # forms.py class PostForm(forms.ModelForm): class Meta: model = Post fields = ['content'] widgets = { 'content': forms.Textarea(attrs={'class': 'markdown-editor'}), } |
1 2 | new MarkdownEditor('.markdown-editor'); // request.POST['content'] contains the markdown on submit |
Laravel
Używać f.text_area z klasą: $request->input('content') otrzymuje bezpośrednio przecenę.
1 2 | <textarea name="content" class="markdown-editor">{{ old('content') }}</textarea> <script>new MarkdownEditor('.markdown-editor');</script> |
Rubin na szynach
Współpracuje z form_with po wyjęciu z pudełka: params[:content] zawiera przecenę. W przypadku Turbo Drive użyj turbo:load zamiast DOMContentLoaded.
1 2 3 4 5 6 | document.addEventListener('turbo:load', () => { document.querySelectorAll('.markdown-editor:not([data-mde-init])').forEach(el => { el.setAttribute('data-mde-init', 'true'); new MarkdownEditor(el); }); }); |
Node.js / Express
req.body.content otrzymuje przecenę po przesłaniu formularza: brak kroku synchronizacji, brak niestandardowej ekstrakcji.
1 2 3 | <textarea name="content" class="markdown-editor"></textarea> <script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script> <script>new MarkdownEditor('.markdown-editor');</script> |
PHP
$_POST['content'] działa dokładnie tak, jak każdy standardowy obszar tekstowy: upuść go, a obsługa istniejącego formularza nie będzie wymagała żadnych zmian.
1 2 3 4 5 6 | <form method="POST" action="save.php"> <textarea name="content" class="markdown-editor"></textarea> <button type="submit">Save</button> </form> <script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script> <script>new MarkdownEditor('.markdown-editor');</script> |
Zareagować
Utwórz edytor w efekcie i zniszcz go po odmontowaniu. Używać defaultValue zamiast value: edytor zapisuje bezpośrednio w obszarze tekstowym, więc kontrolowane wiązanie zastąpiłoby to, co pisze użytkownik.
1 2 3 4 5 6 7 8 9 10 11 12 13 | import { useEffect, useRef } from 'react'; import MarkdownEditor from 'markdown-text-editor'; function MarkdownField({ name, defaultValue = '', onChange }) { const ref = useRef(null); useEffect(() => { const editor = new MarkdownEditor(ref.current, { onChange }); return () => editor.destroy(); }, []); return <textarea ref={ref} name={name} defaultValue={defaultValue} />; } |
Powracający editor.destroy() z efektu obejmuje również StrictMode, który uruchamia efekty dwukrotnie w fazie rozwoju i w przeciwnym razie pozostawiłby dwóch redaktorów w jednym obszarze tekstowym. Pusta tablica zależności jest zamierzona: opcje są odczytywane raz podczas tworzenia edytora, więc ponowne uruchomienie efektu spowodowałoby jego zniszczenie i odbudowanie przy każdej zmianie.
Vue
Ten sam pomysł. Po wprowadzeniu ustaw wartość początkową onMounted i nie wiązać :valuelub każde naciśnięcie klawisza emitowane z powrotem v-model nadpisałby obszar tekstowy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue'; import MarkdownEditor from 'markdown-text-editor'; const el = ref(null); const props = defineProps({ modelValue: { type: String, default: '' } }); const emit = defineEmits(['update:modelValue']); let editor = null; onMounted(() => { el.value.value = props.modelValue; editor = new MarkdownEditor(el.value, { onChange: value => emit('update:modelValue', value) }); }); onBeforeUnmount(() => editor?.destroy()); </script> <template><textarea ref="el"></textarea></template> |
Używany jako <MarkdownField v-model="content" />. W prostej formie możesz pominąć v-model całkowicie i przeczytaj wartość z pola tekstowego po przesłaniu, tak jak w przypadku każdego innego frameworka.
Konfiguracja
Możesz w pełni dostosować zachowanie i interfejs edytora, przekazując plik options obiekt. Jeśli pominiesz opcję, zostanie użyta wartość domyślna.
Opcje są odczytywane jednorazowo, podczas konstruowania edytora. Późniejsza ich zmiana nie ma żadnego efektu: zadzwoń destroy() i zamiast tego utwórz nowy edytor.
| Nieruchomość | Typ | Domyślny | Zamiar |
|---|---|---|---|
mode | string | 'plain' | Ustawia widok początkowy. Użyj hybrydy, aby uzyskać doświadczenie WYSIWYG lub zwykłej, aby uzyskać surową składnię. |
placeholder | string | 'Write...' | Tekst wyświetlany, gdy edytor jest pusty. |
toolbar | array | [...] | Określa, które narzędzia pojawiają się i w jakiej kolejności. |
footer | fałsz | obiekt | wszystko widoczne | Steruje paskiem stanu wyświetlanym pod edytorem. Zabierać się do pracy false aby całkowicie go ukryć, lub przekaż obiekt, aby przełączyć poszczególne statystyki. |
theme | string | inherited | Jawnie ustawia motyw edytora (jasny, ciemny, śnieżna, ciemna jagoda). Jeśli zostanie pominięty, redaktor dziedziczy data-theme od najbliższego elementu nadrzędnego lub <textarea> się. |
minHeight | number | 200 | Minimalna wysokość w pikselach, do której edytor się zmniejszy, gdy treść będzie krótka. W parze z maxHeight aby ustawić zakres automatycznego wzrostu. |
maxHeight | number | 500 | Maksymalna wysokość w pikselach, do jakiej może urosnąć edytor w trybie innym niż pełnoekranowy. Gdy zawartość przekroczy tę wysokość, w edytorze pojawi się pasek przewijania. Edytor posiada również uchwyt przeciągania, dzięki czemu użytkownicy mogą ręcznie zmieniać jego rozmiar powyżej tego limitu. |
renderer | function | marked | Zastępuje analizator przecen używany w podglądzie. Odbiera ciąg przeceny i musi zwrócić ciąg HTML. |
sanitizer | function | DOMPurify | Zastępuje narzędzie dezynfekujące HTML. Odbiera wyrenderowany kod HTML i musi zwrócić bezpieczny kod HTML do wyświetlenia. |
onChange | function | undefined | Wywołanie zwrotne uruchamiane przy każdej zmianie treści: pisaniu, akcjach na pasku narzędzi, cofaniu/ponawianiu i kontynuacji listy. Odbiera bieżący ciąg przeceny jako jedyny argument. |
labels | object | undefined | Tłumaczenia własnego interfejsu edytora, oznaczone angielskim ciągiem znaków. Wszystko, co pominiesz, pozostanie w języku angielskim. |
🛠 Dostosowywanie paska narzędzi
Pasek narzędzi jest modułowy. Modyfikując tablicę, możesz stworzyć minimalne środowisko lub w pełni funkcjonalny pakiet mocy.
Dostępne narzędzia
| Kategoria | Klucze narzędziowe |
|---|---|
| Typografia | heading, bold, italic, strikethrough, blockquote |
| Listy | ul (bullet), ol (numbered), checklist |
| Kod | code (w linii), codeblock (blok ogrodzony) |
| Wkładki | hr (reguła pozioma), table (szablon tabeli) |
| Głoska bezdźwięczna | link, image |
| Redagowanie | undo, redo, indent, outdent |
| Pogląd | preview |
| Szablony | { variables: [...] }, skonfigurowany inline |
Informacje o narzędziu
| Narzędzie | Opis |
|---|---|
heading | Otwiera menu rozwijane umożliwiające wybranie poziomu nagłówka H1–H6 |
bold | Włącza pogrubione formatowanie tekstu. |
italic | Umożliwia formatowanie tekstu kursywą. |
strikethrough | Umożliwia przekreślenie tekstu. |
ol | (Lista uporządkowana): Konwertuje tekst na format listy numerowanej. |
ul | (Lista nieuporządkowana): Konwertuje tekst na listę punktowaną. |
checklist | Dodaje pola wyboru do tekstu, dzięki czemu doskonale nadaje się do zadań, list rzeczy do zrobienia lub śledzenia stanu ukończenia. |
blockquote | Zaznacz cytowany lub podkreślony tekst. |
code | Zawija zaznaczony tekst pojedynczymi znacznikami dla kodu wbudowanego. Ponowne kliknięcie usuwa tylne znaczniki. |
codeblock | Zawija zaznaczony tekst w ogrodzonym bloku kodu z potrójnym znacznikiem. Ponowne kliknięcie usuwa ogrodzenia. |
hr | Wstawia A --- linia pozioma w pozycji kursora w osobnej linii. |
table | Wstawia startowy szablon tabeli przecen 2x3 w pozycji kursora. |
image | Umożliwia wstawianie obrazów za pomocą składni przecen. |
link | Umożliwia dodawanie hiperłączy do tekstu. |
undo | Aby cofnąć ostatnie zmiany. |
redo | Aby ponownie zastosować ostatnio cofnięte zmiany. |
indent | Aby zwiększyć poziom wcięcia. |
outdent | Aby zmniejszyć poziom wcięcia. |
preview | Przełącza pełnoekranowy podgląd obok siebie. Pola wyboru w panelu podglądu można kliknąć i natychmiast zaktualizować źródło przeceny. Naciśnij Escape, aby wyjść z trybu pełnoekranowego. Jeśli stały nagłówek zakrywa edytor w trybie pełnoekranowym, zobacz --mte-fullscreen-z-index. |
💡 Wskazówki dotyczące wdrożenia:
- Zmiana kolejności: Przyciski pojawiają się dokładnie w takiej kolejności, w jakiej zostały wymienione w tablicy
- Usuwanie: Po prostu pomiń dowolny klawisz (np
image) z tablicy, aby całkowicie wyłączyć tę funkcję dla użytkownika - Natywna rezerwa: Jeśli nie podasz pliku
placeholderw JS wtyczka automatycznie użyje plikuplaceholderatrybut z kodu HTML<textarea>
🧩 Narzędzia niestandardowe
Dodaj własny przycisk paska narzędzi bez rozwidlania. Opisz to i wrzuć do toolbar array, w dowolnej pozycji.
1 2 3 4 5 6 7 8 9 10 11 | new MarkdownEditor('#markdown-editor', { toolbar: ['bold', 'italic', { custom: { title: 'Callout', icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20">…</svg>', action(editor) { editor.insertText('> [!NOTE]\n> Something worth noting'); } } }, 'preview'] }); |
The title staje się podpowiedzią i klasą CSS przycisku, więc tę można stylizować jako .callout-btn, dokładnie tak samo jak wbudowane narzędzia.
Kilka narzędzi
Dodaj tyle, ile chcesz. Każdy z nich stanowi odrębny wpis w tablicy, więc umieszczasz je indywidualnie i mogą znajdować się pomiędzy wbudowanymi narzędziami, a nie być zgrupowane na jednym końcu.
1 2 3 4 5 6 7 | toolbar: [ 'bold', 'italic', { custom: { title: 'Callout', icon: svg1, action: insertCallout } }, 'ul', 'ol', { custom: { title: 'Signature', icon: svg2, action: insertSignature } }, 'preview' ] |
Daj każdemu inny title. Dwa narzędzia dzielące tytuł miałyby wspólną klasę CSS, a narzędzie bez jednego wraca do tego .custom-btn.
Opcje
| Nieruchomość | Typ | Zamiar |
|---|---|---|
title | string | Wymagany. Tekst podpowiedzi i źródło klasy CSS przycisku. Możliwość przetłumaczenia labels. |
action | function | Wymagany. Otrzymuje (editor, event). Zdarzenie kliknięcia jest przekazywane, aby narzędzie otwierające okno dialogowe mogło je przekazać MarkdownEditor.modal. |
icon | string | Wbudowany SVG dla tarczy przycisku. |
shortcut | string | Skrót klawiaturowy, np Ctrl+Shift+K. Dodano do podpowiedzi i ma pierwszeństwo przed wbudowanymi przy użyciu tych samych klawiszy. |
Narzędzie bez nr action jest pomijany z ostrzeżeniem w konsoli, zamiast przerywać pasek narzędzi. Nie renderuje żadnego przycisku i tyle shortcut, jeśli został zadeklarowany, również nie jest zarejestrowany.
Praca z treścią
Używać editor.insertText() zamiast samodzielnie pisać do pola tekstowego. Zastępuje zaznaczenie, przywraca fokus, przesuwa kursor, przewija, ponownie renderuje podgląd i uruchamia onChange. Cofanie nadal działa, ponieważ edycja przebiega normalną ścieżką.
1 2 3 4 5 6 7 8 | action(editor) { const textarea = editor.usertextarea; const selected = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd); const wrapped = `==${selected || 'highlight'}==`; // (text, where the caret lands, characters to leave outside the selection) editor.insertText(wrapped, wrapped.length, 0); } |
Skróty
1 2 3 4 5 6 | { custom: { title: 'Highlight', icon: '<svg>…</svg>', shortcut: 'Ctrl+Shift+H', action: (editor) => editor.insertText('====', 2, 2) }} |
Etykietka narzędzia zmieni się na „Podświetlenie (Ctrl+Shift+H)”. Skróty narzędzi są sprawdzane przed skrótami wbudowanymi, więc narzędzie niestandardowe może celowo przejąć kombinację, którą w innym przypadku obsługiwałby edytor.
Narzędzia wymagające własnego znacznika
Listy rozwijanej ani okna dialogowego nie można opisać deklaratywnie, więc zamiast tego napisz klasę. Rozszerzyć MarkdownEditor.Tool, ustawić this.button w konstruktorze i zaimplementuj applySyntax().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class EmbedTool extends MarkdownEditor.Tool { constructor(editor) { super(editor, 'Embed video'); // tooltip, and .embed-video-btn this.shortcut = 'Ctrl+Shift+E'; // optional this.button = this.createButton('<svg>…</svg>'); } applySyntax(event) { const dialog = MarkdownEditor.modal(event, 'fj:max-w-sm', ` <input type="url" class="embed-url fj:me-input fj:w-full"> <button type="button" class="embed-go fj:me-btn fj:me-btn-sm">Insert</button> `, 'Embed'); dialog.querySelector('.embed-go').addEventListener('click', () => { const url = dialog.querySelector('.embed-url').value.trim(); if (url) this.editor.insertText(`\n<iframe src="${url}"></iframe>\n`); dialog.close(); }); } destroy() { // Called by editor.destroy(). Release anything you registered here. } } new MarkdownEditor('#markdown-editor', { toolbar: ['bold', EmbedTool, 'preview'] }); |
Konfiguracja przejścia za pomocą { tool: EmbedTool, config: {...} }, który pojawia się jako drugi argument konstruktora. Ustawienie this.button Do null niczego nie renderuje, co jest przydatne w przypadku narzędzia, które ma zastosowanie tylko w niektórych konfiguracjach.
Jedna rzecz do obejrzenia: Toolwywołania konstruktora createButton(), więc wszystko, czego potrzebujesz do zastąpienia, musi pochodzić this.editor, a nie z pola, które przypisujesz później super().
Pomocnik okna dialogowego
MarkdownEditor.modal to okno dialogowe, z którego korzystają wbudowane narzędzia do tworzenia linków i obrazów, zatem Twoje narzędzie zbierające dane wejściowe wygląda jak reszta edytora, zamiast tworzyć własne wyskakujące okienko. Zwraca <dialog> element, już otwarty, do którego odpytujesz swoje pola i zamykasz dialog.close().
1 2 | // (event, size classes, body HTML, aria label) const dialog = MarkdownEditor.modal(event, 'fj:max-w-sm', '…', 'Embed'); |
Przekaż event twój applySyntax() otrzymane. Pomocnik czyta go, aby dowiedzieć się, z którego edytora pochodzi kliknięcie, więc okno dialogowe jest dołączane do opakowania tego edytora i przejmuje jego motyw. Zastępuje każde okno dialogowe już otwarte w tym edytorze i usuwa się z DOM po zamknięciu, więc nie ma czego czyścić.
Samodzielne renderowanie przeceny
Jeśli Twoje narzędzie osadza renderowaną treść, na przykład treść podpowiedzi, editor.renderMarkdown() tworzy HTML dokładnie tak, jak robi to podgląd, honorując Twoje renderer I sanitizer.
const html = editor.renderMarkdown('**bold** text'); |
Pasek narzędzi dla własnego pola tekstowego
Kiedy okno dialogowe gromadzi tekst sformatowany, udostępnij mu prawdziwe narzędzia do formatowania, zamiast je odbudowywać. Pasek narzędzi jest powiązany z mijanym obszarem tekstowym, Twoim labels są dziedziczone i nie jest dodawany żaden przycisk podglądu.
1 2 3 | const body = dialog.querySelector('textarea'); const bar = editor.createToolbar(body, ['bold', 'italic', 'strikethrough']); dialog.querySelector('.body-toolbar').appendChild(bar); |
Nie zarządza to czasem życia paska narzędzi. Przyciski są czyszczone wraz z oknem dialogowym po jego usunięciu, ale jeśli zawarte w nim narzędzie zarejestruje cokolwiek na całej stronie, odbiornik dokumentu lub licznik czasu, zwolnij to samodzielnie po zamknięciu okna dialogowego.
Tłumaczenie
Narzędzia niestandardowe są tłumaczone jak narzędzia wbudowane, z kluczem według tytułu, który im nadałeś.
labels: { 'Callout': 'Aviso', 'Embed video': 'Insertar video' } |
Klasa CSS pozostaje niezmieniona, więc .callout-btn gorset .callout-btn w każdym języku.
📊 Stopka (pasek stanu)
Stopka znajduje się poniżej edytora i pokazuje linię, kolumnę kursora, liczbę znaków dokumentu i opcjonalnie liczbę słów, wszystko aktualizowane w czasie rzeczywistym. Jest ona domyślnie widoczna i każdą statystykę można przełączać niezależnie.
| Klawisz | Typ | Domyślny | Opis |
|---|---|---|---|
line | boolean | true | Pokaż bieżący numer linii. |
col | boolean | true | Pokaż bieżący numer kolumny. |
chars | boolean | true | Pokaż całkowitą liczbę znaków. |
words | boolean | false | Pokaż całkowitą liczbę słów. Domyślnie wyłączone: ustawione na true włączyć. |
Przykłady użycia
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Default — line, col, and chars visible new MarkdownEditor('#editor'); // Disable the footer entirely new MarkdownEditor('#editor', { footer: false }); // Hide only character count new MarkdownEditor('#editor', { footer: { chars: false } }); // Hide line and column, keep character count new MarkdownEditor('#editor', { footer: { line: false, col: false } }); // Show only line number new MarkdownEditor('#editor', { footer: { col: false, chars: false } }); // Enable word count alongside the defaults new MarkdownEditor('#editor', { footer: { words: true } }); // Show word count only new MarkdownEditor('#editor', { footer: { line: false, col: false, chars: false, words: true } }); |
🔀 Tryby edycji
MarkdownEditor oferuje dwa różne sposoby pisania i formatowania treści. Możesz przełączać się między tradycyjnym widokiem skupionym na składni lub nowoczesnym, opartym na wizualizacjach.
plain(Domyślne): Czyste, wydajne środowisko Markdown, w którym składnia (np**bold**Lub# heading) jest widoczne. Idealny dla programistów i purystów Markdownhybrid: Funkcja inspirowana WYSIWYG, która renderuje formatowanie (pogrubienie, kursywa, nagłówki) w czasie rzeczywistym podczas pisania, zachowując jednocześnie podstawową strukturę Markdown.
1 2 3 4 5 6 7 8 9 10 11 12 | // Default initialization (Plain Mode) new MarkdownEditor('#markdown-editor'); // Explicit Plain Mode new MarkdownEditor('#markdown-editor', { mode: 'plain' }); // Hybrid (Visual) Mode new MarkdownEditor('#markdown-editor', { mode: 'hybrid' }); |
Podgląd trybu hybrydowego i zwykłego:
Tryb hybrydowy
Formatowanie wizualne jest renderowane w czasie rzeczywistym podczas pisania.
Tryb zwykły (domyślny)
Koncentruje się na surowej składni Markdown, aby zapewnić lekkość.
🌙 Tematyka
MarkdownEditor automatycznie dziedziczy motyw z otaczającej strony: nie jest wymagana żadna konfiguracja. Redaktor czyta data-theme od najbliższego przodka podczas inicjalizacji, dzięki czemu pozostaje zsynchronizowany z motywem Twojej witryny od razu po wyjęciu z pudełka.
Sposób rozwiązania tematu (kolejność priorytetów)
themeopcja: jawne zastąpienie przekazane w obiekcie opcjidata-themena<textarea>: ustawiany bezpośrednio na elemenciedata-themena dowolnym przodku: np.<html>,<body>lub opakowanie<div>
Dostępne motywy
'light' (domyślny), 'dark', 'snowberry', 'darkberry'
Opcja 1, dziedzicz z <html> lub dowolny przodek (konfiguracja zerowa)
1 2 3 4 5 6 | <html data-theme="dark"> ... <textarea id="markdown-editor"></textarea> <script> new MarkdownEditor('#markdown-editor'); // picks up dark automatically </script> |
Opcja 2: zestaw data-theme bezpośrednio na <textarea>
1 2 3 4 | <textarea id="markdown-editor" data-theme="dark"></textarea> <script> new MarkdownEditor('#markdown-editor'); </script> |
Opcja 3: wyraźna theme opcja (zastępuje wszystko)
1 2 3 | new MarkdownEditor('#markdown-editor', { theme: 'dark' }); |
🎨 Motyw niestandardowy za pomocą zmiennych CSS
Możesz w pełni dostosować wygląd edytora, zastępując jego zmienne CSS w pliku .markdown-editor-wrapper element lub dowolny [data-theme] selektor. Wszystkie kolory korzystają z Przestrzeń barw OKLCH dla percepcyjnie jednolitych wyników.
| Zmienny | Zamiar | Światło domyślne | Ciemny domyślny |
|---|---|---|---|
--color-base | Tło redaktora | oklch(100% 0 0) | oklch(10.9% 0 0) |
--color-on-base | Podstawowy kolor tekstu | oklch(22% 0 0) | oklch(98% 0 0) |
--color-primary | Akcent podstawowy (aktywny pasek narzędzi, linki) | oklch(51.1% .262 277) | oklch(66.4% .184 286) |
--color-on-primary | Tekst na powierzchniach w kolorze podstawowym | oklch(96.2% .018 272) | oklch(10% .01 270) |
--color-secondary | Akcent wtórny | oklch(59.1% .293 323) | oklch(65% .18 220) |
--color-accent | Podświetl akcent (kod wbudowany, kursywa) | oklch(54.1% .281 293) | oklch(75% .18 50) |
--color-neutral | Powierzchnie neutralne (granice, przegrody) | oklch(15% 0 0) | oklch(85% 0 0) |
--color-error | Kolor stanu błędu | oklch(57.7% .245 27) | oklch(60% .22 30) |
--border-radius | Zaokrąglenie narożników ramki edytora | 0.25rem |
Przykład niestandardowego motywu
Zastąp dowolną zmienną on .markdown-editor-wrapper po zainicjowaniu edytora lub zdefiniuj niestandardowy [data-theme] blok w arkuszu stylów:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /* Override individual variables */ .markdown-editor-wrapper { --color-primary: oklch(60% 0.2 30); /* orange accent */ --border-radius: 0.5rem; } /* Or define a full custom theme */ [data-theme="brand"] .markdown-editor-wrapper, .markdown-editor-wrapper[data-theme="brand"] { --color-base: oklch(15% 0.01 250); --color-on-base: oklch(95% 0 0); --color-primary: oklch(65% 0.22 145); /* green */ --color-on-primary: oklch(10% 0 0); --color-accent: oklch(75% 0.18 60); --color-neutral: oklch(80% 0 0); --border-radius: 0.75rem; } |
new MarkdownEditor('#markdown-editor', { theme: 'brand' }); |
🌐 Język interfejsu
Tekst edytora, podpowiedzi, elementy menu, pola modalne i przyciski są domyślnie w języku angielskim. Przekaż labels obiekt, aby to przetłumaczyć. Klucze to angielskie ciągi znaków, tak działa gettext, więc podajesz tylko to, co chcesz zmienić, a wszystko, czego brakuje, wraca do angielskiego.
1 2 3 4 5 6 7 8 9 10 11 12 13 | new MarkdownEditor('#markdown-editor', { labels: { 'Bold': 'Gras', 'Italic': 'Italique', 'Heading': 'Titre', 'Link': 'Lien', 'Image link': 'Lien de l\'image', 'URL': 'URL', 'Alt text': 'Texte alternatif', 'Apply': 'Appliquer', 'Uploading...': 'Téléversement...', }, }); |
Ma to największe znaczenie w przypadku treści zapisywanych od prawej do lewej. Języki arabski, urdu i perski układają się poprawnie, ale pasek narzędzi, na którym nadal widnieje napis „Pogrubiony” nad tekstem pisanym od prawej do lewej, jest przetłumaczony tylko w połowie.
🏷 Zmienne
Dodaje menu rozwijane paska narzędzi umożliwiające wstawianie symboli zastępczych. Przydatne, gdy osoba pisząca dokument nie jest programistą, który zdefiniował składnię symboli zastępczych, jak w przypadku szablonów wiadomości e-mail, faktur lub umów: wybiera czytelną nazwę i wstawia dla niej poprawną składnię.
1 2 3 4 5 6 7 8 9 | new MarkdownEditor('#markdown-editor', { toolbar: ['bold', 'italic', 'link', { variables: [ { label: 'Customer Name', value: '{{customer.name}}' }, { label: 'Invoice No', value: '{{invoice.number}}' } ]}, 'preview' ] }); |
Narzędzie jest konfigurowane bezpośrednio w pliku toolbar array, więc jego położenie wśród innych przycisków zależy od Ciebie. Przycisk wyświetla listę etykiet, a kliknięcie jednej z nich powoduje jej wstawienie value przy kursorze, zastępując dowolne zaznaczenie. Nic nie jest renderowane, jeśli lista jest pusta.
Przykładowe wartości w podglądzie
Wpis podany a sample pokazuje tę próbkę w podglądzie, podczas gdy obszar tekstowy zachowuje prawdziwy symbol zastępczy. Wpisy niezawierające jednego będą wyświetlane tak, jak zostały zapisane, więc możesz mieszać oba.
1 2 3 4 5 6 7 | { variables: [ { label: 'Customer Name', value: '{{customer.name}}', sample: 'Hannes' }, { label: 'Invoice No', value: '{{invoice.number}}' } ]} // textarea : Hi {{customer.name}}, invoice {{invoice.number}} // preview : Hi Hannes, invoice {{invoice.number}} |
Grupowanie
Daj wpis items zamiast A value aby wyrenderować sekcję nagłówkową. Wpisy grupowane i płaskie można mieszać na jednej liście.
1 2 3 4 5 6 7 8 9 10 | { variables: [ { label: 'Today', value: '{{today}}', sample: '10 September 2026' }, { label: 'Customer', items: [ { label: 'Name', value: '{{customer.name}}', sample: 'Hannes' }, { label: 'Email', value: '{{customer.email}}', sample: 'hannes@example.com' } ]}, { label: 'Invoice', items: [ { label: 'Number', value: '{{invoice.number}}' } ]} ]} |
Rzeczy, które warto wiedzieć
- Edytor nigdy nie rozpoznaje zmiennych. Wstawia tekst, a Twoja aplikacja później zastępuje wartości rzeczywiste, zwykle po stronie serwera.
samplewpływa tylko na to, co wyświetla podgląd. - Etykiety są wyświetlane jako zwykły tekst, więc znaczniki na etykiecie nigdy nie są renderowane.
- Zniekształcone wpisy są pomijane zamiast wyrzucać, a przycisk w ogóle nie jest renderowany, gdy nie skonfigurowano niczego użytecznego.
🖋 Niestandardowy moduł renderujący
Podgląd jest renderowany za pomocą wyraźny i zdezynfekowany DOMPoczyść. Obydwa można wymienić. Użyj tej opcji, jeśli Twoja aplikacja już renderuje przeceny w innej bibliotece i chcesz, aby podgląd dokładnie odpowiadał produkcji.
Obie opcje są zwykłymi funkcjami, które pobierają ciąg znaków i zwracają ciąg znaków, więc każdy parser i każdy środek dezynfekujący będzie działać.
1 2 3 4 5 6 7 8 9 | import MarkdownIt from 'markdown-it'; import taskLists from 'markdown-it-task-lists'; import MarkdownEditor from 'markdown-text-editor'; const md = new MarkdownIt({ linkify: true, breaks: true }).use(taskLists); new MarkdownEditor('#markdown-editor', { renderer: markdown => md.render(markdown) }); |
DOMPurify nadal działa na wyjściu, więc zachowujesz ochronę XSS bez konieczności konfigurowania czegokolwiek.
Niestandardowy środek dezynfekujący
Potrzebne tylko wtedy, gdy domyślnie usuwane są elementy emitowane przez moduł renderujący. DOMPurify usuwa <iframe> domyślnie, więc osadzanie wideo wymaga wyraźnej zgody. DOMPurify należy zaimportować we własnym kodzie, ponieważ kopia dołączona do edytora jest wewnętrzna.
1 2 3 4 5 6 7 8 9 | import DOMPurify from 'dompurify'; new MarkdownEditor('#markdown-editor', { renderer: markdown => md.render(markdown), sanitizer: html => DOMPurify.sanitize(html, { ADD_TAGS: ['iframe'], ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder'] }) }); |
Używając znacznika skryptu zamiast pakietu pakującego, załaduj DOMPurify wraz z edytorem:
1 2 | <script src="https://cdn.jsdelivr.net/npm/dompurify"></script> <script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script> |
Rzeczy, które warto wiedzieć
- Tylko podgląd. Formatowanie na żywo w trybie hybrydowym wykorzystuje oddzielny wewnętrzny moduł renderujący i nie ma na to wpływu.
- Listy zadań wymagają obsługi wtyczek. Klikalne pola wyboru można znaleźć, szukając
input[type="checkbox"]na wyjściu, więc przecena jest potrzebnamarkdown-it-task-lists. Edytor rejestruje ostrzeżenie, jeśli wykryje składnię listy zadań i brak pól wyboru. - Oba muszą być synchroniczne i zwracać ciąg znaków. Jakiś
asyncfunkcja pisze[object Promise]w podglądzie. - Wymiana środka dezynfekującego zastępuje ochronę. Przejście, np
html => htmlcałkowicie wyłącza oczyszczanie i jest bezpieczny tylko dla w pełni zaufanych treści.
🎨 Stylizacja elementów wewnętrznych
Zmienne CSS obejmują większość motywów, ponieważ dziedziczą, więc należy je włączyć .markdown-editor-wrapper dociera do paska narzędzi, przycisków, podglądu i stopki. Sięgaj po nazwę klasy tylko wtedy, gdy żadna zmienna nie ujawnia tego, czego potrzebujesz.
1 2 3 4 5 | /* Variables set on the wrapper inherit down to every child */ .markdown-editor-wrapper { --border-radius: 12px; /* rounds the editor and its toolbar buttons */ --color-primary: oklch(60% 0.2 30); } |
Odniesienie do klasy
Nazwy tych klas są stabilne i bezpieczne w stylizowaniu.
| Klasa | Element |
|---|---|
.markdown-editor-wrapper | Zewnętrzny pojemnik owijający cały edytor |
.toolbar | Pasek narzędzi nad obszarem edycji |
.markdown-btn | Indywidualny przycisk paska narzędzi |
.preview-btn | Przycisk przełączania podglądu/pełnego ekranu |
.editor-layout | Siatka zawierająca obszar edycji i podgląd obok siebie |
.textarea-wrapper | Otok wokół obszaru edycji |
.editor-textarea | Podstawowy element pola tekstowego |
.display-layer | Wyrenderowana warstwa formatowania, tylko tryb hybrydowy |
.preview-wrapper | Kolumna podglądu |
.preview-content | Renderowana przecena w podglądzie |
.editor-footer | Pasek stanu poniżej edytora |
.find-replace-panel | Znajdź i zamień panel |
1 2 3 4 5 6 7 8 9 10 | /* Some variables are set by the component on itself, which beats an inherited value. Target the element directly for those. */ .markdown-editor-wrapper .markdown-btn { --btn--font-size: 0.875rem; } /* And use classes for anything no variable exposes */ .markdown-editor-wrapper .toolbar { border-bottom: 2px solid oklch(60% 0.2 30); } |
Kierowanie na jednego redaktora
Jeśli na jednej stronie znajduje się kilku redaktorów, wybierz jednego z nich za pomocą data-editor. Opakowanie odzwierciedla jego identyfikator <textarea>, Więc <textarea id="notes"> daje ci [data-editor="notes"]. Sam identyfikator pozostaje w obszarze tekstowym, więc getElementById pracuje dalej.
1 2 3 4 | /* one editor only */ [data-editor="notes"] { --border-radius: 0; } |
🪟 Pełny ekran i warstwy (indeks Z)
W trybie pełnoekranowym edytor używa z-index: 10000, co usuwa warstwy, które większość frameworków interfejsu użytkownika rezerwuje dla stałych nagłówków i nakładek. Jeśli stały nagłówek lub pasek boczny nadal zakrywa edytor, zwiększ tę wartość.
Zastąp to za pomocą --mte-fullscreen-z-index. Wartość ma zastosowanie tylko w trybie pełnoekranowym i dziedziczy, więc ustawienie jej na dowolnym przodku obejmuje wszystkich edytorów znajdujących się pod nią.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* every editor on the page */ .markdown-editor-wrapper { --mte-fullscreen-z-index: 20000; } /* one section only - the value inherits down */ #admin-panel { --mte-fullscreen-z-index: 20000; } /* a single editor, by its textarea id */ [data-editor="notes"] { --mte-fullscreen-z-index: 20000; } |
Interfejs API treści
Jedną z głównych zalet MarkdownEditor jest to, że utrzymuje bazę <textarea> doskonale zsynchronizowane. Niezależnie od tego, czy używasz nowoczesnego frameworku JavaScript, czy tradycyjnego backendu, takiego jak Django, PHP czy Laravel, przepływ pracy pozostaje prosty i natywny.
Czytanie i pisanie treści
1. Sposób rodzimy (zalecany)
Ponieważ edytor rozszerza standardowy obszar tekstowy, możesz używać znanych metod DOM. To najszybszy sposób na interakcję z danymi bez konieczności poznawania nowego API.
1 2 3 4 5 | // Retrieve content via ID const markdown = document.getElementById('markdown-editor').value; // Set content via ID (The editor UI updates automatically) document.getElementById('markdown-editor').value = "# New Heading Content"; |
2. Używanie odniesienia do zmiennej
Jeśli masz odwołanie do elementu Textarea, możesz go użyć bezpośrednio: nie jest potrzebny żaden interfejs API specyficzny dla biblioteki.
1 2 3 4 5 6 7 | const textarea = document.getElementById('markdown-editor'); // Retrieve content const markdown = textarea.value; // Set content (the editor UI reflects this immediately) textarea.value = "## Updated via JS"; |
3. Ustawianie początkowej zawartości po stronie serwera
Zalecany sposób ustawienia zawartości początkowej odbywa się bezpośrednio w pliku <textarea> HTML: działa to naturalnie z każdym frameworkiem backendowym (Django, Laravel, Rails, PHP itp.), a edytor renderuje go automatycznie podczas init.
1 2 | <!-- Recommended: set content server-side --> <textarea id="markdown-editor"># Hello World</textarea> |
Aby czytać lub aktualizować zawartość w czasie wykonywania, użyj wersji natywnej textarea wartość. Dzwonić editor.render() po aktualizacji w celu odświeżenia podglądu i warstwy hybrydowej.
1 2 3 4 5 6 7 8 | const textarea = document.getElementById('markdown-editor'); // Read const markdown = textarea.value; // Update at runtime textarea.value = '# New content'; editor.render(); |
4. Zburzenie edytora: destroy()
Dzwonić editor.destroy() aby usunąć opakowanie DOM edytora i przywrócić oryginał <textarea> do swojego miejsca w dokumencie. Przydatne w aplikacjach jednostronicowych podczas odmontowywania widoku.
1 2 3 4 | const editor = new MarkdownEditor('#markdown-editor'); // Remove the editor and restore the plain textarea editor.destroy(); |
Reagowanie na zmiany za pomocą onChange
Przekaż onChange wywołanie zwrotne, aby otrzymywać powiadomienia o każdej zmianie treści. Odbiera bieżący ciąg przeceny.
1 2 3 4 5 | const editor = new MarkdownEditor('#markdown-editor', { onChange(value) { console.log('Content changed:', value.length, 'characters'); } }); |
Wersja robocza automatycznego zapisywania za pomocą localStorage
Używać onChange aby zapisać wersję roboczą przy każdym naciśnięciu klawisza. Przywróć go, wypełniając obszar tekstowy przed inicjalizacją edytora.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | const DRAFT_KEY = 'my-page-draft'; // Restore saved draft before init (only if textarea starts empty) const textarea = document.getElementById('markdown-editor'); const saved = localStorage.getItem(DRAFT_KEY); if (saved && !textarea.value) textarea.value = saved; // Save on every change const editor = new MarkdownEditor('#markdown-editor', { onChange(value) { localStorage.setItem(DRAFT_KEY, value); } }); // Clear draft after successful form submission document.querySelector('form').addEventListener('submit', () => { localStorage.removeItem(DRAFT_KEY); }); |
Przesyłanie formularza
Ponieważ MarkdownEditor jest zbudowany bezpośrednio na wersji natywnej <textarea>, jest kompatybilny z każdym frameworkiem backendowym (Django, Laravel, PHP, Ruby on Rails itp.) od razu po wyjęciu z pudełka.
Tutaj błyszczy filozofia „Native-First”. Nie musisz ręcznie synchronizować danych przed przesłaniem formularza. Przeglądarka traktuje edytor dokładnie tak, jak standardowe pole wejściowe.
1 2 3 4 5 6 7 8 9 10 11 12 | <form method="POST" action="/api/submit"> <textarea id="markdown-editor" name="content" class="h-48" rows="5"> # Initial Content </textarea> <button type="submit">Submit to Server</button> </form> <script> // Just initialize it. That's it. new MarkdownEditor('#markdown-editor'); </script> |
Notatka: Inicjalizacja wtyczki MarkdownEditor jest obowiązkowa
Po prostu użyj standardowego kodu HTML <form>. The name atrybut w obszarze tekstowym jest tym, czego Twój serwer użyje do identyfikacji treści.
🚀 Dlaczego to zmienia zasady gry dla backendów
Ponieważ redaktor zachowuje natywny <textarea> zachowanie, serwer obsługuje dane jako standardowy ciąg znaków. Nie jest wymagana żadna dodatkowa logika: nie preventDefault() i brak instrukcji FormData budowa.
💡 Dlaczego jest to „funkcja zabójcza”:
Większość edytorów (np. Quill, Editor.js, simpleMDE, easyMDE) zapisuje dane w złożonych strukturach JSON. Jeśli programista z nich korzysta, musi przepisać schemat bazy danych i logikę renderowania.
Dzięki MarkdownEditor programista może zastąpić starą witrynę zwykłą <textarea> z edytorem, a backend nawet nie wie, że to się zmieniło. Po prostu otrzymuje ten sam surowy tekst, co zawsze, ale użytkownik otrzymuje 10 razy lepsze wrażenia.
| Ramy / język | Jak uzyskać dostęp do treści Markdown |
|---|---|
| PHP | $_POST['content'] |
| Django | request.POST.get('content') |
| Node.js (ekspresowy) | req.body.content |
| Laravel | $request->input('content') |
| Rubin na szynach | params[:content] |
🖼️ Zaawansowane przesyłanie obrazu
Natywna obsługa przesyłania obrazów: zamiast polegać na powolnych, obciążających pamięć ciągach Base64, jest to znacząca korzyść zarówno pod względem wydajności, jak i SEO.
Wklej i upuść
Wklej zrzut ekranu lub przeciągnij plik obrazu do edytora, a zostanie on przesłany przez ten sam punkt końcowy, co przycisk paska narzędzi. Symbol zastępczy pojawia się natychmiast, dzięki czemu edytor nigdy nie sprawia wrażenia zamrożonego, a po przesłaniu pliku zostaje zastąpiony prawdziwą ścieżką. Jeśli przesyłanie nie powiedzie się, symbol zastępczy zostanie zastąpiony widocznym znacznikiem, a nie znika po cichu.
To wymaga uploadUrl skonfigurowany poniżej. Bez tego wklejenie obrazu nic nie da, a edytor zarejestruje ostrzeżenie wyjaśniające, co dodać, ponieważ alternatywą byłby ciąg znaków Base64 w Twojej bazie danych.
Przesyłanych jest kilka plików na raz, więc obrazy lądują w kolejności, w jakiej przybyły.
Opcje konfiguracji
Narzędzie obrazu obsługuje a fileInput konfiguracja do obsługi bezpośredniego przesyłania na serwer.
accept: zdefiniuj tablicę dozwolonych formatów obrazów (np. „webp”, „avif”)uploadUrl: Określ punkt końcowy zaplecza, w którym plikFileobiekt zostanie przesłany za pośrednictwemPOSTparams: Opcjonalny obiekt do wysyłania dodatkowych danych (takich jak tokeny CSRF, identyfikatory użytkowników lub nazwy folderów) wraz z plikiem obrazu
Przykład użycia (pełna konfiguracja)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | const options = { placeholder: 'Start writing...', toolbar: [ 'link', { image: { fileInput: { accept: ['webp', 'avif'], // restrict the image upload format uploadUrl: '/api/upload', // Your upload endpoint params: { _token: 'your_csrf_token_here', // Essential for Laravel/Django folder: 'blog_posts' } }, // Supports boolean: true/false OR object: { required: true } altInput: { required: true } } }, 'preview' ], } const editor = new MarkdownEditor('#markdown-editor', options); |
📡 Integracja serwerów
Żądanie
Redakcja przesyła POST żądanie jako multipart/form-data. Domyślnie zawiera:
image_file: Rzeczywisty obiekt plikuimage_alt: Tekst alternatywny wprowadzony przez użytkownika- ...plus wszelkie niestandardowe dane zdefiniowane w pliku
paramsobiekt
Wymagana odpowiedź
Aby potwierdzić pomyślne przesłanie i wstawić obraz do edytora, Twój serwer musi zwrócić następującą strukturę JSON:
1 2 3 4 | { "success": true, "image_path": "https://cdn.yourdomain.com/uploads/image.webp" } |
Uwaga: Upewnij się, że używasz klucza image_path dla adresu URL przesłanego obrazu.
Możesz także wrócić image_alt, która jest używana jako sugestia tylko wtedy, gdy użytkownik pozostawił pole alt puste. Wszystko, co sami wpisali, zawsze wygrywa, więc serwer, który pobiera tekst alternatywny z nazwy pliku lub rozpoznawania obrazu, może go zaoferować bez nadpisywania prawdziwego opisu.
1 2 3 4 5 | { "success": true, "image_path": "https://cdn.yourdomain.com/uploads/image.webp", "image_alt": "Two people looking at a laptop" } |
W przypadku niepowodzenia wróć success: false z error Lub message smyczkowy. Ten tekst jest wyświetlany użytkownikowi, więc uczyń go czymś, na podstawie czego będzie mógł podjąć działania.
Walidacja tekstu alternatywnego obrazu (altInput)
Aby mieć pewność, że Twoje treści pozostaną dostępne i przyjazne dla SEO, MarkdownEditor domyślnie wymusza sprawdzanie poprawności tekstu alternatywnego.
- Zachowanie domyślne: Jeśli
altInputnie jest zdefiniowany, domyślnie jest to{ required: true } - Wymuś dostępność: Użytkownicy nie będą mogli wstawić obrazu, dopóki nie zostanie podany opis alternatywny
1. Domyślne (nie wymaga konfiguracji)
1 2 3 4 | // Alt text is REQUIRED by default image: { fileInput: { uploadUrl: '/api/upload' } } |
2. Skrót (wyłącz sprawdzanie poprawności)
Jeśli chcesz zezwolić na obrazy bez opisów, po prostu ustaw wartość logiczną na false.
1 2 3 | image: { altInput: false // Users can now skip the alt text field } |
3. Oparte na obiektach (jawne)
1 2 3 4 5 | image: { altInput: { required: false // Disables alt text validation — users can skip the alt field } } |
Standardowe wykorzystanie obrazu (nie fileInput)
Jeśli fileInput nie jest skonfigurowany, edytor domyślnie korzysta z prostego modalu opartego na adresie URL. Jest to idealne rozwiązanie, jeśli Twoi użytkownicy głównie łączą się z zewnętrznymi hostami obrazów.
1 2 3 4 5 6 7 8 | const options = { toolbar: [ 'link', 'image', 'preview' ], } const editor = new MarkdownEditor('#markdown-editor', options); |
💡 Po co używać parametrów?
W frameworkach takich jak Laravel czy Django nie można przesyłać plików bez tokena CSRF. Dodając _token do params obiekt, Twoje żądanie bezproblemowo przejdzie przez oprogramowanie pośredniczące zabezpieczające zaplecza, zachowując filozofię „Zero Logic” dla kontrolerów po stronie serwera.
⌨️ Skróty klawiaturowe
Typowe czynności formatowania można uruchomić bezpośrednio z klawiatury, bez dotykania paska narzędzi. Każdy skrót jest także pokazany w podpowiedzi odpowiedniego przycisku na pasku narzędzi.
| Skrót | Działanie |
|---|---|
Ctrl + B / ⌘ B | Przełącz Pogrubienie |
Ctrl + I / ⌘ I | Przełącz kursywa |
Ctrl + K / ⌘ K | Wstaw łącze |
Ctrl + ` / ⌘ ` | Przełącz w tekście Code |
Ctrl + Shift + S / ⌘ ⇧ S | Przełącz |
Ctrl + 1 / ⌘ 1 | Nagłówek 1 |
Ctrl + 2 / ⌘ 2 | Nagłówek 2 |
Ctrl + 3 / ⌘ 3 | Nagłówek 3 |
Ctrl + L / ⌘ L | Przełącz listę punktowaną |
Ctrl + Z / ⌘ Z | Anulować |
Ctrl + Shift + Z / ⌘ ⇧ Z | Przerobić |
Tab | Wcięcie wybranych linii |
Shift + Tab | Zmniejsz wybrane linie |
Ctrl + F / ⌘ F | Otwórz panel Znajdź |
Ctrl + H / ⌘ H | Otwórz panel Znajdź i zamień |
Ctrl + Shift + F / ⌘ ⇧ F | Przełącz podgląd pełnoekranowy |
F11 | Przełącz podgląd pełnoekranowy |
Escape | Zamknij panel Znajdź / Wyjdź z podglądu pełnoekranowego |
🔍 Znajdź i zamień
W edytorze dostępny jest wbudowany panel wyszukiwania i zamiany: nie jest potrzebne żadne rozszerzenie przeglądarki ani osobne narzędzie.
- Naciskać
Ctrl + F(Lub⌘ F), aby otworzyć panel Znajdź - Naciskać
Ctrl + H(Lub⌘ H), aby otworzyć panel Znajdź i zamień - Wyszukiwanie nie uwzględnia wielkości liter i wyświetla licznik meczów na żywo (np. 3 z 12)
- Nawiguj po meczach za pomocą przycisków ▲ / ▼ lub
Enter/Shift + Enter - Zamień zastępuje aktualnie podświetlone dopasowanie; Opcja Zamień wszystko powoduje jednoczesne zastąpienie wszystkich wystąpień
- Naciskać
Escapeaby zamknąć panel i powrócić do edytora
Panel unosi się w prawym górnym rogu obszaru zawartości edytora i nie zakłóca pisania.
🔗 Linki z wklejonego adresu URL
Wybierz jakiś tekst, wklej nad nim adres URL, a zamiast adresu URL zastąpisz wybrany link, otrzymasz link przecenowy.
1 2 | Read the docs <- select "the docs", paste https://example.com Read [the docs](https://example.com) <- what you get |
Zasada jest celowo surowa: tylko naga http Lub https Adres URL bez białych znaków jest traktowany w ten sposób. Wklejenie zdania zawierającego adres URL lub wklejenie bez zaznaczenia zachowuje się jak zwykłe wklejanie.
Cechy
🔌 Integracja formularzy natywnych
Działa dokładnie jak standard <textarea>. Żadnych skomplikowanych interfejsów API: po prostu użyj value Lub name atrybut. To „po prostu działa” ze standardowymi formularzami HTML w PHP, Django lub Node.js.
🖼️ Zaawansowane przesyłanie obrazu
Skonfiguruj przesyłanie natywnego serwera za pośrednictwem interfejsu API. Unikaj ciężkich ciągów Base64, aby zapewnić szybsze ładowanie strony i lepsze SEO, hostując obrazy we własnej sieci CDN.
🔀 Tryby hybrydowe i zwykłe
Przełącz między Hybrydowy (WYSIWYG) doświadczenie w edycji wizualnej lub Zwykły Markdown tryb zapewniający tradycyjne wrażenia z kodowania.
🚀 Wysoka wydajność
A 53 KB spakowane w formacie gzip pakiet (zminimalizowany 252 KB, zawiera CSS) zoptymalizowany pod kątem „Ciężkich treści”. Obsługuje ogromne dokumenty i duże pliki bez opóźnień wejściowych i spadków wydajności. Odrzucone aktualizacje podglądu, obliczenia stylu w pamięci podręcznej i bezkonfliktowa obsługa klawiatury: więc Tab i Enter zawsze robią dokładnie jedną rzecz.
🌍 Wbudowana obsługa RTL
Natywna obsługa języków pisanych od prawej do lewej, takich jak arabski, urdu i perski. Idealny do tworzenia aplikacji dostępnych na całym świecie.
✨ Podświetlanie składni
Zwiększona czytelność dzięki przejrzystemu kodowi i formatowaniu przecen.
🌙 Motywy adaptacyjne
Zawiera automatyczną obsługę trybu ciemnego. Synchronizuje się z ustawieniami systemu lub Interfejs użytkownika Frutjama biblioteka zapewniająca płynne wrażenia wizualne.
📝 Inteligentna edycja
Automatyczna kontynuacja list w stylu GitHuba dla list uporządkowanych, list nieuporządkowanych i list kontrolnych: naciśnij Enter a redaktor kontynuuje wzór. Pola wyboru w okienku podglądu można kliknąć i natychmiast synchronizować ze źródłem przecen.
📱 W pełni responsywny
Płynny, dostosowany do urządzeń mobilnych interfejs użytkownika, który idealnie dopasowuje się do komputerów stacjonarnych, tabletów i smartfonów, umożliwiając edycję w dowolnym miejscu.
📦 Uniwersalne wsparcie
Kompatybilny z ESM, UMD, CommonJS i IIFE. Działa od razu po wyjęciu z pudełka przez CDN (<script src>), npm lub dowolny pakiet (Vite, webpack, Rollup): nie jest wymagana dodatkowa konfiguracja.
♿ Dostępne domyślnie
Wbudowana pełna obsługa ARIA: punkt orientacyjny na pasku narzędzi, oznaczony obszar podglądu, przyciski przyjazne dla czytnika ekranu, aria-pressed na przełączniku podglądu, disabled I aria-disabled na nieaktywnych narzędziach i prawidłowe przywracanie ostrości po zamknięciu modułów.
🛡️ Zero konfliktów CSS
Style edytora są w pełni dostępne .markdown-editor-wrapper. Globalna inspekcja wstępna Tailwinda jest wykluczona, więc edytor może bezpiecznie współpracować z Bootstrapem, Tailwindem lub jakimkolwiek innym frameworkiem, nie naruszając ich stylu.
⌨️ Skróty klawiaturowe
Ctrl+B, Ctrl+I, Ctrl+K, Ctrl+`, Ctrl+Shift+S: typowe operacje formatowania bez dotykania myszy. Każdy skrót jest pokazany w etykiecie przycisku paska narzędzi.
🔍 Znajdź i zamień
Naciskać Ctrl+F znaleźć lub Ctrl+H aby otworzyć opcję Znajdź i zamień. Wyszukiwanie bez rozróżniania wielkości liter z licznikiem dopasowań na żywo, nawigacją następny/poprzedni, pojedynczą zamianą i zamianą wszystkich: bez opuszczania edytora.
🔒 Podgląd bezpiecznego XSS
Wyrenderowany podgląd jest oczyszczany poprzez DOMPoczyść przed zapisaniem w DOM. Tagi skryptów, wbudowane procedury obsługi zdarzeń i złośliwe adresy URL w spreparowanych danych wejściowych Markdown są usuwane automatycznie: nie jest wymagana żadna konfiguracja.
▶️ Podgląd w czasie rzeczywistym
Zobacz, jak Twoje przeceny są renderowane natychmiastowo podczas pisania.
🔗 Łatwa integracja
Bezproblemowo integruj się z dowolnym projektem internetowym przy minimalnej konfiguracji.
🛠️ Konfigurowalny pasek narzędzi
Dynamicznie konfiguruj i zmieniaj kolejność opcji paska narzędzi, takich jak pogrubienie, kursywa i inne.
Przykład pełnej konfiguracji
Użyj tego obszernego przykładu, aby zainicjować MarkdownEditor ze wszystkimi podstawowymi funkcjami, w tym niestandardową kolejnością pasków narzędzi i zaawansowaną obsługą przesyłania obrazów.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | const editor = new MarkdownEditor('#markdown-editor', { mode: 'hybrid', placeholder: 'Start writing...', footer: { line: true, col: true, chars: true, words: true, }, onChange(value) { console.log('Content updated:', value.length, 'characters'); }, labels: { 'Bold': 'Gras', 'Italic': 'Italique', 'Uploading...': 'Téléversement...', }, toolbar: [ 'heading', 'bold', 'italic', 'strikethrough', 'blockquote', 'ul', 'ol', 'checklist', 'code', 'codeblock', 'hr', 'table', { image: { fileInput: { accept: ['webp', 'avif', 'png'], uploadUrl: '/api/upload' } } }, 'link', 'undo', 'redo', 'indent', 'outdent', 'preview' ], }); // Read content natively const markdown = document.getElementById('markdown-editor').value; // destroy() when the view unmounts (SPAs) // editor.destroy(); |
Uznałeś to za przydatne?
Gwiazda GitHuba pomaga innym programistom odkryć edytor. To część Frutjam. Gwiazdka też pomaga.