feat(projects): 설명 마크다운 렌더링 + 저장소 README 가져오기
등록 모달의 '한 줄 설명' input 을 여러 줄 textarea 로 바꾸고, 설명을 markdown-it 로 렌더링한다(html:false 로 raw HTML 이스케이프·javascript: 링크 차단 — XSS 안전). 상세 화면은 마크다운으로, 카드 미리보기는 기호를 걷어낸 순수 텍스트(mdPlain) + 3줄 클램프로 표시. '저장소 README 가져오기' 버튼과 GET /api/readme 라우트를 추가해 repo_url 의 Gitea README 를 설명란으로 불러올 수 있다(gitea.js fetchReadme). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
28
src/gitea.js
28
src/gitea.js
@@ -48,3 +48,31 @@ export async function fetchMainLang(repoUrl) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
// 레포의 README 원문(마크다운)을 반환. 실패/없음이면 "".
|
||||
// Gitea raw 엔드포인트로 기본 브랜치의 README 파일을 후보 순서대로 시도한다.
|
||||
export async function fetchReadme(repoUrl) {
|
||||
const r = parseRepo(repoUrl);
|
||||
if (!r) return "";
|
||||
const headers = {};
|
||||
if (config.giteaToken) headers.Authorization = `token ${config.giteaToken}`;
|
||||
const base = `${r.base}/api/v1/repos/${encodeURIComponent(r.owner)}/${encodeURIComponent(r.repo)}/raw`;
|
||||
const candidates = ["README.md", "README.MD", "readme.md", "README", "README.markdown"];
|
||||
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), 4000);
|
||||
try {
|
||||
for (const name of candidates) {
|
||||
const res = await fetch(`${base}/${name}`, { headers, signal: ctrl.signal });
|
||||
if (!res.ok) continue;
|
||||
const text = await res.text();
|
||||
// 너무 큰 README 는 앞부분만(설명란이 감당할 범위). 20KB 컷.
|
||||
return text.length > 20000 ? text.slice(0, 20000) : text;
|
||||
}
|
||||
return "";
|
||||
} catch {
|
||||
return "";
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user