Markdown'ın küçük bir alt kümesini React bileşenlerine nasıl ayrıştırabilirim?


9

Ben React bileşenleri ayrıştırmak istiyorum bazı özel html ile birlikte Markdown çok küçük bir alt kümesi var. Örneğin, şu dizeyi açmak istiyorum:

hello *asdf* *how* _are_ you !doing! today

Aşağıdaki diziye:

[ "hello ", <strong>asdf</strong>, " ", <strong>how</strong>, " ", <em>are</em>, " you ", <MyComponent onClick={this.action}>doing</MyComponent>, " today" ]

ve sonra bir React render işlevinden döndürür (React diziyi biçimlendirilmiş HTML olarak düzgün şekilde oluşturur)

Temel olarak, kullanıcılara metinlerini stilli bileşenlere dönüştürmek için çok sınırlı bir Markdown seti kullanma seçeneği sunmak istiyorum (ve bazı durumlarda kendi bileşenlerim!)

Tehlikeli olarak SetSnerHTML için akıllıca değil ve dışa bağımlılık getirmek istemiyorum, çünkü hepsi çok ağır ve sadece çok temel işlevselliğe ihtiyacım var.

Şu anda böyle bir şey yapıyorum, ama çok kırılgan ve tüm durumlar için çalışmıyor. Daha iyi bir yol olup olmadığını merak ediyordum:

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;
}

İşte buna yol açan önceki sorum .


1
Girdi gibi iç içe öğeler içeriyorsa ne olur font _italic *and bold* then only italic_ and normal? Beklenen sonuç ne olurdu? Yoksa asla yuvalanmayacak mı?
trincot

1
Yuvalama konusunda endişelenmenize gerek yok. Kullanıcıların kullanması çok basit bir işarettir. Uygulaması en kolay olan her şey benim için iyidir. Örneğinizde, iç kalınlık işe yaramazsa tamamen iyi olurdu. Ancak, iç içe yerleştirmeyi uygulamak, sahip olmamaktan daha kolaysa, o zaman da sorun değil.
Ryan Peschel


1
Ben olsa markdown kullanmıyorum. Sadece çok benzer / küçük bir alt kümesidir (iç içe olmayan kalın, italik, kod, alt çizgi ile birlikte birkaç özel bileşeni destekler). Yayınladığım parçacıklar biraz işe yarıyor, ama çok ideal görünmüyor ve bazı önemsiz durumlarda başarısız oluyor, (bunun gibi tek bir yıldız yazamazsınız: asdf*kaybolmadan)
Ryan Peschel

Yanıtlar:


1

Nasıl çalışır?

Gerçekten uzun dizeler için en iyi çözüm olmayabilir, öbek tarafından yığın bir dize okuyarak çalışır.

Ayrıştırıcı, kritik bir parçanın, yani '*'veya başka bir etiketleme etiketinin okunduğunu algıladığında , ayrıştırıcı kapanış etiketini bulana kadar bu öğenin yığınlarını ayrıştırmaya başlar.

Çok satırlı dizelerde çalışır, örneğin koda bakın.

Uyarılar

Belirtmediniz veya gereksinimlerinizi yanlış anlayabilirdim, hem kalın hem de italik etiketleri ayrıştırma zorunluluğu varsa , mevcut çözümüm bu durumda çalışmayabilir.

Ancak, yukarıdaki koşullarla çalışmak gerekiyorsa, sadece buraya yorum yapın ve kodu değiştireceğim.

İlk güncelleme: etiketleme etiketlerinin nasıl ele alındığını ayarlar

Etiketler artık kodlanmış değil, bunun yerine ihtiyaçlarınıza göre kolayca genişletebileceğiniz bir harita.

Yorumlarda bahsettiğiniz hatalar düzeltildi, bu sorunları işaret ettiğiniz için teşekkürler = p

İkinci güncelleme: çok uzunluklu etiketleme etiketleri

Bunu başarmanın en kolay yolu: çok uzunluklu karakterleri nadiren kullanılan bir unicode ile değiştirmek

Yöntem parseMarkdownhenüz çok uzunluklu etiketleri desteklemese de, prop'ımızı string.replace gönderirken bu çok uzunluklu etiketleri kolayca bir basitle değiştirebiliriz rawMarkdown.

Pratikte bunun bir örneğini görmek için ReactDOM.render, kodun sonunda bulunan 'a bakın .

Başvurunuz bile yapar çoklu dil desteği, orada JavaScript hala tespit ettiği geçersiz Unicode karakterleri, ex .: vardır "\uFFFF"(eğer doğru geri çağırmak geçerli bir unicode, değil ama JS hala karşılaştırmak mümkün olacak "\uFFFF" === "\uFFFF" = true)

İlk başta hack-y gibi görünebilir, ancak kullanım durumunuza bağlı olarak, bu rotayı kullanarak önemli bir sorun görmüyorum.

Bunu başarmanın başka bir yolu

Eh, biz kolayca son izleyebilir N(burada Nen uzun çoklu uzunlukta etiket uzunluğuna karşılık gelir) parçalar.

İçindeki döngünün yöntemin parseMarkdowndavranış biçiminde bazı ince ayarlar yapılacaktır, yani, geçerli öbeğin çok uzunluklu bir etiketin parçası olup olmadığını kontrol etmek, etiket olarak kullanılıyorsa; Aksi takdirde, gibi durumlarda ``k, bunu notMultiLengthveya benzer bir şeyi işaretlememiz ve bu yığını içerik olarak itmemiz gerekir.

kod

// Instead of creating hardcoded variables, we can make the code more extendable
// by storing all the possible tags we'll work with in a Map. Thus, creating
// more tags will not require additional logic in our code.
const tags = new Map(Object.entries({
  "*": "strong", // bold
  "!": "button", // action
  "_": "em", // emphasis
  "\uFFFF": "pre", // Just use a very unlikely to happen unicode character,
                   // We'll replace our multi-length symbols with that one.
}));
// Might be useful if we need to discover the symbol of a tag
const tagSymbols = new Map();
tags.forEach((v, k) => { tagSymbols.set(v, k ); })

const rawMarkdown = `
  This must be *bold*,

  This also must be *bo_ld*,

  this _entire block must be
  emphasized even if it's comprised of multiple lines_,

  This is an !action! it should be a button,

  \`\`\`
beep, boop, this is code
  \`\`\`

  This is an asterisk\\*
`;

class App extends React.Component {
  parseMarkdown(source) {
    let currentTag = "";
    let currentContent = "";

    const parsedMarkdown = [];

    // We create this variable to track possible escape characters, eg. "\"
    let before = "";

    const pushContent = (
      content,
      tagValue,
      props,
    ) => {
      let children = undefined;

      // There's the need to parse for empty lines
      if (content.indexOf("\n\n") >= 0) {
        let before = "";
        const contentJSX = [];

        let chunk = "";
        for (let i = 0; i < content.length; i++) {
          if (i !== 0) before = content[i - 1];

          chunk += content[i];

          if (before === "\n" && content[i] === "\n") {
            contentJSX.push(chunk);
            contentJSX.push(<br />);
            chunk = "";
          }

          if (chunk !== "" && i === content.length - 1) {
            contentJSX.push(chunk);
          }
        }

        children = contentJSX;
      } else {
        children = [content];
      }
      parsedMarkdown.push(React.createElement(tagValue, props, children))
    };

    for (let i = 0; i < source.length; i++) {
      const chunk = source[i];
      if (i !== 0) {
        before = source[i - 1];
      }

      // Does our current chunk needs to be treated as a escaped char?
      const escaped = before === "\\";

      // Detect if we need to start/finish parsing our tags

      // We are not parsing anything, however, that could change at current
      // chunk
      if (currentTag === "" && escaped === false) {
        // If our tags array has the chunk, this means a markdown tag has
        // just been found. We'll change our current state to reflect this.
        if (tags.has(chunk)) {
          currentTag = tags.get(chunk);

          // We have simple content to push
          if (currentContent !== "") {
            pushContent(currentContent, "span");
          }

          currentContent = "";
        }
      } else if (currentTag !== "" && escaped === false) {
        // We'll look if we can finish parsing our tag
        if (tags.has(chunk)) {
          const symbolValue = tags.get(chunk);

          // Just because the current chunk is a symbol it doesn't mean we
          // can already finish our currentTag.
          //
          // We'll need to see if the symbol's value corresponds to the
          // value of our currentTag. In case it does, we'll finish parsing it.
          if (symbolValue === currentTag) {
            pushContent(
              currentContent,
              currentTag,
              undefined, // you could pass props here
            );

            currentTag = "";
            currentContent = "";
          }
        }
      }

      // Increment our currentContent
      //
      // Ideally, we don't want our rendered markdown to contain any '\'
      // or undesired '*' or '_' or '!'.
      //
      // Users can still escape '*', '_', '!' by prefixing them with '\'
      if (tags.has(chunk) === false || escaped) {
        if (chunk !== "\\" || escaped) {
          currentContent += chunk;
        }
      }

      // In case an erroneous, i.e. unfinished tag, is present and the we've
      // reached the end of our source (rawMarkdown), we want to make sure
      // all our currentContent is pushed as a simple string
      if (currentContent !== "" && i === source.length - 1) {
        pushContent(
          currentContent,
          "span",
          undefined,
        );
      }
    }

    return parsedMarkdown;
  }

  render() {
    return (
      <div className="App">
        <div>{this.parseMarkdown(this.props.rawMarkdown)}</div>
      </div>
    );
  }
}

ReactDOM.render(<App rawMarkdown={rawMarkdown.replace(/```/g, "\uFFFF")} />, document.getElementById('app'));

Kod bağlantısı (TypeScript) https://codepen.io/ludanin/pen/GRgNWPv

Kod bağlantısı (vanilya / babel) https://codepen.io/ludanin/pen/eYmBvXw


Bu çözümün doğru yolda olduğunu hissediyorum, ancak diğer işaretleme karakterlerini diğerlerinin içine koymakla ilgili sorunlar var gibi görünüyor. Örneğin, This must be *bold*ile değiştirmeyi deneyin This must be *bo_ld*. Sonuçta ortaya çıkan HTML'nin hatalı biçimlendirilmesine neden oluyor
Ryan Peschel

Doğru test yapılmaması bu = p'yi üretti, benim hatam. Zaten düzeltiyorum ve sonucu buraya göndereceğim, düzeltmek için basit bir sorun gibi görünüyor.
Lukas Danin

Evet teşekkürler. Bu çözümü gerçekten çok seviyorum. Çok sağlam ve temiz görünüyor. Bence daha da şıklık için biraz yeniden düzenlenebilir. Onunla biraz uğraşmayı deneyebilirim.
Ryan Peschel

Bu arada, markdown etiketlerini ve ilgili JSX değerlerini tanımlamanın çok daha esnek bir yolunu desteklemek için kodu değiştirdim.
Lukas Danin

Teşekkürler bu harika görünüyor. Son bir şey ve bence mükemmel olacak. Orijinal yazımda kod parçacıkları için de (üçlü backticks içeren) bir fonksiyon var. Bunun için de destek almak mümkün müdür? Böylece etiketler isteğe bağlı olarak birden fazla karakter olabilir mi? Başka bir cevap, `` `örneklerini nadiren kullanılan bir karakterle değiştirerek destek ekledi. Bunu yapmanın kolay bir yolu olurdu, ancak bunun ideal olup olmadığından emin değilim.
Ryan Peschel

4

Görünüşe göre küçük, çok basit bir çözüm arıyorsunuz. "Süper canavarlar" gibi değil react-markdown-it:)

Size oldukça hafif ve hoş görünen https://github.com/developit/snarkdown tavsiye etmek istiyorum ! Sadece 1kb ve son derece basit, başka sözdizimi özelliklerine ihtiyacınız varsa onu kullanabilir ve genişletebilirsiniz.

Desteklenen etiketler listesi https://github.com/developit/snarkdown/blob/master/src/index.js#L1

Güncelleme

Sadece reaksiyon bileşenleri hakkında fark ettim, başlangıçta kaçırdı. Bu sizin için harikadır, kütüphaneyi örnek olarak almaya ve HTML'yi tehlikeli bir şekilde ayarlamadan bunu yapmak için özel gerekli bileşenlerinizi uygulamaya inanıyorum. Kütüphane oldukça küçük ve net. Onunla eğlenin! :)


3
var table = {
  "*":{
    "begin":"<strong>",
    "end":"</strong>"
    },
  "_":{
    "begin":"<em>",
    "end":"</em>"
    },
  "!":{
    "begin":"<MyComponent onClick={this.action}>",
    "end":"</MyComponent>"
    },

  };

var myMarkdown = "hello *asdf* *how* _are_ you !doing! today";
var tagFinder = /(?<item>(?<tag_begin>[*|!|_])(?<content>\w+)(?<tag_end>\k<tag_begin>))/gm;

//Use case 1: direct string replacement
var replaced = myMarkdown.replace(tagFinder, replacer);
function replacer(match, whole, tag_begin, content, tag_end, offset, string) {
  return table[tag_begin]["begin"] + content + table[tag_begin]["end"];
}
alert(replaced);

//Use case 2: React components
var pieces = [];
var lastMatchedPosition = 0;
myMarkdown.replace(tagFinder, breaker);
function breaker(match, whole, tag_begin, content, tag_end, offset, string) {
  var piece;
  if (lastMatchedPosition < offset)
  {
    piece = string.substring(lastMatchedPosition, offset);
    pieces.push("\"" + piece + "\"");
  }
  piece = table[tag_begin]["begin"] + content + table[tag_begin]["end"];
  pieces.push(piece);
  lastMatchedPosition = offset + match.length;

}
alert(pieces);

Sonuç: Koşu sonucu

Normal ifade test sonucu

Açıklama:

/(?<item>(?<tag_begin>[*|!|_])(?<content>\w+)(?<tag_end>\k<tag_begin>))/
  • Etiketlerinizi bu bölümde tanımlayabilirsiniz: [*|!|_]bunlardan biri eşleştiğinde grup olarak yakalanır ve "tag_begin" olarak adlandırılır.

  • Ardından (?<content>\w+)etiket tarafından sarılmış içeriği yakalar.

  • Bitiş etiketi, daha önce eşleşen etiketle aynı olmalıdır, bu yüzden burada kullanır \k<tag_begin>ve testi geçerse grup olarak yakalayın ve ona "tag_end" adı verin, diyorlar (?<tag_end>\k<tag_begin>)).

JS'de şöyle bir tablo ayarladınız:

var table = {
  "*":{
    "begin":"<strong>",
    "end":"</strong>"
    },
  "_":{
    "begin":"<em>",
    "end":"</em>"
    },
  "!":{
    "begin":"<MyComponent onClick={this.action}>",
    "end":"</MyComponent>"
    },

  };

Eşleşen etiketleri değiştirmek için bu tabloyu kullanın.

Sting.replace , yakalanan grupları parametre olarak alabilen aşırı yük String.replace (regexp, function) özelliğine sahiptir, bu yakalanan öğeleri tabloyu aramak ve değiştirme dizesini oluşturmak için kullanırız.

[Güncelleme]
Kodu güncelledim, başka birinin tepki bileşenlerine ihtiyaç duymaması durumunda ilkini sakladım ve aralarında çok az fark olduğunu görebilirsiniz. Reaksiyon Bileşenleri


Maalesef bunun işe yarayıp yaramadığından emin değilim. Çünkü gerçek React bileşenlerine ve elemanlarına ihtiyacım var, bunların dizelerine değil. Orijinal yazıma bakarsanız, gerçek öğeleri kendilerinin dizeye değil, bir diziye eklediğimi göreceksiniz. Ayrıca, kullanıcı kötü amaçlı dizeler girebileceğinden dangerousSetInnerHTML kullanmak tehlikelidir.
Ryan Peschel

Neyse ki React bileşenlerine dize değiştirme dönüştürmek çok basit, ben kodu güncelledim.
Simon

Hımm? Bir şey eksik olmalıyım, çünkü onlar hala benim ucumda ipler. Kodunla bir keman bile yaptım. console.logÇıktıyı okuduysanız dizinin dizelerle dolu olduğunu görürsünüz, gerçek React bileşenleri değil: jsfiddle.net/xftswh41
Ryan Peschel

Dürüst olmak gerekirse, React'i bilmiyorum, bu yüzden her şeyi ihtiyaçlarınıza göre mükemmel bir şekilde yapamıyorum, ancak sorunuzu nasıl çözeceğinizle ilgili bilgilerin yeterli olduğunu düşünüyorum, React makinenize koymanız gerekiyor ve sadece gidebilir.
Simon

Bu iş parçacığının var olmasının nedeni, React bileşenlerine ayrıştırılmasının çok daha zor görünmesidir (bu nedenle, tam ihtiyacı belirten iş parçacığı başlığı). Bunları dizelere ayrıştırmak oldukça önemsizdir ve sadece string replace işlevini kullanabilirsiniz. Dizeler ideal bir çözüm değildir, çünkü tehlikeli olarak aramak zorunda oldukları için yavaş ve XSS'ye duyarlıdırSetInnerHTML
Ryan Peschel

0

bunu şu şekilde yapabilirsiniz:

//inside your compoenet

   mapData(myMarkdown){
    return myMarkdown.split(' ').map((w)=>{

        if(w.startsWith('*') && w.endsWith('*') && w.length>=3){
           w=w.substr(1,w.length-2);
           w=<strong>{w}</strong>;
         }else{
             if(w.startsWith('_') && w.endsWith('_') && w.length>=3){
                w=w.substr(1,w.length-2);
                w=<em>{w}</em>;
              }else{
                if(w.startsWith('!') && w.endsWith('!') && w.length>=3){
                w=w.substr(1,w.length-2);
                w=<YourComponent onClick={this.action}>{w}</YourComponent>;
                }
            }
         }
       return w;
    })

}


 render(){
   let content=this.mapData('hello *asdf* *how* _are_ you !doing! today');
    return {content};
  }

0

A working solution purely using Javascript and ReactJs without dangerouslySetInnerHTML.

Yaklaşmak

İşaretleme öğelerini karakter karakter arama. Biriyle karşılaşır karşılaşmaz, bitiş etiketini arayın ve html'ye dönüştürün.

Snippet'te desteklenen etiketler

  • cesur
  • italik yazı
  • em
  • önceden

Snippet'ten Giriş ve Çıkış:

JsFiddle: https://jsfiddle.net/sunil12738/wg7emcz1/58/

Kod:

const preTag = "đ"
const map = {
      "*": "b",
      "!": "i",
      "_": "em",
      [preTag]: "pre"
    }

class App extends React.Component {
    constructor(){
      super()
      this.getData = this.getData.bind(this)
    }

    state = {
      data: []
    }
    getData() {
      let str = document.getElementById("ta1").value
      //If any tag contains more than one char, replace it with some char which is less frequently used and use it
      str = str.replace(/```/gi, preTag)
      const tempArr = []
      const tagsArr = Object.keys(map)
      let strIndexOf = 0;
      for (let i = 0; i < str.length; ++i) {
        strIndexOf = tagsArr.indexOf(str[i])
        if (strIndexOf >= 0 && str[i-1] !== "\\") {
          tempArr.push(str.substring(0, i).split("\\").join("").split(preTag).join(""))
          str = str.substr(i + 1);
          i = 0;
          for (let j = 0; j < str.length; ++j) {
            strIndexOf = tagsArr.indexOf(str[j])
            if (strIndexOf >= 0 && str[j-1] !== "\\") {
              const Tag = map[str[j]];
              tempArr.push(<Tag>{str.substring(0, j).split("\\").join("")}</Tag>)
              str = str.substr(j + 1);
              i = 0;
              break
             }
          }
        }
      }
      tempArr.push(str.split("\\").join(""))
      this.setState({
        data: tempArr,
      })
    }
    render() {
      return (
        <div>
          <textarea rows = "10"
            cols = "40"
           id = "ta1"
          /><br/>
          <button onClick={this.getData}>Render it</button><br/> 
          {this.state.data.map(x => x)} 
        </div>
      )
    }
  }

ReactDOM.render(
  <App/>,
  document.getElementById('root')
);
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
  <div id="root"></div>
</body>

Ayrıntılı açıklama (örnekle):

Dize'nin How are *you* doing? etiketlerle semboller için eşleme tutup tutmadığını varsayalım

map = {
 "*": "b"
}
  • Önce * buluncaya kadar döngü yapın, bundan önceki metin normal dizedir
  • Bunu dizinin içine it. Dizi, ["How are "]bulana kadar iç döngüyü başlatır *.
  • Now next between * and * needs to be bold, bunları html öğesine metin olarak dönüştürüyoruz ve doğrudan Tag = b'nin haritadan bulunduğu diziyi itiyoruz. Bunu yaparsanız <Tag>text</Tag>, dahili olarak tepki metne dönüştürür ve diziye iter. Şimdi dizi ["nasılsın", sen ]. İç döngüden kopma
  • Şimdi oradan dış döngüyü başlatıyoruz ve hiçbir etiket bulunamadı, bu yüzden dizide kalanları itin. Dizi şöyle olur: ["nasılsın", sen , "yapıyor"].
  • Kullanıcı arayüzünde oluştur How are <b>you</b> doing?
    Note: <b>you</b> is html and not text

Not : Yuvalamak da mümkündür. Yukarıdaki mantığı özyineleme olarak çağırmalıyız

Yeni etiket eklemek için destek

  • * Veya! Gibi bir karakterse, mapkarakterleri karşılık gelen etiket olarak karakter ve değer olarak nesneye ekleyin
  • `` `Gibi birden fazla karakter ise, daha az kullanılan bazı karakterlerle bire bir harita oluşturun ve ekleyin (Neden: şu anda karakter aramaya göre karaktere dayalı yaklaşım ve böylece birden fazla karakter kırılacaktır. , bu da mantığı geliştirerek halledilebilir)

Yuvalamayı destekliyor mu? Hayır
OP tarafından belirtilen tüm kullanım durumlarını destekliyor mu? Evet

Umarım yardımcı olur.


Merhaba, şimdi buna bakıyorum. Bu üçlü backtick desteği ile de kullanılabilir mi? Peki `` asdf``` kod blokları için de işe yarar mı?
Ryan Peschel

Ancak bazı değişiklikler gerekebilir. Şu anda, * veya! İçin sadece tek karakter eşleşmesi var. Bunun biraz değiştirilmesi gerekiyor. Kod blokları temelde koyu arka planla asdfoluşturulacak anlamına gelir <pre>asdf</pre>, değil mi? Bunu bana haber ver, göreceğim. Şimdi bile deneyebilirsiniz. Basit bir yaklaşım: Yukarıdaki çözümde, metindeki `` `'yi ^ veya ~ gibi özel bir karakterle değiştirin ve ön etikete eşleyin. Sonra iyi çalışır. Diğer yaklaşımların biraz daha çalışması gerekiyor
Sunil Chaudhary

Evet, tam olarak, `` asdf '' yi değiştirmek <pre>asdf</pre>. Teşekkürler!
Ryan Peschel

@RyanPeschel Merhaba! preEtiket desteğini de ekledik .
Çalışıp

İlginç bir çözüm (nadir karakteri kullanarak). Yine de gördüğüm bir sorun, orijinal yazımdaki koda destek eklediğimden kaçan (\ * asdf * kalın değil) için destek eksikliğidir (ayrıca bağlantısının sonundaki bağlantılı ayrıntımda da belirtilmiştir) İleti). Eklemek çok zor olur mu?
Ryan Peschel
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.