اذهب إلى المحتوى

حان الوقت الآن للتعمق أكثر في إطار العمل Vue وإنشاء مكوّن مخصَّص، إذ سنبدأ بإنشاء مكوِّن لتمثيل كل عنصر في قائمة المهام، كما سنتعرّف على بعض المفاهيم المهمة مثل استدعاء المكونات ضمن مكونات أخرى وتمرير البيانات إليها باستخدام الخاصيات Props وحفظ حالة البيانات.

ملاحظة: إذا كنت بحاجة إلى التحقق من شيفرتك مقابل نسخة شيفرتنا، فيمكنك العثور على نسخة نهائية من نموذج شيفرة تطبيق Vue في المستودع todo-vue أو يمكنك الحصول على إصدار حي وقيد التشغيل منه.

  • المتطلبات الأساسية: الإلمام بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة استخدام سطر الأوامر أو الطرفية، إذ تُكتَب مكونات Vue بوصفها مجموعة من كائنات جافاسكربت التي تدير بيانات التطبيق وصيغة القوالب المستنِدة إلى لغة HTML المرتبطة مع بنية DOM الأساسية، وستحتاج إلى طرفية مثبَّت عليها node و npm لتثبيت Vue ولاستخدام بعض ميزاته الأكثر تقدمًا مثل مكونات الملف المفرد Single File Components أو دوال التصيير Render.
  • الهدف: تعلّم كيفية إنشاء مكوِّن Vue وتصييره ضمن مكوِّن آخر، وتمرير البيانات إليه باستخدام الخاصيات، وحفظ حالته.

إنشاء المكون ToDoItem

لننشئ مكوننا الأول الذي سيعرض عنصر مهمة واحدًا، إذ سنستخدمه لبناء قائمة المهام.

  1. أنشئ ملفًا جديدًا بالاسم ToDoItem.vue في المجلد moz-todo-vue/src/components، ثم افتح الملف في محرّر الشيفرات.
  2. أنشئ قسم قالب المكوِّن من خلال إضافة العنصر <template></template> في أعلى الملف.
  3. أنشئ القسم <script></script> بعد قسم القالب، ثم أضف فيه كائن تصدير افتراضي export default {}‎، وهو كائن مكوِّنك.

يجب أن يبدو ملفك الآن كما يلي:

 <template> </template> <script> export default {}; </script>

يمكننا الآن إضافة محتوى فعلي إلى الملف ToDoItem، إذ يُسمَح حاليًا لقوالب Vue بإضافة عنصر جذر واحد فقط، كما يجب استخدام عنصر واحد لتغليف كل شيء ضمن قسم القالب، ولكن سيتغير ذلك في الإصدار رقم 3 من Vue،إذ سنستخدِم العنصر <div> لهذا العنصر الجذر.

  1. أضف عنصر <div> فارغ ضمن قالب المكوِّن.
  2. أضِف مربع اختيار وعنوانًا label مقابلًا ضمن العنصر <div>. أضِف السمة id إلى مربع الاختيار، والسمة for التي تربط مربع الاختيار مع العنوان label كما هو موضّح فيما يلي:
 <template> <div> <input type="checkbox" id="todo-item" /> <label for="todo-item">My Todo Item</label> </div> </template>

استخدام المكون TodoItem ضمن تطبيقك

لم نضِف المكوِّن إلى تطبيقنا حتى الآن، لذلك لا توجد طريقة لاختباره ومعرفة ما إذا كان كل شيء على ما يرام أم لا، إذًا لنضفه الآن.

  1. افتح الملف App.vue مرة أخرى.
  2. أضِف السطر التالي لاستيراد المكون ToDoItem في أعلى الوسم <script>:
 import ToDoItem from './components/ToDoItem.vue';
  1. أضِف الخاصية components ضمن كائن المكوِّن ثم أضِف ضمنها المكوِّن ToDoItem لتسجيله.

يجب أن تبدو محتويات الوسم <script> الآن كما يلي:

 import ToDoItem from './components/ToDoItem.vue'; export default { name: 'app', components: { ToDoItem } }; 

هذه هي الطريقة نفسها التي سُجِّل بها المكوِّن HelloWorld بواسطة واجهة CLI الخاصة بإطار العمل Vue سابقًا.

يمكنك تصيير المكوِّن ToDoItem فعليًا في التطبيق من خلال الانتقال إلى العنصر <template> واستدعائه بوصفه عنصر بالشكل:

 <to-do-item></to-do-item> 

ولاحظ أنّ اسم ملف المكوِّن وتمثيله في جافاسكربت يكون دائمًا في حالة أحرف باسكال PascalCase مثل ToDoList، ويكون العنصر المخصّص المكافئ دائمًا في نمط أحرف أسياخ الشواء Kebab-case مثل <to-do-list>.

  1. أنشئ قائمةً غير مرتبة <ul> بعد العنصر <h1> تحتوي على عنصر قائمة واحد <li>.
  2. أضِف العنصر <to-do-item></to-do-item> ضمن عنصر القائمة.

يجب أن تبدو محتويات العنصر <template> في الملف App.vue الآن كما يلي:

<div id="app">
  <h1>To-Do List</h1>
  <ul>
    <li>
      <to-do-item></to-do-item>
    </li>
  </ul>
</div>

إذا تحقّقتَ من تطبيقك المُصيَّر مرةً أخرى، فيجب أن ترى الآن العنصر ToDoItem المُصيَّر الذي يتكون من مربع اختيار وعنوان label.

01_rendered-todoitem.png

إضافة خاصيات للمكونات

لا يزال المكوِّن ToDoItem غير مفيد للغاية لأنه يمكننا تضمينه مرةً واحدة فقط في الصفحة، إذ يجب أن تكون المعرّفات فريدةً، وليس لدينا طريقة لضبط نص العنوان ولا يُعَدّ ذلك ديناميكيًا.

ما نحتاجه الآن هو حالة المكوِّن التي يمكن تحقيقها من خلال إضافة الخاصيات Props إلى المكوِّن، إذ تشبه الخاصيات مدخلات دالة ما، وتعطي قيمة الخاصية للمكونات حالة أولية تؤثر على عرضها.

تسجيل الخاصيات

هناك طريقتان لتسجيل الخاصيات في Vue هما:

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

ملاحظة: لا يحدث التحقق من صحة الخاصية إلا في وضع التطوير، لذلك لا يمكنك الاعتماد عليها بصورة صارمة في عملية الإنتاج، كما تُستدعَى دوال التحقق من صحة الخاصية قبل إنشاء نسخة من المكوِّن، لذلك لا يمكنها الوصول إلى حالة المكوِّن أو الخاصيات الأخرى، وسنستخدِم تابع تسجيل الكائن بالنسبة لهذا المكوِّن.

ارجع إلى الملف ToDoItem.vue، ثم أضِف الخاصية props ضمن كائن التصدير default {}‎ الذي يحتوي على كائن فارغ، وبعدها أضف ضمن هذا الكائن خاصيتين مع المفتاحين label و done، وتذكَّر أنّ قيمة المفتاح label يجب أن تكون كائنًا مع خاصيتن (أو Props كما يطلق عليها في سياق توفرهما للمكوّنات) وهما:

  1. الأولى هي الخاصية required التي ستكون لها القيمة trueمما يخبر Vue أننا نتوقع أن يكون لكل نسخة من هذا المكوِّن حقل عنوان أو تسمية label، وسيعطي Vue تحذيرًا إذا لم يتضمن المكوِّن ToDoItem على حقل عنوان.
  2. الثانية هي خاصية النوع type. اضبط قيمة هذه الخاصية على نوع جافاسكربت String (لاحظ الحرف الكبير "S")، وهذا يخبر Vue أننا نتوقع أن تكون قيمة هذه الخاصية سلسلةً نصيةً.

أما بالنسبة للخاصية done، فأضِف أولًا الحقل default مع القيمة false، وهذا يعني أنّ الخاصية done ستكون لها القيمة false عند عدم تمريرها إلى المكوِّن ToDoItem، وضَع في الحسبان أنّ هذا ليس مطلوبًا، إذ نحتاج فقط إلى الحقل default مع الخاصيات غير المطلوبة، وأضف بعد ذلك حقل النوع type مع القيمة Boolean، وهذا يخبر Vue أننا نتوقع أن يكون لخاصية القيمة value النوع المنطقي boolean في جافاسكربت.

يجب أن يبدو كائن المكوِّن الآن كما يلي:

<script>
  export default {
    props: {
      label: { required: true, type: String },
      done: { default: false, type: Boolean }
    }
  };
</script>

استخدام الخاصيات المسجلة

يمكننا الآن بعد تعريف هذه الخاصيات ضمن كائن المكوِّن استخدام هذه القيم المتغيرة في قالبنا ولنبدأ بإضافة الخاصية label إلى قالب المكوِّن.

ضع مكان محتويات العنصر <label> القيمة {{label}} في عنصر القالب <template>، إذ تُعَدّ {{}} صيغة قوالب خاصة في Vue تتيح طباعة نتيجة تعابير جافاسكربت المُعرَّفة في الصنف Class ضمن القالب، بما في ذلك القيم والتوابع، كما يُعرَض المحتوى ضمن {{}} بوصفه نصًا وليس شيفرة HTML، وبالتالي سنطبع قيمة الخاصية label في هذه الحالة.

يجب أن يبدو قسم قالب المكوِّن الآن كما يلي:

<template>
  <div>
    <input type="checkbox" id="todo-item" />
    <label for="todo-item">{{label}}</label>
  </div>
</template>

ارجع إلى متصفحك وسترى عنصر المهام مُصيَّرًا كما كان سابقًا، ولكن بدون العنصر label، وانتقل إلى أدوات التطوير DevTools في متصفحك وسترى تحذيرًا في الطرفية كما يلي:

[Vue warn]: Missing required prop: "label"

found in

---> <ToDoItem> at src/components/ToDoItem.vue
        <App> at src/App.vue
          <Root>

يكون هذا بسبب أننا ميّزنا الخاصية label بوصفها خاصيةً مطلوبةً أو إجباريةً، لكننا لم نعطِ المكوِّن هذه الخاصية مطلقًا، إذ حدّدنا المكان الذي نريد استخدامه ضمن القالب، لكننا لم نمرّره إلى المكوِّن عند استدعائه، إذًا لنصلح ذلك.

أضف الخاصية label إلى المكوِّن <to-do-item> في الملف App.vue مثل سمة HTML العادية تمامًا:

<to-do-item label="My ToDo Item"></to-do-item>

سترى الآن العنصر label في تطبيقك دون تحذير في الطرفية مرةً أخرى.

كائن الخاصية data في Vue

إذا عدّلتَ قيمة الخاصية label المُمرَّرة إلى المكون <to-do-item> في تطبيقك، فيجب أن تراها مُحدَّثةً.

لدينا الآن مربع اختيار مع عنصر label قابل للتحديث، ولكننا لا نطبّق حاليًا أيّ شيء باستخدام الخاصية done، إذ يمكننا تحديد مربعات الاختيار في واجهة المستخدِم، ولكن لا يوجد مكان في التطبيق نسجل فيه ما إذا كان عنصر المهام المطلوب قد اكتمل فعليًا أم لا.

يمكنك تحقيق ذلك من خلال ربط الخاصية done الخاصة بالمكوِّن مع السمة checked في العنصر <input>، بحيث يمكن أن تكون بمثابة سجل لما إذا كان مربع الاختيار محددًا أم لا، إذ يجب أن تعمل الخاصيات بوصفها رابط بيانات أحادي الاتجاه، ويجب ألّا يغير المكوِّن قيمة خاصياته أبدًا، إذ يمكن أن تجعل خاصيات تعديل المكونات تنقيح الأخطاء تحديًا، فإذا مُرِّرت قيمةً إلى عدة أبناء، فيمكن أن يكون تتبُّع مصدر تغييرات هذه القيمة أمرًا صعبًا، كما يمكن أن يؤدي تغيير الخاصيات إلى إعادة تصيير المكوّنات، لذا فسيؤدي تغيّر خاصيات المكوِّن إلى إعادة تصييره، مما يؤدي بدوره إلى حدوث التغيّر مرةً أخرى.

يمكننا حل هذه المشكلة من خلال إدارة الحالة done باستخدام الخاصية data في Vue، إذ تُعَدّ الخاصية data المكان الذي يمكنك من خلاله إدارة حالة المكوِّن المحلية، فهي توجد ضمن كائن المكوِّن جنبًا إلى جنب مع الخاصية props ولها البنية التالية:

data() {
  return {
    key: value
  }
}

لاحظ أنّ الخاصية data هي دالة للحفاظ على قيم البيانات فريدة لكل نسخة من المكوِّن في وقت التشغيل، إذ تُستدعَى الدالة بصورة منفصلة لكل نسخة من المكوِّن. فإذا عرّفتَ الخاصية data بوصفها كائن فقط، فستشترك جميع نسخ هذا المكوِّن في القيم نفسها، وهذا أحد الآثار الجانبية التي لا نريدها للطريقة التي يسجِّل بها Vue المكوّنات، كما يمكنك استخدام this للوصول إلى خاصيات المكوّن والخاصيات الأخرى من data وسنرى مثالًا عن ذلك لاحقًا.

ملاحظة: بما أن الطريقة التي يعمل بها this يمكن استخدامها في الدوال السهمية Arrow Function أو الارتباط بسياق الآباء، فلن تتمكن من الوصول إلى أيّ من السمات الضرورية من data إذا استخدمتَ هذه الدالة، لذلك لا تستخدِمها مع الخاصية data.

لنضِف الخاصية data إلى المكوِّن ToDoItem، إذ سيعيد ذلك كائنًا يحتوي على خاصية واحدة سنسميها isDone التي تكون قيمتها this.done.

عدّل كائن المكوِّن كما يلي:

export default {
  props: {
    label: { required: true, type: String },
    done: { default: false, type: Boolean }
  },
  data() {
    return {
      isDone: this.done
    };
  }
};

يربط Vue جميع خاصياتك بنسخة المكوِّن مباشرةً، لذلك لا يتعين علينا استدعاء this.props.done، ويربط السمات الأخرى مثل data و methods و computed وغيرها مباشرةً بنسخة المكوِّن لجعلها متاحةً لقالبك، لكن يجب الاحتفاظ بالمفاتيح فريدة لهذه السمات، وهذا هو السبب في أننا أطلقنا على سمة data الاسم isDone عوضًا عن done.

يجب الآن ربط الخاصية isDone بالمكوِّن، إذ يملك Vue بنية صيغة لربط تعابير جافاسكربت بعناصر ومكونات HTML بطريقة مشابهة لكيفية استخدام Vue لتعابير {{}} لعرض تعابير جافاسكربت ضمن القوالب، وهذه الصيغة هي v-bind التي تبدو كما يلي:

v-bind:attribute="expression"

وبالتالي ستسبق العبارة v-bind:‎ أيّ سمة أو خاصية تريد ربطها، ويمكنك استخدام اختصار للخاصية v-bind في أغلب الأحيان، وهذا الاختصار هو استخدام تقطتين قبل السمة أو الخاصية، لذلك تعمل العبارة ‎:attribute="expression"‎ بطريقة مشابهة للعبارة v-bind:attribute="expression"‎، لذلك يمكننا استخدام v-bind لربط الخاصية isDone مع السمة checked في العنصر <input> في حالة استخدام مربع اختيار ضمن المكوِّن ToDoItem، كما أنّ الأمرَين التاليَين متكافئان:

<input type="checkbox" id="todo-item" v-bind:checked="isDone" />

<input type="checkbox" id="todo-item" :checked="isDone" />

أنت حر في استخدام النمط الذي تريده، ولكن يُفضَّل الحفاظ على كل شيء متسقًا قدر الإمكان، وبما أنّ استخدام صيغة الاختصار أكثر شيوعًا، فسنلتزم بهذا النمط في هذا المقال.

عدّل العنصر <input> الآن ليشمل ‎:checked="isDone"‎.

اختبر مكوِّنك عن طريق تمرير ‎:done="true"‎ إلى استدعاء المكوِّن ToDoItem في الملف App.vue، ولاحظ أنك تحتاج إلى استخدام صيغة v-bind، لأنّ القيمة true ستُمرَّر بوصفها سلسلةً نصيةً بخلاف ذلك، كما يجب تحديد مربع الاختيار المعروض.

<template>
  <div id="app">
    <h1>My To-Do List</h1>
    <ul>
      <li>
        <to-do-item label="My ToDo Item" :done="true"></to-do-item>
      </li>
    </ul>
  </div>
</template>

حاول تغيير القيمة true إلى false، ثم أعِد تحميل تطبيقك لترى كيفية تغيّر الحالة.

إعطاء المهام معرفا فريدا

رائع، أصبح لدينا الآن مربع اختيار يعمل بنجاح، إذ يمكننا ضبط الحالة برمجيًا، لكن يمكننا حاليًا إضافة مكوِّن ToDoList واحد فقط إلى الصفحة لأن المعرّف id ثابت، مما يؤدي إلى حدوث أخطاء في التقنية المساعدة لأننا بحاجة هذا المعرّف لربط العناوين أو التسميات إلى مربعات الاختيار المقابلة لها بصورة صحيحة، ويمكن إصلاح هذا الخطأ من خلال ضبط المعرّف id برمجيًا في بيانات المكوِّن.

يمكننا استخدام التابع uniqueid()‎ الخاص بحزمة Lodash للمساعدة في إبقاء الفهرس فريدًا، إذ تصدِّر هذه الحزمة دالةً تأخذ سلسلةً نصيةً وتضيف عددًا صحيحًا فريدًا إلى نهاية البادئة، وسيكون هذا كافيًا لإبقاء معرِّفات المكوّنات فريدة.

يجب إضافة هذه الحزمة إلى مشروعنا باستخدام npm، لذا أوقف خادمك وأدخِل الأمر التالي في طرفيتك:

npm install --save lodash.uniqueid

ملاحظة: إذا أردت استخدام yarn، فيمكنك كتابة الأمر yarn add lodash.uniqueid.

يمكننا الآن استيراد هذه الحزمة إلى المكوِّن ToDoItem، لذا أضِف السطر التالي قبل العنصر <script> في الملف ToDoItem.vue:

import uniqueId from 'lodash.uniqueid';

أضِف بعد ذلك الحقل id إلى الخاصية data بحيث يبدو كائن المكوِّن بالصورة التالية، إذ يعيد التابع uniqueId()‎ البادئة todo-‎ مع سلسلة نصية فريدة ملحقَة بها:

import uniqueId from 'lodash.uniqueid';

export default {
  props: {
    label: { required: true, type: String },
    done: { default: false, type: Boolean }
  },
  data() {
    return {
      isDone: this.done,
      id: uniqueId('todo-')
    };
  }
};

اربط بعد ذلك المعرِّف id مع كل سمة id الخاصة بمربع الاختيار والسمة for الخاصة بالعنوان أو التسمية، وعدِّل السمات id و for الحالية كما يلي:

<template>
  <div>
    <input type="checkbox" :id="id" :checked="isDone" />
    <label :for="id">{{label}}</label>
  </div>
</template>

الخلاصة

لدينا حتى الآن المكوِّن ToDoItem الذي يعمل بنجاح، ويمكنه تمرير عنوان أو تسمية لعرضها، كما سيخزّن حالته المحدَّدة، وسيُصيَّر مع معرِّف id فريد في كل مرة يُستدعَى فيها، كما يمكنك التحقق مما إذا كانت المعرّفات الفريدة تعمل عن طريق إضافة المزيد من استدعاءات المكونات <to-do-item> مؤقتًا في الملف App.vue ثم التحقق من خرجها المُصيَّر باستخدام أدوات التطوير DevTools في متصفحك.

نحن الآن جاهزون لإضافة عدة مكونات ToDoItem إلى تطبيقنا، إذ سنتعلّم في المقال التالي كيفية إضافة مجموعة من بيانات عناصر المهام إلى المكوِّن App.vue، والتي سنكرّرها ونعرضها ضمن المكوّنات ToDoItem باستخدام الموجّه v-for.

ترجمة -وبتصرّف- للمقال Creating our first Vue component.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

زائر

نشر (معدل)

السلام عليكم

إذا حددنا خيار " نعم " لدعم الـ JSX عند إنشاء تطبيق Vue.js ، هل يمكننا مباشرة إنشاء مكونات JSX في ملفات vue. ، واستخدام القسم <script> لكتابة المكونات ، و <style> لتنسيقها ؟ أم يلزم إضافة أو مكتبة ليدعم الصيغة.

طبعا لم أسأل هذا السؤال إلا بعد عدة مقاطع شاهدتها عن الموضوع ، لم يسعف محتواها في إيجاد حل المشاكل التي واجهتني عند التطبيق وهي اثنتين :

  1. الأولى أن الـ VSCode يصر على إنشاء فراغات بين المكونات عند حفظ ملف المكون ، وينشئ بسبب ذلك خطأ في الكود ، مثاله لو كتبت :
    const App = createComponent({
            name: "App",
            render() {
                return {
      			<div>
    			...
      			</div>
                }
            }
    )}
    
    
    export default App

    عند حفظ الملف تصبح كالتالي:

    const App = createComponent({
            name: "App",
            render() {
                return { <
                    div >
                    ... <
                    /div>
                }
            }
        )
    }
    export default App


    بدون معرفة السبب ، ويعطيني عدة رسائل خطأ !
    وفي العادة يكون مكون JSX الذي نكونه  لوناً في محرر الأكواد خاص به - عادة يكون أخضر - لكنه لم يظهر معي وظهر في المقاطع التي تكلمت عن الموضوع ، ربما هذه دلالة أن المحرر لم يتعرف عليها.

  2. الثانية: عند استدعاء أية مكون تم إنشاؤه للصفحة ، وتشغيل الصفحة عن طريق Live server لا يظهر في أي متصفح بل بتقى الصفحة فارغة.

وتعطيني الـ Vue DevTools رسالة :

اقتباس

Vue.js not detected

حتى بعد عمل Reload .
وتبقى باللون الرمادي.
هل يمكنك الإفادة في التعامل مع هاتين الإشكاليتين؟

تم التعديل في بواسطة عمار الضمور
Wael Aljamal

نشر

بتاريخ On 8/29/2022 at 14:39 قال عمار الضمور:

بدون معرفة السبب ، ويعطيني عدة رسائل خطأ !

إن الخطأ في الأقواس ل return يجب أن تكون () وليس {}

return ( 

	<div>
         ... 
	</div>
)

لأنها تعيد قيمة وليس Block of code.

بتاريخ On 8/29/2022 at 14:39 قال عمار الضمور:
  • وفي العادة يكون مكون JSX الذي نكونه  لوناً في محرر الأكواد خاص به - عادة يكون أخضر - لكنه لم يظهر معي وظهر في المقاطع التي تكلمت عن الموضوع ، ربما هذه دلالة أن المحرر لم يتعرف عليها.

في الجزء السفلي من محرر الأكواد، يكون التعرف على الملف أنه JS اقر عليها و غيرها ل JSX

بتاريخ On 8/29/2022 at 14:39 قال عمار الضمور:
  1. الثانية: عند استدعاء أية مكون تم إنشاؤه للصفحة ، وتشغيل الصفحة عن طريق Live server لا يظهر في أي متصفح بل بتقى الصفحة فارغة.

Live server يشغل صفحات الويب الساكنة، أما vue عليك تشغيلها ب npm 

يكون لديك مشروع vue فيه ملف package.json :فيه جزء scripts

{ 
  "scripts": { 
    "serve": "vue-cli-service serve", 
      "build": "vue-cli-service build" 
  } 
}

الآن نريد تشغيل serve لتشغيل خادم vue

npm run serve

يمكنك متابعة الدرس: 

 



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...