Skip to main content

Implementasi Wysiwyg

di alurkerja versi 1 Wysiwyg sudah tidak build in lagi di alurkerja karena menjadi issue di aplikasi NextJS, sehingga di project perlu membuat komponen reusable sendiri contohnya:

// useEditor.ts
import { AuthContext } from "alurkerja-ui";
import axios from "axios";
import { useContext } from "react";

export const useEditor = () => {
const axiosInstance = useContext(AuthContext);

const uploadImageForEditor = async (file: File, withoutToken = true) => {
const url = "https://n8n.merapi.javan.id/webhook/e1cc9b23-6595-4bb5-bd28-da934b5d26b2";
const formData = new FormData();
formData.append("upload", file);

if (withoutToken) {
return axios
.post(url, formData, {
headers: { "Content-Type": "multipart/form-data" },
})
.then((res) => {
if (res.status === 200) {
const url = res.data.url;
return { data: { url } };
}
});
}

return axiosInstance
.post(url, formData, {
headers: { "Content-Type": "multipart/form-data" },
})
.then((res) => {
if (res.status === 200) {
const url = res.data.url;
return { data: { url } };
}
});
};

return { uploadImageForEditor };
};

Install library tambahan dengan mengeksekusi command berikut.

yarn add react-draft-wysiwyg draft-js draftjs-to-html html-to-draftjs moment

Dan menginstall types jika menggunakan typescript.

yarn add -D @types/react-draft-wysiwyg @types/draft-js @types/draftjs-to-html @types/html-to-draftjs

Setting vite.config dengan menambahi define.global = {}

export default defineConfig({
plugins: [react()],
define: {
global: {},
},
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./src/configs/vitestSetup.ts"],
reporters: ["default", "vitest-sonar-reporter"],
// @ts-ignore
sonarReporterOptions: { silent: true },
outputFile: "sonar-report.xml",
coverage: {
reporter: ["text", "lcov"],
},
},
resolve: {
alias: [{ find: "@", replacement: path.resolve(__dirname, "./src") }],
},
});
// Wysiwyg.tsx
import { useEffect, useState } from "react";
import { Editor } from "react-draft-wysiwyg";
import { AtomicBlockUtils, EditorState, convertToRaw, ContentState } from "draft-js";
import draftToHtml from "draftjs-to-html";
import htmlToDraft from "html-to-draftjs";

import { useEditor } from "@/hooks";
import moment from "moment";
import axios from "axios";

import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";

interface WysiwygProps {
onChange?: (value: string) => void;
defaultValue?: string;
}

export const Wysiwyg = ({ onChange, defaultValue }: WysiwygProps) => {
const { uploadImageForEditor } = useEditor();

const [editorState, setEditorState] = useState(EditorState.createEmpty());

const onEditorStateChange = (editorState: EditorState) => {
setEditorState(editorState);
};

useEffect(() => {
if (editorState.getCurrentContent().hasText()) {
const content = draftToHtml(convertToRaw(editorState.getCurrentContent()));
onChange?.(content);
}
}, [editorState]);

useEffect(() => {
if (defaultValue) {
const contentBlock = htmlToDraft(defaultValue);
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
const newEditorState = EditorState.createWithContent(contentState);

setEditorState(newEditorState);
}
}, [defaultValue]);

function getBase64(file: any, success: any) {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
success(reader.result);
};
}

const handlePastedFiles = (files: any) => {
const formData = new FormData();
formData.append(
"upload",
files[0],
"image" + moment().format("DDMMYYYHHmmSS") + "-" + Math.floor(Math.random() * 1000000000)
);

return axios
.post("https://n8n.merapi.javan.id/webhook/e1cc9b23-6595-4bb5-bd28-da934b5d26b2", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((data: any) => {
getBase64(files[0], (res: any) => {
insertImage(data.data.url, res);
});
})
.catch((res: any) => {});
};

const Media = (props: any) => {
const entity = props.contentState.getEntity(props.block.getEntityAt(0));
const { base64 } = entity.getData();
const type = entity.getType();

let media;
if (type === "IMAGE") {
media = <img src={base64} />;
}

return media;
};

function mediaBlockRenderer(block: any) {
if (block.getType() === "atomic") {
return {
component: Media,
editable: false,
};
}

return null;
}

const insertImage = (url: string, base64: string) => {
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity("IMAGE", "IMMUTABLE", {
src: url,
base64: base64,
});
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newEditorState = EditorState.set(editorState, { currentContent: contentStateWithEntity });
setEditorState(AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, url));
};

const handleEditorDrop = (e: any) => {
e.preventDefault();

if (e.dataTransfer.files.length > 0) {
const file = e.dataTransfer.files;

if (file[0].type.startsWith("image")) {
handlePastedFiles(file);
}
}
};

return (
<div onDrop={handleEditorDrop}>
<Editor
wrapperClassName="border"
editorState={editorState}
editorClassName="px-4 min-h-[100px]"
onEditorStateChange={onEditorStateChange}
// Ts nya aneh kalau dari versi react hooknya ada tapi dari ts nya ga ada
// @ts-ignore
handlePastedFiles={handlePastedFiles}
blockRendererFn={mediaBlockRenderer}
toolbar={{
image: {
uploadCallback: (file: File) => uploadImageForEditor(file, true),
alt: { present: true, mandatory: true },
},
}}
/>
</div>
);
};

code diatas berisikan fungsi Wysiwyg pada umumnya + fungsi untuk copy paste image ke editor / memasukkan gambar via toolbar

picture 0