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

سنتعرّف في هذا الدّرس على أداة nw (سابقًا node-webkit) المُخصّصة لتطوير تطبيقات سطح المكتب العابرة للمنصات؛ باستخدام مُتصفّح كروميوم [Chromium  [1 و node.js. حيث أصبح بالإمكان استخدام تقنيات الوِب الحديثة لتطوير برمجيات قادرة على الاستفادة من موارد النظام؛ كالتحكّّم بالملفات، استعمال قواعد البيانات وغيرها من الأمور. في هذا الدّرس سنتعلّم كيفية كتابة تطبيق لسطح المكتب باستخدام nw لتشغيل ملفّات الفيديو. لنبدأ.

nwjs-init.thumb.png.60a6d9fdd852fef1ff0c

تثبيت node.js

إذ كنت تَستخدم نظام التشغيل Windows أو Mac OS عندها استعمل معالج التثبيت الرسمي، أما في حال عملك على نظام لينكس فقم بتثبيتها من خلال مدير الحزم.

تثبيت nw

بعد تركيب node.js من خلال مدير حزم node.js نحتاج إلى تركيب nw، للقيام بذلك نفّذ ما يلي من خلال الطرفية: 

npm install nw -g

أهلا بالعالم

أنشئ مُجلدًا مُخصصًا لمشروعنا الأول باسم helloworld ثم أنشئ بداخله ملفّين: 

package.json

إن سبق لك التطوير باستخدام node.js فستعرف بالتأكيد أن هذا هو الملف الخاص بمعلومات و خصائص التطبيق أو الحزمة، يمكنك إنشاءه بسهولة من خلال سطر الأوامر: 

npm init 

سنضيف إلى الملفّ بعض خصائص nw (حجم النافذة و الرابط للملف الرّئيسيّ) ليصبح شكل الملف بالكامل هكذا: 

{ 
  name”: helloworld”, 
  version”: 1.0.0”, 
  description”: “”, 
  main”: index.html”, 
  window”: { 
    width”: 600, 
    height”: 400 
  }, 
  scripts”: { 
    test”: echo \”Error: no test specified\” && exit 1 
  }, 
  author”: hsoubAcademy”, 
  license”: MIT 
} 

index.html

هو ملف nw الرئّيسيّ الذي قمنا بتحديده من ملف الحزمة (package.json)، سنُنشئ مُستند بسيط لطباعة أهلًا بالعالم.

تشغيل التطبيق

من خلال سطر الأوامر انتقل إلى ملف المشروع وشغّل nw

cd helloworld 
nw 

يجب أن تظهر لك نافذة بحجم 600X400px وبداخلها نص "أهلًا بالعالم".

سياق شفرة javascript

nodewebkit-contexts.thumb.png.1e09136f1e 

يوجد بـ nw سياقان لشِفرة javascript: سياق المُتصفّح وسياق node.js، عند استدعائك لملف javascript أو تشغيلك لشيفرة من خلال مستند html فإن هذه الشفرة تُمرر إلى المتصفح ليقرأها، أما عند تشغيلك لملفّ javascript من خلال خاصية node-main في ملفّ الحزمة (package.json) أو استدعائه من خلال الدّالّة require فإنه يُمرر إلى سياق node.js، حيث يوجد بعض الاختلافات الطفيفة بين هذين السياقين في الكتابة:

1. سياق المتصفح 

في هذا السياق تعمل الشيفرة بالأسلوب الذي تعمل به في المتصفح، المُتغيّر العام هو window مثلًا، هذه الشيفرة ستعمل بشكل صحيح فقط في سياق المُتصفح: 

myVar = test”; // تعريف مُتغير بشكل عام 
alert(window.myVar); // إخراج نافذة بالمتغير 

ولتمرير المُتغيرات بشكل عام إلى سياق node.js يجب تعريفها كتابع للمُتغير window.global مثال ليعمل مثال: 

// ملف window.js 
// هذا الملف يجب أن يعمل من سياق المُتصفح 
window.global.myVar = test”; // تعريف مُتغيّر بشكل عام إلى سياق المُتصفح 
require(“node.js”); // استدعاء ملف node.js بداخل سياق node.js
// ملف node.js 
// هذا الملف يجب أن يعمل من سياق node.js 
window.alert(myVar); // عرض المُتغير الّذي تم تعريفه بشكل عام من سياق المُتصفح

2. سياق node.js 

في هذه السياق تعمل الشيفرة بأسلوب عملها باستخدام node.js، المُتغير العام هو global مثال: 

myVar = test”; // تعريف مُتغير بشكل عام 
window.alert(global.myVar); // إخراج نافذة بالمتغير العام الّذي قمنا بتعريفه 

يُمكن استخدام سياق المتصفح والتمرير إليه من خلال المُتغير window.

استدعاء الحزم التي كتبت للمتصفح بداخل سياق node.js

عند استدعاء حزم تمت كتابتها للمُتصفح بداخل سياق node.js عن طريق الدّالّة require فقد لا تعمل، حيث تحدثنا سابقًا أن المتغيّر العام في سياق node.js هو global و ليس window مما يسبب مشكلة إن تم استدعاء أحد توابع window، عمومًا فالمثال لن يعمل في سياق node.js: 

var div = document.createElement(“div”); 

لجعل هذه الشيفرة تعمل يجب تعريف مُتغير document بشكل عام في سياق node.js: 

global.document = window.document; 

بشكل مبدئي سيجعل هذين السطرين أغلب الحزم تعمل: 

global.document = window.document; 
global.navigator = window.navigator; 

إن كنت تبحث عن حل تلقائي لنقل جميع المُتغيرات إلى سياق node.js (قد لا يُنصح بهذا الحل بسبب حذف المُتغيرات المتطابقة في الاسم): 

var Var; 
for(Var in window) 
  if(Var != require”) 
    global[Var] = window[Var]; 

ملاحظة: لم ننقل مُتغير require لأن التابع window.require يستخدم global.require في استدعاء الحزم. 

لاحظ أيضًا أنه لا يمكن استدعاء حزمة nw.gui الخاصة بواجهة المستخدم من خلال سياق node.js عن طريق الدّالّة require فكما ذكرنا؛ التابع window.require يختلف عن global.require. هذا هو النص المصدري للتابع: 

window.require = function (name) { 
  if (name == nw.gui’) 
    return nwDispatcher.requireNwGui(); 
  return global.require(name); 
} 

وكما تلاحظ إن كان اسم الحزمة node.js فإنه يستخدم هذه الدّالّة لاستدعائها: 

nwDispatcher.requireNwGui(); 

إذًا لاستدعائها من خلال سياق node.js استخدم الدّالّة السابقة أو استخدم window.require.

إنشاء تطبيق لتشغيل الفيديو

ملفات المشروع

هذه قائمة لملفات المشروع: 

اقتباس

src/ 
app.js 
style.css 
index.html 
package.json

ملف الحزمة (package.json)

حجم النافذة الافتراضي هو 800x600px، الملفّ الرّئيسيّ هو index.html، مبدئيًا سيكون الملف بهذا الشكل: 

{ 
  name”: nw-video-player”, 
  version”: 1.0.0”, 
  description”: “تطبيق بسيط لتشغيل الفيديو”, 
  main”: index.html”, 
  window”: { 
    width”: 800, 
    height”: 600 
  }, 
  scripts”: { 
    test”: echo \”Error: no test specified\” && exit 1 
  }, 
  author”: hsoubAcademy”, 
  license”: MIT”, 
  dependencies”: { 
    videojs”: “^4.12.15 
  } 
}

الملف الرئيسي index.html

سنكتب مستند بسيط يحتوي على زر وعنصر، نضيف المشغّل ضمنه من خلال شيفرة javascript:

<!doctype html> 
<html> 
  <head> 
    <meta charset="utf-8"> 
    <title>تطبيق بسيط لتشغيل الفيديو</title> 

    <!-- ملف شفرة js الرّئيسية للتطبيق --> 
    <script src="src/app.js"></script> 
  </head> 
  <body> 
    <button id="play">فتح</button> 
    <div id="player"></div> 
  </body> 
</html>

نافذة فتح الملفات

لا يوجد طريقة مباشرة لفتح الملفات باستخدام نافذة النظام، لكن يُمكننا استخدام مدخل input من نوع file لفتح الملفّات: 

لكن كخلاف المتصفح للمستخدم يُتاح لنا بعض الأشياء عند التطوير بـ nw كجلب المسار الكامل الحقيقي للملفّ، ويتاح لنا فتحه من خلال javascript: 

document.querySelector(“input”).click(); 

من خلال هذا سنكتب دالّة بسيطة لفتح ملفّات الفيديو وإرجاع مسار الملف مع إضافة بروتوكول file لاستخدامه من خلال المُتصفح، الدّالّة: 

// فتح ملف 
var openFile = function openFile (callback) { 
  var input = window.document.createElement(“input”); 
  input.setAttribute(“type”, file”); 
  input.setAttribute(“accept”, “.mp4, .ogg, .ogv, .webm”); 
  input.addEventListener(“change”, function () { // عند إختيار ملف 
    callback(“file://”+this.value); 
  }); 
  input.click() // فتح مُختار الملفات 
} 

تجربة استخدام: 

openFile(alert);

مشغل الفيديو

سنستخدم video.js لتسهيل تصميم المشغّل من خلال سطر الأوامر، اتجه إلى مجلّد المشروع ثم قم بتنصيب video.js من مدير الحزم الخاص بـnode.js: 

npm install videojs save 

كما تحدثنا في بداية الدرس عن استدعاء الحزم في سياق node.js سنحتاج إلى كتابة هذين السطرين: 

global.document = window.document; 
global.navigator = window.navigator; 

من ثم سيمكننا استدعائها بشكل عادي: 

var videojs = require(“videojs”); 

من خلال هذا والدّالّة السابقة لفتح الملفات سنكتب دالّة لإنشاء مشغل فيديو من خلال مسار ملف الفيديو من ثم إضافته إلى عنصر المشغّل في المستند الذي كتبناه من قبل: 

var playVideo = function playVideo (src) {
	var videojs = require("videojs"),
	ext = /[^.]+$/.exec(src)[0], // صيغة الملفّ
	video = document.createElement("video"), // عنصر الفديو
	source = document.createElement("source"), // عنصر المصدر للفديو
	player = document.querySelector("#player"); // عنصر المشغل في وثيقة html
		
	ext = (ext == "ogv") ? "ogg" : ext; // ملفات ogg قد تملك صيغة ogv 
	source.type = "video/"+ext.toLowerCase(); // نوع mime للفديو
	source.src = src; // مسار الفديو
	video.controls = true; // عناصر التحكّم
	video.autoplay = true; // تشغيل تلقائي
	video.classList.add("video-js", "vjs-default-skin"); // الclass الخاصة بvideojs
	video.appendChild(source); // إضافة عنصر المصدر إلى الفديو
	player.innerHTML = ""; // إزالة جميع العناصر السّابقة
	player.appendChild(video); // إضافة الفديو إلى وثيقة html
	videojs(video) // إستخدام video.js
}

مشكلة nw مع الصيغ مُغلقة المصدر: بشكل افتراضي سيمكنك تشغيل ملفات ogg و webm فقط لكن بسبب مشكلة مع التراخيص، لا تتضمن nw مشغّل لصيغة mp4 و الصوت mp3 اطلع على طريقة تضمين الملفّات المُشغلة

الآن تبقى فتح الملفّ عند الضغط على الزر: 

window.addEventListener(“load”, function () { 
  window.document.querySelector(“button#play”).addEventListener(“click”, function () { 
    openFile(playVideo) 
  }); 
}); 

ليصبح كامل ملف app.js

"use strict";
global.document = window.document;
global.navigator = window.navigator;
	
var nwGui = require("nw.gui"),
	Window = nwGui.Window.get();
	
    (function(window, nwGui, Window){
		var maximized;
		
		// فتح ملف
		var openFile = function openFile (callback) {
			var input = document.createElement("input");
			input.setAttribute("type", "file");
			input.setAttribute("accept", ".mp4, .ogg, .ogv, .webm");
			input.addEventListener("change", function () { // عند إختيار ملف
				callback("file://"+this.value);
			});
			input.click() // فتح مُختار الملفات
		}
		
		// تشغيل فديو
		var playVideo = function playVideo (src) {
			var videojs = require("videojs"),
			ext = /[^.]+$/.exec(src)[0], // صيغة الملفّ
			video = document.createElement("video"), // عنصر الفديو
			source = document.createElement("source"), // عنصر المصدر للفديو
			player = document.querySelector("#player"); // عنصر المشغل في وثيقة html
			
			ext = (ext == "ogv") ? "ogg" : ext; // ملفات ogg قد تملك صيغة ogv 
			source.type = "video/"+ext.toLowerCase(); // نوع mime للفديو
			source.src = src; // مسار الفديو
			video.controls = true; // عناصر التحكّم
			video.autoplay = true; // تشغيل تلقائي
			video.classList.add("video-js", "vjs-default-skin"); // الclass الخاصة بvideojs
			video.appendChild(source); // إضافة عنصر المصدر إلى الفديو
			player.innerHTML = ""; // إزالة جميع العناصر السّابقة
			player.appendChild(video); // إضافة الفديو إلى وثيقة html
			videojs(video) // إستخدام video.js
		}
		
		window.addEventListener("load", function () {
			// زر فتح فديو
			window.document.querySelector("button#play").addEventListener("click", function () {
				openFile(playVideo)
			});
		});	
	})
(window, nwGui, Window);

تصميم التطبيق

بداخل مُستند html أضف مسار ملفّ css الافتراضي لـvideo.js الموجود داخل مجلد node_modules

ثم سنلغي شريط الأدوات الخاص بـnw بداخل ملف الحزمة (package.json) وبداخل العنصر window أضف ما يلي:

toolbar”: false 

من ثم سننشئ ملف تصميم src/style.css

html, 
body{ 
  width: 100%; 
  height: 100%; 
  background: #ccc; 
  direction: rtl; 
  margin: 0 
} 

/* زر التشغيل */ 
#play{ 
  height: 25px; 
  background: white; 
  padding: 2px 20px; 
  box-sizing: border-box; 
  border: 1px #ccc solid 
} 

#play:hover{ 
  box-shadow: inset 0 0 1px #333 
} 

/* المُشغل */ 
#player{ 
  background: black; 
  width: 100%; 
  height: calc(100% - 25px) 
} 

#player > .video-js{ 
  width: 100% !important; 
  height: 100% !important 
} 

إضافته من خلال مُستند html: 

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

chromium-args”: “–lang=ar 

بدلًا من ذلك سنقوم بإنشاء إطار مُخصص للنافذة لتبدو بشكلٍ أفضل في مختلف الأنظمة.

تخصيص إطار النافذة

بداخل ملف الحزمة (package.json) وبداخل عنصر window سنلغي الإطار الافتراضي:

frame”: false 

من ثم بداخل مستند html سنضيف أزرار النافذة في أول عنصر جسم الصفحة (body).

سنضيف و نحذف بعض الخصائص من ملف src/style.css ليصبح شكل النافذة شبيه بنافذة نظام OS X: 

html, 
body{ 
  width: 100%; 
  height: 100%; 
  background: #ccc; 
  direction: rtl; 
  margin: 0 
} 

/* زر التشغيل */ 
#play{ 
  -webkit-app-region: no-drag; 
  height: 25px; 
  background: white; 
  padding: 2px 20px; 
  box-sizing: border-box; 
  border: 1px #ccc solid; 
  position: absolute; 
  right: 5px; 
  top: 2px 
} 

#play:hover{ 
  box-shadow: inset 0 0 1px #333 
} 

/* المُشغل */ 
#player{ 
  background: black; 
  width: 100%; 
  height: calc(100% - 25px) 
} 

#player > .video-js{ 
  width: 100% !important; 
  height: 100% !important 
} 

/* النافذة */ 
body{ 
  box-sizing: border-box; 
  border: #DDDDDC 5px solid 
} 

.window-toolbar{ 
  -webkit-app-region: drag; 
  background: -webkit-linear-gradient(#DDDDDC, #C2C2C2); 
  height: 25px; 
  direction: ltr; 
  padding: 2px 5px; 
  box-sizing: border-box 
} 

.window-toolbar > label.title{ 
  position: absolute; 
  left: 50%; 
  transform: translate(-50%, 0) 
} 

.window-toolbar-button{ 
  -webkit-app-region: no-drag; 
  width: 12px; 
  height: 12px; 
  border-radius: 100%; 
  display: inline-block; 
  cursor: pointer; 
  margin-right: 2px 
} 

.window-toolbar-button.close{ background: #FD4E4E } 
.window-toolbar-button.maximize{ background: #96D16F } 
.window-toolbar-button.minimize{ background: #F3BB55 } 

مُلاحظة: الخاصية webkit-app-region- المسؤولة عن جعل العنصر قابل للسحب والإفلات أيضًا يُمكن التحكّم بموقع النافذة من هذا العنصر لهذا استخدمناه في شريط أدوات النافذة لكن هذا العنصر لن يكون قابل للضغط أو المرور عليه لذلك يجب تعطيلها لعناصر التحكّم بداخله. 

لنكتب بعض الشيفرات بداخل ملفّ src/app.js لإضافة الحياة لشريط الأدوات. توفّر nw بعض التوابع للتحكم بالنافذة ما سنحتاجه للعمل: 

require(“nw.gui”).Window.get().close(); // إغلاق النافذة الحالية فقط 
require(“nw.gui”).App.quit(); // إغلاق التطبيق بجميع النوافذ 
require(“nw.gui”).Window.get().minimize(); // تصغير النافذة إلى شريط التطبيقات 
require(“nw.gui”).Window.get().maximize(); // ملء الشاشة بالنافذة 
require(“nw.gui”).Window.get().unmaximize(); // إرجاع النافذة 

أولًا زر الإغلاق: 

var nwGui = require(“nw.gui”); 

// زر إغلاق التطبيق 
window.document.querySelector(“.close”).addEventListener(“click”, function () { 
  nwGui.App.quit(); 
}); 

ثانيًا زر إخفاء النافذة إلى شريط التطبيقات:

var nwGui = require("nw.gui"), Window = nwGui.Window.get(); // زر تصغير النافذة إلى شريط التطبيقات window.document.querySelector(".minimize").addEventListener("click", function() { 
  Window.minimize() 
});

زر ملء النافذة لكن أولًا سنضيف مراقب للحدثين maximize و unmaximize لمعرفة هل النافذة بكامل الحجم أو لا، حيث لا توفّر nw أيّة API لمعرفة ذلك: 

var nwGui = require(“nw.gui”), 
Window = nwGui.Window.get(), 
maximized = false;

// مراقب للحدث maximize 
Window.on("maximize", function(){ maximized = true; }); 

// مراقب للحدث unmaximize 
Window.on("unmaximize", function(){ maximized = false; });

من خلال هذا وعند الضغط على زر الملئ إما أن تذهب النافذة لكامل الحجم أو تعود لحجمها الطبيعي: 

window.document.querySelector(“.maximize”).addEventListener(“click”, function () { 
  maximized ? Window.unmaximize() : Window.maximize() 
}); 

يجب أن يكون كامل ملف src/app.js بهذا الشكل:

"use strict";
global.document = window.document;
global.navigator = window.navigator;
	
var nwGui = require("nw.gui"),
	Window = nwGui.Window.get();
	
    (function(window, nwGui, Window){
		var maximized;
		
		// فتح ملف
		var openFile = function openFile (callback) {
			var input = document.createElement("input");
			input.setAttribute("type", "file");
			input.setAttribute("accept", ".mp4, .ogg, .ogv, .webm");
			input.addEventListener("change", function () { // عند إختيار ملف
				callback("file://"+this.value);
			});
			input.click() // فتح مُختار الملفات
		}
		
		// تشغيل فديو
		var playVideo = function playVideo (src) {
			var videojs = require("videojs"),
			ext = /[^.]+$/.exec(src)[0], // صيغة الملفّ
			video = document.createElement("video"), // عنصر الفديو
			source = document.createElement("source"), // عنصر المصدر للفديو
			player = document.querySelector("#player"); // عنصر المشغل في وثيقة html
			
			ext = (ext == "ogv") ? "ogg" : ext; // ملفات ogg قد تملك صيغة ogv 
			source.type = "video/"+ext.toLowerCase(); // نوع mime للفديو
			source.src = src; // مسار الفديو
			video.controls = true; // عناصر التحكّم
			video.autoplay = true; // تشغيل تلقائي
			video.classList.add("video-js", "vjs-default-skin"); // الclass الخاصة بvideojs
			video.appendChild(source); // إضافة عنصر المصدر إلى الفديو
			player.innerHTML = ""; // إزالة جميع العناصر السّابقة
			player.appendChild(video); // إضافة الفديو إلى وثيقة html
			videojs(video) // إستخدام video.js
		}
		
		window.addEventListener("load", function () {
			// زر فتح فديو
			window.document.querySelector("button#play").addEventListener("click", function () {
				openFile(playVideo)
			});
			
			// زر إغلاق التطبيق
			window.document.querySelector(".close").addEventListener("click", function () {
				nwGui.App.quit()
			});
	
			// مراقب للحدث maximize
			Window.on("maximize", function(){
				maximized = true;
			});
			// مراقب للحدث unmaximize
			Window.on("unmaximize", function(){
				maximized = false;
			});
			
			// زر تصغير النافذة إلى شريط التطبيقات
			window.document.querySelector(".minimize").addEventListener("click", function() {
				Window.minimize()
			});
			
			// زر الملء
			window.document.querySelector(".maximize").addEventListener("click", function () {
				maximized ? Window.unmaximize() : Window.maximize()
			});
		});	
	})
(window, nwGui, Window);

صورة للشكل النهائي: 

app.thumb.png.790447be85d97b1044ee5fcc44 

إن لم يظهر التطبيق بنفس الشكل فإليك ملفات التطبيق النهائية.

بناء الملف التنفيذي

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

من مدير حزم npm قم بتركيب nwbuild

npm install nwbuild -g 

أنظمة التشغيل المتوفرة لـnwbuild:

  • win32 # Windows 32bit 
  • win64 # Windows 64bit 
  • win # Windows 64bit مع 32bit 
  • osx32 # Mac OS 32bit 
  • osx64 # Mac OS 64bit 
  • osx # Mac OS 64bit مع 32bit 
  • linux32 # Linux 32bit 
  • linux64 #  Linux 64bit 
  • linux # Linux 64bit مع 32bit 

ليعمل على أنظمة التشغيل الثلاث سنبنيه لـlinux ، osx64 و win32، اتّجه إلى مجلد التطبيق ونفّذ:

nwbuild -p linux,osx64,win32 ./

سيعمل أولًا على تحميل ملفات البناء لأنظمة التشغيل من ثم بناء الملفات التنفيذية، ستلاحظ أن متوسط الحجم 50 ميغابايت للملفّ الواحد بسبب تضمين المتصفحات والملفات التشغيلية.

التالي

سيكون من الرائع تعلم react لبناء تطبيقات سطح مكتب رائعة باستخدام nw مع react.

مصادر

  1. nw window
  2. nw App
  3. nw javascript contexts
  4. nw build

هوامش

1 - كروميوم (Chromium) هو متصفح ويب مفتوح المصدر، يأخذ متصفح Google كروم الكود المصدري منه. المتصفحان يشتركان في أغلبية الشِفرة البرمجية والميزات، وإن كانت هناك بعض الاختلافات الطفيفة في ملامح كلا منهما وفي تراخيص الأستخدام أيضا. المصدر: ويكيبيديا.


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

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

يوسف سيد

نشر (معدل)

بتاريخ On ٨‏/٧‏/٢٠١٦ at 15:54 قال MnsorGakman:
  • كيفية حذف الاداة ارجو  رد

أي أداة؟ تقصد NW؟ لحذف تنصيبها في العام من خلال سطر الأوامر:

     npm uninstall -g nw

تم التعديل في بواسطة يوسف سيد


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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...