MVP Przekazywanie parametrów do komponentów widoku

MVP Przekazywanie parametrów do komponentów widoku
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1

Na pewno stęskniliście się za ciekawymi rozkminami na temat inżynierii oprogramowania więc wracamy dziś w kolejnej odsłonie. Problem, który chciałbym poruszyć jest związany z przekazywaniem danych do kontrolek w widoku we wzorcu MVP.
W najbardziej klasycznym ujęciu na prezenterze wywołana zostaje jakaś akcja. Prezenter uruchamia odpowiednie serwisy a następnie zebrane dane przesyła do warstwy widoku w celu ich prezentacji. Problem pojawia się właśnie w tym ostatnim punkcie - jak zrobić to przekazywanie z głową?

Załóżmy, że mam widok który składa sie z kilku komponentów, każdy z nich z jeszcze kilku i tak przez pare poziomów. Np. mamy okienko w którym jest kilka paneli, jeden z tych paneli zawiera jeszcze kilka innych paneli i na koniec ma pole tekstowe do którego właśnie ładuje sobie dane. W efekcie z punktu widzenia kodu mam taką śmieszną delegację metod bo prezenter wysyła dane do widoku, widok wysyła do odpowiedniego okienka, okienko do panelu, panel do swojego podpanelu a tamten dopiero do pola tekstowego. Kiedy na tym najniższym poziomie komponentów jest dużo to nagle te klasy wyżej mają po kilkanaście (lub więcej) delegowanych wywołań. Coś w stylu:

Kopiuj
class View{
     private final MainWindow window;
//
     public void loadText(String text){
         window.loadText(text);
     }
}

class MainWindow{
     private final MainPanel mainPanel;
//
     public void loadText(String text){
         mainPanel.loadText(text);
     }    
}

class MainPanel{
     private final TextPanel textPanel;
//
     public void loadText(String text){
         textPanel.loadText(text);
     }    
}

class TextPanel{
     private final TextField textField;
//
     public void loadText(String text){
         textField.loadText(text);
     }    
}

Jednym pomysłem byłoby spłaszczenie struktury, tzn władowanie tych komponentów na poziomie jednej klasy, ale efekt raczej odwrotny od zamierzonego bo mamy wtedy klasę mother-of-all-views ;) Niemniej nie bardzo jest to możliwe bo te komponenty są wielokrotnie re-używane więc ich separacja ma generalnie sens.

Od razu mówie że wiem że są technologie które ten problem rozwiązują poprzez wiązanie danych z komponentami za pomocą identyfikatorów (np. technologie webowe zwykle tak robią, robi tak też chyba WPF). Swing takich cudów nie posiada więc jeśli chciałbym coś takiego mieć to musiałbym to sam napisać i wydaje mi się to niekoniecznie eleganckie, chyba że ktoś ma pomysł jak można by coś takiego zaimplementować (w sposób sensowny więc nie poprzez pchanie jakiegoś Map<String, Object> a potem rzutowanie tego objecta...)

Podkreślam tu że chodzi o MVP ponieważ w MVC zwykle istnieje jakieś powiązanie modelu z widokiem i aktualizacja następuje "automatycznie" za pomocą jakiegoś Observera. Niemniej w tym przypadku trudno mówić o jakimś "modelu" w sensie danych przechowujących stan aplikacji, jak w jakiejś grze komputerowej. Niemniej teoretycznie mógłbym tak zrobić, że komponenty rejestrują sie gdzieś jako obserwatorzy, tylko gdzie? W prezenterze?

@somekind @Koziołek @Krolik @niezdecydowany @karolinaa @msm @winerfresh @stryku @Patryk27 @katelx @WhiteLightning @datdata @spartanPAGE @Wizzie @Azarien @Satirev @MarekR22 @Afish @panryz @Wibowit @azalut @mychal @_Mateusz_ @krzysiek050
(wołam tych co ostatnio plus tych co sie wypowiadali więc jak kogoś nie ma to sam sobie winien bo mi ostatnio nic nie napisał :P)


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
KA
niezatapialnie w top5 :}
Shalom
przeciez skopiowałem z ostatniego tematu ;]
panryz
  • Rejestracja:prawie 17 lat
  • Ostatnio:około 2 godziny
1

Dobra skoro zaś ja to się wypowiem.
Ponieważ w Androidzie (i tutaj zaznaczam ostro, bo wiem że to nie to samo co czysta java) jest tak że często robi się sporo aktywności, fragmentów, które właśnie prezentują dane. Robię to w ten sposób że tworzę sobie jedną klasę (Singleton) gdzie trzymam obiekty, interfejsy. Do tych interfejsów przekazuję instancję każdego Activity

setSomethingListener(this) <- Tak, zapewne swing tego nie ma o czym @Shalom wspomniał. Ale idąc dalej wspominałeś o obserwatorze. Więc wydaje mi się że ta droga może być dobra i tak samo bym zrobił takiego singletona który by mi to trzymał. A potem każdy widok zarejstrował się na wywołanie metody z tego listenera.
W konsekwencji każdy widok który chciałby sobie zaktualizować kontrolkę jest zarejestorwany na to i dzieje się to "automatycznie".

Dispatch design pattern - tak to się nazywa chyba profesjonalnie

Koziołek
Moderator
  • Rejestracja:prawie 18 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Stacktrace
  • Postów:6821
6

W teorii
Można by, ale to nie jest zbyt efektywne, zaimplementować coś w rodzaju "szukajki". Załóżmy, że prezenter zwraca do widoku tą nieszczęsną Map<String, Object> (która technicznie rzecz biorąc może być "pod spodem" JSON-em). Teraz widok powiadamia wszystkie zapisane w nim obserwatory, że ma nowe dane i mogą sobie je przeszukać. Te robią przegląd szukając swoich identyfikatorów.

W praktyce

  1. Naiwnie implementację oparłbym na Guava Event Bus. Każdy "końcowy" komponent niech implementuje sobie jakąś metodę oznaczoną jako @Subscribe i przy tworzeniu niech się rejestruje w EvenBus (globalna, ale zakładam, że to nie problem na teraz). Widok po odebraniu mapy rozsyła ją do elementów.

  2. Trochę inna, bardziej popieprzona, implementacja, która będzie trochę wydajniejsza. robimy jak wyżej, ale zamiast mapy w której kluczem jest jakieś tam id komponentu, a wartość musimy rzutować przekazujemy listę czegoś co dziedziczy po Event<T>:

Kopiuj
public class Event<>{

    final String componentId;
    final T value;

   //...
}

Widok po odebraniu komunikatu z prezentera rozgłasza listę eventów. Zalety:

  • Guava dba o to by komunikaty trafiły do odpowiednich odbiorców zapisanych na konkretny typ. Zatem jeżeli masz pole tekstowe, które oczekuje np. Integer, to dostanie IntegerEvent.
  • Masz możliwość częściowej aktualizacji, bo istnieje mechanizm związany z componentId i jeżeli dany subskrybent nie dopasuje go do swojego identyfikatora to nie zmieni się.

Wady:

  • EventBus jako globalny element widoku. Trzeba by dopisać tak naprawdę kawałek kodu by odciąć komponenty bezpośrednio od szyny.
  • Może się zdarzyć tak, że będzie dużo eventów jednego typu i zrobisz DDOS własnych komponentów.

To tak na szybko.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
panryz
Kuźwa zapomniałem o EventBus :D może dlatego że z niego nie korzystam, ale tak jest to dobre narzędzie.
MA
Ja osobiście, kiedyś w podobnym problemie korzystałem właśnie z takiej rekurencyjnej szukajki. Presenter przekazywał do View KeyValuePair a ten zrobił co trzeba.
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:2 dni
0

Skoro już mnie zawołano.. nie jestem zwolennikiem sztywnego przestrzegania wzorców, więc powiem że powyższy kod ma o jedną warstwę za dużo.

View jest niepotrzebne, albo może być ale jako klasa abstrakcyjna czy interfejs, implementowany przez MainWindow.

Ale jak mamy się trzymać książkowych wzorców, to nie narzekajmy że są nieżyciowe..

Shalom
Och ale to tylko przykład. Wyobraź sobie że widok i prezenter kontrolują kilka okien dialogowych związanych z tą sama funkcjonalnością i wtedy ten Widok ma w sobie kilka "okienek". Poza tym jest też kwestia hierarchi dziedziczenia w Javie - może tak być że Widoki mają z jakiegoś powodu wspólną bazę a MainWindow musi dziedziczyć z jakiegoś javowego Window czy JFrame. Ale to generalnie szczegóły ;)
0

Dane zbieram w jakieś struktury/modele(zazwyczaj wypracowany status to paczka danych). Dzięki temu "klasy wyżej" mają kilka metod i te modele lub ich części delegowane są dalej.
Dodatkowo korzystam z Visitora, czyli wołam na modelach metodę, gdzie argumentem jest gui ukryte za interfejsem z referencją na siebie, dzięki czemu unikam castowania, ifów, instance of...

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

@garai_nz hmm no ale jak mam np. 20 różnych DTO bo tyle funkcji prezentera to chcesz wysłać taki mother-of-all-DTO z tymi 20 polami gdzie tylko jeden jest "wypełniony" i potem każdy sobie wybiera co mu potrzebne? To raczej słabe ;) A jeśli mówisz o samym fakcie pakowania danych do DTO to jasne że tak robię ale to nijak nie zmniejsza liczby delegowanych metod.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 1x, ostatnio: Shalom
GA
przecież nigdzie nie napisałem, żebyś wrzucił wszystko do jednego worka. to raczej Ty chciałeś spłaszczyć gui aby sobie poscrollować :) 20 różnych dto to sporo, więc wnioskuję że albo masz gui od wszystkiego albo dto są wraperami na prymitywy :) może można je sensowniej pogrupować?
Shalom
Nie no tak poważnie to wcale nie ma ich tak dużo ale trzeba wszystko brać pod uwage :D
Wizzie
  • Rejestracja:prawie 11 lat
  • Ostatnio:ponad 7 lat
1

Może coś w stylu okrojonego Fluxa (pattern do kontroli danych z JS'owego Reacta)? Musisz poczytać o tym poza moim postem, żeby załapać zasadę działania.

Kopiuj
public interface <T> Store { // nie wiem czy to dobrze zdefiniowałem, mało ostatnio w Javie pisałem
    void update(T data);  // aktualizuje stan obiektu, woła callbacki zarejestrowane z subscribe
    void subscribe(/* callback */); // callback wołany za każdym razem jak ktoś rzuci update
    // jeszcze ewentualnie getInitialState dla pobrania domyślnego stanu, ale to już jak tam sobie wymyślisz :P
}

Singletony np. TextStore<String> rozszerzające ten Store wyżej (zaczerpnięte fluxowe nazewnictwo) z danymi, masz tą instancję TextStore tylko w klasie View i TextPanel. W View wołasz metodę update na storze, w TextPanel wrzucasz tylko callbacka do subscribe i śmiga.
Dla klasy renderującej np. ustawienia programu zrobisz SettingsStore<Setting> przykładowo itd.

We Fluxie to wygląda inaczej, bo nie ma żadnego update, tylko są akcje i store nasłuchują akcji (cały ten ambaras żeby zachować unidirectional flow, ale u ciebie nie widzę potrzeby jakiejś, poza tym Java to inny świat niż JS)

Teraz dlaczego tak, a nie jeden singleton ze stanem? Bo a) SRP, b) klasy są małe co wynika z SRP, c) nie przychodzi mi do głowy nic lepszego :D

Co o tym myślicie?

edytowany 2x, ostatnio: Wizzie
AL
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 4 lata
  • Postów:25
2
Shalom napisał(a):

Jednym pomysłem byłoby spłaszczenie struktury, tzn władowanie tych komponentów na poziomie jednej klasy, ale efekt raczej odwrotny od zamierzonego bo mamy wtedy klasę mother-of-all-views ;) Niemniej nie bardzo jest to możliwe bo te komponenty są wielokrotnie re-używane więc ich separacja ma generalnie sens.

Nie byłem wołany ale zapytam.
A prezenter w tym co podałeś nie jest w tym momencie mother-of-all-presenters? Bardzo mocno kombinując można by każdemu re-używalnemu komponentowi dołożyć jego własny prezenter, te mniejsze prezentery byłyby zbierane w kolekcji w głównym prezenterze który odpowiadałby za komunikację z modelem i te pomniejsze implementowałyby interfejs który "pytałby" komponentu czy przyjmuje dane informacje (stała,enum ?).
Ale "pogdybałem", to zalety ma jakieś plusy? Ile minusów? ;)

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

@Alag nie da rady bo co prawda w warstwie widoku te komponenty wyglądają tak samo, ale robią coś zupełnie innego ;) Wyobraź sobie że komponentem jest panel z polem tekstowym, buttonem "browse" do przeglądania plików na dysku i guzikiem "load". Jak widać komponent jest zupełnie generyczny i można go wcisnąć w 100 miejsc, ale akcja "load" jest zupełnie inna za każdym razem ;)
A prezentery są raczej bardzo małe, mają raptem po kilka metod ;)

@Wizzie @Koziołek no ten pomysł (bo mówicie o podobnych rzeczach) z event busem / observerem nie jest w sumie taki zły ;)


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 2x, ostatnio: Shalom
Koziołek
Ja wiem, że nie jest zły, bo ja go tak praktycznie zastosowałem przy obróbce obrazków do komiksów. Dawał radę, a trochę danych przepychaliśmy.
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:13 dni
1

Tak na szybko, bez dogłębnej analizy problemu:
Zrób coś w stylu command i potraktuj jako tunelujące się zdarzenie. Miałbyś wtedy

Kopiuj
interface IUpdate{}
class UpdateLabel : IUpdate{
  public String Text {get; private set;
} 

Następnie widok implementuje:

Kopiuj
class View{
  public bool Update(IUpdate details){
    if(!mainWindow.update(details)){
      // Element podrzędny nie obsłużył, więc ja spróbuję
    }
  }
}
 

A panel miałby coś takiego:

Kopiuj
class TextPanel {
  public bool Update(IUpdate details){
    if(details instanceof UpdateLabel){
       UpdateMyLabel(details);
       return true;
    }
    return false;
  }
} 

Oczywiście to tylko zarys. Logikę aktualizacji możesz też wrzucić do jakiejś bazowej klasy dziedziczącej po IUpdate i potem robić double dispatch przy pomocy wizytora.

krzysiek050
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 4 lata
  • Postów:1272
1

Osobiście bardzo podoba mi się system eventów w angularze. I tak tutaj moim zdaniem najlepsza byłaby propagacja w dół od głównego rodzica.

Załóżmy że B to jest główny komponent, który posiada dzieci, które posiadają dzieci. Używając wspomnianego Guavowego event bus, można zrobić coś takiego.

Kopiuj
import java.lang.reflect.Field;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

import org.apache.commons.lang3.ClassUtils;

public class Test {

	public static void main(String[] args) {
		final B b = new B();
		b.post(new TextFieldChanged("Zmiana 1"));
		b.post(new TextFieldChanged("Zmiana 2"));
	}

}

interface EventComponent {
	void registerChildrens(EventBus e);
}

abstract class AI implements EventComponent {
	@Override
	public void registerChildrens(EventBus e) {
		for (Field f : getClass().getDeclaredFields()) {
			for (Class<?> i : ClassUtils.getAllInterfaces(f.getType())) {
				if (i == EventComponent.class) {
					try {
						final EventComponent eventComponent = (EventComponent) f.get(this);
						if (eventComponent != null) {
							e.register(eventComponent);
							eventComponent.registerChildrens(e);
						}
					} catch (Exception e1) {
						e1.printStackTrace();
					}
				}
				;
			}
		}
	}
}

class B extends AI {
	C c;
	C c2 = new C();
	String s;
	EventBus bus = new EventBus();

	B() {
		registerChildrens(bus);
	}

	public void post(Object event) {
		bus.post(event);
	}
}

class C extends AI {
	D d1 = new D();
	D d2 = new D();

	@Subscribe
	public void listen(TextFieldChanged event) {
		System.out.println("From C: " + event.getMessage());
	}

}

class D extends AI {
	@Subscribe
	public void listen(TextFieldChanged event) {
		System.out.println("From D: " + event.getMessage());
	}

}

class TextFieldChanged {

	private final String message;

	public TextFieldChanged(String message) {
		this.message = message;
	}

	public String getMessage() {
		return message;
	}
}

Output:

Kopiuj
From C: Zmiana 1
From D: Zmiana 1
From D: Zmiana 1
From C: Zmiana 2
From D: Zmiana 2
From D: Zmiana 2
 

Zakładając że nie będzie eventów typu String, tylko złożone to self DDOS w cale nam nie grozi. Kod oczywiście klepany na szybko żeby przedstawić koncepcję, także ten ;P

edytowany 1x, ostatnio: krzysiek050
Wizzie
  • Rejestracja:prawie 11 lat
  • Ostatnio:ponad 7 lat
0

@Shalom jak to rozwiązałeś?

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Tak jak sugerowała większość ;) Wykorzystałem sugerowane przez @Koziołek guava Event Bus i wygląda że działa bardzo fajnie. Niemniej nie było tu potrzeby pchać żadnej mapy z objectami. Mam sobie Prezenterów które mają odpowiednie metody. Po wywołaniu metody, prezenter odbiera z odpowiedniego serwisu DTO z danymi i posyła je do Widoku. Każdy Widok ma swój Event Bus i za jego pomocą wysyła event odpowiedniego typu.
Komponenty "końcowe" rejestrują się w Widoku na eventy i maja metody @Subscribe.
Event Bus per Widok jest o tyle konieczne, że komponenty w warstwie widoku mogą być używane w wielu widokach (np. taki sam komponent może być użyty w dwóch różnych okienkach i robić zupełnie co innego) i głupio byłoby jakby się eventy mieszały w takiej sytuacji. Teraz każdy komponent reaguje tylko na zdarzenia ze swojego Widoku. Poza tym było to dość proste w implementacji bo i tak każdy komponent miał dostęp do Widoku (konieczne, bo jeden Prezenter może sterować wieloma Widokami tego samego typu, np. mozesz odpalić kilka identycznych okienek, więc Prezenter wykonując akcje musi wiedzieć który Widok ma dostać dane :) )


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)