보투게더 사용성 개선하기 (2) - 게시글, 댓글 작성 시 링크 첨부

보투게더·2023년 10월 24일
0
post-custom-banner

문제점

  1. 게시글, 댓글의 경우에 링크 넣기 버튼을 눌른 후 [[ ]] 사이에 사용자가 직접 괄호 사이에 링크를 넣고 저장 버튼을 눌러야 했습니다. 그러나 게시글이나 댓글 기능을 사용하면서 링크 첨부를 하는 방식이 불편하다고 느꼈습니다.

해결 방법

  1. 본문 링크 삽입 시 [[ ]] 을 붙혀주고 있는데, 이걸 작성자에게 붙히도록 하지 않고 렌더링 시 http | https | www 로 시작하는 문자를 정규표현식을 이용하여 [[ 주소 ]]를 붙혀주도록 함

  2. 이 때 기존의 사용되던 [[ 링크 ]] 텍스트가 있다면 변환되지 않도록 하였음

구현 방법

  1. 정규식을 이용하여 https로 시작하는 문자가 있는지 확인하여 변환합니다.
  2. https가 변환된 문자 중 www로 시작하는 문자가 있는지 확인하여 변환합니다.
  3. 이 때 [[ ]]로 감싸져 있는 문자는 변환하지 않습니다.

convertTextToUrl.ts

/**
 * https://abc.co.kr/@abc/4
 * https://votogether.com/
 * http://localhost:3000/posts/100035
 * http://votogether.com/
 * (?<!\[\[) 는 앞에 [[로 시작하는 지 여부를 확인한다
 * https?:\/\/는 http:// 혹은 https:// 로 시작하는 지 여부를 확인한다.
 * (?!\]\]) 는 뒤에 ]]로 끝나는 지 여부를 확인한다.
 * [^\s] 는 공백이 아닌 문자인지 여부를 확인한다.
 */
const httpsOrHttpRegex = /(?<!\[\[)(https?:\/\/[^\s]+)(?!\]\])/g;

/**
 * www.naver.com
 * www.tistory.com
 * (?<!\[\[) 는 앞에 [[로 시작하는 지 여부를 확인한다
 * (?<!\/)는 앞에 /로 시작하는 지 여부를 확인한다. https://www 에서 www 앞에 /가 있기에 중복되어 확인하는 것을 방지하기 위함
 * \b(w{3})\b 는 www로 시작하는 지 여부를 정확히 확인한다. w가 4개인 경우 판별하지 않음
 * [^\s] 는 공백이 아닌 문자인지 여부를 확인한다.
 * (?!\]\]) 는 뒤에 ]]로 끝나는 지 여부를 확인한다.
 */
const wwwRegex = /(?<!\[\[)(?<!\/)\b(w{3})\b[^\s]+(?!\]\])/g;

export const convertTextToUrl = (text: string) => {
  const httpOrHttpsConvertedText = text.replace(httpsOrHttpRegex, url => `[[${url}]]`);
  const wwwConvertedText = httpOrHttpsConvertedText.replace(wwwRegex, url => `[[${url}]]`);

  return wwwConvertedText;
};

convertTextToUrl.test.ts

import { convertTextToUrl } from '@utils/post/convertTextToUrl';

test.each([
  ['www.naver.com 이걸 어째', '[[www.naver.com]] 이걸 어째'],
  [
    '반갑다 https://github.com/woowacourse-teams/2023-votogether/issues/703    임',
    '반갑다 [[https://github.com/woowacourse-teams/2023-votogether/issues/703]]    임',
  ],
  ['안녕 wwwww.naver.com', '안녕 wwwww.naver.com'],
  ['http://localhost:3000/ 피카츄', '[[http://localhost:3000/]] 피카츄'],
  [
    'http://localhost:3000/http://localhost:3000/ 피카츄',
    '[[http://localhost:3000/http://localhost:3000/]] 피카츄',
  ],
  ['www.naver.com', '[[www.naver.com]]'],
  ['[[www.naver.com]] www.naver.com', '[[www.naver.com]] [[www.naver.com]]'],
  [
    '[[http://localhost:3000/]] http://localhost:3000/',
    '[[http://localhost:3000/]] [[http://localhost:3000/]]',
  ],
  [
    '[[https://votogether.com/ranking]] https://www.naver.com/',
    '[[https://votogether.com/ranking]] [[https://www.naver.com/]]',
  ],
  [
    'www.naver.com www.naver.com www.naver.com https://www.npmjs.com/package/dotenv-webpack',
    '[[www.naver.com]] [[www.naver.com]] [[www.naver.com]] [[https://www.npmjs.com/package/dotenv-webpack]]',
  ],
])(
  'convertTextToUrl 함수에서 링크가 포함된 문자를 입력했을 때 문자에서 링크는 [[]]로 감싸서 반환한다.',
  (word, expectedWord) => {
    const result = convertTextToUrl(word);

    expect(result).toBe(expectedWord);
  }
);

사용 예시

  1. [[ ]] 로 감싼 문자가 있는 지 정규식으로 확인하여 split 해줍니다.
  2. [[ ]] 를 기준으로 split하게 되면 괄호는 사라지고 링크 내용만 남게되고, index % 2 === 1이 링크 내용이 됩니다.
  3. 링크 내용이라면 a 태그로 반환하고, 아니라면 span으로 반환합니다.

convertTextToElement.tsx

import { MouseEvent } from 'react';

import { convertTextToUrl } from './convertTextToUrl';

export const convertTextToElement = (text: string) => {
  const convertedUrlText = convertTextToUrl(text);
  const linkPattern = /\[\[([^[\]]+)\]\]/g;

  const parts = convertedUrlText.split(linkPattern);

  const elementList = parts.map((part, index) => {
    if (index % 2 === 1) {
      // 링크
      const linkText = part;
      const linkUrl = linkText.startsWith('http' || 'https') ? linkText : `https://${linkText}`;
      return (
        <a
          onClick={(event: MouseEvent<HTMLAnchorElement>) => {
            event.stopPropagation();
          }}
          key={index}
          href={linkUrl}
          target="_blank"
          style={{ textDecoration: 'underline', color: '#004EC5' }}
          rel="noreferrer noopener"
        >
          {linkText}
        </a>
      );
    }

    // 링크가 아닌 문자열
    return <span key={index}>{part}</span>;
  });

  return elementList;
};

본문을 변환 해줘서 사용

  <S.Content>
   {convertTextToElement(content)}
  </S.Content>

링크 첨부하는 영상

댓글 링크 첨부

🚨 safari 에러 발생 🚨

? <와 같은 문법을 safari에서는 최근에서야 지원을 하기 시작해서 업데이트를 하지 않은 safari에서는 사이트가 보이지 않게 됩니다.

https://caniuse.com/js-regexp-lookbehind

https://se9round.dev/post/safari-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D-Invalid-regular-expression-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC

profile
Fun from Choice! 오늘도 즐거운 한 표
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 11월 2일

'사용자에게 책임을 전가하지 않는다' 라는 철학이 느껴지네요!
마지막에 사파리까지 호환성 체크하신 점이 정말 꼼꼼하구요.
에디터 직접 만들어서 배포해봐도 재밌겠는걸요?😆

-제로-

답글 달기