mdast#Heading TypeScript Examples

The following examples show how to use mdast#Heading. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 7 votes vote down vote up
/** Given a header, finds the text of that header, including any wikilinks or hashtags that are included in the header.
   *
   * For example, for the header `## Foo [[Bar|bar]] and #baz`, the text should be `Foo Bar and #baz`.
   */
  static headerText(header: Heading): string {
    const headerText: string[] = [];
    visit(header, (node) => {
      switch (node.type) {
        case DendronASTTypes.TEXT:
          headerText.push((node as Text).value);
          break;
        case DendronASTTypes.WIKI_LINK:
          headerText.push((node as WikiLinkNoteV4).data.alias);
          break;
        case DendronASTTypes.HASHTAG:
          headerText.push((node as HashTag).value);
          break;
        case DendronASTTypes.USERTAG:
          headerText.push((node as UserTag).value);
          break;
        case DendronASTTypes.INLINE_CODE:
          headerText.push((node as InlineCode).value);
          break;
        default:
        /* nothing */
      }
    });
    return _.trim(headerText.join(""));
  }
Example #2
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 7 votes vote down vote up
/** Given a header, finds the range of text that marks the contents of the header.
   *
   * For example, for the header `## Foo [[Bar|bar]] and #baz`, the range will start after `## ` and end at the end of the line.
   */
  static headerTextPosition(header: Heading): Position {
    let start: Point | undefined;
    let end: Point | undefined;
    visit(
      header,
      [
        DendronASTTypes.TEXT,
        DendronASTTypes.WIKI_LINK,
        DendronASTTypes.HASHTAG,
        DendronASTTypes.BLOCK_ANCHOR,
      ],
      (node) => {
        if (node.type === DendronASTTypes.BLOCK_ANCHOR && end) {
          // Preserve whitespace after the header, for example `# foo ^bar`, where
          // `^bar` must be separated with a space since it's not part of the header
          end.column -= 1;
          return;
        }
        if (_.isUndefined(start)) start = node.position!.start;
        end = node.position!.end;
      }
    );
    if (_.isUndefined(start) || _.isUndefined(end))
      throw new DendronError({
        message: "Unable to find the region of text containing the header",
      });

    return { start, end };
  }
Example #3
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 7 votes vote down vote up
/** Given a *parsed* anchor node, returns the anchor id ("header" or "^block" and positioned anchor object for it. */
  static anchorNode2anchor(
    node: Anchor,
    slugger: ReturnType<typeof getSlugger>
  ): [string, DNoteAnchorPositioned] | undefined {
    if (_.isUndefined(node.position)) return undefined;

    const { line, column } = node.position.start;
    if (node.type === DendronASTTypes.HEADING) {
      const headerNode = node as Heading;
      const text = this.headerText(headerNode);
      const value = slugger.slug(this.headerText(headerNode));
      return [
        value,
        {
          type: "header",
          text,
          value,
          line: line - 1,
          column: column - 1,
          depth: headerNode.depth,
        },
      ];
    } else if (node.type === DendronASTTypes.BLOCK_ANCHOR) {
      return [
        `^${node.id}`,
        {
          type: "block",
          value: node.id,
          line: line - 1,
          column: column - 1,
        },
      ];
    } else {
      assertUnreachable(node);
    }
  }
Example #4
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 7 votes vote down vote up
static bumpHeadings(root: Parent, baseDepth: number) {
    const headings: Heading[] = [];
    walk(root, (node: Node) => {
      if (node.type === DendronASTTypes.HEADING) {
        headings.push(node as Heading);
      }
    });

    const minDepth = headings.reduce((memo, h) => {
      return Math.min(memo, h.depth);
    }, MAX_HEADING_DEPTH);

    const diff = baseDepth + 1 - minDepth;

    headings.forEach((h) => {
      h.depth += diff;
    });
  }
Example #5
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 7 votes vote down vote up
static findAnchors(content: string): Anchor[] {
    const parser = MDUtilsV5.procRehypeParse({
      mode: ProcMode.NO_DATA,
    });
    const parsed = parser.parse(content);
    return [
      ...(selectAll(DendronASTTypes.HEADING, parsed) as Heading[]),
      ...(selectAll(DendronASTTypes.BLOCK_ANCHOR, parsed) as BlockAnchor[]),
    ];
  }
Example #6
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 7 votes vote down vote up
static isHeading(node: Node, text: string, depth?: number): node is Heading {
    if (node.type !== DendronASTTypes.HEADING) {
      return false;
    }

    // wildcard is always true
    if (text === "*") {
      return true;
    }
    if (text) {
      const headingText = toString(node);
      return text.trim().toLowerCase() === headingText.trim().toLowerCase();
    }

    if (depth) {
      return (node as Heading).depth <= depth;
    }

    return true;
  }
Example #7
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 7 votes vote down vote up
static h1ToTitle(note: NoteProps, changes: NoteChangeEntry[]) {
    const prevNote = { ...note };
    return function (this: Processor) {
      return (tree: Node, _vfile: VFile) => {
        const root = tree as Root;
        const idx = _.findIndex(
          root.children,
          (ent) => ent.type === DendronASTTypes.HEADING && ent.depth === 1
        );
        if (idx >= 0) {
          const head = root.children.splice(idx, 1)[0] as Heading;
          if (head.children.length === 1 && head.children[0].type === "text") {
            note.title = head.children[0].value;
          }
          changes.push({
            note,
            prevNote,
            status: "update",
          });
        }
      };
    };
  }
Example #8
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 7 votes vote down vote up
static h1ToH2(note: NoteProps, changes: NoteChangeEntry[]) {
    const prevNote = { ...note };
    return function (this: Processor) {
      return (tree: Node, _vfile: VFile) => {
        const root = tree as Root;
        const idx = _.findIndex(
          root.children,
          (ent) => ent.type === DendronASTTypes.HEADING && ent.depth === 1
        );
        if (idx >= 0) {
          const head = root.children[idx] as Heading;
          head.depth = 2;
          changes.push({
            note,
            prevNote,
            status: "update",
          });
        }
      };
    };
  }
Example #9
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 7 votes vote down vote up
static matchHeading(
    node: Node,
    text: string,
    opts: { depth?: number; slugger: ReturnType<typeof getSlugger> }
  ) {
    const { depth, slugger } = opts;
    if (node.type !== DendronASTTypes.HEADING) {
      return false;
    }

    // wildcard is always true
    if (text === "*") {
      return true;
    }

    if (text) {
      const headingText = toString(node);
      return text.trim().toLowerCase() === slugger.slug(headingText.trim());
    }

    if (depth) {
      return (node as Heading).depth <= depth;
    }

    return true;
  }
Example #10
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
/**
   * Given a markdown AST and a target heading node,
   * Find all the node that belongs under the heading.
   * This will extract all nodes until it hits the next heading
   * with the same depth of the target heading.
   * @param tree Abstract syntax tree
   * @param targetHeader Heading to target
   * @returns nodes to extract
   */
  static extractHeaderBlock(tree: Node, targetHeader: Heading) {
    let headerFound = false;
    let foundHeaderIndex: number | undefined;
    let nextHeaderIndex: number | undefined;
    visit(tree, (node, index) => {
      if (nextHeaderIndex) {
        return;
      }
      // @ts-ignore
      const depth = node.depth as Heading["depth"];
      if (!headerFound) {
        if (node.type === DendronASTTypes.HEADING) {
          if (
            depth === targetHeader!.depth &&
            RemarkUtils.hasIdenticalChildren(node, targetHeader!)
          ) {
            headerFound = true;
            foundHeaderIndex = index;
            return;
          }
        }
      } else if (node.type === DendronASTTypes.HEADING) {
        if (foundHeaderIndex) {
          if (depth <= targetHeader!.depth) nextHeaderIndex = index;
        }
      }
    });

    if (!headerFound || !RemarkUtils.isParent(tree)) {
      return [];
    }
    const nodesToExtract = nextHeaderIndex
      ? tree.children.splice(
          foundHeaderIndex!,
          nextHeaderIndex! - foundHeaderIndex!
        )
      : tree.children.splice(foundHeaderIndex!);
    return nodesToExtract;
  }
Example #11
Source File: utils.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
/** Extract all blocks from the note which could be referenced by a block anchor.
   *
   * If those blocks already have anchors (or if they are a header), this will also find that anchor.
   *
   * @param note The note from which blocks will be extracted.
   */
  static async extractBlocks({
    note,
    engine,
  }: {
    note: NoteProps;
    engine: DEngineClient;
  }): Promise<NoteBlock[]> {
    const proc = MDUtilsV5.procRemarkFull({
      engine,
      vault: note.vault,
      fname: note.fname,
      dest: DendronASTDest.MD_DENDRON,
    });
    const slugger = getSlugger();

    // Read and parse the note
    const noteText = NoteUtils.serialize(note);
    const noteAST = proc.parse(noteText);
    // @ts-ignore
    if (_.isUndefined(noteAST.children)) return [];
    // @ts-ignore
    const nodesToSearch = _.filter(noteAST.children as Node[], (node) =>
      _.includes(NODE_TYPES_TO_EXTRACT, node.type)
    );

    // Extract the blocks
    const blocks: NoteBlock[] = [];
    for (const node of nodesToSearch) {
      // Block anchors at top level refer to the blocks before them
      if (node.type === DendronASTTypes.PARAGRAPH) {
        // These look like a paragraph...
        const parent = node as Paragraph;
        if (parent.children.length === 1) {
          // ... that has only a block anchor in it ...
          const child = parent.children[0] as Node;
          if (child.type === DendronASTTypes.BLOCK_ANCHOR) {
            // ... in which case this block anchor refers to the previous block, if any
            const previous = _.last(blocks);
            if (!_.isUndefined(previous))
              [, previous.anchor] =
                AnchorUtils.anchorNode2anchor(child as BlockAnchor, slugger) ||
                [];
            // Block anchors themselves are not blocks, don't extract them
            continue;
          }
        }
      }

      // Extract list items out of lists. We also extract them from nested lists,
      // because block anchors can't refer to nested lists, only items inside of them
      if (node.type === DendronASTTypes.LIST) {
        visit(node, [DendronASTTypes.LIST_ITEM], (listItem: ListItem) => {
          // The list item might have a block anchor inside of it.
          let anchor: DNoteAnchorPositioned | undefined;
          visit(
            listItem,
            [DendronASTTypes.BLOCK_ANCHOR, DendronASTTypes.LIST],
            (inListItem) => {
              // Except if we hit a nested list, because then the block anchor refers to the item in the nested list
              if (inListItem.type === DendronASTTypes.LIST) return "skip";
              [, anchor] =
                AnchorUtils.anchorNode2anchor(
                  inListItem as BlockAnchor,
                  slugger
                ) || [];
              return;
            }
          );

          blocks.push({
            text: proc.stringify(listItem),
            anchor,
            // position can only be undefined for generated nodes, not for parsed ones
            position: listItem.position!,
            type: listItem.type,
          });
        });
      }

      // extract the anchor for this block, if it exists
      let anchor: DNoteAnchorPositioned | undefined;
      if (node.type === DendronASTTypes.HEADING) {
        // Headings are anchors themselves
        [, anchor] =
          AnchorUtils.anchorNode2anchor(node as Heading, slugger) || [];
      } else if (node.type !== DendronASTTypes.LIST) {
        // Other nodes might have block anchors inside them
        // Except lists, because anchors inside lists only refer to specific list items
        visit(node, [DendronASTTypes.BLOCK_ANCHOR], (child) => {
          [, anchor] =
            AnchorUtils.anchorNode2anchor(child as BlockAnchor, slugger) || [];
        });
      }

      // extract the block
      blocks.push({
        text: proc.stringify(node),
        anchor,
        // position can only be undefined for generated nodes, not for parsed ones
        position: node.position!,
        type: node.type,
      });
    }

    return blocks;
  }