Mam bardzo mały podzbiór Markdown wraz z niestandardowym plikiem HTML, który chciałbym przeanalizować w komponentach React. Na przykład chciałbym włączyć następujący ciąg:
hello *asdf* *how* _are_ you !doing! today
Do następującej tablicy:
[ "hello ", <strong>asdf</strong>, " ", <strong>how</strong>, " ", <em>are</em>, " you ", <MyComponent onClick={this.action}>doing</MyComponent>, " today" ]
a następnie zwróć go z funkcji renderowania React (React poprawnie wyrenderuje tablicę jako sformatowany HTML)
Zasadniczo chcę dać użytkownikom możliwość użycia bardzo ograniczonego zestawu Markdown do zamiany tekstu na komponenty w stylu (a w niektórych przypadkach na własne!)
Niebezpiecznie jestSetInnerHTML, i nie chcę wprowadzać zewnętrznej zależności, ponieważ wszystkie są bardzo ciężkie i potrzebuję tylko bardzo podstawowej funkcjonalności.
Obecnie robię coś takiego, ale jest to bardzo kruche i nie działa we wszystkich przypadkach. Zastanawiałem się, czy istnieje lepszy sposób:
function matchStrong(result, i) {
let match = result[i].match(/(^|[^\\])\*(.*)\*/);
if (match) { result[i] = <strong key={"ms" + i}>{match[2]}</strong>; }
return match;
}
function matchItalics(result, i) {
let match = result[i].match(/(^|[^\\])_(.*)_/); // Ignores \_asdf_ but not _asdf_
if (match) { result[i] = <em key={"mi" + i}>{match[2]}</em>; }
return match;
}
function matchCode(result, i) {
let match = result[i].match(/(^|[^\\])```\n?([\s\S]+)\n?```/);
if (match) { result[i] = <code key={"mc" + i}>{match[2]}</code>; }
return match;
}
// Very brittle and inefficient
export function convertMarkdownToComponents(message) {
let result = message.match(/(\\?([!*_`+-]{1,3})([\s\S]+?)\2)|\s|([^\\!*_`+-]+)/g);
if (result == null) { return message; }
for (let i = 0; i < result.length; i++) {
if (matchCode(result, i)) { continue; }
if (matchStrong(result, i)) { continue; }
if (matchItalics(result, i)) { continue; }
}
return result;
}
Oto moje poprzednie pytanie, które doprowadziło do tego.
font _italic *and bold* then only italic_ and normal
? Jaki byłby oczekiwany wynik? Czy nigdy nie będzie zagnieżdżony?asdf*
bez znikania)Odpowiedzi:
Jak to działa?
Działa poprzez czytanie fragmentu łańcucha po kawałku, co może nie być najlepszym rozwiązaniem dla naprawdę długich łańcuchów.
Ilekroć parser wykryje, że krytyczny fragment jest czytany, tj.
'*'
Lub jakikolwiek inny znacznik, zaczyna analizować fragmenty tego elementu, aż parser znajdzie znacznik zamykający.Działa na ciągach wieloliniowych, patrz na przykład kod.
Ostrzeżenia
Nie określiłeś, lub mógłbym źle zrozumieć twoje potrzeby, jeśli istnieje konieczność parsowania tagów pogrubionych i kursywnych , moje obecne rozwiązanie może nie działać w tym przypadku.
Jeśli jednak potrzebujesz pracować z powyższymi warunkami, po prostu skomentuj tutaj, a ja poprawię kod.
Pierwsza aktualizacja: poprawia sposób traktowania znaczników przeceny
Tagi nie są już zakodowane na stałe, ale są mapą, na której można łatwo rozszerzyć w celu dopasowania do swoich potrzeb.
Naprawiono błędy, o których wspomniałeś w komentarzach, dzięki za wskazanie tych problemów = p
Druga aktualizacja: znaczniki przeceny o wielu długościach
Najłatwiejszy sposób na osiągnięcie tego: zastąpienie znaków o wielu długościach rzadko używanym Unicode
Chociaż metoda
parseMarkdown
nie obsługuje jeszcze tagów o wielu długościach, możemy z łatwością zastąpić te znaczniki o dużej długości prostymistring.replace
podczas wysyłania naszegorawMarkdown
rekwizytu.Aby zobaczyć przykład tego w praktyce, spójrz na
ReactDOM.render
, znajdujący się na końcu kodu.Nawet jeśli aplikacja nie obsługuje wiele języków, są nieprawidłowe znaki Unicode że JavaScript nadal wykrywa, np .:
"\uFFFF"
nie jest poprawnym Unicode, jeśli dobrze pamiętam, ale JS nadal będzie mógł je porównać ("\uFFFF" === "\uFFFF" = true
)Na początku może wydawać się hack-y, ale w zależności od przypadku użycia nie widzę większych problemów z korzystaniem z tej trasy.
Kolejny sposób na osiągnięcie tego
Cóż, możemy łatwo śledzić ostatnie
N
(gdzieN
odpowiada długości najdłuższego tagu o wielu długościach).Należy wprowadzić pewne poprawki w sposobie, w jaki
parseMarkdown
zachowuje się metoda wewnątrz pętli , tj. Sprawdzanie, czy bieżący fragment jest częścią znacznika o wielu długościach, jeśli jest on używany jako znacznik; w przeciwnym razie``k
musielibyśmy to oznaczyć jakonotMultiLength
coś podobnego i przesunąć ten fragment jako treść.Kod
Link do kodu (TypeScript) https://codepen.io/ludanin/pen/GRgNWPv
Link do kodu (waniliowy / babel) https://codepen.io/ludanin/pen/eYmBvXw
źródło
This must be *bold*
zThis must be *bo_ld*
. Powoduje zniekształcenie wynikowego kodu HTMLWygląda na to, że szukasz małego, bardzo podstawowego rozwiązania. Nie takie „super-potwory” jak
react-markdown-it
:)Chciałbym polecić Ci https://github.com/developit/snarkdown który wygląda dość lekki i ładny! Tylko 1kb i niezwykle prosty, możesz go użyć i rozszerzyć, jeśli potrzebujesz innych funkcji składni.
Lista obsługiwanych tagów https://github.com/developit/snarkdown/blob/master/src/index.js#L1
Aktualizacja
Zauważyłem, że reagują komponenty, na początku tego nie zauważyłem. To dla ciebie świetne. Uważam, że możesz wziąć bibliotekę jako przykład i zaimplementować niestandardowe wymagane komponenty, aby zrobić to bez niebezpiecznego ustawiania HTML. Biblioteka jest dość mała i przejrzysta. Baw się dobrze! :)
źródło
Wynik:
Wynik testu Regexp
Wyjaśnienie:
Możesz zdefiniować tagi w tej sekcji:
[*|!|_]
po dopasowaniu jednego z nich zostanie on przechwycony jako grupa i nazwany „tag_begin”.A następnie
(?<content>\w+)
przechwytuje zawartość zawiniętą przez tag.Znacznik końcowy musi być taki sam jak poprzednio dopasowany, więc tutaj używa
\k<tag_begin>
, a jeśli pomyślnie przejdzie test, przechwyć go jako grupę i nadaj mu nazwę „tag_end”, tak właśnie(?<tag_end>\k<tag_begin>))
jest.W JS utworzyłeś tabelę taką:
Użyj tej tabeli, aby zastąpić dopasowane tagi.
Sting.replace ma przeciążenie String.replace (regexp, funkcja) która może przyjmować przechwycone grupy jako parametry, używamy tych przechwyconych elementów do wyszukiwania tabeli i generowania zastępującego łańcucha.
[Aktualizacja] Zaktualizowałem
kod, zachowałem pierwszy, na wypadek, gdyby ktoś nie potrzebował reagować na komponenty i widać, że między nimi jest niewielka różnica.
źródło
console.log
wynik, zobaczysz, że tablica jest pełna ciągów, a nie rzeczywistych składników React: jsfiddle.net/xftswh41możesz to zrobić w następujący sposób:
źródło
A working solution purely using Javascript and ReactJs without dangerouslySetInnerHTML.
Podejście
Wyszukiwanie znak po znaku dla elementów przecenionych. Jak tylko zostanie napotkany, wyszukaj tag końcowy dla tego samego, a następnie przekonwertuj go na HTML.
Tagi obsługiwane we fragmencie
Dane wejściowe i wyjściowe z fragmentu:
JsFiddle: https://jsfiddle.net/sunil12738/wg7emcz1/58/
Kod:
Szczegółowe wyjaśnienie (z przykładem):
Załóżmy, że jeśli ciąg znaków to
How are *you* doing?
Zachowaj mapowanie symboli na tagi["How are "]
i rozpoczyna wewnętrzną pętlę, aż znajdziesz następną *.Now next between * and * needs to be bold
, konwertujemy je w elemencie HTML tekstem i bezpośrednio pchamy tablicę, w której Tag = b z mapy. Jeśli tak<Tag>text</Tag>
, reaguj wewnętrznie konwertuje na tekst i wypychaj do tablicy. Teraz tablica to [jak się masz , ty ]. Zerwij z wewnętrznej pętliHow are <b>you</b> doing?
Note: <b>you</b> is html and not text
Uwaga : Zagnieżdżanie jest również możliwe. Musimy nazwać powyższą logikę rekurencją
Aby dodać obsługę nowych tagów
map
obiekcie z kluczem jako znakiem i wartością jako odpowiednim znacznikiemCzy obsługuje zagnieżdżanie? Nie
Czy obsługuje wszystkie przypadki użycia wymienione przez OP? tak
Mam nadzieję, że to pomoże.
źródło
asdf
że będą renderowane na<pre>asdf</pre>
ciemnym tle, prawda? Daj mi znać, a zobaczę. Nawet ty możesz spróbować teraz. Proste podejście: w powyższym rozwiązaniu zamień `` `w tekście na znak specjalny, taki jak ^ lub ~, i zamapuj go na tag wstępny. To będzie działać dobrze. Inne podejście wymaga nieco więcej pracy<pre>asdf</pre>
. Dzięki!pre
obsługę tagów. Daj mi znać, jeśli to