إضافة زر نسخ الكود في صفحة ويب

سليمان العثمان
سليمان العثمان
٢٢ نوفمبر ٢٠٢٣
Cover Image for إضافة زر نسخ الكود في صفحة ويب

سنتطرق في هذه التدوينة إلى طريقة إضافة زر نسخ الشيفرة "code" إلى حاوية الأكواد "Code snippets" في صفحة ويب، سواءً كانت واحدة أم أكثر بأبسط طريقة من خلال جافاسكربت و كذلك رياكت جي اس "Reactjs".

ملاحظة : الترجمة الحرفية لـ "Code snippets" هي مقتطفات الشيفرة، و لكني فضّلت تسميتها بحاوية أكواد لأنها تشبه الحاوية و ربما صندوق الشيفرة أقرب للمعنى الظاهر لها.

إضافة الزر من خلال Vanilla JS

في البداية لنضع الكود :

const addCopyButton = () => {
  const pre = document.querySelectorAll("pre");

  const copy = '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"></path></svg>';

  const copied = '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>';

  pre.forEach(e => {
    e.classList.add("relative");
    const copyButton = document.createElement("button");
    copyButton.innerHTML = `${copy}`;
    copyButton.setAttribute(
      "class",
      "absolute top-1 z-[10000] right-1 bg-neutral-600 px-2 py-2 text-sm"
    );
    copyButton.addEventListener("click", async () => {
      try {
        await navigator.clipboard.writeText(e.innerText.trim());
        copyButton.innerHTML = `${copied}`;
      } catch (err) { }
      setTimeout(() => {
        copyButton.innerHTML = `${copy}`;
      }, 700);
    })
    e.append(copyButton);
  })
}

addCopyButton();

شرح الكود

في البداية نعرّف دالة ( Function ) جديدة بأيّ اسم. و في مثالنا أعطيناها اسم addCopyButton.

في سطر الأول const pre ... نعرّف متغير لنضع فيه كافة عناصر الوسم pre من خلال إستعلام محدّد querySeletorAll عن الوسم ( tag ) pre.

في السطر الثاني و الثالث نعرّف متغيرين copy - copied يحملون قيمة لأيقونة svg تعبّران عن أيقونتيّ "النسخ و علامة الصح" .

في السطر الرابع نبدأ بعملية المرور على كافة العناصر من خلال "forEach".

في السطر الخامس نضيف كلاس ( class ) relative و الذي تكون قيمته ب css position: relative و سنشرح الهدف منه لاحقًا.

السطر السادس و السابع نعرّف متغير لزر النسخ و ننشئ زر html من خلال createElement و نضيف له أيقونة النسخ.

السطر الثامن إلى الحادي العشر نضيف خاصية ( attribute ) class و قيمتها كلاسات من مكتبة tailwindcss و التي تعادل أكواد في css :

position : absolute;
top : 4px;
z-index: 10000;
right : 4px;
padding: 8px;
text-size: 14px;
background-color: #525252;

كود css السابق يثبّت العنصر في مكان المحدد و هو فالأعلى و اليمين مع إضافة حجم للخط لضبط حجم أيقونات svg و لون الخلفية يُناسب الحاوية و z-index بقيمة مُرتفعة لوجود بعض مكتبات highlight تستخدم نظام الطبقات و في هذه الحالة نضمن ظهور الزر فوق الصندوق، و تم إضافة relative لوسم pre من أجل إعلام الزر بأن حدوده هو الوسم pre و ليس body.

في السطر الثاني عشر ننشئ تنصّت "Listener" على ضغطة "click" من خلال addEventListener لزر النسخ

في السطر الثالث عشر إلى السادس عشر عبارة عن شيفرة لنسخ ما بداخل الحاوية من خلال navigator.clipboard.writeText و المنسوخ هو e.innerText و الذي يكون النص الذي بداخل وسم pre.

في السطر السابع عشر إلى التاسع عشر نستخدم setTimeout و الهدف هو إعادة أيقونة النسخ لزر النسخ بعد تغييرها أثناء "click" لإضفاء تأثير.

في النهاية نضيف زر النسخ إلى داخل وسم pre بعد ضبطه.

تبقّت خطوة واحدة و هي إستدعاء الدالة لتتنفذ و هو السطر الأخير من الكود.

ملاحظة : يفضّل إضافة الكود في نهاية الصفحة عند تقفيلة وسم body لضمان إن الكود يتنفّذ بعد عملية render للصفحة.

إضافة الزر من خلال Reactjs components | Nextjs

من أجل تشغيل الكود في reactjs component أو nextjs نحتاج إلى تعديل بضعة أمور للكود السابق :

نستعمل إحدى hooks التي توفّرها لنا reactjs و هي useEffect و نضيف شرط بسيط و هو :

if (document) addCopyButton();

ليصبح كود useEffect :

useEffect(() => {
  if (document) addCopyButton();
}, []);

و لذلك لضمان بأن الدالة تعمل بعد عملية render لـ component.

كود Reactjs Typescript
const addCopyButton = () => {
  const pre: NodeListOf<HTMLPreElement> = document.querySelectorAll("pre");

  const copy: string = '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"></path></svg>';

  const copied: string = '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>';

  pre.forEach((e) => {
    e.classList.add("relative");
    const copyButton: HTMLButtonElement = document.createElement("button");
    copyButton.innerHTML = `${copy}`;
    copyButton.setAttribute(
      "class",
      "absolute top-1 z-[10000] right-1 bg-neutral-600 px-2 py-2 text-sm"
    );
    copyButton.addEventListener("click", async () => {
      try {
        await navigator.clipboard.writeText(e.innerText);
        copyButton.innerHTML = `${copied}`;
      } catch (err) { }
      setTimeout(() => {
        copyButton.innerHTML = `${copy}`;
      }, 700);
    });
    e.append(copyButton);
  });
}

useEffect(() => {
  if (document) addCopyButton();
}, []);
كود Reactjs Javascript
const addCopyButton = () => {
  const pre = document.querySelectorAll("pre")

  const copy =
    '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"></path></svg>'

  const copied =
    '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>'

  pre.forEach(e => {
    e.classList.add("relative")
    const copyButton = document.createElement("button")
    copyButton.innerHTML = `${copy}`
    copyButton.setAttribute(
      "class",
      "absolute top-1 z-[10000] right-1 bg-neutral-600 px-2 py-2 text-sm"
    )
    copyButton.addEventListener("click", async () => {
      try {
        await navigator.clipboard.writeText(e.innerText)
        copyButton.innerHTML = `${copied}`
      } catch (err) {}
      setTimeout(() => {
        copyButton.innerHTML = `${copy}`
      }, 700)
    })
    e.append(copyButton)
  })
}

useEffect(() => {
  if (document) addCopyButton()
}, [])

ملاحظة : الكود السابق يعمل على Nextjs و إذا كُنت تستخدم نسخة 13 فأعلى فتأكد بأن component معلّم عليه بـ 'use client'.

مراجع ستفيدك

للإستزادة في موضوع hooks يمكن قراءة التدوينة من مدونة tutomena شرح React Hooks.. الميزة الجديدة القادمة لِ React.js.

React hooks

React client hook in Server Component

تصميم@sulealothman