नेक्स्टजेएस और एक्सप्रेसजेएस प्लेटोब्लॉकचैन डेटा इंटेलिजेंस के साथ भाषण को पीडीएफ में कनवर्ट करना। लंबवत खोज। ऐ.

नेक्स्टजेएस और एक्सप्रेसजेएस के साथ भाषण को पीडीएफ में कनवर्ट करना

वाक् इंटरफेस के एक चीज के अधिक होने के साथ, यह कुछ चीजों की खोज के लायक है जो हम भाषण बातचीत के साथ कर सकते हैं। जैसे, क्या होगा अगर हम कुछ कह सकते हैं और उसे डाउनलोड करने योग्य पीडीएफ के रूप में ट्रांसक्राइब और पंप कर सकते हैं?

खैर, स्पॉइलर अलर्ट: हम बिल्कुल कर सकते हैं वो करें! ऐसा करने के लिए हम पुस्तकालय और ढांचे को एक साथ जोड़ सकते हैं, और यही हम इस लेख में एक साथ करने जा रहे हैं।

ये वे उपकरण हैं जिनका हम उपयोग कर रहे हैं

सबसे पहले, ये दो बड़े खिलाड़ी हैं: Next.js और Express.js।

Next.js रिएक्ट के लिए अतिरिक्त कार्यात्मकताओं पर हमला करता है, जिसमें स्थैतिक साइटों के निर्माण के लिए प्रमुख विशेषताएं शामिल हैं। डायनेमिक रूटिंग, इमेज ऑप्टिमाइजेशन, बिल्ट-इन-डोमेन और सबडोमेन रूटिंग, फास्ट रीफ्रेश, फाइल सिस्टम रूटिंग, और एपीआई रूट जैसे बॉक्स के ठीक बाहर की पेशकश के कारण यह कई डेवलपर्स के लिए जाना जाता है। कई, कई अन्य चीजें.

हमारे मामले में, हमें निश्चित रूप से इसके लिए Next.js की आवश्यकता है एपीआई मार्ग हमारे क्लाइंट सर्वर पर। हम एक ऐसा मार्ग चाहते हैं जो एक टेक्स्ट फ़ाइल लेता है, इसे पीडीएफ में परिवर्तित करता है, इसे हमारे फाइल सिस्टम को लिखता है, फिर क्लाइंट को प्रतिक्रिया भेजता है।

एक्सप्रेस .js हमें रूटिंग, HTTP हेल्पर्स और टेम्प्लेटिंग के साथ एक छोटा Node.js ऐप प्राप्त करने की अनुमति देता है। यह हमारे अपने एपीआई के लिए एक सर्वर है, जिसकी हमें आवश्यकता होगी क्योंकि हम चीजों के बीच डेटा पास और पार्स करते हैं।

हमारे पास कुछ अन्य निर्भरताएँ हैं जिनका हम उपयोग करेंगे:

  1. प्रतिक्रिया-भाषण-मान्यता: भाषण को पाठ में परिवर्तित करने के लिए एक पुस्तकालय, इसे रिएक्ट घटकों के लिए उपलब्ध कराना।
  2. पुनर्योजी-रनटाइम: समस्या निवारण के लिए एक पुस्तकालय "regeneratorRuntime परिभाषित नहीं है" त्रुटि जो प्रतिक्रिया-भाषण-पहचान का उपयोग करते समय Next.js में दिखाई देती है
  3. एचटीएमएल-पीडीएफ-नोड: एचटीएमएल पेज या सार्वजनिक यूआरएल को पीडीएफ में बदलने के लिए लाइब्रेरी
  4. Axios: ब्राउज़र और Node.js दोनों में HTTP अनुरोध करने के लिए एक पुस्तकालय
  5. CORS: एक पुस्तकालय जो क्रॉस-ओरिजिनल रिसोर्स शेयरिंग की अनुमति देता है

की स्थापना

पहली चीज जो हम करना चाहते हैं वह है दो प्रोजेक्ट फोल्डर बनाना, एक क्लाइंट के लिए और दूसरा सर्वर के लिए। आप जो चाहें उन्हें नाम दें। मैं अपना नामकरण कर रहा हूँ audio-to-pdf-client और audio-to-pdf-server, क्रमशः।

क्लाइंट की ओर से Next.js के साथ आरंभ करने का सबसे तेज़ तरीका इसके साथ बूटस्ट्रैप करना है क्रिएट-नेक्स्ट-ऐप. तो, अपना टर्मिनल खोलें और अपने क्लाइंट प्रोजेक्ट फ़ोल्डर से निम्न आदेश चलाएं:

npx create-next-app client

अब हमें अपने एक्सप्रेस सर्वर की जरूरत है। हम इसे प्राप्त कर सकते हैं cd-सर्वर प्रोजेक्ट फ़ोल्डर में प्रवेश करना और चलाना npm init आदेश। ए package.json एक बार हो जाने के बाद फ़ाइल सर्वर प्रोजेक्ट फ़ोल्डर में बनाई जाएगी।

हमें अभी भी वास्तव में एक्सप्रेस स्थापित करने की आवश्यकता है, तो चलिए अब इसके साथ करते हैं npm install express. अब हम एक नया बना सकते हैं index.js सर्वर प्रोजेक्ट फ़ोल्डर में फ़ाइल करें और इस कोड को वहां छोड़ दें:

const express = require("express")
const app = express()

app.listen(4000, () => console.log("Server is running on port 4000"))

सर्वर चलाने के लिए तैयार हैं?

node index.js

हमें आगे बढ़ने के लिए कुछ और फ़ोल्डर और दूसरी फ़ाइल की आवश्यकता होगी:

  • बनाओ components क्लाइंट प्रोजेक्ट फ़ोल्डर में फ़ोल्डर।
  • बनाओ SpeechToText.jsx में दायर components सबफ़ोल्डर।

इससे पहले कि हम आगे बढ़ें, हमें थोड़ी सफाई करनी है। विशेष रूप से, हमें डिफ़ॉल्ट कोड को बदलने की आवश्यकता है pages/index.js इसके साथ फाइल करें:

import Head from "next/head";
import SpeechToText from "../components/SpeechToText";

export default function Home() {
  return (
    <div className="home">
      <Head>
        <title>Audio To PDF</title>
        <meta
          name="description"
          content="An app that converts audio to pdf in the browser"
        />
        <link rel="icon" href="/hi/favicon.ico" />
      </Head>

      <h1>Convert your speech to pdf</h1>

      <main>
        <SpeechToText />
      </main>
    </div>
  );
}

आयातित SpeechToText घटक अंततः से निर्यात किया जाएगा components/SpeechToText.jsx.

आइए अन्य निर्भरताएँ स्थापित करें

ठीक है, हमारे पास हमारे ऐप के लिए प्रारंभिक सेटअप है। अब हम उन पुस्तकालयों को स्थापित कर सकते हैं जो पारित होने वाले डेटा को संभालते हैं।

हम अपने ग्राहक निर्भरता को इसके साथ स्थापित कर सकते हैं:

npm install react-speech-recognition regenerator-runtime axios

हमारे एक्सप्रेस सर्वर निर्भरता आगे हैं, तो चलिए cd सर्वर प्रोजेक्ट फ़ोल्डर में और उनको स्थापित करें:

npm install html-pdf-node cors

संभवत: रुकने और यह सुनिश्चित करने का एक अच्छा समय है कि हमारे प्रोजेक्ट फोल्डर की फाइलें ठीक हैं। इस बिंदु पर क्लाइंट प्रोजेक्ट फ़ोल्डर में आपके पास क्या होना चाहिए:

/audio-to-pdf-web-client
├─ /components
|  └── SpeechToText.jsx
├─ /pages
|  ├─ _app.js
|  └── index.js
└── /styles
    ├─globals.css
    └── Home.module.css

और यहाँ आपके पास सर्वर प्रोजेक्ट फ़ोल्डर में क्या होना चाहिए:

/audio-to-pdf-server
└── index.js

यूआई का निर्माण

ठीक है, अगर हमारे साथ बातचीत करने का कोई तरीका नहीं है, तो हमारा भाषण-से-पीडीएफ इतना अच्छा नहीं होगा, तो चलिए इसके लिए एक रिएक्ट घटक बनाते हैं जिसे हम कॉल कर सकते हैं <SpeechToText>.

आप अपने खुद के मार्कअप का पूरी तरह से उपयोग कर सकते हैं। यहाँ मैं आपको उन टुकड़ों का एक विचार देने के लिए मिला हूँ जिन्हें हम एक साथ रख रहे हैं:

import React from "react";

const SpeechToText = () => {
  return (
    <>
      <section>
        <div className="button-container">
          <button type="button" style={{ "--bgColor": "blue" }}>
            Start
          </button>
          <button type="button" style={{ "--bgColor": "orange" }}>
            Stop
          </button>
        </div>
        <div
          className="words"
          contentEditable
          suppressContentEditableWarning={true}
        ></div>
        <div className="button-container">
          <button type="button" style={{ "--bgColor": "red" }}>
            Reset
          </button>
          <button type="button" style={{ "--bgColor": "green" }}>
            Convert to pdf
          </button>
        </div>
      </section>
    </>
  );
};

export default SpeechToText;

यह घटक a . लौटाता है प्रतिक्रिया टुकड़ा जिसमें एक HTML है <``section``> तत्व जिसमें तीन div होते हैं:

  • .button-container इसमें दो बटन होते हैं जिनका उपयोग वाक् पहचान को शुरू और बंद करने के लिए किया जाएगा।
  • .words है contentEditable और suppressContentEditableWarning इस तत्व को संपादन योग्य बनाने और प्रतिक्रिया से किसी भी चेतावनी को दबाने के लिए विशेषताएँ।
  • अन्य .button-container दो और बटन रखता है जिनका उपयोग क्रमशः भाषण को पीडीएफ में रीसेट करने और परिवर्तित करने के लिए किया जाएगा।

स्टाइलिंग पूरी तरह से एक और चीज है। मैं इसमें यहां नहीं जाऊंगा, लेकिन कुछ शैलियों का उपयोग करने के लिए आपका स्वागत है जिन्हें मैंने या तो आपके लिए एक शुरुआती बिंदु के रूप में लिखा था styles/global.css फ़ाइल.

पूरा सीएसएस देखें
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}

.home {
  background-color: #333;
  min-height: 100%;
  padding: 0 1rem;
  padding-bottom: 3rem;
}

h1 {
  width: 100%;
  max-width: 400px;
  margin: auto;
  padding: 2rem 0;
  text-align: center;
  text-transform: capitalize;
  color: white;
  font-size: 1rem;
}

.button-container {
  text-align: center;
  display: flex;
  justify-content: center;
  gap: 3rem;
}

button {
  color: white;
  background-color: var(--bgColor);
  font-size: 1.2rem;
  padding: 0.5rem 1.5rem;
  border: none;
  border-radius: 20px;
  cursor: pointer;
}

button:hover {
  opacity: 0.9;
}

button:active {
  transform: scale(0.99);
}

.words {
  max-width: 700px;
  margin: 50px auto;
  height: 50vh;
  border-radius: 5px;
  padding: 1rem 2rem 1rem 5rem;
  background-image: -webkit-gradient(
    linear,
    0 0,
    0 100%,
    from(#d9eaf3),
    color-stop(4%, #fff)
  ) 0 4px;
  background-size: 100% 3rem;
  background-attachment: scroll;
  position: relative;
  line-height: 3rem;
  overflow-y: auto;
}

.success,
.error {
  background-color: #fff;
  margin: 1rem auto;
  padding: 0.5rem 1rem;
  border-radius: 5px;
  width: max-content;
  text-align: center;
  display: block;
}

.success {
  color: green;
}

.error {
  color: red;
}

बटनों के पृष्ठभूमि रंग को नियंत्रित करने के लिए वहां सीएसएस चर का उपयोग किया जा रहा है।

आइए देखते हैं ताजा बदलाव! दौड़ना npm run dev टर्मिनल में और उन्हें जांचें।

जब आप जाएँ तो आपको इसे ब्राउज़र में देखना चाहिए http://localhost:3000:

नेक्स्टजेएस और एक्सप्रेसजेएस के साथ भाषण को पीडीएफ में कनवर्ट करना

पाठ रूपांतरण के लिए हमारा पहला भाषण!

आवश्यक निर्भरता को हमारे में आयात करने के लिए पहली कार्रवाई है <SpeechToText> घटक:

import React, { useRef, useState } from "react";
import SpeechRecognition, {
  useSpeechRecognition,
} from "react-speech-recognition";
import axios from "axios";

फिर हम जाँचते हैं कि वाक् पहचान ब्राउज़र द्वारा समर्थित है या नहीं और यदि समर्थित नहीं है तो एक नोटिस प्रस्तुत करें:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

if (!speechRecognitionSupported) {
  return <div>Your browser does not support speech recognition.</div>;
}

अगला, आइए निकालें transcript और resetTranscript से useSpeechRecognition() हुक:

const { transcript, resetTranscript } = useSpeechRecognition();

यह हमें उस राज्य के लिए चाहिए जो संभालता है listening:

const [listening, setListening] = useState(false);

हमें भी चाहिए ref के लिए div साथ contentEditable विशेषता, तो हमें जोड़ने की जरूरत है ref इसके लिए विशेषता और पास transcript as children:

const textBodyRef = useRef(null);

…तथा:

<div
  className="words"
  contentEditable
  ref={textBodyRef}
  suppressContentEditableWarning={true}
  >
  {transcript}
</div>

आखिरी चीज जो हमें यहां चाहिए वह है एक ऐसा फ़ंक्शन जो वाक् पहचान को ट्रिगर करता है और उस फ़ंक्शन को इसके साथ जोड़ता है onClick हमारे बटन की घटना श्रोता। बटन सुनने के लिए सेट करता है true और इसे लगातार चलाता है। अतिरिक्त ईवेंट को बंद करने से रोकने के लिए हम बटन को उस स्थिति में होने पर अक्षम कर देंगे।

const startListening = () => {
  setListening(true);
  SpeechRecognition.startListening({
    continuous: true,
  });
};

…तथा:

<button
  type="button"
  onClick={startListening}
  style={{ "--bgColor": "blue" }}
  disabled={listening}
>
  Start
</button>

बटन पर क्लिक करने से अब ट्रांसक्रिप्शन शुरू हो जाना चाहिए।

अधिक कार्य

ठीक है, तो हमारे पास एक घटक है जो कर सकता है प्रारंभ सुनना। लेकिन अब हमें कुछ अन्य काम भी करने होंगे, जैसे stopListening, resetText और handleConversion. आइए उन कार्यों को करें।

const stopListening = () => {
  setListening(false);
  SpeechRecognition.stopListening();
};

const resetText = () => {
  stopListening();
  resetTranscript();
  textBodyRef.current.innerText = "";
};

const handleConversion = async () => {}

प्रत्येक फ़ंक्शन को एक में जोड़ा जाएगा onClick उपयुक्त बटन पर ईवेंट श्रोता:

<button
  type="button"
  onClick={stopListening}
  style={{ "--bgColor": "orange" }}
  disabled={listening === false}
>
  Stop
</button>

<div className="button-container">
  <button
    type="button"
    onClick={resetText}
    style={{ "--bgColor": "red" }}
  >
    Reset
  </button>
  <button
    type="button"
    style={{ "--bgColor": "green" }}
    onClick={handleConversion}
  >
    Convert to pdf
  </button>
</div>

RSI handleConversion फ़ंक्शन अतुल्यकालिक है क्योंकि हम अंततः एक एपीआई अनुरोध करेंगे। "रोकें" बटन में अक्षम विशेषता है जिसे सुनने के गलत होने पर ट्रिगर किया जाएगा।

यदि हम सर्वर को पुनरारंभ करते हैं और ब्राउज़र को रीफ़्रेश करते हैं, तो अब हम ब्राउज़र में अपने भाषण प्रतिलेखन को प्रारंभ, रोक और रीसेट कर सकते हैं।

अब हमें जो चाहिए वह है ऐप के लिए टाइप करना उस भाषण को एक पीडीएफ फाइल में परिवर्तित करके पहचाना। उसके लिए, हमें Express.js से सर्वर-साइड पथ की आवश्यकता है।

एपीआई मार्ग की स्थापना

इस रूट का उद्देश्य एक टेक्स्ट फाइल लेना, उसे एक पीडीएफ में बदलना है, उस पीडीएफ को हमारे फाइल सिस्टम में लिखना है, फिर क्लाइंट को प्रतिक्रिया भेजना है।

सेटअप करने के लिए, हम खोलेंगे server/index.js फ़ाइल और आयात करें html-pdf-node और fs निर्भरताएँ जिनका उपयोग हमारे फाइल सिस्टम को लिखने और खोलने के लिए किया जाएगा।

const HTMLToPDF = require("html-pdf-node");
const fs = require("fs");
const cors = require("cors)

अगला, हम अपना मार्ग स्थापित करेंगे:

app.use(cors())
app.use(express.json())

app.post("/", (req, res) => {
  // etc.
})

फिर हम उपयोग करने के लिए आवश्यक अपने विकल्पों को परिभाषित करने के लिए आगे बढ़ते हैं html-pdf-node मार्ग के अंदर:

let options = { format: "A4" };
let file = {
  content: `<html><body><pre style='font-size: 1.2rem'>${req.body.text}</pre></body></html>`,
};

RSI options ऑब्जेक्ट पेपर आकार और शैली सेट करने के लिए एक मान स्वीकार करता है। कागज़ के आकार उन आकार इकाइयों की तुलना में बहुत अलग प्रणाली का पालन करते हैं जिनका हम आमतौर पर वेब पर उपयोग करते हैं। उदाहरण के लिए, A4 विशिष्ट अक्षर आकार है.

RSI file ऑब्जेक्ट या तो किसी सार्वजनिक वेबसाइट का URL या HTML मार्कअप स्वीकार करता है। अपना HTML पेज जेनरेट करने के लिए, हम इसका उपयोग करेंगे html, body, pre HTML टैग्स और से पाठ req.body.

आप अपनी पसंद का कोई भी स्टाइल लगा सकती हैं।

अगला, हम जोड़ देंगे a trycatch रास्ते में आने वाली किसी भी त्रुटि को संभालने के लिए:

try {

} catch(error){
  console.log(error);
  res.status(500).send(error);
}

अगला, हम उपयोग करेंगे generatePdf से html-pdf-node पुस्तकालय एक उत्पन्न करने के लिए pdfBuffer (कच्ची पीडीएफ फाइल) हमारी फाइल से और एक अद्वितीय बनाएं pdfName:

HTMLToPDF.generatePdf(file, options).then((pdfBuffer) => {
  // console.log("PDF Buffer:-", pdfBuffer);
  const pdfName = "./data/speech" + Date.now() + ".pdf";

  // Next code here
}

वहां से, हम फाइल सिस्टम मॉड्यूल का उपयोग लिखने, पढ़ने और (हाँ, अंत में!) क्लाइंट ऐप को प्रतिक्रिया भेजने के लिए करते हैं:

fs.writeFile(pdfName, pdfBuffer, function (writeError) {
  if (writeError) {
    return res
      .status(500)
      .json({ message: "Unable to write file. Try again." });
  }

  fs.readFile(pdfName, function (readError, readData) {
    if (!readError && readData) {
      // console.log({ readData });
      res.setHeader("Content-Type", "application/pdf");
      res.setHeader("Content-Disposition", "attachment");
      res.send(readData);
      return;
    }

    return res
      .status(500)
      .json({ message: "Unable to write file. Try again." });
  });
});

आइए इसे थोड़ा तोड़ दें:

  • RSI writeFile फाइलसिस्टम मॉड्यूल एक फ़ाइल नाम, डेटा और एक कॉलबैक फ़ंक्शन को स्वीकार करता है जो फ़ाइल में लिखने में कोई समस्या होने पर एक त्रुटि संदेश लौटा सकता है। यदि आप एक सीडीएन के साथ काम कर रहे हैं जो त्रुटि समापन बिंदु प्रदान करता है, तो आप इसके बजाय उनका उपयोग कर सकते हैं।
  • RSI readFile फाइलसिस्टम मॉड्यूल एक फ़ाइल नाम और एक कॉलबैक फ़ंक्शन को स्वीकार करता है जो एक पठन त्रुटि के साथ-साथ रीड डेटा को सक्षम या वापस करने में सक्षम है। एक बार जब हमारे पास कोई पठन त्रुटि नहीं होती है और पढ़ा गया डेटा मौजूद होता है, तो हम क्लाइंट को प्रतिक्रिया तैयार करेंगे और भेजेंगे। दोबारा, इसे आपके सीडीएन के एंडपॉइंट से बदला जा सकता है यदि आपके पास है।
  • RSI res.setHeader("Content-Type", "application/pdf"); ब्राउज़र को बताता है कि हम एक पीडीएफ फाइल भेज रहे हैं।
  • RSI res.setHeader("Content-Disposition", "attachment"); प्राप्त डेटा को डाउनलोड करने योग्य बनाने के लिए ब्राउज़र को बताता है।

चूंकि एपीआई मार्ग तैयार है, हम इसे अपने ऐप में उपयोग कर सकते हैं http://localhost:4000. हम इसे पूरा करने के लिए अपने आवेदन के ग्राहक भाग के लिए आगे बढ़ सकते हैं handleConversion समारोह.

रूपांतरण को संभालना

इससे पहले कि हम एक पर काम करना शुरू कर सकें handleConversion फ़ंक्शन, हमें एक ऐसी स्थिति बनाने की आवश्यकता है जो लोडिंग, त्रुटि, सफलता और अन्य संदेशों के लिए हमारे एपीआई अनुरोधों को संभालती है। हम रिएक्ट का उपयोग करने जा रहे हैं useState इसे स्थापित करने के लिए हुक:

const [response, setResponse] = useState({
  loading: false,
  message: "",
  error: false,
  success: false,
});

में handleConversion फ़ंक्शन, हम यह जांचेंगे कि हमारे कोड को चलाने से पहले वेब पेज कब लोड किया गया है और सुनिश्चित करें कि div साथ editable विशेषता खाली नहीं है:

if (typeof window !== "undefined") {
const userText = textBodyRef.current.innerText;
  // console.log(textBodyRef.current.innerText);

  if (!userText) {
    alert("Please speak or write some text.");
    return;
  }
}

हम अपने अंतिम API अनुरोध को a . में लपेटकर आगे बढ़ते हैं trycatch, उत्पन्न होने वाली किसी भी त्रुटि को संभालना, और प्रतिक्रिया स्थिति को अद्यतन करना:

try {

} catch(error){
  setResponse({
    ...response,
    loading: false,
    error: true,
    message:
      "An unexpected error occurred. Text not converted. Please try again",
    success: false,
  });
}

इसके बाद, हम प्रतिक्रिया स्थिति के लिए कुछ मान सेट करते हैं और इसके लिए कॉन्फ़िगरेशन भी सेट करते हैं axios और सर्वर से एक पोस्ट अनुरोध करें:

setResponse({
  ...response,
  loading: true,
  message: "",
  error: false,
  success: false,
});
const config = {
  headers: {
    "Content-Type": "application/json",
  },
  responseType: "blob",
};

const res = await axios.post(
  "http://localhost:4000",
  {
    text: textBodyRef.current.innerText,
  },
  config
);

एक बार जब हम एक सफल प्रतिक्रिया प्राप्त कर लेते हैं, तो हम उचित मूल्यों के साथ प्रतिक्रिया स्थिति निर्धारित करते हैं और ब्राउज़र को प्राप्त पीडीएफ डाउनलोड करने का निर्देश देते हैं:

setResponse({
  ...response,
  loading: false,
  error: false,
  message:
    "Conversion was successful. Your download will start soon...",
  success: true,
});

// convert the received data to a file
const url = window.URL.createObjectURL(new Blob([res.data]));
// create an anchor element
const link = document.createElement("a");
// set the href of the created anchor element
link.href = url;
// add the download attribute, give the downloaded file a name
link.setAttribute("download", "yourfile.pdf");
// add the created anchor tag to the DOM
document.body.appendChild(link);
// force a click on the link to start a simulated download
link.click();

और हम सामग्री के नीचे निम्नलिखित का उपयोग कर सकते हैं संपादन योग्य div संदेश प्रदर्शित करने के लिए:

<div>
  {response.success && <i className="success">{response.message}</i>}
  {response.error && <i className="error">{response.message}</i>}
</div>

अंतिम कोड

मैंने गिटहब पर सब कुछ पैक कर दिया है ताकि आप सर्वर और क्लाइंट दोनों के लिए पूर्ण स्रोत कोड देख सकें।

समय टिकट:

से अधिक सीएसएस ट्रिक्स