AMP

amp-bind

Description

Veri bağlama ve basit JS benzeri ifadeler aracılığıyla kullanıcı işlemlerine veya veri değişikliklerine yanıt olarak öğelerin değişmesine olanak tanır.

 

Required Scripts

<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>

Veri bağlama ve ifadelerle özel etkileşim özelliği ekler.

Zorunlu Komut Dosyası
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
Örnekler
Eğiticiler Etkileşimli AMP sayfaları oluşturma

Genel Bakış

amp-bind bileşeni, veri bağlama ve JS benzeri ifadeler aracılığıyla AMP sayfalarınıza özel durum bilgili etkileşim özelliği eklemenize olanak tanır.

amp-bind tanıtımı için bu videoyu izleyin.

Basit bir örnek

Aşağıdaki örnekte düğmeye dokunduğunuzda, <p> öğesinin "Hello World" olan metni "Hello amp-bind" olarak değişir.

<p [text]="'Hello ' + foo">Hello World</p>

<button on="tap:AMP.setState({foo: 'amp-bind'})">Say "Hello amp-bind"</button>

Yüksek performans elde etmek ve beklenmeyen içerik atlaması riskini önlemek için amp-bind öğesi, sayfa yüklemede ifadeleri değerlendirmez. Bu, görsel öğelere bir varsayılan durum verilmesi ve ilk oluşturma için amp-bind öğesine güvenilmemesi gerektiği anlamına gelir.

İşleyiş şekli

amp-bind üç ana bileşene sahiptir:

  1. State: Bir dokümanın kapsamı, değişebilir JSON durumu. Yukarıdaki örnekte, düğmeye dokunmadan önce durum boştur. Düğmeye dokunulduktan sonra, durum {foo: 'amp-bind'} olur.
  2. Expressions: Bunlar, state başvurusunda bulunabilen JavaScript benzeri ifadelerdir. Yukarıdaki örnekte, dize değişmez değerini ('Hello ') ve durum değişkenini (foo) birbirine bağlayan tek bir 'Hello ' + foo ifadesi bulunmaktadır. Bir ifade içinde 100 işlenen kullanma sınırı vardır.
  3. Bindings: Bunlar, bir öğenin özelliğini bir expression öğesine bağlayan [property] formunun özel özellikleridir. Yukarıdaki örnekte, <p> öğesinin metnini ifade değeri her değiştiğinde güncelleyen tek bir bağlama ([text]) bulunmaktadır.

amp-bind, AMP sayfalarında hız, güvenlik ve performansı sağlamaya özel önem verir.

Biraz daha karmaşık bir örnek

<!-- Store complex nested JSON data in <amp-state> elements. -->
<amp-state id="myAnimals">
  <script type="application/json">
    {
      "dog": {
        "imageUrl": "/img/dog.jpg",
        "style": "greenBackground"
      },
      "cat": {
        "imageUrl": "/img/cat.jpg",
        "style": "redBackground"
      }
    }
  </script>
</amp-state>

<p [text]="'This is a ' + currentAnimal + '.'">This is a dog.</p>

<!-- CSS classes can also be added or removed with [class]. -->
<p class="greenBackground" [class]="myAnimals[currentAnimal].style">
  Each animal has a different background color.
</p>

<!-- Or change an image's src with the [src] binding. -->
<amp-img width="300" height="200" src="/img/dog.jpg" [src]="myAnimals[currentAnimal].imageUrl">
</amp-img>

<button on="tap:AMP.setState({currentAnimal: 'cat'})">Set to Cat</button>

Düğmeye basıldığında:

  1. Durum, 'cat' olarak tanımlanan currentAnimal ile güncellenir.
  2. currentAnimal öğesine bağlı ifadeler değerlendirilir:

    • 'This is a ' + currentAnimal + '.' => 'This is a cat.'
    • myAnimals[currentAnimal].style => 'redBackground'
    • myAnimals[currentAnimal].imageUrl => /img/cat.jpg
  3. Değiştirilen ifadelere bağlı olan bağlamalar güncellenir:

    • İlk <p> öğesinin metni, "This is a cat" olur.
    • İkinci <p> öğesinin class özelliği "redBackground" olur.
    • amp-img öğesi bir kedinin resmini gösterir.

Kod ek açıklamalarının yer aldığı bu örnek için canlı demoyu deneyin!

Ayrıntılar

Durum

amp-bind kullanan her AMP dokümanı, doküman kapsamı değişebilir JSON verilerine veya durum bilgisine sahiptir.

amp-state ile başlangıç durumu

amp-bind durumu, amp-state bileşeni ile başlatılabilir:

<amp-state id="myState">
  <script type="application/json">
    {
      "foo": "bar"
      }
  </script>
</amp-state>

İfadeler, durum değişkenlerine nokta söz dizimi aracılığıyla başvurabilir. Bu örnekte, myState.foo, "bar" olarak değerlendirilir.

  • Bir <amp-state> öğesinin alt JSON'ı en fazla 100 KB olur.
  • Bir <amp-state> öğesi, alt JSON komut dosyası yerine bir CORS URL'si de belirtebilir. Ayrıntılar için Ek bölümüne bakın.

Durumu yenileme

refresh işlemi bu bileşen tarafından desteklenir ve durum içeriğini yenilemek için kullanılabilir.

<amp-state id="amp-state" ...></amp-state>
<!-- Clicking the button will refresh and refetch the json in amp-state. -->
<button on="tap:amp-state.refresh"></button>

Durumu AMP.setState() ile güncelleme

AMP.setState() işlemi, bir nesne değişmez değerini durumla birleştirir. Örneğin, aşağıdaki düğmeye basıldığında AMP.setState() öğesi, nesne değişmez değerini durum ile derinden birleştirir.

<!-- Like JavaScript, you can reference existing
      variables in the values of the  object literal. -->
<button on="tap:AMP.setState({foo: 'bar', baz: myAmpState.someVariable})"></button>

Genel olarak, iç içe yerleştirilmiş nesneler en fazla 10 derinlikte birleştirilir. amp-state tarafından sunulanlar da dahil olmak üzere tüm değişkenler geçersiz kılınabilir.

Belirli etkinlikler tarafından tetiklendiğinde, AMP.setState(), event özelliğindeki etkinlikle ilgili verilere de erişebilir.

<!-- The "change" event of this <input> element contains
      a "value" variable that can be referenced via "event.value". -->
<input type="range" on="change:AMP.setState({myRangeValue: event.value})">

AMP.pushState() ile geçmişi değiştirme

AMP.pushState() işlemi, tarayıcı geçmiş yığınına yeni bir giriş de aktarması haricinde AMP.setState() işlemine benzer. Bu geçmiş girişine dönülmesi (geri gidilerek), AMP.pushState() tarafından ayarlanan değişkenlerin önceki değerini geri yükler.

Örneğin:

<button on="tap:AMP.pushState({foo: '123'})">Set 'foo' to 123</button>
  • Düğmeye dokunulduğunda foo değeri 123 olarak ayarlanır ve yeni bir geçmiş girişi aktarılır.
  • Geri gidildiğinde foo önceki değeri olan "bar" değerine geri yüklenir (AMP.setState({foo: 'bar'}) işleminin çağrılmasıyla eşdeğerdir).

İfadeler

İfadeler, bazı önemli farklılıklarla birlikte JavaScript'e benzer.

JavaScript'ten farklılıklar

  • İfadeler yalnızca ifadeleri içeren dokümanın durumuna erişebilir.
  • İfadeler, window veya document gibi genel öğelere erişmez.
  • Yalnızca beyaz listedeki işlevler ve operatörler kullanılabilir.
  • Özel işlevlere, sınıflara ve döngülere genellikle izin verilmez. Ok işlevlerine parametre olarak izin verilir; ör. Array.prototype.map.
  • Tanımlanmamış değişkenler ve sınırların dışındaki dizi dizini undefined değeri döndürmek veya hata bildirmek yerine null değerini döndürür.
  • Performans açısından şu anda tek bir ifade 50 öğe ile sınırlanmıştır. Sizin kullanım alanınız için bu sayı yeterli değilse lütfen bize ulaşın.

Tam ifade dil bilgisi ve uygulaması, bind-expr-impl.jison ve bind-expression.js içinde bulunabilir.

Örnekler

Aşağıdaki ifadelerin tümü geçerlidir:

1 + '1'           // 11
1 + (+'1')        // 2
!0                // true
null || 'default' // 'default'

Beyaz listedeki işlevler

Nesne türü Fonksiyonlar Örnek
Array1 concat
filter
includes
indexOf
join
lastIndexOf
map
reduce
slice
some
sort (not-in-place)
splice (not-in-place)
// Returns [1, 2, 3].
          [3, 2, 1].sort()
// Returns [1, 3, 5].
            [1, 2, 3].map((x, i) => x + i)
// Returns 6.
              [1, 2, 3].reduce((x, y) => x + y)
Number toExponential
toFixed
toPrecision
toString
// Returns 3.
                (3.14).toFixed()
// Returns '3.14'.
                  (3.14).toString()
String charAt
charCodeAt
concat
indexOf
lastIndexOf
slice
split
substr
substring
toLowerCase
toUpperCase
// Returns 'abcdef'.
                      abc'.concat('def')
Math2 abs
ceil
floor
max
min
random
round
sign
// Returns 1.
                          abs(-1)
Object2 keys
values
// Returns ['a', 'b'].
                            keys({a: 1, b: 2})
// Returns [1, 2].
                              values({a: 1, b: 2}
Global2 encodeURI
encodeURIComponent
// Returns 'Hello%20world'.
                                encodeURIComponent('Hello world')

1Tek parametreli ok işlevlerinde parantez kullanılamaz; örneğin (x) => x + 1 yerine x => x + 1 kullanın. Ayrıca, sort() ve splice(), yerinde çalışma yerine değiştirilmiş kopyalar döndürür.

2Statik işlevler ad alanlı değildir; ör. Math.abs(-1) yerine abs(-1) işlevini kullanın.

Makroları amp-bind-macro ile tanımlama

amp-bind ifade parçaları bir amp-bind-macro tanımlanarak yeniden kullanılabilir. amp-bind-macro öğesi, sıfır veya daha fazla bağımsız değişken alan ve geçerli duruma başvuruda bulunan bir ifade tanımlamanıza olanak tanır. Bir makro, dokümanın herhangi bir yerinden id özelliği değerine başvuruda bulunularak bir işlev gibi çağrılabilir.

<amp-bind-macro id="circleArea" arguments="radius" expression="3.14 * radius * radius"></amp-bind-macro>

<div>
  The circle has an area of <span [text]="circleArea(myCircle.radius)">0</span>.
</div>

Bir makro, kendisinden önce tanımlanan diğer makroları da çağırabilir. Bir makro kendini yinelemeli olarak çağıramaz.

Bağlamalar

Bağlama, bir öğenin özelliğini bir ifadeye bağlayan [property] formunun özel bir özelliğidir. data-amp-bind-property alternatif, XML uyumlu bir söz dizimi de kullanılabilir.

Durum değiştiğinde, ifadeler yeniden değerlendirilir ve bağlı öğelerin özellikleri yeni ifade sonuçlarıyla güncellenir.

amp-bind, dört öğe durumu türünde veri bağlamalarını destekler:

Tür Özellikler Ayrıntılar
Node.textContent [text] Çoğu metin öğesinde desteklenir.
CSS sınıfları [class] İfade sonucu, boşlukla ayrılmış bir dize olmalıdır.
hidden özelliği [hidden] Bir boole ifadesi olmalıdır.
AMP öğelerinin boyutu [width]
[height]
AMP öğesinin genişliğini ve/veya yüksekliğini değiştirir.
Öğeye özel özellikler Çeşitli

Bağlamalar ile ilgili notlar:

  • Güvenlik nedeniyle, innerHTML öğesine bağlamaya izin verilmez.
  • Güvenli olmayan değerler (ör. javascript:) tüm özellik bağlamalarından temizlenir.
  • Boole ifadesi sonuçları, boole özelliklerini açar/kapatır. Örneğin: <amp-video [controls]="expr"...>. expr, true olarak değerlendirildiğinde, <amp-video> öğesi controls özelliğine sahip olur. expr, false olarak değerlendirildiğinde controls özelliği kaldırılır.
  • Özellik adlarındaki köşeli parantez karakterleri [ ve ] XML (ör. XHTML, JSX) veya DOM API'leri aracılığıyla özellikleri yazarken soruna yol açabilir. Bu durumlarda, [x]="foo" yerine alternatif data-amp-bind-x="foo" söz dizimini kullanın.

Öğeye özel özellikler

Yalnızca aşağıdaki bileşenlere ve özelliklere bağlamaya izin verilir:

Bileşen Özellikler Davranış
<amp-brightcove> [data-account]
[data-embed]
[data-player]
[data-player-id]
[data-playlist-id]
[data-video-id]
Görüntülenen Brightcove videosunu değiştirir.
<amp-carousel type=slides> [slide]* Şu anda görüntülenen slayt dizinini değiştirir. Örneğe göz atın.
<amp-date-picker> [min]
[max]
Seçilebilir en erken tarihi ayarlar
Seçilebilir en son tarihi ayarlar
<amp-google-document-embed> [src]
[title]
Güncellenen URL'deki dokümanı görüntüler.
Dokümanın başlığını değiştirir.
<amp-iframe> [src] İframe'in kaynak URL'sini değiştirir.
<amp-img> [alt]
[attribution]
[src]
[srcset]
[src] öğesine bağlanırken, bağlamanın önbellekte çalışması için [srcset] öğesine de bağlama yaptığınızdan emin olun.
İlgili amp-img özelliklerine bakın.
<amp-lightbox> [open]* Lightbox'ın görüntülenmesini etkinleştirir/devre dışı bırakır. İpucu: Lightbox kapatıldığında değişkenleri güncellemek için on="lightboxClose: AMP.setState(...)" işlemini kullanın.
<amp-list> [src] İfade bir dizeyse dize URL'sinden JSON öğesini getirir ve oluşturur. İfade bir nesne veya diziyse ifade verilerini oluşturur.
<amp-selector> [selected]*
[disabled]
Geçerli olarak seçilmiş,
option özelliği değerlerine göre tanımlanan alt öğeleri değiştirir. Çoklu seçim için virgülle ayrılmış değer listesini destekler. Örneğe göz atın
<amp-state> [src] JSON değerini yeni URL'den alır ve mevcut durumla birleştirir. Aşağıdaki güncellemenin döngüleri önlemek için <amp-state> öğelerini yoksayacağını unutmayın.
<amp-video> [alt]
[attribution]
[controls]
[loop]
[poster]
[preload]
[src]
İlgili amp-video özelliklerine bakın.
<amp-youtube> [data-videoid] Görüntülenen YouTube videosunu değiştirir.
<a> [href] Bağlantıyı değiştirir.
<button> [disabled]
[type]
[value]
İlgili button özelliklerine bakın.
<details> [open] İlgili details özelliklerine bakın.
<fieldset> [disabled] Alan grubunu etkinleştirir veya devre dışı bırakır.
<image> [xlink:href]
İlgili image özelliklerine bakın.
<input> [accept]
[accessKey]
[autocomplete]
[checked]
[disabled]
[height]
[inputmode]
[max]
[maxlength]
[min]
[minlength]
[multiple]
[pattern]
[placeholder]
[readonly]
[required]
[selectiondirection]
[size]
[spellcheck]
[step]
[type]
[value]
[width]
İlgili input özelliklerine bakın.
<option> [disabled]
[label]
[selected]
[value]
İlgili option özelliklerine bakın.
<optgroup> [disabled]
[label]
İlgili optgroup özelliklerine bakın
<select> [autofocus]
[disabled]
[multiple]
[required]
[size]
İlgili select özelliklerine bakın.
<source> [src]
[type]
İlgili source özelliklerine bakın.
<track> [label]
[src]
[srclang]
İlgili track özelliklerine bakın.
<textarea> [autocomplete]
[autofocus]
[cols]
[disabled]
[maxlength]
[minlength]
[placeholder]
[readonly]
[required]
[rows]
[selectiondirection]
[selectionend]
[selectionstart]
[spellcheck]
[wrap]
İlgili textarea özelliklerine bakın.

*Bağlanabilir olmayan bir eşdeğeri bulunmayan bağlanabilir özellikleri belirtir.

Hata ayıklama

Geliştirme sırasında ortaya çıkan uyarıları ve hataları vurgulamak ve özel hata ayıklama işlevlerine erişmek için geliştirme modunda (#development=1 URL parçasıyla) test yapın.

Uyarılar

Geliştirme modunda, bir bağlama özelliğinin varsayılan değeri, karşılık gelen ifadenin ilk sonucuyla eşleşmediğinde amp-bind bir uyarı yayınlar. Bu, diğer durum değişkenlerindeki değişikliklerin neden olduğu istenmeyen dönüşümleri önlemeye yardımcı olabilir. Örneğin:

<!-- The element's default class value ('def') doesn't match the expression result for [class] ('abc'),
so a warning will be issued in development mode. -->

<p class="def" [class]="'abc'"></p>

Geliştirme modunda, amp-bind, tanımlanmamış değişkenleri veya özellikleri başvurudan kaldırırken bir uyarı da yayınlar. Bu, aynı zamanda null ifade sonuçları nedeniyle istenmeyen dönüşümleri önlemeye yardımcı olabilir. Örneğin:

<amp-state id="myAmpState">
  <script type="application/json">
    { "foo": 123 }
</script>
</amp-state></p>

<!-- The amp-state#myAmpState does not have a `bar` variable, so a warning
  will be issued in development mode. -->
<p [text]="myAmpState.bar">Some placeholder text.</p>

Hatalar

amp-bind ile çalışırken karşılaşabileceğiniz birkaç çalışma zamanı hatası türü vardır.

Tür Mesaj Öneri
Geçersiz bağlama <P> öğesinde [someBogusAttribute] özelliğine bağlamaya izin verilmiyor. Yalnızca beyaz listedeki bağlamaları kullanın.
Sözdizimi hatası İfade derleme hatası... İfadede yazım hataları olmadığını doğrulayın.
Beyaz listede yer almayan işlevler uyarı desteklenen bir işlev değildir. Yalnızca beyaz listedeki işlevleri kullanın.
Temizlenmiş sonuç "javascript:alert(1)", [href] için geçerli bir sonuç değil. Yasaklanmış URL protokollerini veya AMP Doğrulayıcı'da başarısız olacak ifadeleri kullanmaktan kaçının.
CSP ihlali 'blob:...' öğesinden bir işçi oluşturulması reddedildi. Aksi takdirde, şu İçerik Güvenliği Politikası kuralı ihlal edilecekti... Kaynağınızın İçerik Güvenliği Politikası'na default-src blob: öğesini ekleyin. amp-bind, iyi bir performans sağlamak üzere pahalı işler için özel bir Web İşçisine yetki verir.

Hata Ayıklama Durumu

Geçerli durumu konsola yazdırmak için AMP.printState() kullanın.

Ek

<amp-state> spesifikasyonu

Bir amp-state öğesi, bir alt <script> öğesi VEYA uzak bir JSON uç noktasının CORS URL'sini içeren bir src özelliği içerebilir ancak bunların ikisini birden içeremez.

<amp-state id="myLocalState">
  <script type="application/json">
    {
      "foo": "bar"
      }
  </script>
</amp-state>

<amp-state id="myRemoteState" src="https://data.com/articles.json">
</amp-state>

XHR toplu işlemesi

AMP, XMLHttpRequest öğelerini (XHR'ler) JSON uç noktalarında toplu olarak işler; diğer bir deyişle, bir AMP sayfasında birden çok tüketici (ör. birden fazla amp-state öğesi) için veri kaynağı olarak tek bir JSON veri isteğini kullanabilirsiniz. Örneğin, amp-state öğeniz bir uç noktaya XHR gönderirse XHR iletilirken aynı uç noktaya yapılacak sonraki XHR'lerin hiçbiri tetiklenmez ve bunun yerine, ilk XHR'nin sonuçları döndürülür.

Özellikler

src Bu amp-state öğesini güncelleyecek olan JSON değerini döndürecek uzak uç noktanın URL'si. Bu bir CORS HTTP hizmeti olmalıdır. src özelliği, tüm standart URL değişkeni değişikliklerine izin verir. Daha fazla bilgi için Değişiklik Kılavuzu dokümanına bakın.
Uç nokta, AMP'de CORS İstekleri spesifikasyonunda belirtilen gereksinimleri uygulamalıdır.
credentials (isteğe bağlı) Getirme API'si tarafından belirtildiği şekliyle bir credentials seçeneğini tanımlar.
  • Desteklenen değerler: `omit`, `include`
  • Varsayılan değer: `omit`
Kimlik bilgilerini göndermek için include değerini geçirin. Bu değer ayarlanırsa yanıt, AMP CORS güvenlik yönergelerine uygun olmalıdır.

AMP.setState() ile derin birleştirme

AMP.setState() çağrıldığında, amp-bind, sağlanan nesneyi değişmez değerini geçerli durumla derin birleştirir. Nesne değişmez değerindeki tüm değişkenler, tekrar eden bir şekilde birleştirilen iç içe yerleştirilmiş nesneler haricinde, doğrudan duruma yazılır. Nesne değişmez değerinde bulunan, temel öğeler ve dizilerle aynı ada sahip değişkenlerin değeri her zaman bunların üzerine yazılır.

Aşağıdaki örneği inceleyin:

{
  <!-- State is empty -->
  }
<button on="tap:AMP.setState({employee: {name: 'John Smith', age: 47, vehicle: 'Car'}})"...></button>
<button on="tap:AMP.setState({employee: {age: 64}})"...></button>

İlk düğmeye basıldığında durum şu şekilde değişir:

{
  employee: {
    name: 'John Smith',
    age: 47,
    vehicle: 'Car',
    }
  }

İkinci düğmeye basıldığında, amp-bind nesne değişmez değeri bağımsız değişkenini ({employee: {age: 64}}) yinelenen bir şekilde mevcut durumla birleştirir.

{
  employee: {
    name: 'John Smith',
    age: 64,
    vehicle: 'Car',
    }
  }

employee.age güncellenmiş ancak employee.name ve employee.vehicle anahtarları değişmemiştir.

AMP.setState() işlemini döngüsel başvurular içeren bir nesne değişmez değeriyle çağırırsanız amp-bind öğesinin hata vereceğini lütfen unutmayın.

Bir değişkeni kaldırma

Mevcut bir durum değişkeninin değerini AMP.setState() işleminde null olarak ayarlayarak değişkeni kaldırın. Önceki örnekteki durumla başlayarak:

<button on="tap:AMP.setState({employee: {vehicle: null}})"...></button>

Düğmesine basıldığında durum şu şekilde değişir:

{
  employee: {
    name: 'John Smith',
    age: 48,
    }
  }

Benzer biçimde:

<button on="tap:AMP.setState({employee: null})"...></button>

Düğmesine basıldığında durum şu şekilde değişir:

{
  <!-- State is empty -->
  }

İfade dil bilgisi

amp-bind ifadeleri için BNF benzeri dil bilgisi:

expr:
    operation
  | invocation
  | member_access
  | '(' expr ')'
  | variable
  | literal

operation:
    '!' expr
  | '-' expr
  | '+' expr
  | expr '+' expr
  | expr '-' expr
  | expr '*' expr
  | expr '/' expr
  | expr '%' expr
  | expr '&&' expr
  | expr '||' expr
  | expr '<=' expr
  | expr '<' expr
  | expr '>=' expr
  | expr '>' expr
  | expr '!=' expr
  | expr '==' expr
  | expr '?' expr ':' expr

invocation:
    expr '.' NAME args

args:
    '(' ')'
  | '(' array ')'
  ;

member_access:
    expr member
  ;

member:
    '.' NAME
  | '[' expr ']'

variable:
    NAME
  ;

literal:
    STRING
  | NUMBER
  | TRUE
  | FALSE
  | NULL
  | object_literal
  | array_literal

array_literal:
    '[' ']'
  | '[' array ']'

array:
    expr
  | array ',' expr

object_literal:
    '{' '}'
  | '{' object '}'

object:
    key_value
  | object ',' key_value

key_value:
  expr ':' expr
Daha fazla yardıma mı ihtiyacınız var?

Bu belgeyi defalarca okudunuz ama tüm sorularınıza tatmin edici bir yanıt bulamadınız mı? Belki başka kişiler de bu şekilde hissetmiştir: Stack Overflow'dan onlara ulaşın.

Stack Overflow'a git
Bir hata veya eksik bir özellik mi buldunuz?

AMP projesi, katılımınızı ve katkılarınızı güçlü bir şekilde teşvik ediyor! Açık kaynak topluluğumuzun devamlı bir katılımcısı olacağınızı umuyoruz ancak özel olarak ilgilendiğiniz konularla ilgili tek seferlik katkıları da memnuniyetle karşılıyoruz.

GitHub'a git