jekyll für Nicht-Hacker

Wie auch Herr und Frau Normalsterblich mit dem Hacker-Blog-Generator arbeiten können

Intro: jekyll - ein wahrgewordener Traum?

Jekyll ist ein statischer Seitengenerator. Ein Seitengenerator, der unter einem ganz bestimmten Slogan bekannt ist.

blog like a hacker1

Nun, die „Hacker“ unter uns, sind natürlich begeistert davon. Wir können uns mit jekyll so richtig austoben. Man kann damit alle möglichen Seiten schreiben und so ziemlich all das erreichen, was Anbieter wie WordPress und Co. auch bieten. Aber jekyll Seiten haben einen grossen Unterschied zu diesen vorwiegend PHP-betriebenen Seiten. Sie sind komplett statisch. Bestehen am Ende also aus reinem HTML. Mit anderen Worten: Es gibt keine Datenbanken, die die Seite verwundbar machen. Kein „clutter“ über den wir bei jeder WordPress Seite wieder fluchen, dass er die Geschwindigkeit beeinflusst. Und allem voran: Kein WYSIWYG2-Editor, dem wir jedes Mal auf’s Neue den Kampf ansagen, wenn wir mal Inhalt produzieren müssen. Jekyll-pages entstehen dort, wo wir uns am liebsten aufhalten und mit unseren liebsten Sprachen. Wir können Markdown schreiben und zwar in unserem liebsten Editor.

Ja, für die Leute, die den Editor kennen und sich unter HTML, Markdown und CSS etwas vorstellen können, ist jekyll ein wahrgewordener Traum. Für mich etwas, was ich auch gerne teilen würde. Ich möchte jekyll auch den Leuten näher bringen, die sich im Editor vielleicht nich so ganz zu Hause fühlen, und die manchmal nichts lieber hätten, als WYSIWYG. Für die „Nicht-Hacker“, die sich auf jekyll einlassen wollen, gibt es aber kaum Möglichkeiten.

Jekyll lebt davon, dass man es sich so schustert, wie man es sich nunmal wünscht. Und trotzdem findet man in den unendlichen Strängen vollgefüllter Foren ein paar versteckte Wünsche. Wünsche nach einem Editor. Wünsche nach irgendetwas, was die Produktion von Content mit jekyll einfacher macht.

Wir als Autoren unserer Themes brauchen natürlich keine Anleitung. Wir kennen uns aus, schliesslich haben wir den Code geschrieben. Das Theme spricht unsere Sprache mit uns. Doch sobald wir die Seite erstellt haben, übergeben wir das Generieren von Inhalt meist anderen Leuten. Die Wahrscheinlichkeit, dass diese Leute nicht ganz so technikafin sind wie wir, ist da natürlich gross.

Wie also sollen wir als die Experten für unser Design den Eintritt in die Welt unseres Codes erleichtern?

Ganz einfach: indem wir WYSIWYG umformen. Wir zeigen dem User das, was wir haben wollen, in einem Formular, das nicht ganz so furchteinflössend ist, wie der Gedanke daran, selbt front matter schreiben zu müssen. Wir kreieren also ein neues WYSIWYG.

WYSIWYG 2.0: What You Show Is What You Get

Einführung: WhatYouShowIsWhatYouGet - ein front matter Generator für jekyll

Ich mag den WordPress Posteditor nicht. Ich weiss, dass ich mit meiner Abneigung nicht alleine bin, doch mein Hass für diesen ganz bestimmten Editor, steigert sich exponentiell mit der Zeit, die ich damit verbringe, meinen mühsam in der text-Ansicht (überhaupt schon die Beizeichnung Text. Ich will HTML schreiben, ist das zu viel verlangt?) erstellen Code wieder hinzukriegen, weil irgendjemand meinen Post angesehen hat und dann auf visuell umgestellt hat, um - ha! - zu sehen wie das denn aussehen wird. Das Problem ist, dass WordPress einen danach nicht mehr das gibt, was man eigentlich sehen will. Denn sobald irgendjemand von der HTML-Ansicht in die Visuelle wechselt, macht der Editor etwas Unverzeihliches: Er löscht HTML. Wenn es eines gibt, was ich mehr hasse als falsch formatierter Code, dann ist es gelöschter Code.

Absolut kein Fan davon.

Ich bin der Ansicht, dass ein Post Editor richtiges HTML produzieren sollte. Keine Mischung. Nicht irgendetwas, das ein bisschen ausschaut als wäre es aus Word herauskopiert worden und etwas, was schon fast korrektes HTML sein könnte. Ausserdem sollte es beim Schreiben von Inhalt nicht darum gehen gross formatieren zu müssen. Ich möchte nicht basteln müssen. Ich will einfach schreiben. Schreiben und am Ende schönen Inhalt haben. Ich will nicht entscheiden müssen, zwischen seltsam formatierten HTML, oder selbst geschriebenem HTML, das dann allerdings unnötig ist. Aus irgendeinem Grund gibt es Markdown, das einem p-tags einfügt und die ganzen Headings in h-tags packt.

Wenn ich layouttechnisch etwas umsetzen möchte, dann schreibe ich mein HTML und dann vertraue ich darauf, dass dieses HTML so bleibt und nicht verändert oder gar entfernt wird. Bei jekyll weiss ich, dass mein Code nicht verändert wird. Ich schreibe Markdown. Und ich kann in meinem Markdown HTML tippen, wenn ich es für nötig halte. Diesem HTML passiert dann allerdings nichts, abgesehen von dem, was ich dafür geplant habe. Es gliedert sich wunderbar ein in das aus dem Markdown generierten HTML und sieht am Ende einfach richtig aus. Keine ekligen Leerzeichen auf einer Zeile (&nbsp;) anstelle richtigen Breaks (<br>).

Ich bin ein Fan von jekyll. Wenn ich jekyll Seiten schreiben darf, dann blühe ich auf. Und ich möchte, dass dieses Gefühl auch bei denjenigen unter uns ankommt, die bei schön geschriebenem Sass nicht ein Lächeln im Gesicht haben, oder zusammenzucken, wenn HTML nicht richtig formatiert ist. Also habe ich mich für einen Kompromiss entschieden. Ich gebe den Nicht-Hackern das hübsche Äussere, das sie an WordPress so schätzen und kreire daraus die Markdown files, die ich gerne in meinem Editor schreibe.

Wie ein Front Matter Generator für jekyll entsteht

Hier möchte ich ein Beispiel geben, wie so ein Generator entsteht. Es ist mit etwas Arbeit verbunden, doch meiner Ansicht nach, ist sie absolut lohnenswert. digitalmind.ch ist eine jekyll Seite. Und unseren Generator kannst du dir ansehen: und zwar hier.

Da es allerdings den Rahmen dieses Post sprengen würde, die ganzen ca. 430 Zeilen des Hauptjavascriptfiles dieses Generators zu erklären, habe ich für diesen Post einen kleinen Beispielgenerator geschrieben, der für die meisten jekyll-Projekte ausreichen sollte. Demopage SourceCode

Aufbau

Unser Generator ist relativ ausführlich. Für die meisten Seiten reicht es wahrscheinlich einen Generator zu schreiben, der Posts und Pages generiert. Für unsere Portfolios verwenden wir jekyll collections. Es gibt spezifische Variabeln für jeden dieser File-Typen. Beginnen tut es allerdings mit den globalen Variabeln, dann kommen die spezifischen Variabeln und ein Markdown Editor, mit dem das Schreiben für Markdwon-Ungeübte vereinfacht wird (und eine Vorschau gibt es auch - WYSIWYG!). Am Ende einen Generator-Button, bei dem die ganze Zauberei passiert und dann das generierte File einmal nur mit content zum kopieren und einmal mit herunterladen, sodass man nur noch in den entsprechenden Ort speichern muss.

Pretty self explanatory, oder?

Diggin’ into the Code

Wenn ich schon so weit aushole und so lange deine Aufmerksamkeit behalten konnte, dann sollst du auch mit Code belohnt werden. Hier möchte ich darauf eingehen, wie das alles funktioniert, so ganz ohne Datenbank und alles.

1. Übersicht schaffen - Was für Variabeln gibt es?

Dieser Schritt sollte gemacht werden, während das Theme noch in der Entwicklungsphase ist. Es ist viel einfacher, alle Variabeln an einem Ort zu haben und dann nachträglich noch zu löschen, als wenn man am Ende alle Variabeln zusammensuchen muss und dann eventuell welche vergisst.

excel ist hier dein bester Freund. Tabellen machen Übersichten einfacher (und Sortierfunktionen auch!)

Während des Schreibens deines jekyll Themes, sammelst du einfach mal alle Variabeln, die du einmal brauchst. Ausserm solltest du dir hier auch Gedanken um File-Names machen. Damit wir bei digitalmind zum Beispiel die Reihenfolge im Portfolio bestimmen können, sind die Files durchnummeriert. Eine Nummer ist also einer der Werte, die in unserem Generator für das Portfolio auch definiert werden müssen.

Bei dieser ersten Sammlung, solltes du auch gleich mal definieren, welche der Variabeln absolut notwendig ist, welche auch weggelassen werden kann, welche von welcher abhängig ist etc. Hier geht es nicht darum schon Ordnung zu schaffen, sondern nur darum alle Variabeln an einem Ort zu haben. Ordentlich wird es erst später.

2. Defaults definieren

Defaults ist etwas, was vor allem bei mir oft untergegangen ist. Defaults werden im _config.yml-file definiert und sind etwas, was vor allem dann, wenn man manuell die ganzen front matter generiert, definitv Zeit und Nerven spart. Wer schon einmal über 20 Mal eine Variable in 20 verschiedene Files kopiert hat, wird die Defaults nie mehr vergessen.

Für digitalmind sehen die Defaults so aus:

defaults:
  -
    scope:
      path: ""
      type: pages
    values:
      header: "yes"
      header-pic: "header.png"
      layout: page
  -
    scope:
      path: ""
      type: posts
    values:
      header: "yes"
      header-pic: "header.png"
      layout: post
  -
    scope:
      path: ""
      type: dm-portfolio
    values:
      layout: portfolio
      bodyclass: portfolio
      parent-name: Portfolio
      parent-link: /portfolio
      header: "no"

  -
    scope:
      path: ""
      type: dm-themes
    values:
      layout: portfolio
      bodyclass: themes
      parent-name: Themes
      parent-link: /themes
      header: "no"
  -
    scope:
      path: ""
      type: dm-plugins
    values:
      layout: portfolio
      bodyclass: plugins
      parent-name: Plugins
      parent-link: /plugins
      header: "no"

3. Typen definieren

Sobald die defaults stehen, kannst du mit der effektiven Planung deines Generators beginnen. Als erstes erfasst du die verschiedenen Typen von Files, die du generieren willst. Sind es nur Posts? Posts und Pages? Oder hast du collections drin, die du auch noch irgendwie unterbekommen willst?

Definiere diese Typen und dann kannst du zurück zum excel-File mit der Variabel-Sammlung.

4. Globale Variabeln und Typspezifische Variabeln

Als nächstes sortierst du. Ich sortiere hier nach globalen und typspezifischen Variabeln. Also erstelle ich eine Tabelle mit den Typen und ordne meine Variabeln zu. Die Variabeln, die bei allen Typen auftauchen, sind dann unsere globalen Variabeln, die anderen sind dann typspezifische Variabeln. Und dann am besten so sortieren, wie du am besten die Übersicht behältsts.

jekyll generator excel

5. Projektstruktur

Für unseren Beispiel-Generator bin ich von den Variabeln ausgegangen, die ich persönlich in jedem Projekt verwende.

Meine Tabelle dafür ist folgende:

Variablevar-erklärungPostPageGlobalPflichtOptionalAbhängig von
titleTitel, maximale Länge 55 Zeichenjajajaja
subtitleuntertiteljajajaja
url-titleFalls der Titel nicht für die URL verwendet werden soll, kann dies ausgefüllt werden. Dies ist eine Reine Generator-Variablejajajajacheckbox bzgl. Title = URL-title
tagstags, weden in meta-tag verwendet und bei posts auch angezeigtjajajaja
descriptionmeta beschreibung, maximale Länge 115 Zeichenjajajaja
authorAuthorname wie im author.yml file definiert, damit die Author-box richtig angezeigt werden kannjaja
featured imagefeatured image für den Postjajaheader
headerentscheide ob header mit bild oder nichtja, in den defaults definiertjaja
header-imagebild, falls header auf yesjajaheader
menudisplayentscheide, ob in menu seite in menu aufgenommen werden soll, oder nichtjaja
menu ordernummer für menu reihenfolgejajamenudisplay
contentInhaltjajajaja

Da es sich um einen Front Matter Generator handelt, habe ich den Content Optional gemacht. Das ist vor allem so, weil ich persönlich den Generator nutze um front matter zu generieren, weil es schneller auszufüllen ist, aber meinen Inhalt trotzdem im Editor schreibe weil Editor = toller.

6. jade (HTML)

Okay, ein Generator startet natürlich mit Markup. Ich persönlich benutze hier jade, weil es schneller zu schreiben ist und schön übersichtlich, bei dem eingebetteten Beispiel, kannst du allerdings auch das generierte HTML ansehen, einfach den entsprechenden Knopf klicken.

Der Aufbau schaut so aus.

See the Pen JdYxWg by Myri (@mynimi) on CodePen.

Wir schreiben ein Formular, in diesem Formular starten wir einmal mit radio buttons für den file typen und danach erstellen wir darin drei Container. Einmal einen für alle globalen Variabeln und einmal für den jeweiligen File typen.

Also haben wir einmal einen div-container mit der Klasse global und dann zwei container mit den klassen post und page. Diese werden wir dann entsprechend des ausgewählten Buttons aus- oder einblenden.

Den Content habe ich dann ebenfalls noch einmal in einen globalen container gepackt. Er ist einfach eine textarea. Für den markdown Editor wird dann später ein jquery Plugin verwendet.

Am Ende habe ich zwei container erstellt. Der eine hat die Klasse warn bekommen. In diesem werden die Warnhinweise angezeigt, falls etwas nicht richtig ausgefüllt ist. Drunter kommt dann der Resultatscontainer. In diesem Container platzieren wir einmal eine textarea und einmal einen button um die Datei zu speichern. Die textarea habe ich eingeblendet für die Fälle, in denen das Speichern nicht funktioniert (diese Funktionalität funktioniert in gewissen Browsern (vor allem Älteren) noch nicht ganz reibungslos und deswegen wird der Inhalt des Files ausgespuckt, sodass auch diese Leute, den Generator benutzen können. Sie müssen dann einfach den Inhalt in eine Datei mit dem entsprechenden File-Namen einfügen). Dafür ist der msg-container. In diesem wird am Ende noch eine kurze Anweisung eingefügt, was mit diesem File nun gemacht werden soll.

Das Formular besteht ansonsten aus inputs mit labels. Alle inputs haben eine ID, sodass sie mit jQuery nachher einfach angesprochen werden können. Die abhängigen Variabeln sollen nur angezeigt werden, wenn entweder yes oder no bei der Entscheidungsvariabel gewählt wird, also habe ich auch hier einen container um diese Variabeln gemacht.

7. jQuery (JavaScript)

Die ganze Funktionalität wird mit jQuery gemacht. Wie bereits erwähnt, verwende ich für den Markdown Editor ein Plugin. Dieses Plugin ist Meltdown, und ich habe es ausgewählt, weil damit auch Leute Markdown schreiben können, die davor nur mit WYSIWYG gearbeitet haben.

Um aus der textarea den Editor zu machen, befolge man am besten das ReadMe file des GitHub-Projekts (und sehe sich die Source-Files unseres Demos an). Ich habe die CSS des Editors noch angepasst, weil er mir zu viele Gradients und abgerundete Ecken enthielt.

Okay, damit ich mich nicht ständig wiederholen musste (Wiederholung gibt es genug), habe ich beschlossen, ein paar kleine Plugins und Funktionen zu schreiben, damit der Prozess danach etwas einfacher geht.

Mit diesen möchte ich nun beginnen.

Funktionen

Wenn man es mit der Deutschen Sprache zutun hat, dann hat man auch mit Umlauten zu tun. Umlaute und URLs vertragen sich nicht, also war der erste Gedanke, dass alle äs üs und ös durch aes ues und oes ersetzt wurden. Ausserdem wollte ich die URLs aus einem normalen Input generieren, entweder dem Titel, oder dem URL-Titel. Die Funktion createURL macht einen String also URL-freundlich.

Aus dem Input werden alle Leerzeichen ersetzt, alle Grossbuchstaben klein gemacht und die Umlaute ersetzt.

function createURL(string){
    value = string.replace(/\s+/g, '-').toLowerCase();
    value = value.replace(/\u00e4/g, 'ae');
    value = value.replace(/\u00f6/g, 'oe');
    value = value.replace(/\u00fc/g, 'ue');
    return value;
}

Die nächste Funktion befasst sich mit Nummern. Für die Menu-Ordnungs-Nummer soll jeder einstelligen Zahl ein Null vorangestellt werden. Darum die Funktion, die der Nummer Nullen voranstellt.

// prefix numbers with 0s
function pad(str, max) {
    str = str.toString();
    return str.length < max ? pad("0" + str, max) : str;
}

Für die Front-Matter-Generierung habe ich zum einen eine Funktion und zum anderen ein Plugin geschrieben. Das Plugin verwendet den Wert des Objekts auf das er angewendet wird, die Funktion gibt den Wert gleich mit. Man könnte nur eines von beiden verwenden, ich habe mich entschieden beide zu verwenden. Die Funktion, wenn ich Font-Matter einfach einfüge, ohne Abhängigkeiten von den Inputs und das Plugin, wenn es von den Inputs abhängt.

Die Funktion ist so geschrieben, dann man bei wichtigen Inputs den Warnhinweis übergibt, bei optionalen Variabeln, lässt man diesen aus. Als nächstes gibt man die front-matter variable an und dann den Wert, den die Variable hat. Die Funktion gibt dann entweder die Warnung aus, wenn der Wert leer ist, oder hängt der Resultats-textarea die Informationen an. Falls man etwas ohne Front-Matter-Variable weitergeben will, zum Beispiel den content, gibt man hier auch einfach eine leere front-variable weiter.

// front matter function, use when value is short and easy
function frontMatter(warn, front, val) {
    $('#inputTextToSave').append(
        function () {
            if (val == '') {
                $('.warn').append('<p>' + warn + '<\p>');
            } else {
                if (front != '') {
                    $('#inputTextToSave').append(front + ': ' + val + '\n');
                } else {
                    $('#inputTextToSave').append(val + '\n');
                }
            }
        }
    );
}
Plugins

Zu den Plugins.

das erste Plugin ist dafür da, um die abhängigen Container basierend auf einem yes oder no Wert anzuzeigen. Also checkt es zuerst den Wert des Elements auf das es angewendet wird und blendet den abhängigen container ein oder aus. weitergegeben wird der abhängige container und der default-wert, also der Wert der Variable, die im Markup als checked ausgewählt wurde.

(function ($) {
    // hide input based on yes or no radio button
    $.fn.yesOrNo = function (optionalContainer, defaultValue) {
        return this.each(function () {
            var elem = $(this);

            if (defaultValue == 'no') {
                $(optionalContainer).slideUp();
            } else {
                $(optionalContainer).slideDown();
            }

            elem.click(function () {
                if (elem.is(':checked')) {
                    var value = elem.val();
                }

                if (value == "yes") {
                    $(optionalContainer).slideDown();
                } else {
                    $(optionalContainer).slideUp();
                }
            });
        });
    };
}(jQuery));

Die nächste Funktion ist um einen kleinen Zeichen-Counter einzufügen. Diese habe ich auf den Titel und die Description eingefügt. Beide inputs sind ausserdem mit einer max-length versehen. Der Grund für die Begrenzung ist SEO bedingt und so von Google vorgegeben.

(function ($) {
    // character counter, adds a character counter
    $.fn.charCounter = function (count) {
        var counterClass = 'count',
            counterElementClass = 'counter',
            counterText = 'Characters remaining',
            counterID = this.attr('id') + '-' + counterElementClass;

        // add counter elements
        this.after('<p class="' + counterClass + '"><span id="' + counterID + '" class="' + counterElementClass + '"></span> ' + counterText + '</p>');

        $('#' + counterID).text(count - this.val().length); //initial value of characters
        var id = $('.' + counterElementClass).attr('id');

        this.keyup(function () { //user releases a key on the keyboard
            var thisChars = this.value.replace(/{.*}/g, '').length; //get chars count in textarea
            $('#' + counterID).text(count - thisChars); //count remaining chars
        });
        return this;
    };
}(jQuery));

Und hier haben wir das front-matter plugin, das den Wert aus dem Element liest, auf das es ausgeführt wird. Ausserdem hab ich hier noch die Möglicheit arrays zu definieren. Arrays in Front matter werden in eckigen Klammern geschrieben, und das macht das Plugin, wenn ich den Wert array weitergebe.

(function ($) {
    // front matter with longer value, in complexer cases
    $.fn.frontMatter = function (warn, front, array) {
        var val = this.val();

        if (!array) {
            array = null;
        }

        $('#inputTextToSave').append(
            function () {
                if (val == '') {
                    $('.warn').append('<p>' + warn + '<\p>');
                } else {
                    if (array == null && front != '' && val != '') {
                        $('#inputTextToSave').append(front + ': ' + val + '\n');
                    }
                    if (array != null && front != '' && val != '') {
                        $('#inputTextToSave').append(front + ': [' + val + ']\n');
                    }
                }
            }
        );
        return this;
    };
}(jQuery));
Komposition

Okay, mit dem können wir nun unser Script schreiben.

Ich habe Kommentare eingefügt, die den Code erklären sollten.

$(document).ready(function () {

    // Variabeln definieren
    var d = new Date(),
        month = d.getMonth() + 1,
        day = d.getDate(),
        date = d.getFullYear() + '-' + (('' + month).length < 2 ? '0' : '') + month + '-' + (('' + day).length < 2 ? '0' : '') + day,
        time = d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds(),
        title = $('input#title').val(),
        type = $('input:radio[name=type]:checked').val();

    // Resultat per default nicht anzeigen
    $('.result').slideUp();

    // Zeichen counter einfügen
    $('#title').charCounter(55);
    $('#desc').charCounter(115);

    // Container basierend auf Typ zeigen oder nicht
    if (type == "post") {
        $('.post').slideDown();
    } else {
        $('.post').slideUp();
    }
    if (type == "page") {
        $('.page').slideDown();
    } else {
        $('.page').slideUp();
    }

    // Typcontainer bei clicks
    $('input:radio[name=type]').click(function () {
        var type = $('input:radio[name=type]:checked').val();

        if (type == "post") {
            $('.post').slideDown();
        } else {
            $('.post').slideUp();
        }
        if (type == "page") {
            $('.page').slideDown();
        } else {
            $('.page').slideUp();
        }
    });

    // titel oder URL titel
    $('input:radio[name=title-check]').yesOrNo('.url-title', 'no');

    // Header Bild basierend auf Header
    $('input:radio[name=page-header]').yesOrNo('.page-header-pic', 'yes');

    // Menu Anzeige
    $('input:radio[name=menudisplay]').yesOrNo('.menudisplay', 'yes');

    // Generate Button Click generiert Font matter file
    $('.generatefile').click(function () {
        Resultat anzeigen
        $('.result').slideDown();

        // Container leeren, falls was drin war
        $('#inputTextToSave').empty();
        $('.warn').empty();
        $('.msg').empty();

        // beginne front-matter
        frontMatter('', '', '---');

        // titel
        $('input#title').frontMatter('Set a title!', 'title');

        // untertitel
        $('input#subtitle').frontMatter('', 'subtitle');

        // warnung falls URL titel ausgewählt, aber leer
        if ($('input:radio[name=title-check]:checked').val() == "yes") {
            if ($('input#url').val() == '') {
                $('.warn').append('Add URL title');
            }
        }

        // Beschreibung
        $('textarea#desc').frontMatter('include meta description', 'description');

        // Tags
        $('input#tags').frontMatter('', 'tags', 'array');

        // Variabeln pro Typ
        var type = $('input:radio[name=type]:checked').val();

        if (type == "post") {
            // autor
            $('input#author').frontMatter('include author!', 'author');

            // featured image
            $('input#header-pic').frontMatter('Include Featured Image', 'header-pic');

            // Nachricht
            var urlTitle = $('input:radio[name=title-check]:checked').val();

            if (urlTitle == "no") {
                var fileNameToSaveAs = createURL(date + ' ' + title + '.md');
            } else {
                var fileNameToSaveAs = createURL(date + ' ' + $('input#url').val() + '.md');
            }
            
            $('.msg').append('<p>Save this to <code>_posts</code to publish. (file name will be: "'+ fileNameToSaveAs +'"</p>');
        }

        if (type == "page") {
            var pageHeader = $('input:radio[name=page-header]:checked').val();

            // Header
            if (pageHeader == "yes") {
                $('input#page-header-pic').frontMatter('include header image!', 'header-pic');
            } else {
                $('#inputTextToSave').append('header: no\n');
            }

            // Variable für Navigation
            var menuDisplay = $('input:radio[name="menudisplay"]:checked').val(),
                menuOrder = $('input#menudisplay-order').val();

            if (menuOrder == '') {
                var menuVal = '';
            } else {
                var menuVal = '"navigation-' + pad(menuOrder, 2) + '"';
            }

            if (menuDisplay == "yes") {
                frontMatter('include Menu Order Number!', 'group', menuVal);
            }

            // Info
            $('.msg').append('<p>Save this into a folder named after your title (or URL title) (file name itself is "index.md").</p>');
        }

        // beende front matter
        frontMatter('', '', '---');

        // füge content ein
        frontMatter('', '', $('textarea#content').val());

        // falls Warnung aktiv, entferne Resultat
        if ($(".warn").text().length > 0) {
            $('.result').slideUp();
        } else {
            $('.result').slideDown();
        }

        $(".result textarea").height($(".result textarea")[0].scrollHeight);
    });
});
FileSaver

Und um das am Ende zu speichern machen wir folgendes:

function saveTextAsFile() {
    var d = new Date(),
        month = d.getMonth() + 1,
        day = d.getDate(),
        date = d.getFullYear() + '-' + (('' + month).length < 2 ? '0' : '') + month + '-' + (('' + day).length < 2 ? '0' : '') + day,
        title = $('input#title').val();

    // filename
    // show and hide type containers accordingly
    var type = $('input:radio[name=type]:checked').val();
    if (type == "post") {
        var urlTitle = $('input:radio[name=title-check]:checked').val();

        if (urlTitle == "no") {
            var fileNameToSaveAs = createURL(date + ' ' + title + '.md');
        } else {
            var fileNameToSaveAs = createURL(date + ' ' + $('input#url').val() + '.md');
        }
    }

    if (type == "page") {
        var fileNameToSaveAs = 'index.md';
    }

    var textToWrite = document.getElementById("inputTextToSave").value;
    var textFileAsBlob = new Blob([textToWrite], {});
    var downloadLink = document.createElement("a");
    downloadLink.download = fileNameToSaveAs;
    downloadLink.innerHTML = "Download File";
    if (window.webkitURL != null) {
        // Chrome allows the link to be clicked
        // without actually adding it to the DOM.
        downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
    } else {
        // Firefox requires the link to be added to the DOM
        // before it can be clicked.
        downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
        downloadLink.onclick = destroyClickedElement;
        downloadLink.style.display = "none";
        document.body.appendChild(downloadLink);
    }

    downloadLink.click();
}

function destroyClickedElement(event) {
    document.body.removeChild(event.target);
}

Wenn das gemacht ist, funktioniert unser Generator bereits, aber wirklich ansprechend ist er noch nicht. Um das zu Ändern, schreiben wir dann zum Schluss noch unsere Styles.

Und dann sind wir fertig!

8. Sass (CSS)

Zum Schluss wird das ganze noch gestyled, mit etwas CSS und der Generator ist fertig.

  1. Blogging Like A Hacker war der Posttitel, mit dem Tom Preston Werner seinen Generator vorgestellt hat. Wer sich den Post einmal durchlesen will, kliche hier.

  2. WYSIWYG = What You See Is What You Get. Ein Editor, der kein HTML anzeigt, sondern ein Stylesheet einfügt, sodass der Text bereits formatiert wird.