AMP

amp-bind

Description

Allows elements to mutate in response to user actions or data changes via data binding and simple JS-like expressions.

 

Required Scripts

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

يضيف المكوِّن تفاعلاً مخصصًا باستخدام ربط البيانات والتعبيرات.

[جدول المحتويات]

النص البرمجي المطلوب
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
أمثلة
البرامج التعليمية إنشاء صفحات AMP التفاعلية

نظرة عامة

يتيح لك المكوِّن amp-bind إضافة تفاعل ذي حالة مخصصة إلى صفحات AMP من خلال ربط البيانات والتعبيرات المشابهة لجافا سكريبت.

شاهِد هذا الفيديو للحصول على مقدمة عن amp-bind.

مثال بسيط

في المثال التالي، يؤدي النقر على الزر إلى تغيير نص العنصر &lt;p&gt; من "Hello World" إلى "Hello amp-bind".

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

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

لا يعمل "amp-bind" على تقييم التعبيرات عند تحميل الصفحة وذلك للحفاظ على الأداء ولتجنب خطر التخطي غير المتوقع للمحتوى. ويعني هذا أنه يجب إعطاء حالة تلقائية للعناصر المرئية وعدم الاعتماد على "amp-bind" للعرض الأولي.

كيف يعمل؟

يحتوي "amp-bind" على ثلاثة مكونات رئيسية:

  1. State: حالة JSON قابلة للتحويل على نطاق المستند. في المثال أعلاه، الحالة فارغة قبل النقر على الزر. أما بعد النقر، تصبح الحالة {foo: 'amp-bind'}.
  2. Expressions: هذه تعبيرات تشبيه جافا سكريبت وتشير إلى state. يحتوي المثال أعلاه على تعبير واحد هو 'Hello ' + foo والذي يربط السلسلة الحرفية 'Hello ' مع متغير الحالة foo. يمكن استخدام ما يصل إلى 100 معامل كحد أقصى في التعبير.
  3. Bindings: هذه سمات خاصة لخاصية [property] النموذج التي تربط خاصية العنصر بالتعبير expression. يحتوي المثال أعلاه على ربط واحد هو [text] والذي يعدّل نص العنصر `` كلما تغيرت قيمة التعبير.

يحتاج amp-bind إلى عناية خاصة لضمان السرعة والأمان وجودة الأداء على صفحات AMP.

مثال أكثر تعقيدًا

<!-- 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>

عند الضغط على الزر:

  1. يتم تعديل الحالة بـ currentAnimal الذي يتم تحديده على أنه 'cat'.
  2. يتم تقييم التعبيرات التي تعتمد على الحالة currentAnimal:

    • 'This is a ' + currentAnimal + '.' => 'This is a cat.'
    • myAnimals[currentAnimal].style => 'redBackground'
    • myAnimals[currentAnimal].imageUrl => /img/cat.jpg
  3. يتم تعديل عمليات الربط التي تعتمد على التعبيرات التي تم تغييرها:

    • نص عنصر <p> الأول سيصبح "This is a cat."
    • السمة class للعنصر <p> الثاني ستصبح "redBackground".
    • العنصر amp-img سيعرض صورة قطة.

شاهِد العرض التوضيحي المباشر لهذا المثال مع تعليقات توضيحية للترميز.

التفاصيل

الحالة

كل مستند AMP يستخدم amp-bind له بيانات JSON أو حالة قابلة للتحويل على نطاق المستند.

إعداد الحالة باستخدام amp-state

يمكن إعداد حالة amp-bind باستخدام المكوِّن amp-state:

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

يمكن أن تشير التعبيرات إلى متغيرات الحالة عبر بنية النقاط. في هذا المثال، سيتم تقييم myState.foo إلى "bar".

  • الحد الأقصى لحجم JSON للعنصر الثانوي للعنصر <amp-state> هو 100 كيلوبايت.
  • يمكن للعنصر <amp-state> أيضًا تحديد عنوان CORS URL بدلاً من نص برمجي لعنصر JSON الثانوي. يمكن مراجعة الملحق للحصول على التفاصيل.

إعادة تحميل الحالة

يتيح هذ المكوِّن الإجراء refresh الذي يمكن استخدامه لإعادة تحميل محتوى الحالة.

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

تعديل الحالة باستخدام AMP.setState()

يُدمِج الإجراء AMP.setState() كائنًا حرفيًا في الحالة. عند الضغط على الزر أدناه مثلاً، سيعمل AMP.setState() على الدمج العميق للكائن الحرفي مع الحالة.

<!-- 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>

بشكل عام، سيتم دمج الكائنات المدمجة بحد أقصى للعمق يساوي 10. يمكن إلغاء كل المتغيرات، بما في ذلك المتغيرات التي قدمها amp-state.

عند تشغيل الإجراء AMP.setState() بواسطة أحداث معينة، يمكنه أيضًا الوصول إلى البيانات المتعلقة بالأحداث في الخاصية event.

<!-- 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()

يشبه الإجراء AMP.pushState() الإجراء AMP.setState() إلا أنه يدفع إدخالاً جديدًا في حِزم سجلّ التصفُّح. يؤدي دفع إدخال السجلّ هذا (بالانتقال إلى الخلف مثلاً) إلى استعادة القيمة السابقة للمتغيرات التي عينها AMP.pushState().

على سبيل المثال:

<button on="tap:AMP.pushState({foo: '123'})">Set 'foo' to 123</button>
  • سيؤدي النقر على الزر إلى تعيين المتغير foo على 123 ودفع إدخال سجلّ جديد.
  • سيؤدي الانتقال إلى الخلف إلى استعادة foo قيمتها السابقة وهي "bar" (أي ما يعادل استدعاء AMP.setState({foo: 'bar'}).

التعبيرات

التعبيرات تشبه جافا سكريبت مع بعض الاختلافات المهمة.

الاختلافات التي تميّزها عن جافا سكريبت

  • قد تصل التعبيرات فقط إلى حالة المستند الحاوية.
  • ليست للتعبيرات إمكانية الوصول إلى المتغيرات العمومية مثل window أو document.
  • لا يمكن استخدام سوى الدالات المدرجة في القائمة البيضاء وعوامل التشغيل.
  • غير مسموح عمومًا بالدالات والفئات والحلقات المخصصة. يُسمح بالدالات السهمية كمعلَمات، مثل Array.prototype.map.
  • المتغيرات غير المحددة وarray-index-out-of-bound تعرض null بدلاً من undefined أو إظهار الأخطاء.
  • للتعبير الواحد حاليًا حد قيمته 50 معاملًا بغرض جودة الأداء. يرجى الاتصال بنا إذا لم يكن هذا كافيًا لحالة الاستخدام لديك.

يمكن العثور على القواعد الكاملة للتعبيرات وتنفيذها في bind-expr-impl.jison وbind-expression.js.

أمثلة

جميع ما يلي تعبيرات صالحة:

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

الدالات المدرجة في القائمة البيضاء

نوع الكائن الدالات مثال
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')

1لا يمكن أن تحتوي الدالات السهمية ذات المعلَمة الفردية على أقواس، استخدِم مثلاً x => x + 1 بدلاً من (x) => x + 1. وتعرض أيضًا sort() وsplice() نسخًا معدّلة بدلاً العمل في مكانها.

2الدالات الثابتة لا تستخدم كمساحات أسماء، استخدِم مثلاً abs(-1) بدلاً من Math.abs(-1).

تحديد وحدات الماكرو باستخدام amp-bind-macro

يمكن إعادة استخدام أجزاء التعبير amp-bind من خلال تحديد amp-bind-macro. يتيح لك العنصر amp-bind-macro تحديد تعبير يأخذ وسيطات صفرية أو أكثر ويشير إلى الحالة الحالية. يمكن استدعاء وحدة ماكرو كدالة من خلال الإشارة إلى قيمة سمتها id من أي مكان في المستند.

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

يمكن لوحدة الماكرو أيضًا استدعاء وحدات ماكرو أخرى يكون قد تم تحديدها قبلها. لا يمكن لوحدة الماكرو استدعاء نفسها بشكل متكرر.

عمليات الربط

الربط سمة خاصة للخاصية [property] للنموذج تربط خاصية العنصر بأحد التعبيرات. ويمكن أيضًا استخدام بنية متوافقة مع XML في شكل data-amp-bind-property.

عندما تتغير الحالة، يتم إعادة تقييم التعبيرات ويتم تعديل خصائص العناصر المرتبطة بنتائج التعبيرات الجديدة.

يتيح amp-bind روابط البيانات في أربعة أنواع من حالة العنصر:

النوع السمات التفاصيل
Node.textContent [text] سمة متاحة في معظم العناصر النصية.
فئات CSS [class] يجب أن تكون نتيجة التعبير سلسلة محددة بمسافة.
السمة hidden [hidden] يجب أن تكون تعبيرًا منطقيًا.
حجم عناصر AMP [width]
[height]
تغيّر عرض و/أو ارتفاع عنصر AMP.
السمات الخاصة بالعناصر متنوعة

ملاحظات عن عمليات الربط:

  • إن الربط بـ innerHTML غير مسموح به لأسباب تتعلق بالأمان.
  • يتم تصحيح جميع عمليات ربط السمات من القيم غير الآمنة (مثل javascript:).
  • يؤدي التعبير المنطقي إلى تبديل السمات المنطقية على سبيل المثال: <amp-video [controls]="expr"...>. عندما يتم تقييم expr إلى true، يحتوي العنصر <amp-video> على السمة controls. عند تقييم expr إلى false، تتم إزالة السمة controls.
  • يمكن أن تكون أحرف الأقواس [ and ] في أسماء السمات مشكلة عند كتابة XML (مثل XHTML وJSX) أو كتابة السمات عبر واجهات برمجة التطبيقات DOM. في هذه الحالات، استخدِم البنية البديلة data-amp-bind-x="foo" بدلاً من [x]="foo".

السمات الخاصة بالعناصر

يُسمح فقط بالربط مع المكونات والسمات التالية:

المكوِّن السمات السلوك
<amp-brightcove> [data-account]
[data-embed]
[data-player]
[data-player-id]
[data-playlist-id]
[data-video-id]
يغيّر فيديو Brightcove المعروض.
<amp-carousel type=slides> [slide]** يغيّر فهرس الشريحة المعروض حاليًا. اطّلِع على مثال.
<amp-date-picker> [min]
[max]
يعيّن أقرب تاريخ يمكن اختياره.
يعيّن أبعد تاريخ يمكن اختياره.
<amp-google-document-embed> [src]
[title]
يعرض المستند على عنوان URL المُعدَل.
يغيّر عنوان المستند.
<amp-iframe> [src] يغيّر عنوان URL لمصدر إطار iframe.
<amp-img> [alt]
[attribution]
[src]
[srcset]
عند الربط بـ [src]، احرص أيضًا على الربط بـ [srcset] لجعل الربط يعمل على ذاكرة التخزين المؤقت.
اطّلِع على سمات amp-img المقابلة.
<amp-lightbox> [open]** يبدّل العرض المبسط. نصيحة: استخدِم on="lightboxClose: AMP.setState(...)" لتعديل المتغيرات عند غلق العرض المبسط.
<amp-list> [src] إذا كان التعبير عبارة عن سلسلة، يجلب المكَوِن JSON ويعرضه من عنوان URL للسلسلة. إذا كان التعبير كائنًا أو مصفوفة، سيعرض بيانات التعبير.
<amp-selector> [selected]**
[disabled]
يغيّر العناصر الثانوية المختارة حاليًا
التي حددتها قيم السمة option. يتيح عمل قائمة قيم مفصولة بينها فواصل للاختيار من متعدد. اطّلِع على مثال.
<amp-state> [src] يجلب JSON من عنوان URL الجديد ويدمجه في الحالة الحالية. لاحظ أن التعديل التالي سيتجاهل العناصر <amp-state> لمنع الدورات.
<amp-video> [alt]
[attribution]
[controls]
[loop]
[poster]
[preload]
[src]
اطّلِع على سمات amp-video المقابلة.
<amp-youtube> [data-videoid] يغيّر فيديو YouTube المعروض.
<a> [href] يغيّر الرابط.
<button> [disabled]
[type]
[value]
اطّلِع على سمات الزر المقابلة.
<details> [open] اطّلِع على سمات التفاصيل المقابلة.
<fieldset> [disabled] يفعّل مجموعة الحقول أو يوقفها.
<image> [xlink:href]
| اطّلِع على سمات الصور المقابلة.
<input> [accept]
[accessKey]
[autocomplete]
[checked]
[disabled]
[height]
[inputmode]
[max]
[maxlength]
[min]
[minlength]
[multiple]
[pattern]
[placeholder]
[readonly]
[required]
[selectiondirection]
[size]
[spellcheck]
[step]
[type]
[value]
[width]
اطّلِع على سمات الإدخالات المقابلة.
<option> [disabled]
[label]
[selected]
[value]
اطّلِع على سمات الخيارات المقابلة.
<optgroup> [disabled]
[label]
اطّلِع على سمات optgroup المقابلة.
<select> [autofocus]
[disabled]
[multiple]
[required]
[size]
اطّلِع على سمات select المقابلة.
<source> [src]
[type]
اطّلِع على سمات source المقابلة.
<track> [label]
[src]
[srclang]
اطّلِع على سمات track المقابلة.
<textarea> [autocomplete]
[autofocus]
[cols]
[disabled]
[maxlength]
[minlength]
[placeholder]
[readonly]
[required]
[rows]
[selectiondirection]
[selectionend]
[selectionstart]
[spellcheck]
[wrap]
اطّلِع على سمات textarea المقابلة.

تشير إلى سمات قابلة للربط ليس لها نظير غير قابل للربط.

تصحيح الأخطاء

اختبِر في وضع مطور البرامج (باستخدام جزء عنوان URL #development=1) لإبراز التحذيرات والأخطاء أثناء التطوير والوصول إلى الوظائف الخاصة لتصحيح الأخطاء.

التحذيرات

في وضع مطور البرامج، سيصدر amp-bind تحذيرًا عندما لا تتطابق القيمة التلقائية لسمة مرتبطة مع النتيجة الأولية للتعبير المقابل. يمكن أن يساعد هذا في منع الطفرات غير المقصودة الناجمة عن التغييرات في متغيرات الحالة الأخرى. مثال:

<!-- 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]="'abc'" class="def"></p>

في وضع مطور البرامج، سيصدر amp-bind أيضًا تحذيرًا عند الوصول إلى محتوى المتغيرات أو الخصائص غير المحددة. يمكن أن يساعد هذا أيضًا في منع الطفرات غير المقصودة بسبب نتائج التعبير null. مثال:

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

<!-- 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>

الأخطاء

هناك عدة أنواع من أخطاء وقت التشغيل التي يمكن مواجهتها عند التعامل مع amp-bind.

النوع الرسالة الاقتراح
الربط غير صالح Binding to [someBogusAttribute] on <P> is not allowed. استخدِم فقط عمليات الربط المدرجة في القائمة البيضاء.
خطأ في البنية Expression compilation error in... تحقق من التعبير بحثًا عن الأخطاء الإملائية.
الدالات غير مدرجة في القائمة البيضاء alert is not a supported function. استخدِم فقط الدالات المدرجة في القائمة البيضاء.
نتيجة مصحَحة "javascript:alert(1)" is not a valid result for [href]. تجنب بروتوكولات URL المحظورة أو التعبيرات المحظورة التي تخفق في "مدقق AMP".
مخالفة CSP Refused to create a worker from 'blob:...' because it violates the following Content Security Policy directive... أضِف default-src blob: إلى سياسة أمان المحتوى للأصل. يفوض amp-bind العمل المُكلِف إلى عامل ويب مخصص لضمان الأداء الجيد.

حالة تصحيح الأخطاء

استخدِم AMP.printState() لطباعة الحالة الحالية إلى وحدة التحكم.

الملحق

مواصفات <amp-state>

قد يحتوي عنصر amp-state على عنصر <script> الثانوي أو سمة src التي تحتوي على عنوان CORS URL إلى نقطة نهاية JSON بعيدة، ولكن ليس على كليهما.

<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 في دفعات

AMP ترسل XMLHttpRequests (XHRs) إلى نقاط نهاية JSON في دفعات، أي أنه يمكنك استخدام طلب بيانات JSON واحد كمصدر بيانات لعدة مستهلكين (عدة عناصر amp-state مثلاً) على صفحة AMP. على سبيل المثال، إذا أرسل العنصر amp-state طلب XHR إلى نقطة نهاية، وكان الطلب في رحلته، لن يتم تشغيل جميع طلبات XHR اللاحقة إلى نقطة النهاية ونفسها وسيتم عرض النتائج من طلب XHR الأول.

السمات

src هي عنوان URL لنقطة النهاية البعيدة التي ستعرض JSON الذي سيعمل على تعديل amp-state هذا. ويجب أن تكون خدمة CORS HTTP. تتيح السمة src جميع استبدالات متغيرات عنوان URL القياسية. اطّلِع على دليل الاستبدالات للحصول على المزيد من المعلومات.
يجب أن تنفذ نقطة النهاية المتطلبات المحددة في مواصفات طلبات CORS في AMP.
credentials (اختيارية) تعرِّف هذه السمة خيار credentials بالشكل الذي تحدده واجهة برمجة تطبيقات الجلب.
  • القيم المسموح بها: `omit` و`include`
  • القيمة التلقائية: `omit`
لإرسال بيانات الاعتماد، مرِر قيمة include. إذا تم تعيين هذه القيمة، يجب أن تتبع الاستجابة إرشادات الأمان AMP CORS.

الدمج العميق باستخدام AMP.setState()

عندما يتم استدعاء AMP.setState()، يدمج amp-bind الكائن الحرفي المتوفر بعمق مع الحالة الحالية. تتم كتابة جميع المتغيرات من الكائن الحرفي إلى الحالة مباشرة باستثناء الكائنات المدمجة والتي يتم دمجها بشكل متكرر. يتم دائمًا إلغاء العناصر الأولية والمصفوفات في الحالة بمتغيرات تحمل الاسم نفسه في الكائن الحرفي.

راجع المثال التالي:

{
<!-- 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>

عند الضغط على الزر الأول، تتغير الحالة إلى:

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

عند الضغط على الزر الثاني، سيدمج amp-bind الوسيطة الحرفية للكائن {employee: {age: 64}} بشكل متكرر في الحالة الحالية.

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

تم تعديل employee.age إلا أن المفاتيح employee.name وemployee.vehicle لم تتغير.

يُذكر أن amp-bind ستعطي خطأ إذا استدعيت AMP.setState() بكائن حرفي يحتوي على مراجع دائرية.

إزالة متغير

أزِل متغير حالة قائم عن طريق تعيين قيمته إلى القيمة null في AMP.setState(). بالبدء من الحالة في المثال السابق، سيؤدي الضغط إلى:

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

تغيير الحالة إلى:

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

بالمثل:

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

تغيير الحالة إلى:

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

قواعد التعبيرات

القواعد المشابهة لـ BNF لتعبيرات amp-bind:

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
هل تحتاج إلى مزيد من المساعدة؟

لقد قرأت هذا المستند عشرات المرات ولكن ألم يُجِب عن جميع أسئلتك؟ ربما حصل ذات الأمر مع أشخاص آخرين: تواصل معهم على Stack Overflow.

الذهاب إلى Stack Overflow
هل وجدت خطأ أو تفتقد لميزة؟

يشجع مشروع AMP مشاركتك ومساهمتك بشدة! ونأمل أن تكون مشاركًا دائمًا في مجتمعنا مفتوح المصدر، ولكننا نشجع أيضًا المساهمات التي تحدث لمرة واحدة في الأمور التي تتحمس لها.

الانتقال إلى GitHub