snow-editor

small markdown and org-mode editor
Log | Files | Refs | README

parseDocument.js (2460B)


      1 import { getSettings, parse } from 'orga';
      2 import { normalizeOrgContent } from './normalize.js';
      3 
      4 function headlineTitle(headline) {
      5   const parts = [];
      6   for (const child of headline.children ?? []) {
      7     if (child.type === 'text' && child.value) {
      8       parts.push(child.value);
      9     }
     10   }
     11   return parts.join('').trim() || 'Untitled section';
     12 }
     13 
     14 function headlineLevel(headline) {
     15   const stars = headline.children?.find((child) => child.type === 'stars');
     16   if (stars?.level) return stars.level;
     17   if (stars?.value) return stars.value.length;
     18   return 1;
     19 }
     20 
     21 function headlineTodo(headline) {
     22   const todoNode = headline.children?.find((child) => child.type === 'todo');
     23   return todoNode?.keyword ?? null;
     24 }
     25 
     26 function headlineTags(headline) {
     27   const tagsNode = headline.children?.find((child) => child.type === 'tags');
     28   if (!tagsNode?.tags?.length) return [];
     29   return tagsNode.tags;
     30 }
     31 
     32 function walkSection(section, headings) {
     33   if (!section?.children) return;
     34 
     35   for (const child of section.children) {
     36     if (child.type === 'headline') {
     37       headings.push({
     38         level: headlineLevel(child),
     39         title: headlineTitle(child),
     40         todo: headlineTodo(child),
     41         tags: headlineTags(child),
     42         line: child.position?.start?.line ?? 1,
     43       });
     44     }
     45     if (child.type === 'section') {
     46       walkSection(child, headings);
     47     }
     48   }
     49 }
     50 
     51 export function parseOrgDocument(content) {
     52   if (!content?.trim()) {
     53     return {
     54       keywords: {},
     55       headings: [],
     56       title: null,
     57       author: null,
     58       todoKeywords: ['TODO', 'DONE'],
     59     };
     60   }
     61 
     62   const normalized = normalizeOrgContent(content);
     63   const settings = getSettings(normalized);
     64   const doc = parse(normalized);
     65   const headings = [];
     66 
     67   for (const child of doc.children ?? []) {
     68     if (child.type === 'section') {
     69       walkSection(child, headings);
     70     }
     71   }
     72 
     73   const titleSetting = settings.title;
     74   const authorSetting = settings.author;
     75   const todoSetting = settings.todo;
     76 
     77   let todoKeywords = ['TODO', 'DONE'];
     78   if (typeof todoSetting === 'string') {
     79     todoKeywords = todoSetting.split(/\s+/).filter(Boolean);
     80   } else if (Array.isArray(todoSetting)) {
     81     todoKeywords = todoSetting.flatMap((entry) => entry.split(/\s+/)).filter(Boolean);
     82   }
     83 
     84   return {
     85     keywords: settings,
     86     headings,
     87     title: typeof titleSetting === 'string' ? titleSetting : null,
     88     author: typeof authorSetting === 'string' ? authorSetting : null,
     89     todoKeywords,
     90   };
     91 }