MangBoard Markdown Support — 개발 문서 기록

2026.04.28

# MangBoard Markdown Support — 개발 문서


# # 개요


**MangBoard Markdown Support**는 워드프레스 망보드(MangBoard) 게시판에서 마크다운 문법을 사용하여 글을 작성할 수 있게 해주는 플러그인입니다.


| 항목 | 내용 |

|------|------|

| 버전 | 7.6.0 |

| 파일 | `mangboard-markdown.php` (611줄, 단일 파일) |

| 렌더링 | JavaScript 클라이언트 사이드 (PHP는 JS/CSS 출력만) |

| 마크다운 파서 | **자체 제작** (marked.js 등 외부 라이브러리 미사용) |

| 스타일 | wiki.js-inspired |

| 의존 | MangBoard WP, WordPress 5.0+, PHP 7.0+ |


## # JS 구성요소


```mermaid

flowchart LR

    subgraph 자체제작["자체 제작 (플러그인 내장)"]

        A["parseMarkdown()<br/>상태머신 기반 라인 파서"]

        B["convertTable()<br/>표 파서 (백틱 보호)"]

        C["renderCodeBlock()<br/>코드블록 HTML 생성"]

        D["renderMarkdownContainers()<br/>컨테이너 탐색 + 전처리"]

    end

    subgraph CDN["CDN 로드 (외부)"]

        E["Prism.js 1.29.0<br/>코드 구문강조 + 라인번호"]

        F["Mermaid.js 10.9.1<br/>다이어그램 SVG 렌더링"]

        G["D2Coding 1.3.2<br/>코드블록 폰트"]

    end

    A --> C

    A --> B

    D --> A

    C -->|"언어 지정 시"| E

    C -->|"lang=mermaid 시"| F

```


> **marked.js, markdown-it 등 외부 마크다운 파서를 사용하지 않는 이유**: 망보드 CKEditor가 마크다운 원문을 HTML(p/span/br)로 감싸서 저장하므로, 일반 파서로는 처리 불가. HTML→마크다운 전처리 + 상태머신 파서를 직접 구현해야 CKEditor 호환성 확보 가능.


---


# # 설치 위치


```

/var/www/html/wp-content/plugins/mangboard-markdown/

├── mangboard-markdown.php    # 메인 플러그인 (611줄)

└── Parsedown.php             # 미사용 — 초기에 PHP SSR 시도용으로 포함, 현재 dead code

```


---


# # 아키텍처


## # PHP 레이어 (서버)


```mermaid

flowchart TD

    A["plugins_loaded<br/>init()"] --> B{"URL에<br/>board_pid<br/>또는 vid?"}

    B -->|"NO"| Z["아무것도 안 함"]

    B -->|"YES"| C{"mode=write?"}

    C -->|"YES"| Z

    C -->|"NO"| D["wp_head → output_css()"]

    D --> E["wp_footer → output_js()"]

    E --> F["브라우저에서 JS 실행"]

```


| 훅 | 메서드 | 역할 |

|----|--------|------|

| `plugins_loaded` (prio 1) | `init()` | URL 파라미터 검증, 조건부 훅 등록 |

| `wp_head` | `output_css()` | wiki.js 스타일 CSS + CDN (D2Coding, Prism.js) |

| `wp_footer` | `output_js()` | 마크다운 파서 + Prism.js + Mermaid.js 로더 |


**PHP는 JS/CSS 문자열만 출력합니다.** 실제 마크다운 파싱은 전부 브라우저 JavaScript에서 수행합니다.


## # JS 레이어 (브라우저)


```mermaid

flowchart TD

    A["IIFE 즉시 실행"] --> B{"document.readyState"}

    B -->|"loading"| C["DOMContentLoaded 대기"]

    B -->|"complete/interactive"| D["renderMarkdownContainers()"]

    C --> D

    D --> E["querySelectorAll로<br/>컨테이너 탐색"]

    E --> F{"textContent에<br/>마크다운 마커<br/>1개 이상?"}

    F -->|"NO"| G["스킵"]

    F -->|"YES"| H["CKEditor HTML 전처리"]

    H --> I["parseMarkdown() 실행"]

    I --> J["innerHTML 교체"]

    J --> K["data-md-rendered=true 설정"]

    K --> L["setTimeout 200ms"]

    L --> M["Prism.js CDN 로드"]

    L --> N["Mermaid.js CDN 로드<br/>(mermaid 블록 있을 때만)"]

    L --> O["복사 버튼 추가"]

```


## # 대상 컨테이너


| 선택자 | 대상 | 설정 |

|--------|------|------|

| `td.content-box` | 망보드 기본 스킨 본문 | 자동 |

| `.mb-answer-content` | Q&A 답변 영역 | 자동 |

| `.mb-markdown-content` | 커스텀 페이지 | HTML에 클래스 수동 추가 |

| `.mc-product-content` | 상품 페이지 | 자동 (`mb-markdown-content`로 래핑) |


---


# # CKEditor HTML → 마크다운 변환 흐름


망보드는 CKEditor를 사용하므로, 게시글 본문이 p, span, br 태그로 감싸진 HTML로 DB에 저장됩니다. 파서는 이 HTML을 마크다운으로 인식할 수 있는 형태로 전처리합니다:


```mermaid

flowchart LR

    A["CKEditor HTML"] --> B["&lt;script&gt; 보호"]

    B --> C["&lt;p&gt; → 줄바꿈"]

    C --> D["&lt;span&gt; 제거"]

    D --> E["&lt;a&gt; 텍스트만 유지"]

    E --> F["엔티티 복원"]

    F --> G["parseMarkdown()"]

```


## # 전처리 상세


> renderMarkdownContainers() 내부에서 CKEditor HTML을 마크다운 파싱 가능한 형태로 변환:

>

> 1. pre 태그 내 script 태그 보호 (HTML 엔티티로 변환)

> 2. br 태그를 줄바꿈 문자로 변환

> 3. p 닫는 태그를 줄바꿈으로 변환, p 여는 태그 제거

> 4. span 태그 제거

> 5. a 태그에서 텍스트만 유지 (href 제거)

> 6. HTML 엔티티 복원 (nbsp, gt, lt, amp)


## # 마크다운 마커 감지


textContent에서 정규식으로 마커를 카운트하여 마크다운 여부를 판별:


> 정규식으로 헤딩(#), 코드블록(백틱3개), 인라인코드(백틱), 굵은텍스트(**), 링크([]()) 패턴을 검색하여 1개 이상 감지되면 마크다운으로 간주합니다.


---


# # 파서 내부 로직 (parseMarkdown)


## # 상태 머신


파서는 라인 단위로 상태를 관리합니다:


```mermaid

stateDiagram-v2

    [*] --> Normal

    Normal --> CodeBlock: 백틱3개 감지

    CodeBlock --> Normal: 백틱3개 닫기

    Normal --> Table: 파이프 문자 감지

    Table --> Normal: 비테이블 라인

    Normal --> List: * / - / 1. 감지

    List --> Normal: 비목록 라인

    Normal --> Blockquote: > 감지

    Blockquote --> Normal: 비인용 라인

```


| 상태 변수 | 타입 | 의미 |

|-----------|------|------|

| `inCodeBlock` | boolean | 코드 블록 내부 |

| `inTable` | boolean | 표 파싱 중 |

| `inList` | boolean | 목록 파싱 중 |

| `inBlockquote` | boolean | 인용문 파싱 중 |

| `codeContent[]` | string[] | 코드 블록 누적 라인 |

| `tableLines[]` | string[] | 표 누적 라인 |

| `listItems[]` | string[] | 목록 아이템 |


## # 라인 처리 우선순위


각 라인은 다음 순서로 매칭됩니다:


1. **미디어 태그 스킵** — img, iframe, video 등 (백틱 안의 텍스트는 제외)

2. **코드 블록** — 백틱3개 열기/닫기

3. **코드 블록 placeholder** — `___CODEBLOCK0___` (전처리 단계에서 보호된 블록)

4. **헤딩** — `#`, `##`, `###`, `####`

5. **수평선** — `---`, `***`

6. **인용문** — `>` 또는 `&gt;`

7. **표** — 파이프로 셀 구분

8. **순서 없는 목록** — `*` 또는 `-`

9. **순서 있는 목록** — `1.`, `2.`, ...

10. **일반 단락** — 나머지


> 각 매칭 시 기존 열린 구조(표/목록/인용문)를 자동으로 닫습니다.


## # 코드 블록 처리


```mermaid

flowchart TD

    A["백틱3개 감지"] --> B{"inCodeBlock?"}

    B -->|"false"| C["코드 블록 열기<br/>언어 추출"]

    B -->|"true"| D{"언어 미지정<br/>+ 첫 줄이 언어명?"}

    D -->|"YES"| E["첫 줄을 언어로 감지<br/>내용에서 제거"]

    D -->|"NO"| F["renderCodeBlock() 호출"]

    E --> F

    F --> G{"codeLang == mermaid?"}

    G -->|"YES"| H["div.mb-mermaid > pre.mermaid"]

    G -->|"NO"| I{"codeLang 지정됨?"}

    I -->|"YES"| J["div.mb-code-block<br/>언어 레이블 + 라인번호"]

    I -->|"NO"| K["div.mb-code-block.mb-code-plain<br/>다른 배경색"]

```


## # 표 파싱 (convertTable)


**백틱 내용 보호**: 표 셀을 파이프(|)로 분리하기 전에 백틱 안의 내용을 null character 기반 placeholder로 보호:


> 처리 과정: 인라인 코드를 정규식으로 찾아 null character 조합의 placeholder로 치환 → split으로 셀 분리 → placeholder를 원래 값으로 복원

>

> 예: A (인라인코드 B|C 인라인코드) D → A PLACEHOLDER D → split → 복원 → 셀이 잘리지 않음


> **null character를 사용하는 이유**: HTML 엔티티 등은 CKEditor/브라우저에서 충돌 가능. null character는 일반 텍스트에 등장하지 않으므로 안전.


---


# # Mermaid 렌더링


## # 로딩 흐름


```mermaid

sequenceDiagram

    participant P as parseMarkdown()

    participant D as DOM

    participant M as Mermaid CDN


    P->>D: div.mb-mermaid > pre.mermaid 삽입

    Note over D: 200ms setTimeout

    D->>D: .mb-mermaid .mermaid 유무 확인

    D->>M: mermaid.min.js 동적 로드

    M->>D: mermaid.initialize({startOnLoad:false})

    loop 각 mermaid 블록

        D->>M: mermaid.render(id, code)

        M-->>D: Promise → SVG

        D->>D: block.innerHTML = svg

    end

```


## # mermaid.render() vs mermaid.run()


v7.6.0에서 mermaid.run()에서 mermaid.render()로 전환:


| | mermaid.run (nodes) | mermaid.render (id, code) |

|---|---|---|

| gitgraph 감지 | ❌ No diagram type detected | ✅ 정상 |

| 에러 전파 | 하나 에러 → 후속 전부 중단 | 개별 try-catch |

| 반환값 | DOM 직접 조작 | Promise → svg |

| 에러 처리 | catch 불가 | .catch()로 개별 처리 |


## # 에러 핸들링


```javascript

mermaid.render(id, code)

    .then(function(result) { block.innerHTML = result.svg; })

    .catch(function(err) {

        block.innerHTML = '⚠️ Mermaid 렌더링 에러 — 문법을 확인하세요';

    });

```


한 다이어그램의 에러가 다른 다이어그램 렌더링에 영향을 주지 않습니다.


---


# # 주요 버그 수정 이력


## # 1. 백틱 내 파이프 문자 오인식


**현상**: 표 셀에 인라인 코드로 감싼 파이프 문자(|)가 표 구분자로 인식되어 셀이 잘림


**원인**: convertTable()에서 line.split('|') 시 백틱 내용 보호 없음


**해결**: split 전 \x00CODE placeholder로 백틱 내용 보호 후 복원


## # 2. mermaid.run() gitgraph 미감지


**현상**: gitgraph 다이어그램이 "No diagram type detected" 에러


**원인**: `mermaid.run({nodes: [block]})`은 DOM 노드에서 다이어그램 타입을 감지하는데, gitgraph 등 일부 타입이 누락됨


**해결**: `mermaid.render(id, code)` 방식으로 전환. 텍스트에서 다이어그램 타입을 감지하므로 모든 타입 지원


## # 3. mermaid 에러 전파로 전체 렌더링 중단


**현상**: 하나의 mermaid 에러가 11번째 다이어그램 이후 전부 렌더링 안 됨


**원인**: `mermaid.run({nodes: allBlocks})`이 에러 발생 시 후속 블록 처리 중단


**해결**: `forEach` + 개별 `try-catch` + `Promise.catch()` 로 각 블록 독립 렌더링


## # 4. 백틱 내 미디어 태그 오인식으로 표 강제 종료


**현상**: 표에서 미디어 보호 셀에 인라인 코드로 감싼 HTML 태그(img, iframe, video)가 포함된 행에서 표 파싱이 강제 종료되고, 이후 모든 섹션이 렌더링 누락


**원인**: 

1. CKEditor가 인라인 코드 안의 HTML 태그를 HTML 엔티티로 저장

2. 브라우저 innerHTML에서 이를 실제 HTML 태그로 디코딩

3. 파서의 미디어 태그 체크가 인라인 코드 안의 텍스트를 실제 미디어 태그로 오인

4. 진행 중인 표를 강제 종료 → 이후 전체 내용이 P 태그에 갇힘


**해결**: 미디어 태그 체크 전 인라인 코드를 제거한 lineNoCode에서 검사:


> 인라인 코드 패턴을 빈 문자열로 치환 후, 남은 텍스트에서만 img/iframe/video/audio/object/embed 태그를 검사합니다. 이렇게 하면 코드 안의 텍스트가 실제 미디어 태그로 오인되지 않습니다.


## # 5. 중첩 코드 블록 금지 (CKEditor 호환성)


**현상**: 4백틱 중첩 코드 블록이 CKEditor에서 P 태그로 분리되어 빈 렌더링 발생


**원인**: CKEditor가 백틱을 일반 텍스트로 처리하고, P 태그로 줄바꿈 → 파서가 닫는 백틱을 인식하지 못함


**해결**: 모든 예시를 3백틱 평면 구조로 통일, 중첩 코드블록 사용 금지


## # 6. Smart Editor 중첩 헤딩 정규화


**현상**: Smart Editor에서 `# # 텍스트` 패턴이 H2가 아닌 깨진 텍스트로 렌더링


**해결**: 헤딩 감지 시 `#` 개수를 카운트하여 병합:


```javascript

var normalizedLine = line.replace(/^(#+)(\s*#+)*\s*/, function(match) {

    var hashCount = (match.match(/#/g) || []).length;

    return '#'.repeat(Math.min(hashCount, 4)) + ' ';

});

```


---


# # CSS 아키텍처


## # Specificity 전략


망보드 기본 CSS가 `.mb-style1 .table-view > tbody th` (specificity 0,2,2 + `!important`)로 테이블 헤더를 강제 지정합니다. 이를 오버라이드하기 위해 **모든 CSS 규칙에 `body .mb-style1 td.content-box` prefix** (specificity 0,2,3)를 사용합니다:


```css

/* ❌ 망보드에 덮어씌워짐 */

td.content-box th { background-color: #E0E0E0 !important; }


/* ✅ specificity 0,2,3으로 오버라이드 성공 */

body .mb-style1 td.content-box th { background-color: #E0E0E0 !important; }

```


> `.mb-answer-content`은 `.table-view > tbody` 밖에 있으므로 이 문제가 없습니다.


## # 주요 스타일


| 요소 | 스타일 |

|------|--------|

| 코드 블록 | `#212121` 다크 배경, D2Coding 폰트, Prism Tomorrow Night |

| Plain 코드블록 | `#263238` 배경 + `#CFD8DC` 보더 (언어 미지정 시) |

| 인라인 코드 | `#E8EAF6` 배경, `#283593` 텍스트, Roboto Mono |

| 인용문 | `55px solid #607D8B` 좌측 보더 + 따옴표 SVG 아이콘 |

| H1-H4 | 하단 그라데이션 보더 (100% → 90% → 70%) |

| 표 | `#E0E0E0` 헤더, `#FAFAFA` 짝수행, `1px solid #BDBDBD` 보더 |

| Mermaid | 중앙 정렬, 투명 배경, `max-width: 100%` |


---


# # 외부 의존성 (CDN)


| 라이브러리 | 버전 | 로드 조건 |

|-----------|------|----------|

| D2Coding Font | 1.3.2 | 항상 (`wp_head`) |

| Prism.js Core | 1.29.0 | 항상 (200ms setTimeout) |

| Prism Tomorrow Night | 1.29.0 | 항상 |

| Prism Line Numbers | 1.29.0 | 항상 |

| Prism Autoloader | 1.29.0 | 항상 (언어 컴포넌트 온디맨드) |

| Mermaid.js | 10.9.1 | `.mb-mermaid .mermaid` 요소 있을 때만 |


---


# # 배포 방법


## # Docker 컨테이너


```bash

# 파일 전송

docker cp mangboard-markdown.php wellcoms_wp:/var/www/html/wp-content/plugins/mangboard-markdown/


# 권한 설정

docker exec -u root wellcoms_wp chown -R www-data:www-data /var/www/html/wp-content/plugins/mangboard-markdown/


# OPcache 클리어 + PHP-FPM 리로드

docker exec wellcoms_wp php -r 'opcache_reset();'

docker exec wellcoms_wp kill -USR2 $(docker exec wellcoms_wp pgrep -f 'php-fpm: master')

```


## # GitLab


```bash

git clone https://gitlab.wellcoms.co.kr/jskim/wp-mangboard-markdown.git

cp -r wp-mangboard-markdown /var/www/html/wp-content/plugins/

```


---


# # 트러블슈팅


## # 마크다운이 렌더링되지 않음


1. 플러그인 활성화 확인

2. URL에 `board_pid` 또는 `vid` 파라미터 확인

3. `mode=write`가 아닌지 확인 (편집 모드는 렌더링 스킵)

4. 브라우저 콘솔에서 JS 에러 확인


## # 표/다이어그램 이후 내용이 짤림


파서가 특정 행에서 구조(table/list/blockquote)를 잘못 종료했을 가능성:

- 인라인 코드 안의 img, video 등이 미디어 태그로 오인되었는지 확인

- 인라인 코드 안의 파이프 문자가 표 구분자로 오인되었는지 확인


## # Mermaid 다이어그램이 렌더링되지 않음


1. 언어 지정이 `mermaid`인지 확인 (소문자)

2. 브라우저 콘솔에서 에러 확인 (F12 > Console)

3. CDN 연결 상태 확인 (`cdn.jsdelivr.net` 접근 가능해야 함)

4. mindmap은 한글 특수문자 오류 시 flowchart로 대체

5. 코드블록 내부에 백틱 3개나 HTML 태그가 포함되면 파서가 코드블록 경계를 잘못 인식할 수 있음


## # OPcache로 인해 수정사항 반영 안 됨


```bash

docker exec wellcoms_wp php -r 'opcache_reset();'

```


PHP-FPM 프로세스를 재시작해야 할 수도 있음:


```bash

docker exec wellcoms_wp kill -USR2 $(docker exec wellcoms_wp pgrep -f 'php-fpm: master')

```


## # 트러블슈팅 순서도


```mermaid

flowchart TD

    Problem["렌더링 안 됨"] --> A{"플러그인 활성화?"}

    A -->|"NO"| FixA["WordPress 관리자 > 플러그인 > 활성화"]

    A -->|"YES"| B{"URL 파라미터 있음?<br/>board_pid 또는 vid"}

    B -->|"NO"| FixB["게시글 상세 페이지로 이동"]

    B -->|"YES"| C{"마크다운 문법<br/>올바른가?"}

    C -->|"NO"| FixC["# 뒤 공백, 백틱 줄바꿈 확인"]

    C -->|"YES"| FixD["브라우저 캐시 삭제<br/>Ctrl+Shift+R"]

```


---


# # 다른 서버에 설치


```bash

git clone https://gitlab.wellcoms.co.kr/jskim/wp-mangboard-markdown.git

cp -r wp-mangboard-markdown /var/www/html/wp-content/plugins/

chown -R www-data:www-data /var/www/html/wp-content/plugins/wp-mangboard-markdown

# WordPress 관리자에서 활성화 — 별도 설정 불필요

```


| 필수 조건 | 버전 |

|-----------|------|

| WordPress | 5.0+ |

| PHP | 7.0+ |

| 망보드(MangBoard) | 설치됨 |

| JavaScript | 활성화 |


---


# # 버전 이력


| 버전 | 날짜 | 변경 내용 |

|------|------|-----------|

| 7.6.0 | 2026-04-28 | Mermaid 다이어그램 지원, mermaid.run에서 mermaid.render로 전환, 개별 try-catch 에러 핸들링, 표 파서 백틱 내용 보호, 미디어 태그 감지 개선, mb-code-plain 클래스 추가 |

| 7.5.0 | 2026-04-23 | wiki.js 스타일 개편: Prism.js, 라인 번호, D2Coding, 인용문 보더, 테이블 CSS specificity 수정 |

| 7.4.6 | 2026-03-25 | Smart Editor 중첩 헤딩 정규화 |

| 7.4.3 | 2026-03-17 | 줄바꿈 분할 지원, 감지 임계값 낮춤, 즉시 실행 |

| 7.4.2 | 2026-03-17 | mode=write 시 렌더링 스킵 |

| 7.4.1 | 2026-02-27 | board_pid 파라미터 지원 추가 |

| 7.4.0 | 2026-02-14 | JavaScript 기반 렌더링으로 전환 |

| 5.4.0 | 2026-02-14 | 코드 블록 줄바꿈 처리 수정 |

| 5.0.0 | 2026-02-13 | 초기 안정 버전 |


---


# # 라이선스


- **mangboard-markdown.php**: MIT License (자체 개발)

- **Parsedown.php**: MIT License (https://github.com/erusev/parsedown) — 미사용, 향후 제거 예정


▣ 마크 다운(Markdown) 문서(Mermaid 포함) 지원합니다.
글보기
제목MangBoard Markdown Support — 개발 문서 기록2026-04-28 17:08
카테고리 기술노트
작성자 Level 10

# MangBoard Markdown Support — 개발 문서


## 개요


**MangBoard Markdown Support**는 워드프레스 망보드(MangBoard) 게시판에서 마크다운 문법을 사용하여 글을 작성할 수 있게 해주는 플러그인입니다.


| 항목 | 내용 |

|------|------|

| 버전 | 7.6.0 |

| 파일 | `mangboard-markdown.php` (611줄, 단일 파일) |

| 렌더링 | JavaScript 클라이언트 사이드 (PHP는 JS/CSS 출력만) |

| 마크다운 파서 | **자체 제작** (marked.js 등 외부 라이브러리 미사용) |

| 스타일 | wiki.js-inspired |

| 의존 | MangBoard WP, WordPress 5.0+, PHP 7.0+ |


### JS 구성요소


```mermaid

flowchart LR

    subgraph 자체제작["자체 제작 (플러그인 내장)"]

        A["parseMarkdown()<br/>상태머신 기반 라인 파서"]

        B["convertTable()<br/>표 파서 (백틱 보호)"]

        C["renderCodeBlock()<br/>코드블록 HTML 생성"]

        D["renderMarkdownContainers()<br/>컨테이너 탐색 + 전처리"]

    end

    subgraph CDN["CDN 로드 (외부)"]

        E["Prism.js 1.29.0<br/>코드 구문강조 + 라인번호"]

        F["Mermaid.js 10.9.1<br/>다이어그램 SVG 렌더링"]

        G["D2Coding 1.3.2<br/>코드블록 폰트"]

    end

    A --> C

    A --> B

    D --> A

    C -->|"언어 지정 시"| E

    C -->|"lang=mermaid 시"| F

```


> **marked.js, markdown-it 등 외부 마크다운 파서를 사용하지 않는 이유**: 망보드 CKEditor가 마크다운 원문을 HTML(p/span/br)로 감싸서 저장하므로, 일반 파서로는 처리 불가. HTML→마크다운 전처리 + 상태머신 파서를 직접 구현해야 CKEditor 호환성 확보 가능.


---


## 설치 위치


```

/var/www/html/wp-content/plugins/mangboard-markdown/

├── mangboard-markdown.php    # 메인 플러그인 (611줄)

└── Parsedown.php             # 미사용 — 초기에 PHP SSR 시도용으로 포함, 현재 dead code

```


---


## 아키텍처


### PHP 레이어 (서버)


```mermaid

flowchart TD

    A["plugins_loaded<br/>init()"] --> B{"URL에<br/>board_pid<br/>또는 vid?"}

    B -->|"NO"| Z["아무것도 안 함"]

    B -->|"YES"| C{"mode=write?"}

    C -->|"YES"| Z

    C -->|"NO"| D["wp_head → output_css()"]

    D --> E["wp_footer → output_js()"]

    E --> F["브라우저에서 JS 실행"]

```


| 훅 | 메서드 | 역할 |

|----|--------|------|

| `plugins_loaded` (prio 1) | `init()` | URL 파라미터 검증, 조건부 훅 등록 |

| `wp_head` | `output_css()` | wiki.js 스타일 CSS + CDN (D2Coding, Prism.js) |

| `wp_footer` | `output_js()` | 마크다운 파서 + Prism.js + Mermaid.js 로더 |


**PHP는 JS/CSS 문자열만 출력합니다.** 실제 마크다운 파싱은 전부 브라우저 JavaScript에서 수행합니다.


### JS 레이어 (브라우저)


```mermaid

flowchart TD

    A["IIFE 즉시 실행"] --> B{"document.readyState"}

    B -->|"loading"| C["DOMContentLoaded 대기"]

    B -->|"complete/interactive"| D["renderMarkdownContainers()"]

    C --> D

    D --> E["querySelectorAll로<br/>컨테이너 탐색"]

    E --> F{"textContent에<br/>마크다운 마커<br/>1개 이상?"}

    F -->|"NO"| G["스킵"]

    F -->|"YES"| H["CKEditor HTML 전처리"]

    H --> I["parseMarkdown() 실행"]

    I --> J["innerHTML 교체"]

    J --> K["data-md-rendered=true 설정"]

    K --> L["setTimeout 200ms"]

    L --> M["Prism.js CDN 로드"]

    L --> N["Mermaid.js CDN 로드<br/>(mermaid 블록 있을 때만)"]

    L --> O["복사 버튼 추가"]

```


### 대상 컨테이너


| 선택자 | 대상 | 설정 |

|--------|------|------|

| `td.content-box` | 망보드 기본 스킨 본문 | 자동 |

| `.mb-answer-content` | Q&A 답변 영역 | 자동 |

| `.mb-markdown-content` | 커스텀 페이지 | HTML에 클래스 수동 추가 |

| `.mc-product-content` | 상품 페이지 | 자동 (`mb-markdown-content`로 래핑) |


---


## CKEditor HTML → 마크다운 변환 흐름


망보드는 CKEditor를 사용하므로, 게시글 본문이 p, span, br 태그로 감싸진 HTML로 DB에 저장됩니다. 파서는 이 HTML을 마크다운으로 인식할 수 있는 형태로 전처리합니다:


```mermaid

flowchart LR

    A["CKEditor HTML"] --> B["&lt;script&gt; 보호"]

    B --> C["&lt;p&gt; → 줄바꿈"]

    C --> D["&lt;span&gt; 제거"]

    D --> E["&lt;a&gt; 텍스트만 유지"]

    E --> F["엔티티 복원"]

    F --> G["parseMarkdown()"]

```


### 전처리 상세


> renderMarkdownContainers() 내부에서 CKEditor HTML을 마크다운 파싱 가능한 형태로 변환:

>

> 1. pre 태그 내 script 태그 보호 (HTML 엔티티로 변환)

> 2. br 태그를 줄바꿈 문자로 변환

> 3. p 닫는 태그를 줄바꿈으로 변환, p 여는 태그 제거

> 4. span 태그 제거

> 5. a 태그에서 텍스트만 유지 (href 제거)

> 6. HTML 엔티티 복원 (nbsp, gt, lt, amp)


### 마크다운 마커 감지


textContent에서 정규식으로 마커를 카운트하여 마크다운 여부를 판별:


> 정규식으로 헤딩(#), 코드블록(백틱3개), 인라인코드(백틱), 굵은텍스트(**), 링크([]()) 패턴을 검색하여 1개 이상 감지되면 마크다운으로 간주합니다.


---


## 파서 내부 로직 (parseMarkdown)


### 상태 머신


파서는 라인 단위로 상태를 관리합니다:


```mermaid

stateDiagram-v2

    [*] --> Normal

    Normal --> CodeBlock: 백틱3개 감지

    CodeBlock --> Normal: 백틱3개 닫기

    Normal --> Table: 파이프 문자 감지

    Table --> Normal: 비테이블 라인

    Normal --> List: * / - / 1. 감지

    List --> Normal: 비목록 라인

    Normal --> Blockquote: > 감지

    Blockquote --> Normal: 비인용 라인

```


| 상태 변수 | 타입 | 의미 |

|-----------|------|------|

| `inCodeBlock` | boolean | 코드 블록 내부 |

| `inTable` | boolean | 표 파싱 중 |

| `inList` | boolean | 목록 파싱 중 |

| `inBlockquote` | boolean | 인용문 파싱 중 |

| `codeContent[]` | string[] | 코드 블록 누적 라인 |

| `tableLines[]` | string[] | 표 누적 라인 |

| `listItems[]` | string[] | 목록 아이템 |


### 라인 처리 우선순위


각 라인은 다음 순서로 매칭됩니다:


1. **미디어 태그 스킵** — img, iframe, video 등 (백틱 안의 텍스트는 제외)

2. **코드 블록** — 백틱3개 열기/닫기

3. **코드 블록 placeholder** — `___CODEBLOCK0___` (전처리 단계에서 보호된 블록)

4. **헤딩** — `#`, `##`, `###`, `####`

5. **수평선** — `---`, `***`

6. **인용문** — `>` 또는 `&gt;`

7. **표** — 파이프로 셀 구분

8. **순서 없는 목록** — `*` 또는 `-`

9. **순서 있는 목록** — `1.`, `2.`, ...

10. **일반 단락** — 나머지


> 각 매칭 시 기존 열린 구조(표/목록/인용문)를 자동으로 닫습니다.


### 코드 블록 처리


```mermaid

flowchart TD

    A["백틱3개 감지"] --> B{"inCodeBlock?"}

    B -->|"false"| C["코드 블록 열기<br/>언어 추출"]

    B -->|"true"| D{"언어 미지정<br/>+ 첫 줄이 언어명?"}

    D -->|"YES"| E["첫 줄을 언어로 감지<br/>내용에서 제거"]

    D -->|"NO"| F["renderCodeBlock() 호출"]

    E --> F

    F --> G{"codeLang == mermaid?"}

    G -->|"YES"| H["div.mb-mermaid > pre.mermaid"]

    G -->|"NO"| I{"codeLang 지정됨?"}

    I -->|"YES"| J["div.mb-code-block<br/>언어 레이블 + 라인번호"]

    I -->|"NO"| K["div.mb-code-block.mb-code-plain<br/>다른 배경색"]

```


### 표 파싱 (convertTable)


**백틱 내용 보호**: 표 셀을 파이프(|)로 분리하기 전에 백틱 안의 내용을 null character 기반 placeholder로 보호:


> 처리 과정: 인라인 코드를 정규식으로 찾아 null character 조합의 placeholder로 치환 → split으로 셀 분리 → placeholder를 원래 값으로 복원

>

> 예: A (인라인코드 B|C 인라인코드) D → A PLACEHOLDER D → split → 복원 → 셀이 잘리지 않음


> **null character를 사용하는 이유**: HTML 엔티티 등은 CKEditor/브라우저에서 충돌 가능. null character는 일반 텍스트에 등장하지 않으므로 안전.


---


## Mermaid 렌더링


### 로딩 흐름


```mermaid

sequenceDiagram

    participant P as parseMarkdown()

    participant D as DOM

    participant M as Mermaid CDN


    P->>D: div.mb-mermaid > pre.mermaid 삽입

    Note over D: 200ms setTimeout

    D->>D: .mb-mermaid .mermaid 유무 확인

    D->>M: mermaid.min.js 동적 로드

    M->>D: mermaid.initialize({startOnLoad:false})

    loop 각 mermaid 블록

        D->>M: mermaid.render(id, code)

        M-->>D: Promise → SVG

        D->>D: block.innerHTML = svg

    end

```


### mermaid.render() vs mermaid.run()


v7.6.0에서 mermaid.run()에서 mermaid.render()로 전환:


| | mermaid.run (nodes) | mermaid.render (id, code) |

|---|---|---|

| gitgraph 감지 | ❌ No diagram type detected | ✅ 정상 |

| 에러 전파 | 하나 에러 → 후속 전부 중단 | 개별 try-catch |

| 반환값 | DOM 직접 조작 | Promise → svg |

| 에러 처리 | catch 불가 | .catch()로 개별 처리 |


### 에러 핸들링


```javascript

mermaid.render(id, code)

    .then(function(result) { block.innerHTML = result.svg; })

    .catch(function(err) {

        block.innerHTML = '⚠️ Mermaid 렌더링 에러 — 문법을 확인하세요';

    });

```


한 다이어그램의 에러가 다른 다이어그램 렌더링에 영향을 주지 않습니다.


---


## 주요 버그 수정 이력


### 1. 백틱 내 파이프 문자 오인식


**현상**: 표 셀에 인라인 코드로 감싼 파이프 문자(|)가 표 구분자로 인식되어 셀이 잘림


**원인**: convertTable()에서 line.split('|') 시 백틱 내용 보호 없음


**해결**: split 전 \x00CODE placeholder로 백틱 내용 보호 후 복원


### 2. mermaid.run() gitgraph 미감지


**현상**: gitgraph 다이어그램이 "No diagram type detected" 에러


**원인**: `mermaid.run({nodes: [block]})`은 DOM 노드에서 다이어그램 타입을 감지하는데, gitgraph 등 일부 타입이 누락됨


**해결**: `mermaid.render(id, code)` 방식으로 전환. 텍스트에서 다이어그램 타입을 감지하므로 모든 타입 지원


### 3. mermaid 에러 전파로 전체 렌더링 중단


**현상**: 하나의 mermaid 에러가 11번째 다이어그램 이후 전부 렌더링 안 됨


**원인**: `mermaid.run({nodes: allBlocks})`이 에러 발생 시 후속 블록 처리 중단


**해결**: `forEach` + 개별 `try-catch` + `Promise.catch()` 로 각 블록 독립 렌더링


### 4. 백틱 내 미디어 태그 오인식으로 표 강제 종료


**현상**: 표에서 미디어 보호 셀에 인라인 코드로 감싼 HTML 태그(img, iframe, video)가 포함된 행에서 표 파싱이 강제 종료되고, 이후 모든 섹션이 렌더링 누락


**원인**: 

1. CKEditor가 인라인 코드 안의 HTML 태그를 HTML 엔티티로 저장

2. 브라우저 innerHTML에서 이를 실제 HTML 태그로 디코딩

3. 파서의 미디어 태그 체크가 인라인 코드 안의 텍스트를 실제 미디어 태그로 오인

4. 진행 중인 표를 강제 종료 → 이후 전체 내용이 P 태그에 갇힘


**해결**: 미디어 태그 체크 전 인라인 코드를 제거한 lineNoCode에서 검사:


> 인라인 코드 패턴을 빈 문자열로 치환 후, 남은 텍스트에서만 img/iframe/video/audio/object/embed 태그를 검사합니다. 이렇게 하면 코드 안의 텍스트가 실제 미디어 태그로 오인되지 않습니다.


### 5. 중첩 코드 블록 금지 (CKEditor 호환성)


**현상**: 4백틱 중첩 코드 블록이 CKEditor에서 P 태그로 분리되어 빈 렌더링 발생


**원인**: CKEditor가 백틱을 일반 텍스트로 처리하고, P 태그로 줄바꿈 → 파서가 닫는 백틱을 인식하지 못함


**해결**: 모든 예시를 3백틱 평면 구조로 통일, 중첩 코드블록 사용 금지


### 6. Smart Editor 중첩 헤딩 정규화


**현상**: Smart Editor에서 `# # 텍스트` 패턴이 H2가 아닌 깨진 텍스트로 렌더링


**해결**: 헤딩 감지 시 `#` 개수를 카운트하여 병합:


```javascript

var normalizedLine = line.replace(/^(#+)(\s*#+)*\s*/, function(match) {

    var hashCount = (match.match(/#/g) || []).length;

    return '#'.repeat(Math.min(hashCount, 4)) + ' ';

});

```


---


## CSS 아키텍처


### Specificity 전략


망보드 기본 CSS가 `.mb-style1 .table-view > tbody th` (specificity 0,2,2 + `!important`)로 테이블 헤더를 강제 지정합니다. 이를 오버라이드하기 위해 **모든 CSS 규칙에 `body .mb-style1 td.content-box` prefix** (specificity 0,2,3)를 사용합니다:


```css

/* ❌ 망보드에 덮어씌워짐 */

td.content-box th { background-color: #E0E0E0 !important; }


/* ✅ specificity 0,2,3으로 오버라이드 성공 */

body .mb-style1 td.content-box th { background-color: #E0E0E0 !important; }

```


> `.mb-answer-content`은 `.table-view > tbody` 밖에 있으므로 이 문제가 없습니다.


### 주요 스타일


| 요소 | 스타일 |

|------|--------|

| 코드 블록 | `#212121` 다크 배경, D2Coding 폰트, Prism Tomorrow Night |

| Plain 코드블록 | `#263238` 배경 + `#CFD8DC` 보더 (언어 미지정 시) |

| 인라인 코드 | `#E8EAF6` 배경, `#283593` 텍스트, Roboto Mono |

| 인용문 | `55px solid #607D8B` 좌측 보더 + 따옴표 SVG 아이콘 |

| H1-H4 | 하단 그라데이션 보더 (100% → 90% → 70%) |

| 표 | `#E0E0E0` 헤더, `#FAFAFA` 짝수행, `1px solid #BDBDBD` 보더 |

| Mermaid | 중앙 정렬, 투명 배경, `max-width: 100%` |


---


## 외부 의존성 (CDN)


| 라이브러리 | 버전 | 로드 조건 |

|-----------|------|----------|

| D2Coding Font | 1.3.2 | 항상 (`wp_head`) |

| Prism.js Core | 1.29.0 | 항상 (200ms setTimeout) |

| Prism Tomorrow Night | 1.29.0 | 항상 |

| Prism Line Numbers | 1.29.0 | 항상 |

| Prism Autoloader | 1.29.0 | 항상 (언어 컴포넌트 온디맨드) |

| Mermaid.js | 10.9.1 | `.mb-mermaid .mermaid` 요소 있을 때만 |


---


## 배포 방법


### Docker 컨테이너


```bash

# 파일 전송

docker cp mangboard-markdown.php wellcoms_wp:/var/www/html/wp-content/plugins/mangboard-markdown/


# 권한 설정

docker exec -u root wellcoms_wp chown -R www-data:www-data /var/www/html/wp-content/plugins/mangboard-markdown/


# OPcache 클리어 + PHP-FPM 리로드

docker exec wellcoms_wp php -r 'opcache_reset();'

docker exec wellcoms_wp kill -USR2 $(docker exec wellcoms_wp pgrep -f 'php-fpm: master')

```


### GitLab


```bash

git clone https://gitlab.wellcoms.co.kr/jskim/wp-mangboard-markdown.git

cp -r wp-mangboard-markdown /var/www/html/wp-content/plugins/

```


---


## 트러블슈팅


### 마크다운이 렌더링되지 않음


1. 플러그인 활성화 확인

2. URL에 `board_pid` 또는 `vid` 파라미터 확인

3. `mode=write`가 아닌지 확인 (편집 모드는 렌더링 스킵)

4. 브라우저 콘솔에서 JS 에러 확인


### 표/다이어그램 이후 내용이 짤림


파서가 특정 행에서 구조(table/list/blockquote)를 잘못 종료했을 가능성:

- 인라인 코드 안의 img, video 등이 미디어 태그로 오인되었는지 확인

- 인라인 코드 안의 파이프 문자가 표 구분자로 오인되었는지 확인


### Mermaid 다이어그램이 렌더링되지 않음


1. 언어 지정이 `mermaid`인지 확인 (소문자)

2. 브라우저 콘솔에서 에러 확인 (F12 > Console)

3. CDN 연결 상태 확인 (`cdn.jsdelivr.net` 접근 가능해야 함)

4. mindmap은 한글 특수문자 오류 시 flowchart로 대체

5. 코드블록 내부에 백틱 3개나 HTML 태그가 포함되면 파서가 코드블록 경계를 잘못 인식할 수 있음


### OPcache로 인해 수정사항 반영 안 됨


```bash

docker exec wellcoms_wp php -r 'opcache_reset();'

```


PHP-FPM 프로세스를 재시작해야 할 수도 있음:


```bash

docker exec wellcoms_wp kill -USR2 $(docker exec wellcoms_wp pgrep -f 'php-fpm: master')

```


### 트러블슈팅 순서도


```mermaid

flowchart TD

    Problem["렌더링 안 됨"] --> A{"플러그인 활성화?"}

    A -->|"NO"| FixA["WordPress 관리자 > 플러그인 > 활성화"]

    A -->|"YES"| B{"URL 파라미터 있음?<br/>board_pid 또는 vid"}

    B -->|"NO"| FixB["게시글 상세 페이지로 이동"]

    B -->|"YES"| C{"마크다운 문법<br/>올바른가?"}

    C -->|"NO"| FixC["# 뒤 공백, 백틱 줄바꿈 확인"]

    C -->|"YES"| FixD["브라우저 캐시 삭제<br/>Ctrl+Shift+R"]

```


---


## 다른 서버에 설치


```bash

git clone https://gitlab.wellcoms.co.kr/jskim/wp-mangboard-markdown.git

cp -r wp-mangboard-markdown /var/www/html/wp-content/plugins/

chown -R www-data:www-data /var/www/html/wp-content/plugins/wp-mangboard-markdown

# WordPress 관리자에서 활성화 — 별도 설정 불필요

```


| 필수 조건 | 버전 |

|-----------|------|

| WordPress | 5.0+ |

| PHP | 7.0+ |

| 망보드(MangBoard) | 설치됨 |

| JavaScript | 활성화 |


---


## 버전 이력


| 버전 | 날짜 | 변경 내용 |

|------|------|-----------|

| 7.6.0 | 2026-04-28 | Mermaid 다이어그램 지원, mermaid.run에서 mermaid.render로 전환, 개별 try-catch 에러 핸들링, 표 파서 백틱 내용 보호, 미디어 태그 감지 개선, mb-code-plain 클래스 추가 |

| 7.5.0 | 2026-04-23 | wiki.js 스타일 개편: Prism.js, 라인 번호, D2Coding, 인용문 보더, 테이블 CSS specificity 수정 |

| 7.4.6 | 2026-03-25 | Smart Editor 중첩 헤딩 정규화 |

| 7.4.3 | 2026-03-17 | 줄바꿈 분할 지원, 감지 임계값 낮춤, 즉시 실행 |

| 7.4.2 | 2026-03-17 | mode=write 시 렌더링 스킵 |

| 7.4.1 | 2026-02-27 | board_pid 파라미터 지원 추가 |

| 7.4.0 | 2026-02-14 | JavaScript 기반 렌더링으로 전환 |

| 5.4.0 | 2026-02-14 | 코드 블록 줄바꿈 처리 수정 |

| 5.0.0 | 2026-02-13 | 초기 안정 버전 |


---


## 라이선스


- **mangboard-markdown.php**: MIT License (자체 개발)

- **Parsedown.php**: MIT License (https://github.com/erusev/parsedown) — 미사용, 향후 제거 예정


댓글
자동등록방지
(자동등록방지 숫자를 입력해 주세요)