Add org-mode title extraction support (#50)

Refactor extractTitle to use extension-based extractors:
- .md: preserves original markdown logic (Notes skip behavior)
- .org: extracts from #+TITLE: property or first * heading

Extensions are lowercased for case-insensitive matching.
Easy to add more file types in the future.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
sh54 2026-01-26 17:38:49 +01:00 committed by GitHub
parent 65c0f89560
commit ba7391832d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -891,17 +891,36 @@ export async function hashContent(content: string): Promise<string> {
return hash.digest("hex");
}
export function extractTitle(content: string, filename: string): string {
const match = content.match(/^##?\s+(.+)$/m);
if (match) {
const title = (match[1] ?? "").trim();
if (title === "📝 Notes" || title === "Notes") {
const nextMatch = content.match(/^##\s+(.+)$/m);
if (nextMatch?.[1]) return nextMatch[1].trim();
const titleExtractors: Record<string, (content: string) => string | null> = {
'.md': (content) => {
const match = content.match(/^##?\s+(.+)$/m);
if (match) {
const title = (match[1] ?? "").trim();
if (title === "📝 Notes" || title === "Notes") {
const nextMatch = content.match(/^##\s+(.+)$/m);
if (nextMatch?.[1]) return nextMatch[1].trim();
}
return title;
}
return title;
return null;
},
'.org': (content) => {
const titleProp = content.match(/^#\+TITLE:\s*(.+)$/im);
if (titleProp?.[1]) return titleProp[1].trim();
const heading = content.match(/^\*+\s+(.+)$/m);
if (heading?.[1]) return heading[1].trim();
return null;
},
};
export function extractTitle(content: string, filename: string): string {
const ext = filename.slice(filename.lastIndexOf('.')).toLowerCase();
const extractor = titleExtractors[ext];
if (extractor) {
const title = extractor(content);
if (title) return title;
}
return filename.replace(/\.md$/, "").split("/").pop() || filename;
return filename.replace(/\.[^.]+$/, "").split("/").pop() || filename;
}
// =============================================================================