Link Search Menu Expand Document

Summary

These documents are a high level overview of the code inside Dendron. They are written in a type script like pseudo code. The code blocks are broken down into the following structure:

# {Function}  --> what is happening
## Summary    --> high level function call of what is happening
## Flows      --> pseudo code describing high level functions

### {Flow Entry}

- loc: ... --> where code is located. {package}/{basename}
- desc: ... --> description 

Pseudo Code

The code description is typescript-ish. The goal isn’t to write valid typescript but be a summarized version of the code. The syntax is evolving but here is a loosely documented starting point:

  • @: equivalent of this.
  • $: referencing a global variable
  • ...: code that is omitted because it would clutter the summary
  • =: magic equality,

The places where you see this convention being broken (eg. actual valid typescript) are areas where the author got lazy and just copied code into the summary. Try to not do this because that defeats the point of a summarized view. That being said, Dendron is all about being pragmatic and taking an iterative approach to things.

Initializing the Engine

Summary

  • engine.query
    • store.query
      • FileParser.parse
        allFiles {
          getFileMeta
          toNode
        }
        
      • NodeBuilder.buildNoteFromProps
        {
          root, childrenIds = getRoot
          nodes = [root]
          while nodes {
            toNote it
          }
        }
        

Flow

Initialize the engine

  • loc: engine-server/engine.ts
  • desc: initial query to index all notes
init {
  @query("**/*", "schema", {
    fullNode: false,
    initialQuery: true,
  });

  @query("**/*", "note", {
    fullNode: false,
    initialQuery: true,
  });
}

Query - Engine

  • loc: engine-server/engine.ts
  • desc: engine will query the store
async query(scope: Scope, queryString: string, opts?: QueryOpts) {
  opts = _.defaults(opts || {}, {
    fullNode: false
  });
  if (queryString === '**/*') {
    const data = await this.store.query(scope, '**/*', opts);
    this.refreshNodes(data.data);
  }
}

Query - Store

  • loc: engine-server/store.ts
    • FileStore.query
  • desc: store is swappable. currently, we only support FileStore
  if (@isQueryAll(queryString)) {
      noteProps = @_getNoteAll() {
          allFiles = getAllFiles({
              root: this.opts.root,
              include: ["*.md"]
          })
          return @files2Notes(allfiles) {
              fp = new FileParser({ errorOnEmpty: false })
              data = fp.parse(allFiles);
              return data.map(n => n.toRawProps());
          }
      }
      const data = new NodeBuilder().buildNoteFromProps(noteProps);
  }

Parse Files

  • loc: engine-server/parser.ts
  • details: read files
parse(data: string[]): Note[] {
    fileMetaDict: FileMetaDict = getFileMeta(data) {
      metaDict: FileMetaDict = {};
      _.forEach(fpaths, fpath => {
        { name } = path.parse(fpath);
        lvl = name.split(".").length;
        if (!_.has(metaDict, lvl)) {
          metaDict[lvl] = [];
        }
        metaDict[lvl].push({ prefix: name, fpath });
      });
      return metaDict;
    }
    ...
    root = fileMetaDict[1].find(n => n.fpath === "root.md") as FileMeta;
    const { node: rootNode } = this.toNode(root, [], store, {
      isRoot: true,
      errorOnBadParse: this.opts.errorOnBadParse
    }) as { node: Note };
    ...
    while (_.has(fileMetaDict, lvl)) {
        ...
        const { node, missing } = this.toNode(ent, prevNodes, store, {})
    }
}

toNode {
    noteProps = mdFile2NodeProps(path.join(store.opts.root, ent.fpath)) {
        const { data, content: body } = (matter.read(fpath, {}) as unknown) as {
        const { name: fname } = path.parse(fpath);
        const dataProps = DNodeRaw.createProps({ ...data, fname, body });
    }
    // missing
    if (!parent && !opts.isRoot) {
      missing = parentPath;
      if (opts.errorOnEmpty) {
        throw new Error(JSON.stringify(errorMsg));
      }
    }
    const note = new Note({ ...noteProps, parent, children: [] });
}

Build Nodes

  • loc: src/node.ts
buildNoteFromProps(nodes) {
    // initialize root
    out = []
    root, childrenIds = getRoot nodes {
        if root not in nodes
            throw DendronError // this gets caught in the engine
        return root
    }
    out.push root
    parentNodes = [root]
    nodeIds = childrenIds

    while nodeIds {
      nodeIds.map {
        nodeProps = nodes.find it.id
        @toNote nodeProps, parentNodes
      }
    }
}

toNote {
  note = new Note(..., children: [], parent: null)
  parent = find(parents)
  parent.addChild note
  ret
}

New Node

Flow

  • loc: engine-server/engine.ts
write(node){
  @store.write
  @updateNodes node
}

Updating a Node

Summary

Flow

  • loc: engine-server/engine.ts
updateNodes(nodes) {
  if node.type == 'schema' {
    ...
  } else {
    @_updateNote(nodes)
  }
}

_updateNote(nodes, opts) {
  addParent(nodes) if !opts.noAddParent
  @refreshNodes
}

Renaming a Note

Flow

  • engine-server/src/drivers/file/storev2.ts
renameNote opts {
  oldNote, oldLoc, newLoc := opts
  notesToChange = getNotesWithLinkTo(oldNote)
  notesToChange.forEach { n =>
    replaceLinks(n, from: oldLoc, to: newLoc)
  }
}
  • src/topics/markdown/utilsv2.ts
replaceLinks opts {
  remark = getRemark({replaceLinks: opts})
  return remark.process(opts.content).toString()
}

Deleting a Node

Summary

Flow

  • loc: engine-server/engine.ts
delete(idOrFname, mode, opts) {
  if (mode == 'note') {
    noteToDelete := @notes
  } else {
    // schemas
    ...
  }

  // delete from store
  if !opts.metaOnly {
    @store.delete(noteToDelete)
  }

  // remove from index
  @deleteFromNodes noteToDelete

  // if node has children , keep it in index as a stub
  if noteToDelete.children {
    noteToDelete.stub = true
    @refreshNotes noteToDelete
  } else {
    // remove node from parent 
    noteToDelete.parent.children.reject { noteToDelete } if noteToDelete.parent
  }



}

Parsing note references

Dendron allows you to reference content from other notes and embed them in your current note.

Currently, Dendron supports 3 types of references:

  • note references
  • block references
  • block range references

References have the following syntax

((
    ref: [[ NAME_OF_NOTE ]]                     # name of note, required
    #STARTING_HEADER                            # optional
    :#ENDING_HEADER                             # optional
))
  • NOTE: Dendron has a Copy Note Ref command that will copy the reference of the current note.

Summary

Flow

  • engine-server/src/topics/markdown/plugins/dendronRefsPlugin.ts
  • data:
dendronRefsPlugin({..., replaceRefs}) {
    // match note ref
    match := /^\(\((?<ref>[^)]+)\)\)/.match(text)

    stringify(link) := {
      body = read(join(root, link.name + ".md))
      bodyAST = getProcessor.parse(body)

      refRange = calculateRefRange(body, link)
      bodyAST.children = bodyAST.children.slice(refRange)
      out = getProcessor().stringify(bodyAST);
      ...
      if (renderWithOutline) {
        link = getProcessor()
          .use(replaceRef, {
            wikiLink2Html: true
          })
          .process(link)
        return doRenderWithOutline(link, ...)
      } 
      return out
    }

}
  • engine-server/src/topics/markdown/plugins/replaceRefs.ts
replaceRefs(node) {
  if node.type == 'wikilink' {

  }
}

  • engine-server/src/utils.ts
parseDendronRef {
  ...
}

Reference

Refresh Node

  • loc: engine-server/src/engine.ts
refreshNodes(nodes) {
  ...
  type = nodes[0].type
  if type == schema {

  } else {
    ...
  }
  @updateLocalCollection nodes {
    @schemaFuse.setCollection nodes
  }
  @store.updateNodes nodes
}