สร้าง Book Tracking Library ด้วย Obsidian
เราเป็นคนที่อ่านกับซื้อหนังสือเยอะมาก ปัญหานึงที่ประสบมาหลายรอบและน่าหงุดหงิดมาก ๆ คือ ซื้อหนังสือซ้ำเจ้าค่ะ ทำให้เราจะต้องมีระบบง่าย ๆ สักตัวในการจัดการ วันนี้เลยจะมาเล่าวิธีการที่เราใช้ Obsidian ในการจัดการหนังสือที่เรามีกัน
ปล. จริง ๆ มันมี Plugin ชื่อว่า book-search ที่มัน Automate หลาย ๆ อย่างให้เราเองเลย แต่ในบทความนี้เราจะไม่ใช้งานมัน เน้นทำเองซะมากกว่า ใครใคร่ทำเองก็ทำเองตามบทความนี้และเอาไปดัดแปลงตามการใช้งานของเราได้เลย
ปล 2. โครงทั้งหมดเราดึงมาจากวีดีโอด้านบนนี้ และเอามาปรับ Workflow เพิ่มบางอย่างเข้าไปเพื่อให้เขากับ Workflow การทำงานของเราอีกที
ปล 3. ทั้งหมดที่เห็นในบทความนี้เป็น Version "หยุดก่อนอานนท์ ส้มหยุด" เอาแบบคร่าว ๆ พอนะ ของจริงที่เราใช้ มัน Beyond ไปกว่านี้เยอะ เพราะอานนท์หยุดทำไม่ได้ ไว้มันเรียบร้อยกว่านี้แล้วจะมาอัพเดทใน Youtube อีกทีเด้อ
Requirements
ก่อนเราจะพาไปดูว่า เราทำ หรือตั้งค่าอะไรยังไง เราขอเล่า Requirement ก่อนว่าเราจะต้องใช้อะไรบ้าง เรื่องแรกคือ เราต้องการเก็บรายชื่อหนังสือที่เรามี เวลาเราจะซื้อหนังสือสักเล่ม เราก็แค่ค้นหาด้วยชื่อหนังสือ แต่บางทีชื่อหนังสือบางเล่มอาจจะเหมือนกัน ทำให้เราจะต้องมีชื่อคนแต่งเก็บลงไปด้วย หลัก ๆ คือ เราต้องการให้มัน Searchable นั่นเอง
อีกปัญหาที่เราเจอคือ เรามีหนังสืออยู่ในหลากหลายรูปแบบ และ Platform เรามีหนังสือที่เป็นเล่มจริง และเป็น E-Book ที่แยกย่อยไปอีกว่า มันอยู่บน Platform ไหน เพราะเวลาเราซื้อหนังสือ E-Book เราจะเปรียบเทียบแต่ละ Platform ถ้าเจ้าไหนให้ถูกกว่า เราก็จะกดเจ้านั้น ทำให้ปัญหามันอยู่ที่ว่า หากเรารู้ละว่าเรามีเล่มนี้ การที่เราจะต้องไปไล่เปิดหา มันก็น่าจะเป็นเรื่องที่น่าหงุดหงิดพอสมควร ระบบที่สร้างขึ้นมาจึงจำเป็นต้องเก็บที่อยู่ของหนังสือด้วยว่ามันเก็บอยู่ที่ไหนกันแน่
สุดท้ายคือ เราอยากให้การจัดการแบบ File-Based มัน Transparent หายออกไป อยากให้มันสามารถกด ๆ สร้าง หรือจัดการหนังสือได้โดยที่ไม่ต้องมานั่งคลิกขวา New Note อะไรแบบนั้น อยากได้แบบที่กด New Book แล้วมันสร้างเป็นหนังสือออกมาให้เราได้เลยจะดีมาก
Template
---
Author:
Title:
Date: <% tp.date.now("YYYY-MM-DD-dddd") %>
Shelf:
tags:
Status: Not started
---
เราขอเริ่มต้นจากการสร้าง Template สำหรับการเก็บหนังสือแต่ละเล่ม ขอเริ่มจาก Metadata ที่เราจะต้องมีแน่นอนคือ Title หรือชื่อหนังสือ, Author ชื่อคนแต่ง, Date วันที่ ที่เราเพิ่มหนังสือเข้าไป, Shelf ใช้เก็บที่อยู่ของหนังสือไม่ว่าจะเป็นเล่มจริง หรือ E-Book Platform ไหน, Tag เราใช้เก็บ Tag ที่เกี่ยวข้องส่วนใหญ่จะเป็นประเภทของหนังสือ และสุดท้ายคือ Status เก็บสถานะของหนังสือว่า เราอ่านหรือยัง เล่มไหนดองอยู่บ้าง
ก่อนเราจะไปที่ Content เราขอพูดถึง Naming Convention ที่เราใช้งานก่อน คือ เราจะใช้เป็น ปี เดือน วัน คั่นด้วยขีด ตามด้วย 📚 และค่อยเป็นชื่อหนังสือ อันนี้เราเอาจากต้นฉบับมาเลย เพราะก็รู้สึกว่า เออง่ายดี หรือถ้าใครอยากแก้ไขยังไง ก็จัดการได้ตามสบาย เอาที่สบายใจได้เลย
# [[<% tp.date.now("YYYY-MM-DD") + " 📚 " + tp.file.title %>]]
## Notes
-
## Highlights
-
## Action
```meta-bind-button
label: Delete this book
icon: trash
hidden: false
class: ""
tooltip: ""
id: ""
style: default
actions:
- type: command
command: app:delete-file
```
## Tags
[[Books MOC]]
<% await tp.file.move("/Books/" + tp.date.now("YYYY-MM-DD") + " 📚 " + tp.file.title) %>
ในส่วนของ Content เราจะแบ่งออกเป็น 4 Sections ย่อยด้วยกัน เริ่มจาก Title เราใช้เป็น Self Link ด้วยการเขียน Templater เอาให้มันใช้ ก็คือให้เอาชื่อไฟล์มาแปลงตาม Naming Convention ข้อดีของการทำแบบนี้คือ เมื่อเราเปลี่ยนชื่อไฟล์มันก็จะอัพเดทตามชื่อไฟล์ทันที จากนั้น เราจะพบกับ Subsection ที่เขียนว่า Notes เราจะใช้เขียนพวกอะไรก็ได้จิปาถะเลย แต่ส่วนที่เราจะเน้นการเขียนเกี่ยวกับเนื้อหาในหนังสือจะอยู่ใน Highlights เราจะเน้นเขียนส่วนที่คิดว่าน่าสนใจ น่าเอามาใช้งานต่อได้ หรือเกี่ยวข้องกับงานที่เรากำลังทำ หรือเกี่ยวข้องกับสิ่งที่เรากำลัง Research อยู่ก็ได้เหมือนกัน
```meta-bind-button
label: Delete this book
icon: trash
hidden: false
class: ""
tooltip: ""
id: ""
style: default
actions:
- type: command
command: app:delete-file
```
Subsection ถัดไปคือ Action อันนี้เราเพิ่มเข้ามาตาม Requirement ที่เราบอกว่า เราไม่อยากจัดการเป็น File Based นั่งหาไฟล์ที่ต้องลบ ก็ทำเป็นปุ่มมันซะเลยสิ เราใช้ Plugin ที่ชื่อว่า Meta Bind โดยเราสามารถใช้ Wizard ของเขาในการ Generate Snippet ของปุ่มแบบด้านบนได้ เราสามารถตั้งค่าพวก Label และ Icon ได้ค่อนข้างอิสระ แต่สิ่งสำคัญคือ Action หรือกดแล้วจะให้มันทำอะไร เราให้มันเรียกคำสั่ง ลบไฟล์ที่เปิดอยู่ หรือก็คือไฟล์หนังสือเล่มที่เรากำลังเปิดอยู่
ส่วนล่างสุด Subsection Tags เราใช้เป็น Backlink กลับไปหาหน้า MOC ที่เรากำลังจะเล่าในส่วนต่อไป ส่วนด้านล่างสุดที่เป็นชุดคำสั่ง คือคำสั่งสำหรับการย้ายไฟล์ไปที่ Folder ที่เราเก็บหนังสือ พร้อมกับตั้งชื่อตาม Naming Convention ที่เรากำหนด ดั้งนั้นเมื่อเราสร้างไฟล์, Apply Template และรัน Templater เข้าไปมันก็จะย้ายไฟล์ไปอยู่ใน Folder ที่เรากำหนดไว้ และตั้งชื่อให้เราเรียบร้อย ในส่วนนี้ ให้เราเช็คดี ๆ นะว่าไฟล์หนังสือเราจะเก็บไว้ให้ ก็อัพเดทให้ตรงกันด้วย
Book MOC
Book MOC เป็นหน้าที่เหมือนเป็น Portal สำหรับการเข้าถึงหนังสือต่าง ๆ ที่เราเก็บเอาไว้ ส่วนแรกสุด จะเป็นพวก Title และ Subtitle แน่นอนว่า Subtitle เราก็ให้ ChatGPT เขียนข้อความที่ Motivate ให้เราอ่านหนังสือแล้วเอามาแปะเพื่อ Cosmetics ล้วน ๆ 🤣
```meta-bind-button
label: Create Book
icon: "pencil"
hidden: false
class: ""
tooltip: ""
id: ""
style: default
actions:
- type: templaterCreateNote
templateFile: templates/book-template.template.md
folderPath: Books
fileName: ""
openNote: true
```
ส่วนต่อมาคือ ปุ่มสำหรับสร้างหนังสือเล่มใหม่ โดยเราใช้ Plugin Meta Bind เหมือนเดิม แต่เปลี่ยน Action ไปเรียก Templater ให้มันสร้าง Note ใหม่แล้ว Apply Template เลย ทำให้การสร้างหนังสือเล่มใหม่ทำได้ง่ายมาก ๆ ไม่ต้องไปสร้างไฟล์ กด Apply Template นั่นนี่ เหลือแค่กด New Book ก็จะได้ไฟล์ที่ Apple Template มาเรียบร้อยแล้ว พร้อมกรอกข้อมูลได้เลย สะดวกมาก ๆ
## Template
[Book Template](templates/book-template.template.md)
จากนั้นเป็น Subsection ของ Book Template เราก็จะลิงค์ไปที่ Template File ของเรา ที่เรายังเก็บไว้เป็นเพราะ หากเราต้องการแก้ Template เราสามารถกดเข้าไปที่ลิงค์ด้านบนแล้วแก้ได้เลย แทนที่จะต้องมานั่งหาใน Template Folder ให้ยุ่งยาก
## Find based on shelf
- [[Physical]](`$= dv.pages('"Books"').where(p => dv.func.contains(p.Shelf, dv.func.link("Physical"))).length`)
- [[Apple Book]](`$= dv.pages('"Books"').where(p => dv.func.contains(p.Shelf, dv.func.link("Apple Book"))).length`)
- [[Meb]](`$= dv.pages('"Books"').where(p => dv.func.contains(p.Shelf, dv.func.link("Meb"))).length`)
- [[Pinto]](`$= dv.pages('"Books"').where(p => dv.func.contains(p.Shelf, dv.func.link("Pinto"))).length`)
ถัดไปจะเป็นส่วนที่เราใช้ Browse หนังสือละ โดยเกณฑ์แรก เราจะแบ่งตาม Shelf หรือที่อยู่ของหนังสือ สำหรับเรา เราจะแบ่งออกเป็น 4 ส่วนคือ Physical หรือเล่มจริง และที่เหลืออีก 3 กลุ่ม เป็น E-Book Platform ต่าง ๆ เอา โดยเราจะลิงค์ไปที่ MOC ย่อยของแต่ละ Shelf ด้วย ด้านหลัง เราเขียน Inline ไว้ให้มันเข้าไปหาว่าใน Shelf ของเล่มไหนที่เป็นส่วนนั้น ๆ เราก็จะได้เป็นจำนวนของหนังสือในแต่ละ Shelf ออกมานั่นเอง
## Find based on genre
- [[Computer Science]] (`$= dv.pages("#book/computer-science").length`)
- [UX/UI](Books/genre/UX-UI) (`$= dv.pages("#book/ux-ui").length`)
- [[Personal Development]] (`$= dv.pages("#book/personal-development").length`)
- [[Literature]] (`$= dv.pages("#book/literature").length`)
อันถัดไปคล้าย ๆ กันคือ แบ่งตามแนวของหนังสือว่ามันเป็นหนังสืออะไรเช่น หนังสือนิยาย หรือหนังสือที่เกี่ยวกับ Computer Science เหมือนเดิม เราก็จะ Link กลับไปที่หน้า MOC ของหมวดนั้น ๆ แต่สิ่งที่แตกต่างคือ การนับจำนวนเล่มในแต่ละหมวด จะเห็นว่า เราเปลี่ยนคำสั่งไป ไม่เหมือนกับตอน หา Shelf เพราะเราเอาแนวของหนังสือใส่ไปเป็น Tag จึงทำให้หาได้ง่ายกว่า และ Naming Convention สำหรับหนังสือ เราจะขึ้นต้นด้วย "book/" แล้วตามด้วยแนวของหนังสือเล่มนั้นเสมอ เช่นหนังสือที่เขียวกับ Computer Science เราก็จะใส่ Tag เป็น "#book/computer-science" เอาแค่นั้นเลย
## Books
```dataview
TABLE Date, Title, Author, Shelf, tags as "Type"
FROM "Books" and !"Books/shelf" and !"Books/genre"
SORT file.name DESC
WHERE file.name != "Books MOC"
```
และ Subsection สุดท้าย เราเอาตารางหนังสือที่มีทั้งหมดโชว์ขึ้นมาให้หมดไปเลย แต่เราอยากให้ระวังสำหรับคนที่เก็บจำนวนหนังสือเยอะ ๆ ให้ใส่พวก Limit ไว้หน่อย ถ้าเอาไปเปิดเครื่องที่ CPU ไม่แรงจริง อาจจะต้องใช้เวลาเพิ่มขึ้นนิดหน่อยในการเปิดส่วนนี้
Searching Book
สำหรับการค้นหาหนังสือ เราใช้ Plugin ที่ชื่อว่า Omnisearch เป็น Plugin ที่ไม่ได้ใช้แค่กับการค้นหาหนังสือ แต่มันสามารถค้นหาอะไรก็ได้ใน Obsidian Vault ของเราได้เลย โดยเราสามารถเรียก Omnisearch Pane ได้จาก Command ได้ตรง ๆ เลย และเราสามารถพิมพ์ชื่อหนังสือ หรือคำบางอย่างที่เราเขียนอยู่ภายในไฟล์หนังสือของเราเข้าไปมันก็จะเจอได้ ใช่แล้ว มันคือ Full-Text Search ไม่ใช่แค่ชื่อหนังสือของเรานะ จริง ๆ เรารู้จักเพราะเราเอามาทำส่วนสำหรับจัดการ Reference ของเรานี่แหละ เพราะมัน Search ทะลวงเข้าไปใน PDF และ OCR จากรูปภาพได้เลย อย่างดุดันไม่เกรงใจใครเลยทีเดียว
แต่เพื่อความง่ายขึ้น เราสามารถตั้ง Hotkey ไว้ได้ เราจะตั้งเป็น Cmd + O เอาไว้ เหมือนเวลาเราจะเปิดไฟล์ ก็เปลี่ยนเป็นการค้นหาทั้ง Obsidian Vault แทน
```meta-bind-button
label: Search Book
icon: search
hidden: false
class: ""
tooltip: ""
id: "search"
style: default
actions:
- type: command
command: omnisearch:show-modal
```
หรืออยากทำให้แซ่บขึ้น เราสามารถทำปุ่มใน Meta Bind เพื่อเรียก OmniSearch ก็ได้เช่นกัน สามารถใส่เพิ่มเข้าไปในหน้า Book MOC ก็ได้ จะได้ดูสมบูรณ์ขึ้นหน่อย
เท่านี้มันยังไม่พอ ด้วยความที่ Obsidian Vault มันคือ 2nd Brain ของเราจริง ๆ มันต้อง Integrate หนักกว่านั้นอีก ให้เปิดหน้าแรก แล้วเป็นหน้า Search เหมือนเวลาเราเข้า Google แค่จากเดิมมัน Search จากเว็บ มันก็จะ Search จากสมองที่สองของเรานั่นเองด้วย Plugin ที่ชื่อว่า Home Tab แต่เราต้องบอกก่อนนะว่า ณ วันที่เขียน Developer ที่พัฒนาเขาไม่ได้ Maintain ต่อแล้วนะ ก็ต้องรอดูว่า ใครจะเอามาทำต่ออะไรยังไงมั้ยนะ ณ วันที่เขียนนี้ เราก็ยังใช้ได้อยู่ เราสามารถตั้งให้มันเปิดหน้า Home Tab ขึ้นมาเป็นหน้าแรกเมื่อเราเปิด Tab ใหม่ เพื่อให้เราสามารถค้นหาข้อมูลได้ทันทีแบบเร็ว ๆ ได้เลยละ เราชอบมาก ๆ
Shelf & Genre MOC
จากนั้น เราจะมาใส่ความสัมพันธ์ของ Shelf และ Genre เพื่อให้เราสามารถค้นหาหนังสือตาม Shelf และประเภทของหนังสือได้ง่ายมากขึ้น โดยการสร้างหน้า MOC ขึ้นมากัน
Layout หน้าของทั้ง Shelf และ Genre จะเหมือนกัน นั่นคือ การมีชื่อ และภายในวงเล็บเป็นจำนวนหนังสือที่อยู่ภายใต้ Shelf และ Genre นั้น ๆ ด้านล่างจะเป็นตาราง List หนังสือไล่ลงไปเรื่อย ๆ
# [[<%tp.file.title %>]] (`$= dv.pages('"Books"').where(p => dv.func.contains(p.Shelf, dv.func.link(dv.current().file.name))).length`)
```dataview
TABLE Date, Title, Author, tags
FROM "Books" and !"Books/shelf" and !"Books/genre"
SORT file.name DESC
WHERE file.name != "Books MOC" and contains(Shelf, link("{{tp.file.title}}"))
```
<% await tp.file.move("/Books/shelf/" + tp.file.title) %>
เริ่มจากหน้า Shelf MOC เราแค่เขียนให้ Templater เรียกชื่อไฟล์ออกมา ส่วนด้านในวงเล็บเราใช้ Dataview JS เขียนเป็นแบบ Inline เข้าไปหาว่าใน Folder ชื่อ Books มีไฟล์ที่ Shelf เป็นชื่อไฟล์อยู่กี่อัน ดังนั้น Script ในวงเล็บนี้เราทำมาให้เป็น Dynamic ตามชื่อไฟล์อยู่แล้ว ไม่ต้องไล่แก้เอาเองนะ มันไล่ไปตามชื่อไฟล์ได้เลย
ส่วนต่อมาเป็นตารางแสดงหนังสือที่อยู่ภายใน Shelf นั้น ๆ เราเขียน DQL ออกมาตามปกติ แค่ว่า ใส่เงื่อนไขว่า เราจะไม่นับหน้าที่อยู่ใน shelf และ genre หรือก็คือ MOC Folder ย่อย และเงื่อนไขที่ว่า ใน Shelf มันจะต้องประกอบด้วย Link ของชื่อไฟล์ หรือก็คือประกอบด้วย Shelf ตามชื่อไฟล์นั่นเอง
---
name:
---
# [[<%tp.file.title %>]] (`$= dv.pages("#book/" + dv.current().name).length`)
```dataview
TABLE Date, Title, Author, Shelf, tags
FROM "Books" and !"Books/shelf" and !"Books/genre"
SORT file.name DESC
WHERE file.name != "Books MOC" and contains(tags, "book/" + this.name)
```
<% await tp.file.move("/Books/genre/" + tp.file.title) %>
และฝั่งของ Genre ที่เราใช้ Tag เป็นตัวกำกับ ซึ่งมันจะต้องมีทั้งชื่อที่เราเข้าใจ และชื่อ Tag ที่เราใส่ ดังนั้นชื่อที่เราเข้าใจ เราก็แค่ใส่ผ่าน Templater จากชื่อไฟล์ ส่วนชื่อ Tag เราเอาใส่ไว้ใน Frontmatter ที่ชื่อว่า name เอา ส่วนตารางแสดงรายการหนังสือก็ทำเหมือนเดิม แค่แก้ตรง Where ให้ไปเช็ค tags แทนแค่นั้น
Workflow
การทำงานของ Book Database ตัวนี้คือ เราจะเริ่มจากหน้า Book MOC โดยเราอาจจะสร้างเป็นหน้า Home ที่มี Link มาหน้า MOC ต่าง ๆ จะได้เข้าถึงได้สะดวกมากขึ้นได้เช่นกัน
อย่างแรกคือ การเพิ่มหนังสือ เราสามารถกดที่ Create Book ที่อยู่ในหน้า Book MOC ได้เลย จากนั้น มันจะสร้างไฟล์ใหม่ให้เราเอง เราแค่ตั้งชื่อ จากนั้นก็ใส่พวกข้อมูลต่าง ๆ ใน Frontmatter ให้เรียบร้อยเท่านี้ก็เป็นอันจบ
และเมื่อเราอ่านจบ เราก็จะเข้าไปเปลี่ยนสถานะจาก Not started เป็น Completed สำหรับใครที่อยากจะทำระบบ Tracking ลงไปอีกว่า หนังสือที่เรามีดองอยู่เท่าไหร่ อ่านจบแล้วเท่าไหร่ นอกจากนั้น เรายังใช้กับการจด Journal ของเราได้ด้วย คือ เราสามารถ Refernece ไปที่หนังสือเล่มนั้น ๆ ได้จากการใช้ Link ก็จะช่วย Organise Idea ของเราได้ดีขึ้นอีกแรง
กับถ้าหนังสือเล่มนั้นมันมีเล่มต่อ เช่นนิยาย อาจจะมีเล่ม 2 ต่อจากนั้น เราจะเขียน Next แล้วใส่ Link ของเล่มต่อไปไว้ด้านล่าง เวลาถ้าเราอ่านเล่มนึงจบแล้ว เรามาเปลี่ยน Status เราจะได้เห็นว่ามีเล่มต่อไปนะ และสามารถกดเข้าไปดูได้ว่าเล่มต่อไปมันอยู่ที่ไหนยังไงต่อ
สรุป
Obsidian เป็นเครื่องมือที่ทรงพลังมาก ๆ สำหรับการเรียนรู้ของเรา และวันนี้เราได้ใช้มันเข้ามาแก้ปัญหาอีกอย่างในชีวิตคือการ ซื้อหนังสือซ้ำด้วยการทำระบบสำหรับจัดการหนังสือ เพื่อให้เราสามารถเช็คได้ว่า เรามีหนังสือเล่มนี้แล้วหรือยังก่อนจะซื้อ และเราสามารถค้นหาได้ด้วยว่า หนังสือของราอยู่ที่ไหน และที่สำคัญยังเป็น Knowledge Base ในการ Extract สิ่งที่เราได้จากหนังสือขึ้นมา เพื่อเชื่อมโยงกับการเรียนรู้ของเราได้อีก สำหรับใครที่เข้ามาอ่านจริง ๆ สามารถลง Plugin และก๊อป Snippet ของเราไปดัดแปลงใช้งานตามลักษณะการใช้งานของเราได้เลย แล้วเดี๋ยวในครั้งหน้าเราจะมาแชร์วิธีการจัดการพวก Academic Publication ที่เราอ่านเพื่อนำไปใช้ในการเขียนงานวิจัย บอกเลยว่าอันนั้นคือ Advance น่ากลัวกว่าหนังสือปกติเยอะมาก