React Quill ile Sunucuya Resim Yükleme

Quill oldukça güçlü tarayıcılar için geliştirilmiş bir metin editörüdür. Kendi projelerimden birinde uygulama için resim ekleme ihtiyacım doğdu fakat bu tarz metin editörleri yüklenen resimleri 64'lük tabana çevirip sakladıkları için çok etkili olmuyorlar. Bunun çözümü için NextJS'e entegre şekilde bir yükleme sisteminin temel yapısını açıklayacağım.

NextJs entegre etmek için React Quill paketini kullanıyorum. Bunun için yarn add react-quill demek yeterli.

NextJS sunucu temelli bir render teknolojisi kullanıyor. Fakat quill çalışmak için tarayıcıya erişmek zorunda bunun için component'in dinamik olarak import edilmesi gerekli.

import dynamic from "next/dynamic";

const ReactQuill = dynamic(
  async () => {
    const { default: RQ } = await import("react-quill");
    return ({ forwardedRef, ...props }) => <RQ ref={forwardedRef} {...props} />;
  },
  {
    ssr: false,
  }
);
Dinamik ReactQuill import etmek

Burada eklediğimiz ssr: false componentin sadece tarayıcı üzerinde çalışması gerektiğini belirtiyor. forwardedRef component'in dom üzerinde olan yansımasına erişmek için kullanacağız. Normal şartlarda dynamic referansları iletmiyor.

Yapıyı test etmek için basit bir sayfa oluşturalım.

import { useMemo, useRef } from "react";


const Editor = () => {
    const editorRef = useRef(null);
    const modules = useMemo(() => ({
        toolbar: {
            container: [
                ["image"],
            ],

            handlers: {
                image: imageHandler,
            },
        },
    }));

    return (
        <ReactQuill 
            modules={modules}
            forwardedRef={editorRef}
        />
    )
}

export default Editor;
editor.js

Editörün DOM üzeirnde olan halinin referansını tutmak için useRef kullanıyoruz. Normalde javascript metodlarıyla veya id ile arayabiliriz fakat React bu arama sonucunda elde edeceğimiz yapının gerçek yapı olmayabileceğini belirtiyor. Fakat useRef bu konuda aldığımız referansın her zaman en güncel ve doğru eleman olacağının garantisini vermektedir.

Editörün alet satırını oluşturmak için modules yapısını oluşturuyoruz. useMemo burada farklı renderlar bu yapının sadece 1 kez oluşturulmasının sağlıyor. Bu sayede editör üzerinde tekrar tekrar render alma gibi bir hatanın önüne geçiliyor.

Burada şimdilik sadece ["image"] ekledik ve bu işlevin çalıştırması gereken methodun adını verdik. Kullanıcı ne zaman bu butonu kullansa bizim metodumuz çalışacak anlamına geliyor. Aynı zamanda benzer bir şekilde farklı formatlamalar ve benzeri geliştirmesi de gerçekleştirilebilir.

Bundan sonra eklediğim bütün kod blokları bir üstteki sayfada Editor metodunun içinde yer almalıdır. Farklı bir yerde saklandığı durumda referansı bu blokta bulunmalıdır.

...
    const imageHandler = (a) => {
        const input = document.createElement("input");
        input.setAttribute("type", "file");
        input.setAttribute("accept", "image/*");
        input.click();

        input.onchange = () => {
            const file = input.files[0];

            // file type is only image.
            if (/^image\//.test(file.type)) {
                saveToServer(file);
            } else {
                console.warn("You could only upload images.");
            }
        };
    };
  ...
editor.js

Kullanıcı her resim ekleme butonunu kullandığında biz gizli bir şekilde yeni bir dosya girişi oluşturup, sanki kullanıcı buna kullanmış gibi yapıyoruz. Bu sayede kullanıcı arayüzünde dosya seçebileceği bir pencere açılıyor.

Biz burada type ve accept parametreleri ile girdi tipini ve kabul gören dosyaları belirtiyoruz. Bu sayede sadece resim dosyaları seçilebilecek.

Kullanıcı bir dosyayı seçtikten sonra onchange olayı sonucunda bizim bu dosyaya erişimimiz oluyor. Biz bu dosyayı istersek DOM üzerinde bir yere yerleştirebilir istersek de uzakta olan bir sunucuya yönlendirebiliriz.

...
function saveToServer(file) {
    const fd = new FormData();
    fd.append("upload", file);

    const xhr = new XMLHttpRequest();
    xhr.open("POST", "/api/media", true);
    xhr.onload = () => {
        if (xhr.status === 201) {
            // this is callback data: url
            const url = JSON.parse(xhr.responseText).url;
            insertToEditor(url);
        }
    };
    xhr.send(fd);
}
...
editor.js

Burada yeni bir formData yapısı oluşturarak resmimize buna ekliyor ve XHR ile sunucuya gönderiyoruz. Burada fetch veya axios da kullanılabilir. Sunucudan bize gelen resim urlsini direkt editggodago

...
function insertToEditor(url) {
    editorRef.current.getEditor().insertEmbed(null, "image", url);
}
...
editor.js

Burada daha önce oluşturduğumuz editör referensı ile editöre erişip resmi ekleyebiliyoruz. Bu sayede resimler metnin içinde değil haricinde bulundurulabilir, düzenlenebilir veya optimize edilebilir.

Tabi NextJS'in bize sağladığı SSR dışındaki özellik API rotaları. Bu rotalar sayesinde kullanıcının yüklediği dosyaları üçüncü parti sunuculara yönlendirip linkini alabiliriz.

Ben dosyalari S3 Bucket'a göndermek istiyorum. Bu amaç için bir kaç ek pakete ihtiyacımız olacak.

    yarn add multer multer-s3 aws-sdk

Multer nodejs ortamında dosya yükleme işlemlerinde kullanmak için kullanılan bir aracı. AWS dosyaları S3 atmak için ihtiyacımız olan arayüzleri sağlıyor. Multer-s3 bu iki paket arasında köprü görevi görüyor.

export const config = {
  api: {
    bodyParser: false, 
  },
};

NextJS normalde gelen çağrıları JSON olarak parse ediyor. Fakat dosya yüklerken bir akış olarak erişilmesi gerekli bunu ayarlamak için yukarıdaki yapıyı eklememiz yeterli.

import * as multer from "multer";
import * as AWS from "aws-sdk";
import * as multerS3 from "multer-s3";

const AWS_S3_BUCKET_NAME = process.env.AWS_S3_BUCKET_NAME;
const s3 = new AWS.S3();

AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID_PER,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY_PER,
});

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: AWS_S3_BUCKET_NAME,
    acl: "public-read",
    key: function (request, file, cb) {
      cb(null, `${Date.now().toString()} - ${file.originalname}`);
    },
  }),
}).array("upload", 1);

export default handle(async (req, res) => {
  upload(req, res, function (error) {
    if (error) {
      return res.status(404);
    }
    res.status(201).json({ url: req.files[0].location });
  });
});

export const config = {
  api: {
    bodyParser: false, 
  },
};
/api/media.js

upload metodu gelen çağrıda olan dosyanın çıkartılması ve S3 sunucusuna atılmasından sorumlu. Multer ve multer-s3 kütüphaneleri burada işi tamamıyla hallediyorlar bize bir iş düşmüyor. Sadece gelen çağrının hatalı olup olmadığını kontrol etmek ve sonuca göre istemciye resmin URL'sini göndermek yeterli.


781 kelime

2021-04-19