all repos — snow-editor @ 65993a713586363acceab129af67161a5fbe71cc

small and cozy markdown, and orgmode editor

src/lib/org/parseDocument.js (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
import { getSettings, parse } from 'orga';
import { normalizeOrgContent } from './normalize.js';

function headlineTitle(headline) {
  const parts = [];
  for (const child of headline.children ?? []) {
    if (child.type === 'text' && child.value) {
      parts.push(child.value);
    }
  }
  return parts.join('').trim() || 'Untitled section';
}

function headlineLevel(headline) {
  const stars = headline.children?.find((child) => child.type === 'stars');
  if (stars?.level) return stars.level;
  if (stars?.value) return stars.value.length;
  return 1;
}

function headlineTodo(headline) {
  const todoNode = headline.children?.find((child) => child.type === 'todo');
  return todoNode?.keyword ?? null;
}

function headlineTags(headline) {
  const tagsNode = headline.children?.find((child) => child.type === 'tags');
  if (!tagsNode?.tags?.length) return [];
  return tagsNode.tags;
}

function walkSection(section, headings) {
  if (!section?.children) return;

  for (const child of section.children) {
    if (child.type === 'headline') {
      headings.push({
        level: headlineLevel(child),
        title: headlineTitle(child),
        todo: headlineTodo(child),
        tags: headlineTags(child),
        line: child.position?.start?.line ?? 1,
      });
    }
    if (child.type === 'section') {
      walkSection(child, headings);
    }
  }
}

export function parseOrgDocument(content) {
  if (!content?.trim()) {
    return {
      keywords: {},
      headings: [],
      title: null,
      author: null,
      todoKeywords: ['TODO', 'DONE'],
    };
  }

  const normalized = normalizeOrgContent(content);
  const settings = getSettings(normalized);
  const doc = parse(normalized);
  const headings = [];

  for (const child of doc.children ?? []) {
    if (child.type === 'section') {
      walkSection(child, headings);
    }
  }

  const titleSetting = settings.title;
  const authorSetting = settings.author;
  const todoSetting = settings.todo;

  let todoKeywords = ['TODO', 'DONE'];
  if (typeof todoSetting === 'string') {
    todoKeywords = todoSetting.split(/\s+/).filter(Boolean);
  } else if (Array.isArray(todoSetting)) {
    todoKeywords = todoSetting.flatMap((entry) => entry.split(/\s+/)).filter(Boolean);
  }

  return {
    keywords: settings,
    headings,
    title: typeof titleSetting === 'string' ? titleSetting : null,
    author: typeof authorSetting === 'string' ? authorSetting : null,
    todoKeywords,
  };
}