mirror of
https://github.com/sotam0316/brain_dogfood.git
synced 2026-04-25 03:48:38 +09:00
Enhance: Non-destructive metadata system, Korean support, and UI readability
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 메모 카드 컴포넌트
|
||||
*/
|
||||
import { escapeHTML, parseInternalLinks, fixImagePaths } from '../utils.js';
|
||||
import { escapeHTML, parseInternalLinks, fixImagePaths, stripMetadata } from '../utils.js';
|
||||
import { renderAttachmentBox } from './AttachmentBox.js';
|
||||
import { Constants } from '../utils/Constants.js';
|
||||
import { I18nManager } from '../utils/I18nManager.js';
|
||||
@@ -36,10 +36,8 @@ export function createMemoCardHtml(memo, isDone) {
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// 본문에서 하단 메타데이터 블록(--- 이후)을 제외하고 렌더링 (중복 표시 방지)
|
||||
let content = memo.content || '';
|
||||
const footerIndex = content.lastIndexOf('\n\n---\n');
|
||||
const displayContent = footerIndex !== -1 ? content.substring(0, footerIndex) : content;
|
||||
// 렌더링 시에는 태그와 그룹명을 시각적으로 가립니다 (원본 보존 정책)
|
||||
const displayContent = stripMetadata(memo.content || '');
|
||||
|
||||
// marked로 파싱한 후 DOMPurify로 살균하여 XSS 방지
|
||||
htmlContent = DOMPurify.sanitize(marked.parse(displayContent));
|
||||
|
||||
@@ -164,21 +164,10 @@ export const ModalManager = {
|
||||
const memo = memos.find(m => m.id == id);
|
||||
if (!memo) return;
|
||||
|
||||
import('../utils.js').then(({ parseInternalLinks, fixImagePaths }) => {
|
||||
// 메모 본문과 메타데이터 푸터 분리 렌더링
|
||||
let content = memo.content || '';
|
||||
const footerIndex = content.lastIndexOf('\n\n---\n');
|
||||
let html;
|
||||
|
||||
if (footerIndex !== -1) {
|
||||
const mainBody = content.substring(0, footerIndex);
|
||||
const footerPart = content.substring(footerIndex + 5).trim(); // '---' 이후
|
||||
|
||||
html = DOMPurify.sanitize(marked.parse(mainBody));
|
||||
html += `<div class="memo-metadata-footer"><hr style="border:none; border-top:1px dashed rgba(255,255,255,0.1); margin-bottom:15px;">${DOMPurify.sanitize(marked.parse(footerPart))}</div>`;
|
||||
} else {
|
||||
html = DOMPurify.sanitize(marked.parse(content));
|
||||
}
|
||||
import('../utils.js').then(({ parseInternalLinks, fixImagePaths, stripMetadata }) => {
|
||||
// 렌더링 시에는 태그와 그룹명을 시각적으로 가립니다 (원본 보존 정책)
|
||||
const displayContent = stripMetadata(memo.content || '');
|
||||
let html = DOMPurify.sanitize(marked.parse(displayContent));
|
||||
|
||||
html = parseInternalLinks(html);
|
||||
html = fixImagePaths(html);
|
||||
|
||||
@@ -41,3 +41,31 @@ export function fixImagePaths(html) {
|
||||
return `<img src="/api/download/${filename}"`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 본문에서 메타데이터 기능어($그룹, #태그)를 시각적으로 가립니다.
|
||||
* 원본 보존 정책에 따라 렌더링 시에만 필터링 용도로 사용됩니다.
|
||||
*/
|
||||
export function stripMetadata(text) {
|
||||
if (!text) return '';
|
||||
|
||||
let processed = text;
|
||||
|
||||
// 1. 기존 자동생성 푸터 블록(--- 및 하단 메타데이터) 시각적 제거
|
||||
const footerRegex = /\n+[\*\-\_]{3,}\s*\n(?:^[\$\#][^\s\#].*$(?:\n|$))*/gm;
|
||||
processed = processed.replace(footerRegex, '');
|
||||
|
||||
// 태그/그룹 구성에서 제외할 특수문자들 (한글 및 유니코드 지원을 위해 제외 문자 방식 사용)
|
||||
const excludeChars = "\\s\\#\\!\\@\\%\\^\\&\\*\\(\\)\\=\\+\\[\\]\\{\\}\\;\\:\\'\\\"\\,\\<\\.\\>\\/\\?\\-";
|
||||
|
||||
// 2. 본문 내 $그룹 제거
|
||||
const groupRegex = new RegExp("\\$[^" + excludeChars + "]+", "g");
|
||||
processed = processed.replace(groupRegex, '');
|
||||
|
||||
// 3. 본문 내 #태그 제거 (마크다운 헤더 및 내부 링크 제외)
|
||||
// 패턴 설명: 공백 또는 줄 시작 뒤의 #로 시작하고, 뒤에 숫자가 아닌 문자가 오는 태그 매칭
|
||||
const tagRegex = new RegExp("(^|\\s)#[^" + excludeChars + "0-9]+[^" + excludeChars + "]*", "g");
|
||||
processed = processed.replace(tagRegex, '$1');
|
||||
|
||||
return processed.trim();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user