ক্লিপবোর্ড অ্যাক্সেস আনব্লক করা হচ্ছে

পাঠ্য এবং চিত্রগুলির জন্য নিরাপদ, অবরোধমুক্ত ক্লিপবোর্ড অ্যাক্সেস

সিস্টেম ক্লিপবোর্ডে অ্যাক্সেস পাওয়ার ঐতিহ্যগত উপায়টি ছিল document.execCommand() এর মাধ্যমে ক্লিপবোর্ড ইন্টারঅ্যাকশনের জন্য। যদিও ব্যাপকভাবে সমর্থিত, কাটা এবং পেস্ট করার এই পদ্ধতিটি একটি খরচে এসেছিল: ক্লিপবোর্ড অ্যাক্সেস সিঙ্ক্রোনাস ছিল এবং শুধুমাত্র DOM-এ পড়তে এবং লিখতে পারে।

লেখার ছোট বিটগুলির জন্য এটি ঠিক আছে, কিন্তু এমন অনেক ক্ষেত্রে রয়েছে যেখানে ক্লিপবোর্ড স্থানান্তরের জন্য পৃষ্ঠাটি ব্লক করা একটি খারাপ অভিজ্ঞতা। কন্টেন্ট নিরাপদে পেস্ট করার আগে সময় সাপেক্ষ স্যানিটাইজেশন বা ইমেজ ডিকোডিং প্রয়োজন হতে পারে। ব্রাউজারটিকে পেস্ট করা নথি থেকে লিঙ্কযুক্ত সংস্থানগুলি লোড বা ইনলাইন করতে হতে পারে। এটি ডিস্ক বা নেটওয়ার্কে অপেক্ষা করার সময় পৃষ্ঠাটিকে ব্লক করবে। মিশ্রণে অনুমতি যোগ করার কল্পনা করুন, ক্লিপবোর্ড অ্যাক্সেসের অনুরোধ করার সময় ব্রাউজারটি পৃষ্ঠাটিকে ব্লক করতে হবে। একই সময়ে, ক্লিপবোর্ড ইন্টারঅ্যাকশনের জন্য document.execCommand() চারপাশে রাখা অনুমতিগুলি আলগাভাবে সংজ্ঞায়িত করা হয় এবং ব্রাউজারগুলির মধ্যে পরিবর্তিত হয়।

Async ক্লিপবোর্ড API এই সমস্যাগুলির সমাধান করে, একটি সু-সংজ্ঞায়িত অনুমতি মডেল প্রদান করে যা পৃষ্ঠাটিকে ব্লক করে না। Async ক্লিপবোর্ড API বেশিরভাগ ব্রাউজারে পাঠ্য এবং চিত্রগুলি পরিচালনা করার জন্য সীমাবদ্ধ, তবে সমর্থন পরিবর্তিত হয়। নিম্নলিখিত প্রতিটি বিভাগের জন্য ব্রাউজার সামঞ্জস্যের ওভারভিউ সাবধানে অধ্যয়ন করতে ভুলবেন না।

অনুলিপি: ক্লিপবোর্ডে ডেটা লেখা

লেখার পাঠ্য()

ক্লিপবোর্ডে টেক্সট কপি করতে writeText() কল করুন। যেহেতু এই APIটি অ্যাসিঙ্ক্রোনাস, তাই writeText() ফাংশন একটি প্রতিশ্রুতি প্রদান করে যা পাস করা পাঠ্য সফলভাবে অনুলিপি করা হয়েছে কিনা তার উপর নির্ভর করে সমাধান বা প্রত্যাখ্যান করে:

async function copyPageUrl() {
  try {
    await navigator.clipboard.writeText(location.href);
    console.log('Page URL copied to clipboard');
  } catch (err) {
    console.error('Failed to copy: ', err);
  }
}

ব্রাউজার সমর্থন

  • ক্রোম: 66।
  • প্রান্ত: 79।
  • ফায়ারফক্স: 63.
  • সাফারি: 13.1।

উৎস

লিখুন()

প্রকৃতপক্ষে, writeText() হল জেনেরিক write() পদ্ধতির জন্য একটি সুবিধাজনক পদ্ধতি, যা আপনাকে ক্লিপবোর্ডে ছবি কপি করতে দেয়। writeText() মত, এটি অ্যাসিঙ্ক্রোনাস এবং একটি প্রতিশ্রুতি প্রদান করে।

ক্লিপবোর্ডে একটি চিত্র লিখতে, আপনার একটি blob হিসাবে চিত্রটি প্রয়োজন। এটি করার একটি উপায় হল fetch() ব্যবহার করে একটি সার্ভার থেকে চিত্রের অনুরোধ করা, তারপর প্রতিক্রিয়াতে blob() কল করা।

সার্ভার থেকে একটি ইমেজ অনুরোধ করা পছন্দসই বা বিভিন্ন কারণে সম্ভব নাও হতে পারে। সৌভাগ্যবশত, আপনি একটি ক্যানভাসে ছবিটি আঁকতে পারেন এবং ক্যানভাসের toBlob() পদ্ধতিতে কল করতে পারেন।

এরপর, write() পদ্ধতিতে একটি প্যারামিটার হিসাবে ClipboardItem অবজেক্টের একটি অ্যারে পাস করুন। বর্তমানে আপনি একটি সময়ে শুধুমাত্র একটি ছবি পাস করতে পারেন, কিন্তু আমরা ভবিষ্যতে একাধিক ছবির জন্য সমর্থন যোগ করার আশা করি। ClipboardItem একটি অবজেক্টকে MIME টাইপের ইমেজটিকে কী হিসাবে এবং ব্লবটিকে মান হিসাবে নেয়৷ fetch() বা canvas.toBlob() থেকে প্রাপ্ত ব্লব বস্তুর জন্য, blob.type বৈশিষ্ট্য স্বয়ংক্রিয়ভাবে একটি চিত্রের জন্য সঠিক MIME প্রকার ধারণ করে।

try {
  const imgURL = '/images/generic/file.png';
  const data = await fetch(imgURL);
  const blob = await data.blob();
  await navigator.clipboard.write([
    new ClipboardItem({
      // The key is determined dynamically based on the blob's type.
      [blob.type]: blob
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

বিকল্পভাবে, আপনি ClipboardItem অবজেক্টে একটি প্রতিশ্রুতি লিখতে পারেন। এই প্যাটার্নের জন্য, আপনাকে আগে থেকেই ডেটার MIME প্রকার জানতে হবে।

try {
  const imgURL = '/images/generic/file.png';
  await navigator.clipboard.write([
    new ClipboardItem({
      // Set the key beforehand and write a promise as the value.
      'image/png': fetch(imgURL).then(response => response.blob()),
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

ব্রাউজার সমর্থন

  • ক্রোম: 76।
  • প্রান্ত: 79।
  • ফায়ারফক্স: 127।
  • সাফারি: 13.1।

উৎস

অনুলিপি ঘটনা

যে ক্ষেত্রে একজন ব্যবহারকারী একটি ক্লিপবোর্ড অনুলিপি শুরু করেন এবং preventDefault() কল করেন না , copy ইভেন্টে ইতিমধ্যে সঠিক বিন্যাসে থাকা আইটেমগুলির সাথে একটি clipboardData বৈশিষ্ট্য অন্তর্ভুক্ত থাকে। আপনি যদি আপনার নিজস্ব যুক্তি বাস্তবায়ন করতে চান তবে আপনার নিজের বাস্তবায়নের পক্ষে ডিফল্ট আচরণ প্রতিরোধ করতে আপনাকে preventDefault() কল করতে হবে। এই ক্ষেত্রে, clipboardData খালি থাকবে। পাঠ্য এবং একটি চিত্র সহ একটি পৃষ্ঠা বিবেচনা করুন এবং ব্যবহারকারী যখন সমস্ত নির্বাচন করে এবং একটি ক্লিপবোর্ড অনুলিপি শুরু করে, তখন আপনার কাস্টম সমাধানটি পাঠ্য বাতিল করে এবং কেবল চিত্রটি অনুলিপি করতে হবে। নীচের কোড নমুনায় দেখানো হিসাবে আপনি এটি অর্জন করতে পারেন। ক্লিপবোর্ড এপিআই সমর্থিত না হলে কীভাবে আগের এপিআই-এ ফিরে যেতে হয় তা এই উদাহরণে কভার করা হয়নি।

<!-- The image we want on the clipboard. -->
<img src="kitten.webp" alt="Cute kitten.">
<!-- Some text we're not interested in. -->
<p>Lorem ipsum</p>
document.addEventListener("copy", async (e) => {
  // Prevent the default behavior.
  e.preventDefault();
  try {
    // Prepare an array for the clipboard items.
    let clipboardItems = [];
    // Assume `blob` is the blob representation of `kitten.webp`.
    clipboardItems.push(
      new ClipboardItem({
        [blob.type]: blob,
      })
    );
    await navigator.clipboard.write(clipboardItems);
    console.log("Image copied, text ignored.");
  } catch (err) {
    console.error(err.name, err.message);
  }
});

copy ইভেন্টের জন্য:

ব্রাউজার সমর্থন

  • ক্রোম: ১.
  • প্রান্ত: 12।
  • ফায়ারফক্স: 22।
  • সাফারি: 3.

উৎস

ClipboardItem জন্য:

ব্রাউজার সমর্থন

  • ক্রোম: 76।
  • প্রান্ত: 79।
  • ফায়ারফক্স: 127।
  • সাফারি: 13.1।

উৎস

পেস্ট করুন: ক্লিপবোর্ড থেকে ডেটা পড়া

পাঠ্য()

ক্লিপবোর্ড থেকে পাঠ্য পড়তে, navigator.clipboard.readText() কল করুন এবং সমাধানের জন্য ফিরে আসা প্রতিশ্রুতির জন্য অপেক্ষা করুন:

async function getClipboardContents() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Pasted content: ', text);
  } catch (err) {
    console.error('Failed to read clipboard contents: ', err);
  }
}

ব্রাউজার সমর্থন

  • ক্রোম: 66।
  • প্রান্ত: 79।
  • ফায়ারফক্স: 125।
  • সাফারি: 13.1।

উৎস

পড়ুন()

navigator.clipboard.read() পদ্ধতিটিও অ্যাসিঙ্ক্রোনাস এবং একটি প্রতিশ্রুতি প্রদান করে। ক্লিপবোর্ড থেকে একটি ইমেজ পড়তে, ClipboardItem অবজেক্টের একটি তালিকা প্রাপ্ত করুন, তারপর তাদের উপর পুনরাবৃত্তি করুন।

প্রতিটি ClipboardItem এর বিষয়বস্তু বিভিন্ন প্রকারে ধরে রাখতে পারে, তাই আপনাকে আবার একটি for...of লুপ ব্যবহার করে প্রকারের তালিকার উপর পুনরাবৃত্তি করতে হবে। প্রতিটি প্রকারের জন্য, সংশ্লিষ্ট ব্লব পাওয়ার জন্য একটি যুক্তি হিসাবে বর্তমান প্রকারের সাথে getType() পদ্ধতিটিকে কল করুন। আগের মতো, এই কোডটি চিত্রের সাথে আবদ্ধ নয় এবং ভবিষ্যতের অন্যান্য ফাইলের সাথে কাজ করবে।

async function getClipboardContents() {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      for (const type of clipboardItem.types) {
        const blob = await clipboardItem.getType(type);
        console.log(URL.createObjectURL(blob));
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
}

ব্রাউজার সমর্থন

  • ক্রোম: 76।
  • প্রান্ত: 79।
  • ফায়ারফক্স: 127।
  • সাফারি: 13.1।

উৎস

আটকানো ফাইল নিয়ে কাজ করা

ক্লিপবোর্ড কীবোর্ড শর্টকাট যেমন ctrl + c এবং ctrl + v ব্যবহার করতে সক্ষম হওয়া ব্যবহারকারীদের জন্য এটি দরকারী। Chromium ক্লিপবোর্ডে শুধুমাত্র পঠনযোগ্য ফাইলগুলিকে নীচের রূপরেখা হিসাবে প্রকাশ করে৷ এটি ট্রিগার হয় যখন ব্যবহারকারী অপারেটিং সিস্টেমের ডিফল্ট পেস্ট শর্টকাট হিট করে বা ব্যবহারকারী যখন সম্পাদনা ক্লিক করে তখন ব্রাউজারের মেনু বারে পেস্ট করে । আর কোন প্লাম্বিং কোডের প্রয়োজন নেই।

document.addEventListener("paste", async e => {
  e.preventDefault();
  if (!e.clipboardData.files.length) {
    return;
  }
  const file = e.clipboardData.files[0];
  // Read the file's contents, assuming it's a text file.
  // There is no way to write back to it.
  console.log(await file.text());
});

ব্রাউজার সমর্থন

  • ক্রোম: 3.
  • প্রান্ত: 12।
  • ফায়ারফক্স: 3.6।
  • সাফারি: 4।

উৎস

পেস্ট ঘটনা

যেমন আগে উল্লেখ করা হয়েছে, ক্লিপবোর্ড API এর সাথে কাজ করার জন্য ইভেন্টগুলি চালু করার পরিকল্পনা রয়েছে, তবে আপাতত আপনি বিদ্যমান paste ইভেন্টটি ব্যবহার করতে পারেন। এটি ক্লিপবোর্ড পাঠ্য পড়ার জন্য নতুন অ্যাসিঙ্ক্রোনাস পদ্ধতির সাথে সুন্দরভাবে কাজ করে। copy ইভেন্টের মতো, preventDefault() কল করতে ভুলবেন না।

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  const text = await navigator.clipboard.readText();
  console.log('Pasted text: ', text);
});

ব্রাউজার সমর্থন

  • ক্রোম: ১.
  • প্রান্ত: 12।
  • ফায়ারফক্স: 22।
  • সাফারি: 3.

উৎস

একাধিক MIME প্রকার পরিচালনা করা

বেশিরভাগ বাস্তবায়ন একক কাট বা অনুলিপি অপারেশনের জন্য ক্লিপবোর্ডে একাধিক ডেটা ফর্ম্যাট রাখে। এর দুটি কারণ রয়েছে: একজন অ্যাপ ডেভেলপার হিসেবে, আপনার কাছে অ্যাপের ক্ষমতাগুলি জানার কোনো উপায় নেই যা একজন ব্যবহারকারী টেক্সট বা ছবি কপি করতে চায় এবং অনেক অ্যাপ্লিকেশান প্লেইন টেক্সট হিসেবে স্ট্রাকচার্ড ডেটা পেস্ট করা সমর্থন করে। এটি সাধারণত একটি সম্পাদনা মেনু আইটেম সহ ব্যবহারকারীদের কাছে উপস্থাপন করা হয় যেমন একটি নাম সহ পেস্ট এবং ম্যাচ শৈলী বা বিন্যাস ছাড়াই আটকান

নিম্নলিখিত উদাহরণ দেখায় কিভাবে এটি করতে হয়. এই উদাহরণটি ইমেজ ডেটা পেতে fetch() ব্যবহার করে, তবে এটি একটি <canvas> বা ফাইল সিস্টেম অ্যাক্সেস API থেকেও আসতে পারে।

async function copy() {
  const image = await fetch('kitten.png').then(response => response.blob());
  const text = new Blob(['Cute sleeping kitten'], {type: 'text/plain'});
  const item = new ClipboardItem({
    'text/plain': text,
    'image/png': image
  });
  await navigator.clipboard.write([item]);
}

নিরাপত্তা এবং অনুমতি

ক্লিপবোর্ড অ্যাক্সেস সবসময় ব্রাউজারগুলির জন্য একটি নিরাপত্তা উদ্বেগ উপস্থাপন করে। যথাযথ অনুমতি ব্যতীত, একটি পৃষ্ঠা নিঃশব্দে ব্যবহারকারীর ক্লিপবোর্ডে সমস্ত ধরণের ক্ষতিকারক সামগ্রী অনুলিপি করতে পারে যা পেস্ট করার সময় বিপর্যয়কর ফলাফল তৈরি করবে৷ একটি ওয়েব পৃষ্ঠা কল্পনা করুন যেটি নীরবে আপনার ক্লিপবোর্ডে rm -rf / অথবা একটি ডিকম্প্রেশন বোমার ছবি কপি করে।

ব্রাউজার প্রম্পট ব্যবহারকারীকে ক্লিপবোর্ডের অনুমতির জন্য জিজ্ঞাসা করছে।
ক্লিপবোর্ড API-এর জন্য অনুমতি প্রম্পট।

ক্লিপবোর্ডে ওয়েব পৃষ্ঠাগুলিকে নিরবচ্ছিন্নভাবে পড়ার অ্যাক্সেস দেওয়া আরও বেশি ঝামেলার। ব্যবহারকারীরা নিয়মিতভাবে ক্লিপবোর্ডে পাসওয়ার্ড এবং ব্যক্তিগত বিবরণের মতো সংবেদনশীল তথ্য কপি করে, যা ব্যবহারকারীর অজান্তেই যেকোনো পৃষ্ঠা পড়তে পারে।

অনেক নতুন API-এর মতো, ক্লিপবোর্ড API শুধুমাত্র HTTPS-এ পরিবেশিত পৃষ্ঠাগুলির জন্য সমর্থিত। অপব্যবহার প্রতিরোধে সাহায্য করার জন্য, ক্লিপবোর্ড অ্যাক্সেস শুধুমাত্র তখনই অনুমোদিত হয় যখন একটি পৃষ্ঠা সক্রিয় ট্যাব হয়। সক্রিয় ট্যাবে থাকা পৃষ্ঠাগুলি অনুমতির অনুরোধ ছাড়াই ক্লিপবোর্ডে লিখতে পারে, কিন্তু ক্লিপবোর্ড থেকে পড়ার জন্য সর্বদা অনুমতির প্রয়োজন হয়৷

অনুলিপি এবং পেস্টের জন্য অনুমতিগুলি অনুমতি API এ যোগ করা হয়েছে৷ clipboard-write অনুমতি স্বয়ংক্রিয়ভাবে পৃষ্ঠাগুলিতে মঞ্জুর করা হয় যখন তারা সক্রিয় ট্যাব হয়। clipboard-read অনুমতি অবশ্যই অনুরোধ করতে হবে, যা আপনি ক্লিপবোর্ড থেকে ডেটা পড়ার চেষ্টা করে করতে পারেন। নীচের কোডটি পরবর্তীটি দেখায়:

const queryOpts = { name: 'clipboard-read', allowWithoutGesture: false };
const permissionStatus = await navigator.permissions.query(queryOpts);
// Will be 'granted', 'denied' or 'prompt':
console.log(permissionStatus.state);

// Listen for changes to the permission state
permissionStatus.onchange = () => {
  console.log(permissionStatus.state);
};

আপনি ব্যবহারকারীর অঙ্গভঙ্গি ব্যবহার করে কাটিং বা পেস্ট করার প্রয়োজন কিনা তাও নিয়ন্ত্রণ করতে পারেন allowWithoutGesture বিকল্পটি ব্যবহার করে। এই মানের জন্য ডিফল্ট ব্রাউজার দ্বারা পরিবর্তিত হয়, তাই আপনার সর্বদা এটি অন্তর্ভুক্ত করা উচিত।

এখানে ক্লিপবোর্ড API-এর অ্যাসিঙ্ক্রোনাস প্রকৃতি সত্যিই কাজে আসে: ক্লিপবোর্ড ডেটা পড়ার বা লেখার চেষ্টা করা স্বয়ংক্রিয়ভাবে ব্যবহারকারীকে অনুমতির জন্য অনুরোধ করে যদি এটি ইতিমধ্যে মঞ্জুর করা না হয়। যেহেতু API প্রতিশ্রুতি-ভিত্তিক, এটি সম্পূর্ণরূপে স্বচ্ছ, এবং ক্লিপবোর্ডের অনুমতি অস্বীকারকারী ব্যবহারকারী প্রতিশ্রুতি প্রত্যাখ্যান করে যাতে পৃষ্ঠাটি যথাযথভাবে প্রতিক্রিয়া জানাতে পারে।

যেহেতু একটি পৃষ্ঠা সক্রিয় ট্যাব হলে ব্রাউজারগুলি শুধুমাত্র ক্লিপবোর্ড অ্যাক্সেসের অনুমতি দেয়, আপনি দেখতে পাবেন যে এখানে কিছু উদাহরণ সরাসরি ব্রাউজারের কনসোলে পেস্ট করা হলে চলবে না, যেহেতু বিকাশকারী সরঞ্জামগুলি নিজেই সক্রিয় ট্যাব। একটি কৌশল আছে: setTimeout() ব্যবহার করে ক্লিপবোর্ড অ্যাক্সেস স্থগিত করুন, তারপর ফাংশনগুলি কল করার আগে এটিকে ফোকাস করতে দ্রুত পৃষ্ঠার ভিতরে ক্লিক করুন:

setTimeout(async () => {
  const text = await navigator.clipboard.readText();
  console.log(text);
}, 2000);

অনুমতি নীতি একীকরণ

আইফ্রেমে API ব্যবহার করার জন্য, আপনাকে অনুমতি নীতির সাথে এটি সক্ষম করতে হবে, যা এমন একটি প্রক্রিয়া সংজ্ঞায়িত করে যা বিভিন্ন ব্রাউজার বৈশিষ্ট্য এবং APIগুলিকে বেছে বেছে সক্রিয় এবং নিষ্ক্রিয় করার অনুমতি দেয়৷ নির্দিষ্টভাবে, আপনার অ্যাপের প্রয়োজনের উপর নির্ভর করে আপনাকে clipboard-read বা clipboard-write উভয়ই পাস করতে হবে।

<iframe
    src="index.html"
    allow="clipboard-read; clipboard-write"
>
</iframe>

বৈশিষ্ট্য সনাক্তকরণ

সমস্ত ব্রাউজার সমর্থন করার সময় Async ক্লিপবোর্ড API ব্যবহার করতে, navigator.clipboard পরীক্ষা করুন এবং আগের পদ্ধতিতে ফিরে যান। উদাহরণস্বরূপ, অন্যান্য ব্রাউজারগুলিকে অন্তর্ভুক্ত করার জন্য আপনি কীভাবে পেস্টিং প্রয়োগ করতে পারেন তা এখানে।

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  let text;
  if (navigator.clipboard) {
    text = await navigator.clipboard.readText();
  }
  else {
    text = e.clipboardData.getData('text/plain');
  }
  console.log('Got pasted text: ', text);
});

এটা পুরো গল্প নয়। Async ক্লিপবোর্ড API এর আগে, ওয়েব ব্রাউজার জুড়ে বিভিন্ন কপি এবং পেস্ট বাস্তবায়নের মিশ্রণ ছিল। বেশিরভাগ ব্রাউজারে, document.execCommand('copy') এবং document.execCommand('paste') ব্যবহার করে ব্রাউজারের নিজস্ব কপি এবং পেস্ট ট্রিগার করা যেতে পারে। যদি অনুলিপি করা পাঠ্যটি DOM-এ উপস্থিত না থাকে এমন একটি স্ট্রিং হলে, এটি অবশ্যই DOM-এ ইনজেকশন করতে হবে এবং নির্বাচন করতে হবে:

button.addEventListener('click', (e) => {
  const input = document.createElement('input');
  input.style.display = 'none';
  document.body.appendChild(input);
  input.value = text;
  input.focus();
  input.select();
  const result = document.execCommand('copy');
  if (result === 'unsuccessful') {
    console.error('Failed to copy text.');
  }
  input.remove();
});

ডেমো

আপনি নিচের ডেমোতে Async ক্লিপবোর্ড API এর সাথে খেলতে পারেন। Glitch-এ আপনি টেক্সট ডেমো বা ইমেজ ডেমো রিমিক্স করতে পারেন সেগুলো নিয়ে পরীক্ষা করতে।

প্রথম উদাহরণ ক্লিপবোর্ডের উপর এবং বন্ধ পাঠ্য সরানো প্রদর্শন করে।

ছবি সহ API চেষ্টা করতে, এই ডেমো ব্যবহার করুন. মনে রাখবেন যে শুধুমাত্র PNG সমর্থিত এবং শুধুমাত্র কয়েকটি ব্রাউজারে

স্বীকৃতি

অ্যাসিঙ্ক্রোনাস ক্লিপবোর্ড APIটি ডারউইন হুয়াং এবং গ্যারি কামার্চিক দ্বারা প্রয়োগ করা হয়েছিল। ডারউইন ডেমোও দিয়েছেন। এই নিবন্ধের কিছু অংশ পর্যালোচনা করার জন্য Kyarik এবং আবার Gary Kačmarčík কে ধন্যবাদ।

আনস্প্ল্যাশে মার্কাস উইঙ্কলারের হিরো ছবি।