비동기 게시글 자동 저장 기능 구현

jkky98·2025년 1월 27일
0

ProjectSpring

목록 보기
8/20

임시글, 공개글 구분

Post 엔티티의 writable필드로 하여금 임시글 공개글을 구분토록 하였다. 게시글 카드를 모아보는 개인 블로그 첫 페이지에서 게시글(태그 포함) 정보를 가져올때 writable이 true인 경우만 가져오도록 하였으며, writable이 false로 저장된 경우는 따로 임시글 탭에서 이를 확인할 수 있도록 하였다.

임시글을 클릭할 경우 기존의 수정 기능을 통해 이어작성 가능하도록 하였다.

자동저장 시나리오

  1. 새로운 작성시 자동저장 시나리오
  2. 임시글을 이어 작성하는 경우 자동저장 시나리오
  3. 공개글 수정에 대한 자동저장 시나리오

자동저장은 수정 템플릿에서, 첫 작성 템플릿에서 각각 수정 자동저장, 첫작성 자동저장으로 구분했다.

첫 작성 템플릿에서 자동저장 기능이 수행되면, writable=false로 하여금 게시글을 저장하고 곧바로 해당 게시글 수정 템플릿으로 리다이렉트 시켰다.

1번 시나리오에 대해 1번의 auto-save 이후 지속적인 auto-edit이 진행된다. 작성 이전까지 해당 글은 계속 임시글로 남게 된다.

2번 시나리오는 1번 시나리오에서 auto-save 과정이 빠지고 auto-edit이 계속 동작한다.

3번 시나리오에서는 자동저장이 수행되지 않는다.

auto-save 스크립트

document.addEventListener('DOMContentLoaded', () => {
        const autoSaveInterval = 60000; // 1분 (60,000ms)
        const editor = window.editor; // Toast UI Editor 인스턴스 가져오기

        function autoSave() {
            if (document.hidden) {
                console.log("브라우저가 비활성화 상태입니다. 자동 저장을 건너뜁니다.");
                return;
            }

            const content = encodeURIComponent(editor.getMarkdown()); // 에디터에서 작성 중인 내용
            const title = document.querySelector('input[name="title"]').value; // 제목 가져오기
            const tags = document.querySelector('#tags').value; // 태그 가져오기
            const tagsList = tags.split(','); // 쉼표를 기준으로 문자열 분리

            // 제목이 빈값인 경우 실행 중단
            if (!title) {
                console.warn("제목이 비어있어 자동 저장을 건너뜁니다.");
                return;
            }

            // Ajax 요청 생성
            const xhr = new XMLHttpRequest();
            xhr.open("POST", "/api/write/auto-save", true); // 비동기 POST 요청
            xhr.setRequestHeader("Content-Type", "application/json");

            // 요청 데이터 준비
            const data = JSON.stringify({
                title: title,
                content: content,
                tags: tagsList,
            });

            console.log(data);

            // 요청 전송
            xhr.send(data);

            // 요청 상태 확인
            xhr.onreadystatechange = function () {
                if (xhr.readyState === XMLHttpRequest.DONE) {
                    if (xhr.status === 200) {
                        console.log("자동 저장 성공:", xhr.responseText);

                        const response = JSON.parse(xhr.responseText);

                        // postUrl이 존재할 경우 리다이렉트
                        if (response.postUrl) {
                            window.location.href = `/edit/${response.postUrl}`;
                        } else {
                            console.error("postUrl이 응답에 없습니다.");
                        }
                    } else {
                        console.error("자동 저장 실패:", xhr.statusText);
                    }
                }
            };
        }

        // 1분마다 자동 저장 실행
        setInterval(autoSave, autoSaveInterval);
    });

auto-edit 스크립트

function autoEdit() {
            if (document.hidden) {
                console.log("브라우저가 비활성화 상태입니다. 자동 저장을 건너뜁니다.");
                return;
            }

            const content = encodeURIComponent(editor.getMarkdown()); // 에디터에서 작성 중인 내용
            const title = document.querySelector('input[name="title"]').value; // 제목 가져오기
            const tags = document.querySelector('#tags').value; // 태그 가져오기
            const tagsList = tags.split(','); // 쉼표를 기준으로 문자열 분리
            const postUrl = window.location.pathname.split('/').filter(Boolean).pop();

            console.log(postUrl);

            // 제목이 빈값인 경우 실행 중단
            if (!title) {
                console.warn("제목이 비어있어 자동 저장을 건너뜁니다.");
                return;
            }

            // Ajax 요청 생성
            const xhr = new XMLHttpRequest();
            xhr.open("POST", "/api/write/auto-edit", true); // 비동기 POST 요청
            xhr.setRequestHeader("Content-Type", "application/json");

            // 요청 데이터 준비
            const data = JSON.stringify({
                title: title,
                content: content,
                tags: tagsList,
                postUrl: postUrl
            });

            console.log(data);

            // 요청 전송
            xhr.send(data);

            // 요청 상태 확인
            xhr.onreadystatechange = function () {
                if (xhr.readyState === XMLHttpRequest.DONE) {
                    if (xhr.status === 200) {
                        console.log("자동 저장 성공:", xhr.responseText);

                        // JSON 데이터 파싱
                        const response = JSON.parse(xhr.responseText);

                        // postUrl이 존재할 경우 리다이렉트
                        if (response) {
                            console.log("자동 업데이트 성공")
                            new Toastify({
                                text: "🦄 게시글 자동 저장 성공!",
                                duration: 5000, // autoClose 5000ms
                                gravity: "top", // top-right 위치 설정
                                position: "right",
                                stopOnFocus: true, // pauseOnHover
                                close: true, // closeOnClick
                                style: {
                                    background: "linear-gradient(to right, #00b09b, #96c93d)", // 테마 스타일
                                    color: "#fff",
                                    borderRadius: "8px",
                                    boxShadow: "0 2px 6px rgba(0, 0, 0, 0.15)",
                                }
                            }).showToast();
                        } else {
                            console.error("postUrl이 응답에 없습니다.");
                        }

                    } else {
                        console.error("자동 저장 실패:", xhr.statusText);
                    }
                }
            };
        }

        // 1분마다 자동 저장 실행
        setInterval(autoEdit, autoSaveInterval);
    });

XMLHttpRequest를 사용하여 ajax 비동기 요청을 구현하였다.(정상적인 작동을 확인하기 위해 일단 log를 잔뜩 찍어놓았다.)

후에 리다이렉트 될 때 스크롤 위치를 동일하게 하여 이용자가 화면이 리다이렉트된 지 모르도록 할 계획이다.

제목이 비었을 경우 자동저장을 수행하지않는다. 또한 여러 작성 탭을 띄워뒀을 경우 활성화된 탭만 코드가 작동하도록 함수 초반에 이를 필터링해두었다.

자동저장 수행시 알림


Toastify를 활용하여 자동 저장 성공시 자동저장을 알려주는 기능을 껴넣었다.

profile
자바집사의 거북이 수련법

5개의 댓글

관련 채용 정보