Dinamik HTML İçerikleri Oluşturmak Hakkında

Herkese merhaba,

Bu yazıda dinamik web sayfalarının nasıl hazırlanabileceğinden bahsedelim biraz. Bunun için basit bir proje seçtim, konuyu bu proje üzerinden anlatacağım. Bahsettiğim proje şu: Bir web uygulaması tasarlayacağız. Bu uygulamanın sadece bir sayfası ve bu tek sayfanın da bir <textarea> ve bir <button> elementi olacak. <textarea>'ya yazı yazacağız sonra, <button> ile bu yazıyı server’a göndereceğiz. Sonra server’a gönderilen yazının kod kısımlarına kod görünümü kazandıracağız yani metni yeniden biçimlendirip yazıyı kullanıcıya göndereceğiz. Gelen yanıta göre bu tek sayfa üzerinde yeni HTML elementleri oluşacak.

Web sunucusu için flask kütüphanesini, yazacağımız metinlere kod görünümü kazandırmak için pygments ve markdown kütüphanelerini kullanacağız.

Önce gerekli kütüphaneleri yükleyelim.

pip install flask, markdown, pygments

Şimdi, öncelikle bu basit projenin dosya düzenini göstereyim, siz de aşağıdakine benzer bir dizin ağaç yapısı oluşturun isterseniz:

.
├── app.py
├── static
│   └── js
│       └── main.js
├── templates
│   └── main.html
└── utils.py

3 directories, 4 files

Öncelikle app.py’yi oluşturalım, sonra da sırayla diğer dosyaları oluşturalım.

app.py’de basit bir route tanımlayalım.

from flask import Flask, render_template


app = Flask(__name__)


@app.route("/")
def main():
    return render_template("main.html")
    
    
if __name__ == "__main__":
    app.run()

Yukardaki kodları herhalde açıklamama gerek yoktur diye tahmin ediyorum. Az önce projeyi tanıtırken <button> yardımıyla server’a yazı göndereceğimizi söylemiştim. Server’a bir veri göndermek istediğimiz zaman bir XMLHttpRequest nesnesi kullanırız ve bu nesne istemci tarafında çalışır. Farklı XMLHttpRequest fiilleri var, biz bu örnekte POST fiilini kullanacağız. O halde, istemciden gelen talebi alabilmek için yukardaki app.py dosyasındaki main route’u için izin verilen http metodlarını tanımlayalım.

from flask import Flask, render_template


app = Flask(__name__)


@app.route("/", methods=["GET", "POST"])
def main():
    return render_template("main.html")
    
    
if __name__ == "__main__":
    app.run()

POST metodu ile değişik türden veriler alabiliriz. Ne türden veriler istediğimizi POST isteğine ekleyeceğimiz FORM nesneleri ile belirteceğiz. Sunucu da gelen formun anahtar kelimelerine göre ne yapması gerektiğine karar verecek. Bu örnekte, formun içinde create isimli bir anahtar olacak. Sunucu bu anahtar vasıtasıyla gelen isteğin nasıl değerlendirilmesi gerektiğini belirleyecek.

Şimdi, yukardaki kodları güncelleyelim biraz:

from flask import Flask, render_template, request


app = Flask(__name__)


@app.route("/", methods=["GET", "POST"])
def main():
    if request.method == "POST":
        if "create" in request.form:
            pass
    return render_template("main.html")
    
    
if __name__ == "__main__":
    app.run()

Evet, bu kısımda da anlaşılmayan bir kısım yoktur diye tahmin ediyorum. Yukarda anlattıklarımın koda dökülmüş hali sadece. app.py şimdilik böyle kalsın, sıradaki dosyayı oluşturmaya geçelim. O dosyayı oluşturduktan sonra app.py’ye tekrar döner ve kalan kısımları tamamlarız.

Şimdi, dinamik web sayfası içeriklerinin oluşmasını sağlayacak olan js kodlarını yazacağız. Bu örnekte responsive html içerikleri oluşturmak için bootstrap sınıflarını kullanacağım.

JS dosyamızda, göndereceğimiz metni bir sınıf ile gösterelim; bu sınıftan örnek oluşturduğumuz zaman, html elementi oluşsun. Aşağıdaki js classını inceleyin lütfen.

class Post {
    constructor(id, parent, content) {
        var div = document.createElement("div");
        div.id = `container-${id}`;
        div.className = "card-body container text-light rounded";
        div.style.backgroundColor = "black";
        var p = document.createElement("p");
        p.id = `content-${id}`
        p.innerHTML = content;
        div.append(p);
        document.getElementById(parent).append(div);
    }
}

Burada yaptığımız şey de sanıyorum çok karışık değil. Post isimli bir sınıf tanımladık. Sınıfın kurucu fonksiyonu constructor üç adet parametre alıyor. Kurucu fonksiyon altında da her bir yazı için oluşacak olan html elementlerini tanımladık. Son olarak da oluşturacağımız html element grubunu parent ne olacaksa ona ekledik. class yerine function da kullanabilirdik elbette.

Sınıfı oluşturduğumuza göre, istemci ile sunucu arasındaki iletişimi sağlayacak mekanizmayı tanımlayalım şimdi. Yukarda bahsettiğimiz <button> nesnesine id olarak btn-send yazacağım. Bu buttona bastığımz zaman istemcinin sunucuya bir XMLHttpRequest göndermesi gerekiyor. Henüz html sayfasını oluşturmadık ama js dosyasını doldurmaya devam edebiliriz. Şimdi bu button için onclick olayında çalışacak bir fonksiyon yazıyorum.

static/js/main.js

class Post {
    constructor(id, parent, content) {
        var div = document.createElement("div");
        div.id = `container-${id}`;
        div.className = "card-body container text-light rounded";
        div.style.backgroundColor = "black";
        var p = document.createElement("p");
        p.id = `content-${id}`
        p.innerHTML = content;
        div.append(p);
        document.getElementById(parent).append(div);
    }
}

document.getElementById("btn-send").onclick = function (e) {
    var form = new FormData();
    form.append("create", true);
    form.append("content", document.getElementById("textarea").value);
    fetch("/", {
        method: "POST",
        body: form
    })
    .then(function(response) {
        if (response.status === 201) {
            return response.json();
        } else {
            throw new Error("Request failed.");
        }
    })
    .then(function(post) {
        new Post(post.id, "posts", post.content);
    })
    .catch(function(error) {
        console.error(error);
    });
}

İzninizle yukardaki kodları açıklayayım biraz. Post sınıfını zaten tanıtmıştık, tekrar tanıtmaya gerek yok. Post’un hemen altında, id değeri btn-send olacak olan bir <button> için onclick fonksiyonunun nasıl olması gerektiğini tanımladık. Bu fonksiyonun içinde önce yeni bir Form nesnesi oluşturduk. Sonra form nesnesinin anahtarlarını ve bu anahtarların değerlerini yazdık. Hatırlayın, sunucu tarafında if "create" in request.form: gibi bir sorgu yazmıştık. İşte yukardaki formun içinde create isimli bir anahtarın olma sebebi bu. Değerinin true veya false olmasının bu proje için bir önemi yok ama server’a göndereceğimiz formun içinde create isimli bir anahtar olmalı çünkü server tarafında bu anahtara uygun bir işlem var. Yazının içeriğini <textarea> elementinin değerinden alarak forma ekliyoruz. Sonra da formu sunucuya fetch metodu ile gönderiyoruz. Sunucu, gelen isteğe göre uygun bir yanıt gönderecek (yanıta dair olan kodları server tarafında henüz yazmadık, birazdan yazacağız.). Sonra da bu yanıt önce bir response.json() nesnesine dönüştürülecek sonra da bu nesnenin nitelikleri kullanılarak sayfa içinde yeni bir Post nesnesi oluşturulacak.

Bu proje için js kodları bu kadar; bir Post sınıfı ve <button’un onclick olayı için çalışan bir fonksiyon tanımladık.

Şimdi app.py’nin geri kalan kısımlarını tanımlamaya devam edelim. Aşağıdaki kodları inceleyin lütfen:

from flask import Flask, render_template, request, Response, json


app = Flask(__name__)

datas = []


@app.route("/", methods=["GET", "POST"])
def main():
    if request.method == "POST":
        if "create" in request.form:
            data = {
                "id": len(datas),
                "content": request.form["content"]
            }
            datas.append(data)
            return Response(json.dumps(data), 201)
    return render_template("main.html")
    
    
if __name__ == "__main__":
    app.run()

Burada, daha önce yazdığımız app.py’den farklı olarak, Response ve json kütüphanelerini programın içine aktardık sonra da data isminde bir sözlük oluşturup, sözlüğü datas isimli bir listeye ekledik. Bu liste veritabanını temsil ediyor. Gerçek bir projede, list yerine gerçek bir veritabanı sistemi kullanmalıyız.

Şimdi gelin templates/main.html sayfasını oluşturalım:

templates/main.html

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div>
    <div id="form" class="input-group mb-3 container rounded mx-auto w-100">
        <textarea id="textarea" class="form-control"></textarea>
        <button id="btn-send" class="btn btn-outline-secondary">Send</button>
    </div>
    <div id="posts" class="container"></div>
    <script type="text/javascript" src="{{ url_for('static', filename='js/main.js') }}"></script>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>

Burada, ilk satırdaki <link> ve son üç satırdaki <script> bootstrap’i aktif etmek için yazıldı. Bunları çıkarırsanız, şöyle bir kod yazdık aslında:

<div>
    <div id="form" class="input-group mb-3 container rounded mx-auto w-100">
        <textarea id="textarea" class="form-control"></textarea>
        <button id="btn-send" class="btn btn-outline-secondary">Send</button>
    </div>
    <div id="posts" class="container"></div>
    <script type="text/javascript" src="{{ url_for('static', filename='js/main.js') }}"></script>
</div>

Buradaki html elementlerinin class nitelikleri bootstrap sınıflarıdır dolayısıyla her birinin oluşturacağımız html elementlerinin görüntüsüne katkısı var. Hangi bootstrap sınıfına ihtiyacınız olduğunu, bootstrap sınıflarını incelemeden karar veremezsiniz. Bootstrap’in resmi web sayfasında oldukça ayrıntılı bir şekilde her sınıfın ne türden bir özelliğe sahip olduğu yazıyor. İncelemenizi tavsiye ederim. Neyse açıklamaya devam edelim.

Gördüğünüz gibi, dışta bir <div> ve bu div’in iki tane child elementi var. Bu elementlerin tipleri de <div>. Divlerden birisinin id değeri form; <textarea> elementini ve yazıyı gönderirken kullanacağımız <button> elementini içeriyor; diğerinin id değeri de posts; oluşacak olan gönderileri içerecek olan div bu. Son satırda da yukarda yazdığımız main.js dosyasını template içine aktarıyoruz.

Aslında projemiz bitti sayılır. Yani şu haliyle istemci tarafında yazı yazıp sunucuya gönderebilir, sunucu tarafından da yanıtı alıp html sayfasının değişmesini sağlayabiliriz. Ancak bu projede, yazacağımız yazılara kod görünümü kazandırmaktan bahsetmiştim. Şimdi gelin bunun kodlarını oluşturalım.

Bir çırpıda kod görünümü kazandıran kodların tamamını paylaşayım, açıklamasını sonra yapayım:

utils.py

import re

from markdown import markdown
from pygments import highlight
from pygments.styles import get_all_styles
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter


class MetaHTMLCodeFormat(type):
    def __call__(cls, text: str):
        if not isinstance(text, str):
            return
        return super().__call__(cls.reformat(text))

    def reformat(cls, text: str):
        d_container = "<div class=\"container\">\n"
        d_flex = "<div class=\"d-flex\">\n"
        d_rows = "<div class=\"bg-dark pt-2 pl-2 pr-2 rounded-left text-light\">\n"
        d_close = "\n</div>\n"
        d_code = "<div class=\"bg-dark pt-2 pl-2 pr-2 rounded-right container\">\n"
        patterns = re.findall(f"```(?:(?!```).)*```", text, re.DOTALL)
        for code in sorted(set(patterns), key=patterns.index):
            html = "".join(
                [
                    d_container,
                    d_flex,
                    d_rows,
                    "```\n",
                    *map(lambda i: f"{i + 1}\n", range(len(code.split("\n")) - 2)), 
                    "```\n",
                    d_close,
                    d_code,
                    code,
                    d_close, 
                    d_close, 
                    d_close
                ]
            )
            text = text.replace(code, html)
        return text


class HTMLCodeFormat(str, metaclass=MetaHTMLCodeFormat):
    def __init__(self, text: str):
        super().__init__()


    def highlight(self, style: str = "github-dark"):
        if style not in get_all_styles():
            return self
        return "".join(
            [
                markdown(self, extensions=["fenced_code", "codehilite"]),
                "<style>",
                HtmlFormatter(style=style, full=True, cssclass="codehilite").get_style_defs(),
                "</style>"
            ]
        )

Yukardaki proje ağaç yapısında, utils.py dosyası, app.py ile aynı dizinde yer alıyor demiştik. Açıklamayı birazdan yapacağım ama önce app.py’ye de son şeklini verelim:

app.py

from flask import Flask, render_template, request, Response, json
from utils import HTMLCodeFormat


app = Flask(__name__)

datas = []


@app.route("/", methods=["GET", "POST"])
def main():
    if request.method == "POST":
        if "create" in request.form:
            data = {
                "id": len(datas),
                "content": request.form["content"]
            }
            datas.append(data)
            data["content"] = HTMLCodeFormat(data["content"]).highlight()
            return Response(json.dumps(data), 201)
    return render_template("main.html")
    
    
if __name__ == "__main__":
    app.run()

Evet, bu küçük projenin bütün dosyaları bu kadar. Şimdi utils.py’nin içindeki kodları açıklamaya koyulabilirim:

Öncelikle metaclass’ın ne olduğunu bilmiyorsanız aşağıdaki başlığı okumanızı tavsiye ederim.

Ancak, yukardaki metaclass’ı ve class’ı fonksiyon olarak da kullanabilirdik elbette. Burada bu classların kafanızı karıştırmasını istemem, mesela reformat fonksiyonunu MetaHTMLCodeFormat sınıfından, highlight fonksiyonunu da HTMLCodeFormat sınıfından dışarı çıkarıp kullanabilirsiniz. Özelleştirilmiş bir str sınıfı oluşturmak istemiştim sadece. Meta sınıfların nasıl kullanılabileceğine dair bir örnek olarak da görebilirsiniz.

Neyse, bu sınıf kısmıyla alakalı açıklamayı yaptıktan sonra açıklanması gereken kod parçalarından bahsedeyim.

MetaHTMLCodeFormat sınıfının reformat fonksiyonuna bakalım. Burada aşağıdaki gibi bir ifade görüyorsunuz.

patterns = re.findall(f"```(?:(?!```).)*```", text, re.DOTALL)

Dıştan içe doğru gidelim:

re.findall ile bir str içindeki belirli bir örüntüyü ararsınız. str içinde bu belirli örüntüye uygun kaç tane parça varsa o parçalar bir liste olarak geri döndürülür.

Burada aradığımız örüntü f"```(?:(?!```).)*```" örüntüsüdür. Burada str içinde üç çentik ile başlayan, üç çentik ile biten ama arasında üç çentik hariç herşeyi içerebilen bir örüntüyü arıyoruz. Bu regex kalıbında ünlem işareti ve iki nokta üst üste işaretinin kullanılabilmesi için re.DOTALL ifadesinin, re.findall fonksiyonuna argüman olarak verilmesi gerekir.

(?:) → Örüntünün bir veya daha fazla tekrarını arar ama grubun içindeki benzer patternleri bulmaz.

Örnek yapalım:

import re

assert re.findall("(?:\d{2})", "1213a1234", re.DOTALL) == ["12", "13", "12", "34"]
assert re.findall("(?:\d{2}-)+\d{2}", "12-13-a-12-34", re.DOTALL) == ["12-13", "12-34"]
assert re.findall("(?:\d{2}-)+\d{2}", "12-13-14-a-12-34-36", re.DOTALL) == ["12-13-14", "12-34-36"]

(?!) → Negatif bir anlamı var. Örüntü aramasında bu kalıba uyan örüntüler yok sayılır.

Örnek yapalım:

import re

assert re.findall("(?:\d)+(?!\s)(?:\d)+", "1213a1234", re.DOTALL) == ["1213", "1234"]

+ işareti, 1 veya daha fazla eşleşme; * işareti ise, 0 veya daha fazla eşleşme için kullanılır. \d sayının; \s ise, str’nin yerini tutar.

Umarım aşağıdaki ifadenin ne anlama geldiği şimdi daha iyi anlaşılıyordur:

patterns = re.findall(f"```(?:(?!```).)*```", text, re.DOTALL)

Daha sonra da patterns listesinden özgün elemanları olan bir liste oluşturduk. Burada set kullandım çünkü kodun ilerleyen kısımlarında text = text.replace(code, html) gibi bir ifade var. Eğer sadece özgün elemanlar olmasaydı, tekrar eden kod satırları replace edilirken tekrar sayısı kadar replace gerçekleşirdi. Bu da bizim istemediğimiz bir senaryo. Varsa tekrar eden kod yapıları, tek seferde hepsi değişsin diye özgün elemanlar kullanıyoruz.

Sonra da for döngüsüne giriyoruz. Burada da yazdığımız yazıda kod yapısına uyan örüntüler yeniden biçimlendirilir ve yazdığımız yazıdaki örüntü ile bu yeni biçim yer değiştirilir.

En sonda da highlight fonksiyonuyla içeriğimizi yeniden biçimlendirip, html’de kod görünümü kazanacak hale getiriyoruz.

Sonuç olarak aşağıdaki gibi dinamik bir web sayfa elde ediyoruz.

Button’a basmadan önce:
Ekran Görüntüsü - 2023-10-08 13-05-02

Button’a bastıktan sonra:
Ekran Görüntüsü - 2023-10-08 13-11-34

Button’a basmadan önce:
Ekran Görüntüsü - 2023-10-08 13-09-27

Button’a bastıktan sonra:
Ekran Görüntüsü - 2023-10-08 13-09-48

Evet, bu örnekte, dinamik web sayfası hazırlamak için sunucu ile istemci arasında iletişim kurduk, sunucu tarafında istenen veriyi hazır hale getirip istemci tarafına tekrar yolladık. İstemcinin gördüğü web sayfası sunucudan gelen cevaba göre değiştirildi. Bu değiştirme işlemi için html elementlerini js’de dinamik olarak tanımladık. Sonuç olarak yazdığımız yazıyı biçimlendirerek sayfaya ekleyen bir mekanizma kurduk. Benzer şekilde, eklediğimiz yazıyı silebileceğimiz veya değiştirebileceğimiz mekanizmaları da oluşturabiliriz.

Not: Web uygulamasını tasarlarken, istemcinin sunucuya belirli süreler için en fazla kaç tane istek gönderebileceğini belirleyen sınırları ve html elementlerini değiştirme yetkisinin her kullanıcıda mı yoksa belirli kullanıcılarda mı olacağını düşünmeliyiz.

Bu başlık için anlatacaklarım şimdilik bu kadar. Umarım faydası olur.

Herkese iyi günler.

2 Beğeni

Bu arada regex örüntüsünü şu şekilde değiştirdim:

patterns = re.findall(f'\[code="([^"]*)"\](.*?)\[/code\]', text, re.DOTALL)

Yazdığımız metin markdown'ı destekliyor ama kod bloklarını yazarken markdown’a göre blokları tayin etmeye çalışmanın biçimlendirme problemleri yarattığını gördüm.

Kod blokunu [code="language"]\n...\n[/code] olacak şekilde değiştirdim. Yazının kendisi markdown destekliyor. Yani ```python\nprint("hello")\n``` yazdığımız zaman yazı markdown’a göre biçimlendiriliyor. Ancak ekstradan [code=...] ifadelerini de eklersek github gistleri gibi bir görüntü elde ederiz.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import re

from markdown import markdown
from pygments import highlight
from pygments.styles import get_all_styles
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments.formatters import Terminal256Formatter


class MetaHTMLCodeFormat(type):
    def __call__(cls, text: str):
        if not isinstance(text, str):
            return
        return super().__call__(cls.reformat(text))

    def reformat(cls, text: str):
        d_container = "<div class=\"container\">\n"
        d_flex = "<div class=\"d-flex\">\n"
        d_rows = "<div class=\"bg-dark pt-2 pl-2 pr-2 rounded-left text-light\">\n"
        d_close = "\n</div>\n"
        d_code = "<div class=\"bg-dark pt-2 pl-2 pr-2 rounded-right container\">\n"
        patterns = re.findall(f'\[code="([^"]*)"\](.*?)\[/code\]', text, re.DOTALL)
        for code in sorted(set(patterns), key=patterns.index):
            print(code)
            lang, code = tuple(map(str.strip, code))[:]
            html = "".join(
                [
                    d_container,
                    d_flex,
                    d_rows,
                    "```\n",
                    *map(lambda i: f"{i + 1}\n", range(len(code.split("\n")))), 
                    "```\n",
                    d_close,
                    d_code,
                    f"```{lang}\n" + code + "\n```",
                    d_close, 
                    d_close, 
                    d_close
                ]
            )
            text = text.replace(code, html).replace(f"[code=\"{lang}\"]", "").replace("[/code]", "")
        return text


class HTMLCodeFormat(str, metaclass=MetaHTMLCodeFormat):
    def __init__(self, text: str):
        super().__init__()


    def highlight(self, style: str = "github-dark"):
        if style not in get_all_styles():
            return self
        return "".join(
            [
                markdown(self, extensions=["fenced_code", "codehilite"]),
                "<style>",
                HtmlFormatter(style=style, full=True, cssclass="codehilite").get_style_defs(),
                "</style>"
            ]
        )

Örnek str:

example = """
Python kodu:
[code="python"]
class HTMLCodeFormat(str, metaclass=MetaHTMLCodeFormat):
    def __init__(self, text: str):
        super().__init__()


    def highlight(self, style: str = "github-dark"):
        if style not in get_all_styles():
            return self
        return "".join(
            [
                markdown(self, extensions=["fenced_code", "codehilite"]),
                "<style>",
                HtmlFormatter(style=style, full=True, cssclass="codehilite").get_style_defs(),
                "</style>"
            ]
        )
[/code]

JS kodu:
[code="python"]
class Post {
    constructor(id, parent, content) {
        var div = document.createElement("div");
        div.id = `container-${id}`;
        div.className = "card-body container text-light rounded";
        div.style.backgroundColor = "black";
        var p = document.createElement("p");
        p.id = `content-${id}`
        p.innerHTML = content;
        div.append(p);
        document.getElementById(parent).append(div);
    }
}
[/code]

HTML'de markdown biçiminde görünecek ifade:

```python
from flask import Flask, render_template, request, Response, json
from utils import HTMLCodeFormat


app = Flask(__name__)

datas = []


@app.route("/", methods=["GET", "POST"])
def main():
    if request.method == "POST":
        if "create" in request.form:
            data = {
                "id": len(datas),
                "content": request.form["content"]
            }
            datas.append(data)
            data["content"] = HTMLCodeFormat(data["content"]).highlight()
            return Response(json.dumps(data), 201)
    return render_template("main.html")
    
    
if __name__ == "__main__":
    app.run()
```
"""

Bu str’yi server’a gönderiyorum:


Server’ın yanıtıyla birlikte sayfa şu şekilde değişiyor:

Gördüğünüz gibi, [code="python"]...[/code] ifadesi, zaten markdown ile biçimlendirilmiş bir metni yeniden biçimlendirerek yazının geri kalan kısımlarından ayrı bir çerçeve olarak sunulmasını sağlıyor. Kod blokunda kaç satır olduğu da MetaHTMLCodeFormat sınıfında belirleniyor; satır sayıları için de ayrı bir div oluşturuyoruz. Ve bu iki div'in ortak ebeveyni ve bir bootstrap sınıfı yoluyla yan yana getiriyoruz. Böylece her bir sayı ilgili satırı işaret etmiş oluyor.

2 Beğeni