[Javascript] 웹 사이트나 블로그등에 소스 코드 보기(highlightjs.min.js)


안녕하세요. 명월입니다.


이 글은 Javascript를 이용해서 웹 사이트나 블로그등에 소스 코드 보기(highlightjs.min.js)에 대한 글입니다.


우리가 프로그램 관련된 블로그를 작성하거나(저처럼..) 웹 사이트를 작성한다고 한다고 하면 프로그램 소스 보기가 필요합니다. 이 소스 보기는 단순히 메모장처럼 소스를 라인별로 보이는 것이 중요한 것이 아니라 IDE 툴처럼 함수나 클래스 등에 색깔을 달리해서 가독성을 높이는 게 중요하겠네요.

이런 가독성을 높이기 위한 라이브러리를 보통 소스 코드 하이라이트(highlight) 라이브러리라고 이야기하는데 종류가 굉장히 많이 있습니다.

저는 그 중에서 highlightjs를 사용하고 있습니다.

링크 - https://highlightjs.org/


일단 사이트에 가면 정말 친절하게 사용법을 자세하게 소개하고 있으나 여기서는 간단하게 구현하는 방법과 결과를 확인하겠습니다.

<!DOCTYPE html>
<html>
<!-- 헤더 라인 -->
<head>
  <!-- 타이틀 -->
  <title>Source highlight test</title>
  <!-- 하이라이트 rainbow 스타일 라이브러리 -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/styles/rainbow.min.css">
</head>
<!-- 바디 라인 -->
<body>
  <!-- 소스 표시 영역 -->
  <pre>
    <!-- code 태그에 class에 표시할 언어를 설정한다. -->
    <!-- code 태그 안에 소스 코드를 입력합니다만, 스페이스와 탭 영역을 주의해서 입력한다. -->
    <code class="javascript">function test() {
  alert('hello world');
}</code>
  </pre>
  <!-- 하이라이트 라이브러리 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js"></script>
  <!-- 하이라이트 라이브러리 실행 -->
  <script>hljs.initHighlightingOnLoad();</script>
</body>
</html>

먼저 저는 로컬에서 테스트를 했기 때문에 CDN에 프로토콜(https)를 붙였습니다.

저는 rainbow 스타일을 썻습니다. rainbow 스타일은 기본적으로 어두운 색 계열에 문자가 밝게 표시하는 스타일입니다. 각 스타일은 홈페이지에서 확인이 가능합니다.

홈페이지에서 language와 style부분을 클릭하면 변경되는데 이런저런 스타일을 확인할 수 있습니다.


작성이 되었으면 확인해 보겠습니다.

일단 어두운 색에서 키워드나 함수 색이 확실히 구분되었습니다.

그러나 지금 제가 사용하는 블로그와는 차이가 있네요.. 먼저 줄(라인) 넘버가 표시되지 않네요.


이 라인 넘버는 이 홈페이지에서 제공하지 않고 다른 분이 많든 것 같습니다.

링크 - https://github.com/wcoder/highlightjs-line-numbers.js/

라인 넘버를 추가하는 방법은 라이브러리를 추가하고 hljs.initLineNumbersOnLoad()함수를 호출하는 것으로 실행이 됩니다.

<!DOCTYPE html>
<html>
<!-- 헤더 라인 -->
<head>
  <!-- 타이틀 -->
  <title>Source highlight test</title>
  <!-- 하이라이트 rainbow 스타일 라이브러리 -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/styles/rainbow.min.css">
  <!-- 하이라이트 줄 넘버 스타일-->
  <style>
    /* 숫자 스타일 */
    .hljs-ln-numbers {
      -webkit-touch-callout: none;
      -webkit-user-select: none;
      -khtml-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      /* 가운데 정렬 */
      text-align: center;
      /* 글자 색 설정 */
      color: #ccc;
      /* 오른쪽 선 설정 */
      border-right: 1px solid #CCC;
      /* 세로 정렬 */
      vertical-align: top;
      /* 오른쪽 여백 */
      padding-right: 10px !important;
      /* 글자 사이즈 */
      font-size: 10pt;
      /* 라인 높이 */
      line-height: 15px;
    }
    /* 코드 스타일 */
    .hljs-ln-code {
      /* 왼쪽 여백 */
      padding-left: 10px !important;
      /* 글자 사이즈 */
      font-size: 10pt;
      /* 공백 설정 */
      white-space: pre-wrap;
      /* 라인 높이 */
      line-height: 15px;
    }
  </style>
</head>
<!-- 바디 라인 -->
<body>
  <!-- 소스 표시 영역 -->
  <pre>
    <!-- code 태그에 class에 표시할 언어를 설정한다. -->
    <!-- code 태그 안에 소스 코드를 입력합니다만, 스페이스와 탭 영역을 주의해서 입력한다. -->
    <code class="javascript">function test() {
  alert('hello world');
}</code>
  </pre>
  <!-- 하이라이트 라이브러리 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js"></script>
  <!-- 줄(라인) 넘버 라이브러리 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>
  <!-- 하이라이트 라이브러리 실행 -->
  <script>hljs.initHighlightingOnLoad();</script>
  <!-- 줄(라인) 넘버 라이브러리 실행 -->
  <script>hljs.initLineNumbersOnLoad();</script>
</body>
</html>

소스 하이라이트에 줄 넘버까지 표시가 되었습니다.


그런데 제 블로그에 있는 화면과는 또 약간의 차이가 있습니다. 나머지는 제가 작성한 부분입니다.

테두리를 만들고 메테리얼 스타일을 적용하고 collapse 기능(접기, 펼치기 기능) 추가하고 소스를 복사할 수 있는 기능을 추가했습니다.

<!DOCTYPE html>
<html>
<!-- 헤더 라인 -->
<head>
  <!-- 타이틀 -->
  <title>Source highlight test</title>
  <!-- 하이라이트 rainbow 스타일 라이브러리 -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/styles/rainbow.min.css">
  <!-- 메시지 알람 라이브러리 -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css">
  <!-- 아이콘 라이브러리 -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
  <!-- 하이라이트 줄 넘버 스타일-->
  <style>
    /* 숫자 스타일 */
    .hljs-ln-numbers {
      -webkit-touch-callout: none;
      -webkit-user-select: none;
      -khtml-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      /* 가운데 정렬 */
      text-align: center;
      /* 글자 색 설정 */
      color: #ccc;
      /* 오른쪽 선 설정 */
      border-right: 1px solid #CCC;
      /* 세로 정렬 */
      vertical-align: top;
      /* 오른쪽 여백 */
      padding-right: 10px !important;
      /* 글자 사이즈 */
      font-size: 10pt;
      /* 라인 높이 */
      line-height: 15px;
    }
    /* 코드 스타일 */
    .hljs-ln-code {
      /* 왼쪽 여백 */
      padding-left: 10px !important;
      /* 글자 사이즈 */
      font-size: 10pt;
      /* 공백 설정 */
      white-space: pre-wrap;
      /* 라인 높이 */
      line-height: 15px;
    }
  </style>
  <style>
    /* pre 태그 스타일*/
    pre {
      /* 화면*/
      display: block;
      /* 글자 크기 */
      font-size: 13px;
      /* 라인*/
      line-height: 1.42857143;
      /* 글자 크기 */
      color: #333;
      /* 글자가 테이블을 넘어서면 자르기 */
      word-break: break-all;
      word-wrap: break-word;
      /* 테두리 색깔 설정 */
      background-color: #f5f5f5;
      /* 테두리 색깔 설정 */
      border: 1px solid #ccc;
      /* 선 모서리 스타일 */
      border-radius: 4px;
      /* 여백 */
      margin: 10px 0px;
      /* 여백 */
      padding: 0px;
      /* 메테리얼 스타일 (그림자 설정) */
      box-shadow: 1px 2px 4px;
    }
    /* 타이틀 스타일 */
    .code-title {
      /* 배경색 설정 */
      background-color: #e9e9e9;
      /* 왼쪽 오른쪽 여백*/
      padding-left: 10px;
      padding-top: 5px;
      /* 글자 색 */
      color:black;
      /* 글자 두께*/
      font-weight: 800;
    }
    /* 접기 했을 때 스타일 */
    pre.code-view-disabled code{
      /* 화면에서 없애기 */
      display: none;
      height: 0px;
    }
    /* copy 버튼 스타일*/
    .code-copy {
      /* 오른쪽 정렬 */
      float: right;
      /* 상대적 위치 설정 */
      position: relative;
      /* 오른쪾에서의 위치 */
      right: 10px;
      /* 위쪽 여백 설정 */
      padding-top: 2px;
      /* 글자 색깔 */
      color: #337ab7;
      /* 텍스트 밑줄 없애기 */
      text-decoration: none;
      /* 배경 색깔*/
      background-color: transparent;
    }
  </style>
</head>
<!-- 바디 라인 -->
<body>
  <!-- 소스 표시 영역 -->
  <!-- code 태그에 class에 표시할 언어를 설정한다. -->
  <!-- code 태그 안에 소스 코드를 입력합니다만, 스페이스와 탭 영역을 주의해서 입력한다. -->
  <!-- data-type은 타이틀 제목이다. -->
  <pre><code class="javascript" data-type="test">function test() {
  alert('hello world');
}</code></pre>
  <!-- Jquery 추가 -->
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <!-- 메시지 라이브러리 추가 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
  <!-- 하이라이트 라이브러리 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js"></script>
  <!-- 줄(라인) 넘버 라이브러리 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>
  <!-- 하이라이트 라이브러리 실행 -->
  <script>hljs.initHighlightingOnLoad();</script>
  <!-- 줄(라인) 넘버 라이브러리 실행 -->
  <script>hljs.initLineNumbersOnLoad();</script>
  <script>
    /* 실행 함수 */
    (function (obj) {
      /* 페이지 로드 완료되면 실행*/
      $(obj.onLoad);
    })({
      onLoad: function () {
        /* 하이라이트 코드 스타일 설정 */
        $("pre code.hljs").each(function () {
          /* copy 버튼 만들기 */
          $(this).before($("<a href='javascript:void(0);' class='code-copy'><i class='fa fa-copy'></i>Copy!</a>"));
          /* 타이틀 만들기 */
          $(this).before($("<div class='code-title'></div>")
               .append($("<i class='fa fa-minus-square code-collapse' style='margin-right:10px;'></i>"))
               .append(" [소스 보기] " + $(this).data("type")));
          /* 클래스 추가 */
          $(this).parent().addClass("code-view");
        });
        /* 타이틀을 클릭 했을 때*/
        $(document).on("click", ".code-title", function () {
          $this = $(this);
          /* collapse 효과 만들기 */
          $i = $this.find("i.code-collapse");
          /* + 버튼일때*/
          if ($i.hasClass("fa-plus-square")) {
            /* 버튼을 -로 교체 */
            $i.removeClass("fa-plus-square");
            $i.addClass("fa-minus-square");
            /* 코드 클래스를 제거함으로 표시한다. */
            $this.parent().removeClass("code-view-disabled");
          } else {
            /* 버튼을 +로 교체*/
            $i.removeClass("fa-minus-square");
            $i.addClass("fa-plus-square");
            /* 코드 클래스를 추가함으로 화면에서 없앤다. */
            $this.parent().addClass("code-view-disabled");
          }
        });
        /* copy 버튼을 클릭했을 경우 */
        $(document).on("click",'.code-copy', function(){
          /* escape 문자 치환하기 */
          function escapeHTML(str) {
            return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'");
          }
          /* code-view 클래스를 찾는다. */
          $parent = $(this).closest(".code-view");
          /* collapse로 코드 화면이 닫겨져 있을 때*/
          if($parent.hasClass("code-view-disabled")){
            /* 펼친다. */
            $i = $parent.find("i.code-collapse");
            /* -에서 + 버튼으로 교체*/
            $i.removeClass("fa-plus-square");
            $i.addClass("fa-minus-square");
            /* 화면 표시*/
            $parent.removeClass("code-view-disabled");
          }
          /* 메시지 표시*/
          toastr.success(null,"소스가 복사되었습니다.", {timeOut: 700});
          /* code 태그를 찾는다. */
          var code_element = $(this).closest("pre").find("code")[0];
          /* 연속 개행일 경우 수정한다. */
          var value = code_element.innerText.replace(/\n\n/ig, '\n').replace(/\n\n\n/ig, '').replace('  \n','').replace(/\t/ig, '');
          /* 셀렉션 취득 */
          var selection = window.getSelection();
          /* body 태그 찾는다. */
          var body_element = document.getElementsByTagName('body')[0];
          /* div 태그 생성*/
          var newdiv = document.createElement('div');
          /* 절대 위치로 브라우져에 보이지 않는 곳에 생성*/
          newdiv.style.position = 'absolute';
          newdiv.style.left = '-10000px';
          newdiv.style.top = '-10000px';
          /* body 태그에 div 태그 추가 */
          body_element.appendChild(newdiv);
          /* 복사해 온 값을 표시한다. */
          newdiv.innerHTML = "<pre>" + escapeHTML(value) + "</pre>";
          /* 선택한다.*/
          selection.selectAllChildren(newdiv);
          /* 10초 후에 div 태그 삭제한다.*/
          setTimeout(function(){
            newdiv.remove();
          },10000);
          /* 복사한다.*/
          document.execCommand('copy');
        });
      }
    });
  </script>
</body>
</html>

제 블로그에 있는 오브젝트와 똑같이 만들어 졌습니다.


접기, 펼치기 테스트를 해보겠습니다.

정상적으로 작동되네요.


이번에는 copy 버튼을 테스트를 해보겠습니다.

소스 복사되었다고 메시지가 표시됩니다.

소스 복사도 문제없이 돌아갑니다.


이 소스는 무단으로 가져다가 쓰셔도 되고 마구마구 수정하셔서 사용해도 괜찮습니다.

출처를 안남겨도 상관 없습니다만, 가능하면 남겨주세요.


여기까지 Javascript를 이용해서 웹 사이트나 블로그등에 소스 코드 보기(highlightjs.min.js)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.