إنشاء إطار عمل لتطبيقات الويب باستخدام أدوات حديثة
مقدمة
مرحبًا، يعلم أي مستخدم يكتب تطبيق ويب مدى أهمية الحفاظ على إنتاجيته. يشكّل ذلك تحديًا عندما يكون عليك القلق بشأن المهام المملة، مثل العثور على النموذج المناسب وإعداد سير عمل التطوير والاختبار وتصغير جميع مصادرك وضغطها.
لحسن الحظ، يمكن أن تساعد أدوات الواجهة الأمامية الحديثة في التشغيل الآلي لكثير من هذه المهام، ما يترك لك الوقت للتركيز على كتابة تطبيق رائع. ستوضّح لك هذه المقالة كيفية استخدام Yeoman، وهو سير عمل للأدوات المخصّصة لتطبيقات الويب من أجل تبسيط إنشاء التطبيقات باستخدام Polymer، وهي مكتبة من الإضافات البرمجية وأدوات تطوير التطبيقات باستخدام Web Components.
التعرّف على "يو" و"غرانت" و"بوور"
"يومان" هو رجل يرتدي قبعة ويقدّم ثلاث أدوات لتحسين إنتاجيتك:
- yo هي أداة إنشاء إطار عمل تقدّم منظومة متكاملة من إطارات العمل الخاصة بالإطارات، والتي تُعرف باسم "المنشئين"، ويمكن استخدامها لتنفيذ بعض المهام المملة التي ذكرناها سابقًا.
- يُستخدَم grunt لإنشاء مشروعك ومعاينته واختباره، وذلك بفضل المهام التي ينظّمها فريق Yeoman وgrunt-contrib.
- يُستخدَم bower لإدارة التبعيات، لكي لا تضطر إلى تنزيل النصوص البرمجية وإدارتها يدويًا.
باستخدام أمر أو أمرَين فقط، يمكن لـ Yeoman كتابة رمز نموذجي لتطبيقك (أو أجزاء فردية مثل النماذج)، وتجميع ملفات Sass، وتصغير ملفات CSS وJavaScript وHTML والصور وتجميعها، وتشغيل خادم ويب بسيط في الدليل الحالي. ويمكنه أيضًا تنفيذ اختبارات الوحدة وغير ذلك.
يمكنك تثبيت منشئي النماذج من Node Packaged Modules (npm) وهناك أكثر من 220 منشئ نماذج متاحًا الآن، وقد كتب الكثير منها مجتمع البرامج المفتوحة المصدر. تشمل مولّدات المحتوى الشائعة generator-angular وgenerator-backbone وgenerator-ember.
بعد تثبيت أحدث إصدار من Node.js، انتقِل إلى أقرب وحدة طرفية ونفِّذ ما يلي:
$ npm install -g yo
هذا كل شيء! لديك الآن Yo وGrunt وBower ويمكنك تشغيلها مباشرةً من سطر الأوامر. في ما يلي نتيجة تشغيل yo
:
أداة إنشاء البوليمر
كما ذكرت سابقًا، Polymer هي مكتبة من الرموز البرمجية polyfills وsugar التي تتيح استخدام مكوّنات الويب في المتصفّحات الحديثة. يتيح المشروع للمطوّرين إنشاء تطبيقات باستخدام المنصة المستقبلية وإعلام W3C بالأماكن التي يمكن فيها تحسين المواصفات أثناء التشغيل.
generator-polymer هو أداة إنشاء جديدة تساعدك في إنشاء إطار عمل لتطبيقات Polymer باستخدام Yeoman، ما يتيح لك إنشاء عناصر Polymer (مخصّصة) وتخصيصها بسهولة من خلال سطر الأوامر واستيرادها باستخدام أداة HTML Imports. ويساعدك ذلك في توفير الوقت من خلال كتابة الرمز البرمجي الأساسي نيابةً عنك.
بعد ذلك، ثبِّت مُنشئ Polymer من خلال تشغيل:
$ npm install generator-polymer -g
ما مِن إجراءات أخرى مطلوبة. أصبح تطبيقك الآن يمتلك قدرات فائقة من Web Component.
يتضمّن المولد الذي تم تثبيته حديثًا بعض العناصر المحدّدة التي يمكنك الوصول إليها:
- يتم استخدام
polymer:element
لإنشاء إطار عمل لعناصر Polymer الفردية الجديدة. على سبيل المثال:yo polymer:element carousel
- يتم استخدام
polymer:app
لإنشاء إطار عمل لملف index.html الأوّلي، وهو ملف Gruntfile.js يحتوي على إعدادات وقت الإنشاء لمشروعك بالإضافة إلى مهام Grunt وبنية مجلد مقترَحة للمشروع. وسيتيح لك أيضًا استخدام Sass Bootstrap لتزيين مشروعك.
لننشئ تطبيقًا باستخدام Polymer
سننشئ مدوّنة بسيطة باستخدام بعض عناصر Polymer المخصّصة وأداة الإنشاء الجديدة.
للبدء، انتقِل إلى وحدة التحكّم الطرفية، وأنشئ دليلاً جديدًا وانتقِل إليه باستخدام mkdir my-new-project && cd $_
. يمكنك الآن بدء تطبيق Polymer من خلال تنفيذ ما يلي:
$ yo polymer
يحصل هذا الإجراء على أحدث إصدار من Polymer من Bower وينشئ إطار عمل لملف index.html وبنية الدليل ومهام Grunt لسير عملك. ننصحك بتناول كوب من القهوة أثناء انتظار انتهاء التطبيق من الاستعداد.
حسنًا، يمكننا بعد ذلك تشغيل grunt server
لمعاينة شكل التطبيق:
يتيح الخادم ميزة LiveReload، ما يعني أنّه يمكنك تشغيل محرِّر نصوص وتعديل عنصر مخصّص وستتم إعادة تحميل المتصفّح عند الحفظ. يمنحك ذلك عرضًا رائعًا في الوقت الفعلي للحالة الحالية لتطبيقك.
بعد ذلك، لننشئ عنصر Polymer جديدًا لتمثيل مشاركة مدونة.
$ yo polymer:element post
يطرح Yeoman علينا بعض الأسئلة، مثل ما إذا أردنا تضمين مُنشئ أو استخدام استيراد HTML لتضمين عنصر المشاركة في index.html
. لنفترض أنّنا نقول "لا" للخيارَين الأولَين في الوقت الحالي ونترك الخيار الثالث فارغًا.
$ yo polymer:element post
[?] Would you like to include constructor=''? No
[?] Import to your index.html using HTML imports? No
[?] Import other elements into this one? (e.g 'another_element.html' or leave blank)
create app/elements/post.html
يؤدي ذلك إلى إنشاء عنصر Polymer جديد في الدليل /elements
باسم post.html:
<polymer-element name="post-element" attributes="">
<template>
<style>
@host { :scope {display: block;} }
</style>
<span>I'm <b>post-element</b>. This is my Shadow DOM.</span>
</template>
<script>
Polymer('post-element', {
//applyAuthorStyles: true,
//resetStyleInheritance: true,
created: function() { },
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
ويشتمل على ما يلي:
- رمز نموذجي للعنصر المخصّص، ما يتيح لك استخدام نوع عنصر DOM مخصّص في صفحتك (مثل
<post-element>
) - علامة نموذج للنماذج "المضمّنة" من جهة العميل وعينات أنماط النطاق لتضمين أنماط العنصر
- نموذج تسجيل العنصر وأحداث دورة الحياة
العمل مع مصدر بيانات حقيقي
ستحتاج مدونتنا إلى مساحة لكتابة المشاركات الجديدة وقراءتها. لتوضيح كيفية العمل مع خدمة بيانات حقيقية، سنستخدم Google Apps Spreadsheets API. يتيح لنا ذلك قراءة محتوى أي جدول بيانات تم إنشاؤه باستخدام "مستندات Google" بسهولة.
لنبدأ عملية الإعداد:
في المتصفّح (ننصح باستخدام Chrome لتنفيذ هذه الخطوات)، افتح هذا جدول البيانات في "مستندات Google". يحتوي على عيّنات من بيانات المشاركات ضمن الحقول التالية:
- رقم التعريف
- العنوان
- المؤلّف
- المحتوى
- التاريخ
- الكلمات الرئيسية
- البريد الإلكتروني (للمؤلف)
- الاسم المعرِّف (لعنوان URL الخاص بالاسم المعرِّف لنشرتك)
انتقِل إلى قائمة ملف واختَر إنشاء نسخة لإنشاء نسختك الخاصة من جدول البيانات. يمكنك تعديل المحتوى متى شئت، وإضافة مشاركات أو إزالتها.
انتقِل إلى قائمة ملف مرة أخرى واختَر النشر على الويب.
انقر على بدء النشر.
ضمن الحصول على رابط إلى البيانات المنشورة، انسخ الجزء المفتاح من عنوان URL المقدَّم في مربّع النص الأخير. يظهر على النحو التالي: https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0
الصِق المفتاح في عنوان URL التالي حيث يظهر النص your-key-goes-here: https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json-in-script&callback=. قد يبدو مثال على استخدام المفتاح أعلاه على النحو التالي: https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script.
يمكنك لصق عنوان URL في المتصفّح والانتقال إليه لعرض نسخة JSON من محتوى مدونتك. دوِّن عنوان URL ثم اقضِ بعض الوقت في مراجعة تنسيق هذه البيانات لأنّك ستحتاج إلى تكرارها لعرضها على الشاشة لاحقًا.
قد يبدو لك ناتج JSON في المتصفّح مخيفًا بعض الشيء، ولكن لا داعي للقلق. لا يهمّنا سوى بيانات مشاركاتك.
تُخرج Google Sheets API كل حقل في جدول بيانات مدونتك باستخدام بادئة خاصة post.gsx$
. على سبيل المثال: post.gsx$title.$t
وpost.gsx$author.$t
وpost.gsx$content.$t
وما إلى ذلك. عند تكرار كل "صف" في إخراج JSON، سنشير إلى هذه الحقول لاسترداد القيم ذات الصلة لكل مشاركة.
يمكنك الآن تعديل عنصر المشاركة الذي تمّ إعداده حديثًا لربط أجزاء من الترميز بالبيانات في جدول البيانات. لإجراء ذلك، سنُعرِض السمة post
التي ستقرأ عنوان المشاركة والمؤلف والمحتوى والحقول الأخرى التي أنشأناها سابقًا. تُستخدَم سمة selected
(التي سنملؤها لاحقًا) لعرض مشاركة فقط إذا انتقل المستخدم إلى العنوان المختصر الصحيح لها.
<polymer-element name="post-element" attributes="post selected">
<template>
<style>
@host { :scope {display: block;} }
</style>
<div class="col-lg-4">
<template if="[[post.gsx$slug.$t === selected]]">
<h2>
<a href="#[[post.gsx$slug.$t]]">
[[post.gsx$title.$t ]]
</a>
</h2>
<p>By [[post.gsx$author.$t]]</p>
<p>[[post.gsx$content.$t]]</p>
<p>Published on: [[post.gsx$date.$t]]</p>
<small>Keywords: [[post.gsx$keywords.$t]]</small>
</template>
</div>
</template>
<script>
Polymer('post-element', {
created: function() { },
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
بعد ذلك، لننشئ عنصر مدونة يحتوي على مجموعة من المشاركات وتنسيق مدونتك من خلال تشغيل yo polymer:element blog
.
$ yo polymer:element blog
[?] Would you like to include constructor=''? No
[?] Import to your index.html using HTML imports? Yes
[?] Import other elements into this one? (e.g 'another_element.html' or leave blank) post.html
create app/elements/blog.html
سنستورد هذه المرة المدوّنة إلى index.html باستخدام عمليات استيراد HTML على النحو الذي نريد أن تظهر به في الصفحة. بالنسبة إلى الطلب الثالث على وجه التحديد، نحدّد post.html
كعنصر نريد تضمينه.
كما في السابق، يتم إنشاء ملف عنصر جديد (blog.html) وإضافته إلى /elements، وهذه المرة يتم استيراد post.html وتضمين <post-element>
داخل علامة النموذج:
<link rel="import" href="post.html">
<polymer-element name="blog-element" attributes="">
<template>
<style>
@host { :scope {display: block;} }
</style>
<span>I'm <b>blog-element</b>. This is my Shadow DOM.</span>
<post-element></post-element>
</template>
<script>
Polymer('blog-element', {
//applyAuthorStyles: true,
//resetStyleInheritance: true,
created: function() { },
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
بما أنّنا طلبنا استيراد عنصر المدونة باستخدام عمليات استيراد HTML (وهي طريقة لتضمين مستندات HTML وإعادة استخدامها في مستندات HTML أخرى) إلى فهرسنا، يمكننا أيضًا التحقّق من أنّه تمت إضافته بشكل صحيح إلى المستند <head>
:
<!doctype html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="styles/main.css">
<!-- build:js scripts/vendor/modernizr.js -->
<script src="bower_components/modernizr/modernizr.js"></script>
<!-- endbuild -->
<!-- Place your HTML imports here -->
<link rel="import" href="elements/blog.html">
</head>
<body>
<div class="container">
<div class="hero-unit" style="width:90%">
<blog-element></blog-element>
</div>
</div>
<script>
document.addEventListener('WebComponentsReady', function() {
// Perform some behaviour
});
</script>
<!-- build:js scripts/vendor.js -->
<script src="bower_components/polymer/polymer.min.js"></script>
<!-- endbuild -->
</body>
</html>
رائع.
إضافة التبعيات باستخدام Bower
بعد ذلك، لنعدِّل العنصر لاستخدام عنصر الأداة Polymer JSONP لقراءة posts.json. يمكنك الحصول على المحوِّل من خلال استنساخ المستودع باستخدام git أو تثبيت polymer-elements
من خلال Bower عن طريق تشغيل bower install polymer-elements
.
بعد الحصول على الأداة، عليك تضمينها كملف مستورَد في عنصر blog.html باستخدام:
<link rel="import" href="../bower_components/polymer-jsonp/polymer-jsonp.html">
بعد ذلك، أدرِج العلامة الخاصة بها وأدخِل url
إلى جدول بيانات مشاركات المدونة الذي أنشأناه سابقًا، مع إضافة &callback=
في النهاية:
<polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/your-key-value/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>
بعد تنفيذ ذلك، يمكننا الآن إضافة نماذج للتكرار في جدول البيانات بعد قراءته. يعرض القسم الأول جدول محتويات يتضمّن عنوانًا مرتبطًا بمشاركة يشير إلى العنوان المختصر لها.
<!-- Table of contents -->
<ul>
<template repeat="[[post in posts.feed.entry]]">
<li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>
</template>
</ul>
يعرض العنصر الثاني مثيلًا واحدًا من post-element
لكل إدخال يتم العثور عليه، مع تمرير محتوى المشاركة إليه وفقًا لذلك. يُرجى العِلم أنّنا نُدخل سمة post
تمثّل محتوى المشاركة لصف واحد في جدول البيانات وسمة selected
التي سنملؤها بمسار.
<!-- Post content -->
<template repeat="[[post in posts.feed.entry]]">
<post-element post="[[post]]" selected="[[route]]"></post-element>
</template>
إنّ سمة repeat
التي تظهر لك في النموذج تُنشئ مثيلًا مع [[ عمليات الربط ]] وتحافظ عليه لكل عنصر في مجموعة الصفيف للمشاركات، عند توفيرها.
الآن لنتمكّن من تعبئة [[route]] الحالية، سنخدع النظام ونستخدم مكتبة باسم Flatiron director ترتبط بـ [[route]] كلما تغيّر تجزئة عنوان URL.
لحسن الحظ، يتوفّر عنصر Polymer (جزء من حزمة more-elements) يمكننا استخدامه. بعد نسخه إلى الدليل /elements، يمكننا الإشارة إليه باستخدام <flatiron-director route="[[route]]" autoHash></flatiron-director>
، مع تحديد route
كسمة نريد الربط بها وطلب قراءة قيمة أي تغييرات في التجزئة تلقائيًا (autoHash).
بعد تجميع كل المعلومات، نحصل على ما يلي:
<link rel="import" href="post.html">
<link rel="import" href="polymer-jsonp/polymer-jsonp.html">
<link rel="import" href="flatiron-director/flatiron-director.html">
<polymer-element name="blog-element" attributes="">
<template>
<style>
@host { :scope {display: block;} }
</style>
<div class="row">
<h1><a href="/#">My Polymer Blog</a></h1>
<flatiron-director route="[[route]]" autoHash></flatiron-director>
<h2>Posts</h2>
<!-- Table of contents -->
<ul>
<template repeat="[[post in posts.feed.entry]]">
<li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>
</template>
</ul>
<!-- Post content -->
<template repeat="[[post in posts.feed.entry]]">
<post-element post="[[post]]" selected="[[route]]"></post-element>
</template>
</div>
<polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdHVQUGd2M2Q0MEZnRms3c3dDQWQ3V1E/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>
</template>
<script>
Polymer('blog-element', {
created: function() {},
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
رائع! لدينا الآن مدوّنة بسيطة تقرأ البيانات من ملف JSON وتستخدم عنصرَي Polymer تمّ إعدادهما باستخدام Yeoman.
العمل مع عناصر تابعة لجهات خارجية
في الآونة الأخيرة، شهدت المنظومة المتكاملة للعناصر حول Web Components نموًا مع ظهور مواقع إلكترونية لمعارض العناصر، مثل customelements.io. أثناء تصفّح العناصر التي أنشأها المنتدى، عثرت على عنصر لاسترداد ملفات تعريف Gravatar ويمكننا الحصول عليها وإضافتها إلى موقع مدونتنا الإلكتروني أيضًا.
انسخ مصادر عناصر gravatar إلى دليل /elements
، وأدرِجها من خلال عمليات استيراد HTML في post.html، ثم أضِف
<link rel="import" href="gravatar-element/src/gravatar.html">
<polymer-element name="post-element" attributes="post selected">
<template>
<style>
@host { :scope {display: block;} }
</style>
<div class="col-lg-4">
<template if="[[post.gsx$slug.$t === selected]]">
<h2><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></h2>
<p>By [[post.gsx$author.$t]]</p>
<gravatar-element username="[[post.gsx$email.$t]]" size="100"></gravatar-element>
<p>[[post.gsx$content.$t]]</p>
<p>[[post.gsx$date.$t]]</p>
<small>Keywords: [[post.gsx$keywords.$t]]</small>
</template>
</div>
</template>
<script>
Polymer('post-element', {
created: function() { },
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
لنلقِ نظرة على المزايا التي يوفّرها لنا ذلك:
رائع.
في وقت قصير نسبيًا، أنشأنا تطبيقًا بسيطًا يتألف من عدة مكوّنات ويب بدون الحاجة إلى القلق بشأن كتابة الرموز البرمجية المتكررة أو تنزيل التبعيات يدويًا أو إعداد خادم محلي أو إنشاء سير عمل.
تحسين طلبك
يتضمّن سير عمل Yeoman مشروعًا آخر مفتوح المصدر يُسمى Grunt، وهو أداة لتشغيل المهام يمكنها تنفيذ عدد من المهام المتعلّقة بالإنشاء (يتم تحديدها في ملف Gruntfile) لإنشاء إصدار محسّن من تطبيقك. سيؤدي تشغيل grunt
بمفرده إلى تنفيذ مهمة default
أعدّها المولد لفحص الأخطاء واختبارها وبنائها:
grunt.registerTask('default', [
'jshint',
'test',
'build'
]);
ستتحقّق مهمة jshint
أعلاه من ملف .jshintrc
للتعرّف على إعداداتك المفضّلة، ثم ستُجريها على جميع ملفات JavaScript في مشروعك. للاطّلاع على نظرة شاملة على خياراتك باستخدام JSHint، اطّلِع على المستندات.
تبدو مهمة test
على النحو التالي، ويمكنها إنشاء تطبيقك وعرضه لإطار الاختبار الذي ننصح به، وهو Mocha. وسينفِّذ أيضًا اختباراتك نيابةً عنك:
grunt.registerTask('test', [
'clean:server',
'createDefaultTemplate',
'jst',
'compass',
'connect:test',
'mocha'
]);
بما أنّ تطبيقنا في هذه الحالة بسيط إلى حدٍ ما، سنترك لك كتابة الاختبارات كنشاط منفصل. هناك بعض الإجراءات الأخرى التي يجب أن تتعامل معها عملية الإنشاء، لذا لنلقِ نظرة على ما ستفعله مهمة grunt build
المحدّدة في Gruntfile.js
:
grunt.registerTask('build', [
'clean:dist', // Clears out your .tmp/ and dist/ folders
'compass:dist', // Compiles your Sassiness
'useminPrepare', // Looks for <!-- special blocks --> in your HTML
'imagemin', // Optimizes your images!
'htmlmin', // Minifies your HTML files
'concat', // Task used to concatenate your JS and CSS
'cssmin', // Minifies your CSS files
'uglify', // Task used to minify your JS
'copy', // Copies files from .tmp/ and app/ into dist/
'usemin' // Updates the references in your HTML with the new files
]);
شغِّل grunt build
، ومن المفترض أن يتم إنشاء إصدار جاهز للنشر من تطبيقك ويصبح جاهزًا للشحن. فلنجرّب ذلك.
اكتمال عملية النقل بنجاح
إذا واجهت مشكلة، يمكنك الاطّلاع على إصدار مُعدّ مسبقًا من polymer-blog على https://github.com/addyosmani/polymer-blog.
ما هي الميزات الإضافية التي نقدّمها؟
لا تزال مكوّنات الويب في مرحلة التطوير، وبالتالي فإنّ الأدوات المرتبطة بها لا تزال في مرحلة التطوير أيضًا.
نحن ننظر حاليًا في كيفية تسلسل عمليات استيراد HTML لتحسين أداء التحميل من خلال مشاريع مثل Vulcanize (أداة من مشروع Polymer) وكيفية عمل منظومة المكونات المتكاملة مع أداة إدارة حِزم مثل Bower.
سنُعلمك عندما نحصل على إجابات أفضل عن هذه الأسئلة، ولكن هناك الكثير من الأحداث المشوّقة في المستقبل.
تثبيت Polymer بشكل مستقل باستخدام Bower
إذا كنت تفضّل بدء استخدام Polymer بشكل أخف، يمكنك تثبيته بشكل مستقل مباشرةً من Bower عن طريق تشغيل:
bower install polymer
سيؤدي ذلك إلى إضافته إلى دليل bower_components. ويمكنك بعد ذلك الإشارة إليها في فهرس تطبيقك يدويًا وتحقيق النجاح في المستقبل.
ما رأيك؟
الآن أنت تعرف كيفية إنشاء إطار عمل لتطبيق Polymer باستخدام Web Components مع Yeoman. إذا كانت لديك ملاحظات حول أداة إنشاء الصفحات، يُرجى إخبارنا بها في التعليقات أو إرسال تقرير خطأ أو نشر مشاركة في أداة تتبُّع المشاكل في Yeoman. نودّ معرفة ما إذا كان هناك أيّ ميزة أخرى تريد تحسينها في أداة إنشاء المحتوى، لأنّنا لا يمكننا إجراء التحسينات إلا من خلال استخدامك لهذه الأداة وملاحظاتك بشأنها.