[옵시디언] 독서노트 꾸미기 (ft. Vive Coding)

taez·2025년 8월 27일
1

Obsidian 사용기

목록 보기
2/2
post-thumbnail

옵시디언 최고의 장점이자 진입장벽, 커스터마이징을 Vive Coding으로 해결하기

최근 다시 Obsidian을 제대로 활용해보고자 이것저것 시도하는 중인데,
이번 포스팅에서는 독서 노트 관리 & 시각화를 어떻게 하면 보기 좋게 만들 수 있을지 정리해봤다.

최소한의 Obsidian 경험은 있다는 가정 하에 작성..


📦 Plugins

Korean Book Info

👉 참고: Plugin Github Repo

국내 도서 정보를 크롤링해서 독서 노트의 frontmatter에 넣어주는 플러그인이다.
필자는 크레마클럽을 사용중이라 이걸 사용하지만, 교보문고 기반 플러그인도 있는 것으로 알고있다.

Note title에 책 제목을 입력하고 해당 플러그인을 실행하면

  • 저자, 출판사, 출판일, 표지 이미지 등

메타데이터들이 자동으로 frontmatter에 들어가기 때문에 이후 Dataview로 다루기가 편해진다.

해당 플러그인에 대한 상세 설명은 Obsidian 내의 Plugin Store 또는 플러그인 Github Repo 를 참고하면 된다.


Dataview

이건 Obsidian 내에서는 너무 유명한 플러그인이라 따로 자세한 설명은 생략한다.
이번 글에서는 dataviewjs까지 활용하므로

OptionsDataviewEnable JavaScript Queries 를 켜주자.

가장 단순하게, 작성되어있는 독서 노트들을 아래와 같이 테이블로 출력할 수 있다.

// dataview
TABLE WITHOUT ID 
	"![|50](" + cover_url + ")" as "표지", 
	file.link as "제목", 
	author as "저자", 
	my_rate as "별점", 
	book_note as "한줄평"  
FROM #📚독서 
SORT my_rate DESC  

역시 순정은 깔끔하긴 하지만, 뭔가 밋밋하다.


🎨 CSS Snnipets

Obsidian에서는 OptionsAppearanceCSS Snippets에서 CSS 파일을 등록할 수 있다.

그리고 Note의frontmattercssclasses 를 입력하면 해당 note에 스타일이 적용된다.

이걸로 note를 내맘대로 커스텀 할 수 있긴한데..

obsidian 처음 쓸 때(2023년)만해도 필자는 CSS의 ㅅ자도 모르는 늅늅이었기에 당시에는 거의 신경도 못썼었다.

full-width.css

지금은 모르겠는데 2년전만 해도
Note 너비를 변경하는 옵션global로만 있고 Note 별로 각각 설정은 못했었다.
특정 Note만 full-width 설정하기위해 구글링해서 찾은 CSS 파일을 사용했었다.

Cards.css

DataView로 만들어진 테이블을 Card 형식으로 꾸며주는 css 파일을 써봤는데,
기본적으로 깔끔하긴하지만 뭔가 나만의 taste를 추가하기엔 어려움이 있었다.

🤖 Vive Coding

BookCards.css

필자는 여전히 CSS 전문가는 아니지만 이제 Vive Coding(AI 딸깍)이 가능해졌다.
위의 Cards.css 를 바탕으로, 독서노트 전용으로 사용하기 위한 CSS Snnipet을 llm과 함께 만들었다.

👉 참고: BookCard.css - Github


📝 Template & DataviewJS

book-note

독서 노트 작성 Flow
1. 독서 노트 생성 및 Title로 책 제목 입력
2. Korean Book Info 플러그인을 통해 frontmatter에 책 정보 등록
3. 읽으면서 노트 내용 채우기
4. 완독 후frontmattermy_rate (별점)book_note (한줄평) 입력


DataviewJS + BookCards.css

dataviewjs, CSS와 함께라면 옵시디언 꾸미기, 옵꾸를 마음껏 누릴 수 있다.

100% Vibe Coding이 목표였지만,
LLM이 dataviewjs 데이터 훈련은 많이 못했는지 5% 아쉬워서 그정도는 직접 수정

/* dataviewjs */

// 1️⃣ Book Notes 페이지 가져오기 (정렬: 별점 -> 생성일)
const currentFolder = dv.current().file.folder;
const pages = dv.pages(`"${currentFolder}"`)
    .where(p => p.file.tags.includes("#📚독서"))
    .sort(p => [p.my_rate ?? 0, p.file.cday], 'desc');

// 2️⃣ 카드 컨테이너 생성
const container = document.createElement("div");
container.className = "book-cards";

// 3️⃣ 카드 생성
pages.forEach(p => {
    
    const rate = p.my_rate ?? 0;

    // 카드
    const card = document.createElement("div");
    card.className = "book-card";

    // 이미지 (클릭 시 이동)
    const img = document.createElement("img");
    img.src = p.cover_url ?? "";
    img.alt = "표지";
    img.addEventListener("click", () => {
        app.workspace.openLinkText(p.file.path, "/", true);
    });
    card.appendChild(img);

    // 콘텐츠 div
    const content = document.createElement("div");
    content.className = "content";
    card.appendChild(content);
    
    // 제목 (링크 포함)
	const titleDiv = document.createElement("div");
	titleDiv.className = "title";
	
	const titleLink = document.createElement("a");
	titleLink.textContent = p.file.name;
	titleLink.href = "#"; 
	titleLink.style.textDecoration = "none"; 
	titleLink.style.color = "inherit"; 
	titleLink.addEventListener("click", (e) => {
	    e.preventDefault();
	    app.workspace.openLinkText(p.file.path, "/", true);
	});
	titleDiv.appendChild(titleLink);
	content.appendChild(titleDiv);
	
	// 별점 + 한줄평 div
	const ratingNoteWrapper = document.createElement("div");
	ratingNoteWrapper.className = "rating-note-wrapper";
	ratingNoteWrapper.style.display = "flex";
	ratingNoteWrapper.style.flexDirection = "column";
	ratingNoteWrapper.style.gap = "0.25rem";

    // 별점
    const ratingDiv = document.createElement("div");
    ratingDiv.className = "rating";

    if (rate) {
        for (let i = 1; i <= 5; i++) {
            const fillPercent = Math.max(Math.min(rate - (i - 1), 1), 0) * 100;
            
            const starSpan = document.createElement("span");
            starSpan.className = "star";
            starSpan.textContent = "★";

            const filled = document.createElement("span");
            filled.className = "filled";
            filled.textContent = "★";
            filled.style.width = `${fillPercent}%`;

            starSpan.appendChild(filled);
            ratingDiv.appendChild(starSpan);
        }
        const numText = document.createElement("span");
        numText.textContent = ` ${rate}`;
        ratingDiv.appendChild(numText);
    } else {
        ratingDiv.textContent = "별점 없음";
    }
    ratingNoteWrapper.appendChild(ratingDiv);

    // 한줄평
    const noteDiv = document.createElement("div");
    noteDiv.className = "note";
    noteDiv.textContent = p.book_note ?? "";
    ratingNoteWrapper.appendChild(noteDiv);
    
    content.appendChild(ratingNoteWrapper);

    // 메타데이터
    const metaDiv = document.createElement("div");
    metaDiv.className = "meta";
    if (p.author) {
        const authorDiv = document.createElement("div");
        authorDiv.className = "author";
        authorDiv.textContent = p.author;
        metaDiv.appendChild(authorDiv);
    }
    if (p.publisher || p.publish_date) {
        const pubDiv = document.createElement("div");
        pubDiv.className = "publisher"
        pubDiv.textContent = `${p.publisher ?? ""} / ${p.publish_date ? dv.date(p.publish_date).toFormat("yyyy-MM-dd") : ""}`;
        metaDiv.appendChild(pubDiv);
    }
    if (p.status || p.start_read_date || p.finish_read_date) {
        const statusDiv = document.createElement("div");
        statusDiv.className = "status";
        statusDiv.textContent = `${p.status ?? ""} / ${p.start_read_date ? dv.date(p.start_read_date).toFormat("yyyy-MM-dd") : ""} ~ ${p.finish_read_date ? dv.date(p.finish_read_date).toFormat("yyyy-MM-dd") : ""}`;
        metaDiv.appendChild(statusDiv);
    }
    content.appendChild(metaDiv);

    // 카드 컨테이너에 추가
    container.appendChild(card);
});

// 4️⃣ DataviewJS에 container 삽입
dv.container.appendChild(container);

일단 바이브만 느끼고 디테일엔 신경쓰지 말자..?
dataviewjs와 css가 제대로 먹혔다면 다음과 같이 Cards 형태로 보기좋게 정리된다.


📊 티어메이커 스타일

여기서 끝내면 아쉬워서 별점 구간을 티어(S, A, B, …)로 매핑해서 한눈에 보는 표도 만들어봤다. 물론 vive로..

// dataview
TABLE WITHOUT ID
	Tier,
    join(rows.file.link, ", ") as "책 목록"
FROM #📚독서
SORT my_rate DESC
GROUP BY choice(floor(my_rate) = 5, "S",
         choice(floor(my_rate) = 4, "A",
         choice(floor(my_rate) = 3, "B",
         choice(floor(my_rate) = 2, "C",
         choice(floor(my_rate) = 1, "D", "미분류"))))) as "Tier"
SORT rows.my_rate DESC

✨ 마무리

  • 기본 Dataview 테이블 → DataviewJS + CSS → Card 뷰 옵꾸 성공
  • 옵시디언 최고의 장점이자 진입장벽인 커스터마이징을 Vive Coding으로 해결
    • 회사에서 Vive Coding 누리면 등짝스매싱이지만 여기선 마음껏 누리기
profile
흔하지 않은 개발자

0개의 댓글