OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note / lib / cli / commands / links.js

165 lines · javascript ref:
/**
 * hn links/backlinks/graph — Link management commands
 */

import { HnClient } from '../client.js'
import { table, log, error, jsonOut } from '../output.js'

export async function linksCommand (cmdArgs, flags) {
  const client = new HnClient(flags)
  const notebook = cmdArgs[0]
  const noteId = cmdArgs[1]

  if (!notebook || !noteId) {
    error('Usage: hn links <notebook> <noteId>')
  }

  const links = await client.get(`/api/notebooks/${enc(notebook)}/notes/${enc(noteId)}/links`)

  if (flags.json) {
    jsonOut(links)
    return
  }

  if (links.length === 0) {
    log('No outgoing links.')
    return
  }

  table(links, [
    { key: 'raw', label: 'LINK', width: 30 },
    { key: 'targetNotebook', label: 'NOTEBOOK', width: 16 },
    { key: 'title', label: 'TITLE', width: 30 },
    { key: 'resolved', label: 'RESOLVED', width: 8 }
  ])
}

export async function backlinksCommand (cmdArgs, flags) {
  const client = new HnClient(flags)
  const notebook = cmdArgs[0]
  const noteId = cmdArgs[1]

  if (!notebook || !noteId) {
    error('Usage: hn backlinks <notebook> <noteId>')
  }

  const backlinks = await client.get(`/api/notebooks/${enc(notebook)}/notes/${enc(noteId)}/backlinks`)

  if (flags.json) {
    jsonOut(backlinks)
    return
  }

  if (backlinks.length === 0) {
    log('No backlinks (nothing links to this note).')
    return
  }

  table(backlinks, [
    { key: 'sourceNotebook', label: 'NOTEBOOK', width: 16 },
    { key: 'sourceId', label: 'NOTE ID', width: 18 },
    { key: 'targetTitle', label: 'TARGET', width: 30 }
  ])
}

export async function graphCommand (cmdArgs, flags) {
  const client = new HnClient(flags)
  const notebook = cmdArgs[0]

  const path = notebook
    ? `/api/graph?notebook=${enc(notebook)}`
    : '/api/graph'

  const graph = await client.get(path)

  if (flags.json) {
    jsonOut(graph)
    return
  }

  log(`Nodes: ${graph.nodes.length}`)
  log(`Edges: ${graph.edges.length}`)
  log('')

  if (graph.nodes.length === 0) {
    log('No notes found.')
    return
  }

  // Print nodes
  for (const node of graph.nodes) {
    const links = graph.edges.filter(e => e.source === node.id)
    const backlinks = graph.edges.filter(e => e.target === node.id)
    const linkInfo = links.length || backlinks.length
      ? ` [${links.length} out, ${backlinks.length} in]`
      : ''
    log(`  ${node.notebook}/${node.noteId}  ${node.title}${linkInfo}`)
  }

  if (graph.edges.length > 0) {
    log('')
    log('Edges:')
    for (const edge of graph.edges) {
      log(`  ${edge.source} -> ${edge.target} (${edge.title})`)
    }
  }
}

export async function mergeCommand (cmdArgs, flags) {
  const client = new HnClient(flags)
  const notebook = cmdArgs[0]
  const noteId1 = cmdArgs[1]
  const noteId2 = cmdArgs[2]

  if (!notebook || !noteId1 || !noteId2) {
    error('Usage: hn merge <notebook> <noteId1> <noteId2> [--title "..."]')
  }

  const body = { noteId1, noteId2 }
  if (flags.title) body.title = flags.title

  const merged = await client.post(`/api/notebooks/${enc(notebook)}/merge`, body)

  if (flags.json) {
    jsonOut(merged)
    return
  }

  log(`Merged into: ${merged.id} (${merged.title})`)
}

export async function splitCommand (cmdArgs, flags) {
  const client = new HnClient(flags)
  const notebook = cmdArgs[0]
  const noteId = cmdArgs[1]

  if (!notebook || !noteId) {
    error('Usage: hn split <notebook> <noteId> [--delimiter "## "]')
  }

  const body = {}
  if (flags.delimiter) body.delimiter = flags.delimiter

  const result = await client.post(`/api/notebooks/${enc(notebook)}/notes/${enc(noteId)}/split`, body)

  if (flags.json) {
    jsonOut(result)
    return
  }

  if (!result.split) {
    log('Nothing to split (no heading boundaries found).')
    return
  }

  log(`Split into ${result.parts.length + 1} parts:`)
  log(`  Original note updated (first section)`)
  for (const part of result.parts) {
    log(`  New: ${part.id} — ${part.title}`)
  }
}

function enc (s) {
  return encodeURIComponent(s)
}