mdast#Root TypeScript Examples

The following examples show how to use mdast#Root. 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
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 #2
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 #3
Source File: remarkTransclusion.ts    From vite-plugin-mdx with MIT License 6 votes vote down vote up
function findMdxImports(ast: import('mdast').Root) {
  const imports: ParsedImport[] = []
  ast.children.forEach((node: Node, index) => {
    // "import" type is used by @mdx-js/[email protected] and under
    if (node.type === 'mdxjsEsm' || node.type === 'import') {
      const id = importRE.exec(node.value as string)?.[1]
      if (id && mdxRE.test(id)) {
        imports.push({ id, node, index })
      }
    }
  })
  return imports
}
Example #4
Source File: remarkTransclusion.ts    From vite-plugin-mdx with MIT License 5 votes vote down vote up
export function remarkTransclusion({
  resolve,
  readFile,
  getCompiler,
  importMap,
  astCache
}: {
  resolve(id: string, importer?: string): Promise<string | undefined>
  readFile(filePath: string): Promise<string>
  getCompiler(filePath: string): Processor
  importMap?: ImportMap
  astCache?: MdxAstCache
}): () => Transformer {
  return () => async (ast, file) => {
    if (!isRootNode(ast)) return

    const importer = file.path!
    importMap?.deleteImporter(importer)

    const imports = findMdxImports(ast)
    if (imports.length) {
      type Splice = [index: number, deleteCount: number, inserted: any[]]

      const splices = await Promise.all(
        imports.map(
          async ({ id, index }): Promise<Splice> => {
            const importedPath = await resolve(id, importer)
            if (!importedPath) {
              // Strip unresolved imports.
              return [index, 1, []]
            }
            importMap?.addImport(importedPath, importer)
            let ast = astCache?.get(importedPath)
            if (!ast) {
              const importedFile = {
                path: importedPath,
                contents: await readFile(importedPath)
              }
              const compiler = getCompiler(importedPath)
              const parsedFile = compiler.parse(importedFile)
              const compiledFile = await compiler.run(parsedFile, importedFile)
              ast = (compiledFile as Root).children
              astCache?.set(importedPath, ast)
            }
            // Inject the AST of the imported markdown.
            return [index, 1, ast]
          }
        )
      )

      // Apply splices in reverse to ensure preceding indices are stable.
      let { children } = ast
      for (const [index, deleteCount, inserted] of splices.reverse())
        children = children
          .slice(0, index)
          .concat(inserted, children.slice(index + deleteCount))

      ast.children = children
    }
  }
}
Example #5
Source File: remarkTransclusion.ts    From vite-plugin-mdx with MIT License 5 votes vote down vote up
function isRootNode(node: Node): node is import('mdast').Root {
  return node.type === 'root'
}
Example #6
Source File: case.ts    From reskript with MIT License 5 votes vote down vote up
stringifyNodesToMarkdown = (nodes: Content[]): string => {
    const root: Root = {
        type: 'root',
        children: nodes,
    };
    return serializer.stringify(root);
}
Example #7
Source File: create-types.ts    From website-docs with MIT License 5 votes vote down vote up
createExtraType = ({ actions }: CreatePagesArgs) => {
  const { createTypes, createFieldExtension } = actions

  const typeDefs = `
    """
    Markdown Node
    """
    type Mdx implements Node @dontInfer {
      frontmatter: Frontmatter
    }

    """
    Markdown Frontmatter
    """
    type Frontmatter {
      title: String!
      summary: String
      aliases: [String!]
      draft: Boolean
    }
  `

  createTypes(typeDefs)

  createFieldExtension({
    name: 'navigation',
    extend() {
      return {
        async resolve(
          mdxNode: any,
          args: unknown,
          context: unknown,
          info: any
        ) {
          if (mdxNode.nav) return mdxNode.nav
          const types = info.schema.getType('Mdx').getFields()
          const slug = await types['slug'].resolve(mdxNode, args, context, {
            fieldName: 'slug',
          })

          const mdxAST: Root = await types['mdxAST'].resolve(
            mdxNode,
            args,
            context,
            {
              fieldName: 'mdxAST',
            }
          )

          if (!slug.endsWith('TOC'))
            throw new Error(`unsupported query in ${slug}`)
          const { config } = generateConfig(slug)
          const res = mdxAstToToc(
            (mdxAST.children.find(node => node.type === 'list') as List)
              .children,
            config
          )
          mdxNode.nav = res
          return res
        },
      }
    },
  })
  createTypes(`
    type Mdx implements Node {
      navigation: JSON! @navigation
    }
  `)
}
Example #8
Source File: index.ts    From website-docs with MIT License 5 votes vote down vote up
module.exports = function ({
  markdownAST,
  markdownNode,
}: {
  markdownAST: Root
  markdownNode: { fileAbsolutePath: string }
}) {
  visit(markdownAST, (node: any) => {
    if (Array.isArray(node.children)) {
      node.children = node.children.flatMap((node: any) => {
        if (node.type === 'link' && !node.url.startsWith('#')) {
          const ele = node as Link

          if (ele.url.startsWith('http')) {
            return [
              {
                type: 'jsx',
                value: `<a href="${ele.url}" target="_blank" referrerPolicy="no-referrer-when-downgrade">`,
              },
              ...node.children,
              { type: 'jsx', value: '</a>' },
            ]
          } else {
            const urlSeg = ele.url.split('/')
            const fileName = urlSeg[urlSeg.length - 1].replace('.md', '')
            const path = markdownNode.fileAbsolutePath.endsWith('_index.md')
              ? fileName
              : '../' + fileName
            return [
              {
                type: 'jsx',
                value: `<Link to="${path}">`,
              },
              ...node.children,
              { type: 'jsx', value: '</Link>' },
            ]
          }
        }

        if (node.type === 'blockquote') {
          const ele = node as Blockquote
          const first = ele.children[0]
          if (
            first?.type === 'paragraph' &&
            first.children?.[0].type === 'strong' &&
            first.children[0].children?.[0].type === 'text'
          ) {
            const text = first.children[0].children[0].value
            switch (text) {
              case 'Note:':
              case '注意:':
              case 'Warning:':
              case '警告:':
              case 'Tip:':
              case '建议:':
              case 'Important:':
              case '重要:': {
                const children = node.children.slice(1)
                const jsx = textToJsx(text)
                return [
                  { type: 'jsx', value: `<${jsx}>` },
                  ...children,
                  { type: 'jsx', value: `</${jsx}>` },
                ]
              }

              default:
                return ele
            }
          }
          return ele
        }

        return node
      })
    }
  })
}
Example #9
Source File: index.ts    From website-docs with MIT License 5 votes vote down vote up
module.exports = ({
  markdownAST,
  markdownNode,
}: {
  markdownAST: Root
  markdownNode: { fileAbsolutePath: string }
}) =>
  visit(markdownAST, (node: any) => {
    if (Array.isArray(node.children)) {
      node.children = node.children.flatMap((node: any) => {
        if (node.type === 'code' && node.lang === 'ebnf+diagram') {
          node.lang = 'ebnf'
          try {
            const grammar = ebnfParser.parse(node.value, {
              context: {
                appendNodeToChoices,
                appendNodeToSequence,
                isRTLCapable,
              },
            })
            const diagrams = grammar
              .map(({ name, content }: any) => {
                const diagram = new rr.Diagram(toRailroad(content)).format(2)
                return `<dt>${name}</dt><dd>${diagram}</dd>`
              })
              .join('')

            return [
              { type: 'jsx', value: '<SyntaxDiagram>' },
              { type: 'html', value: `<dl>${diagrams}</dl>` },
              node,
              { type: 'jsx', value: '</SyntaxDiagram>' },
            ]
          } catch (e) {
            console.error('invalid EBNF', markdownNode.fileAbsolutePath)
          }
        }
        return node
      })
    }
  })
Example #10
Source File: backlinks.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
plugin: Plugin = function (this: Unified.Processor) {
  const proc = this;
  function transformer(tree: Node): void {
    const root = tree as Root;
    let fname: string;
    let vault: DVault;
    let dest: DendronASTDest;
    let insideNoteRef: boolean | undefined;
    let config: IntermediateDendronConfig;
    let engine: DEngineClient;

    if (MDUtilsV5.isV5Active(proc)) {
      ({ fname, vault, dest, insideNoteRef, config, engine } =
        MDUtilsV5.getProcData(proc));
    } else {
      ({ fname, vault, dest, insideNoteRef, config } =
        MDUtilsV4.getDendronData(proc));
      engine = MDUtilsV4.getEngineFromProc(proc).engine;
    }

    // Don't show backlinks for the following cases:
    // - we are inside a note ref
    // - the destination isn't HTML
    // - the note can't be found
    // - neableChild links is toggled off
    // enableBackLinks is set to false
    if (!fname || insideNoteRef) {
      return;
    }
    if (dest !== DendronASTDest.HTML) {
      return;
    }

    const note = NoteUtils.getNoteByFnameFromEngine({ fname, vault, engine });
    if (_.isUndefined(note)) {
      return;
    }

    if (
      ConfigUtils.getEnableBackLinks(config, {
        note,
        shouldApplyPublishingRules: MDUtilsV5.shouldApplyPublishingRules(proc),
      }) === false
    ) {
      return;
    }

    const backlinks = _.uniqBy(
      (note?.links || []).filter((ent) => ent.type === "backlink"),
      (ent) => ent.from.fname + (ent.from.vaultName || "")
    );

    const backlinksToPublish = _.filter(backlinks, (backlink) => {
      const vaultName = backlink.from.vaultName!;
      const vault = VaultUtils.getVaultByName({
        vaults: engine.vaults,
        vname: vaultName,
      })!;
      const note = NoteUtils.getNoteByFnameFromEngine({
        fname: backlink.from.fname!,
        engine,
        vault,
      });

      if (!note) {
        return false;
      }
      const out = SiteUtils.canPublish({
        note,
        engine,
        config: engine.config,
      });
      return out;
    });

    if (!_.isEmpty(backlinksToPublish)) {
      root.children.push({
        type: "thematicBreak",
      });
      root.children.push(u("strong", [{ type: "text", value: "Backlinks" }]));
      root.children.push(
        list(
          "unordered",
          backlinksToPublish.map((mdLink) => {
            let alias;
            const note = NoteUtils.getNoteByFnameFromEngine({
              fname: mdLink.from.fname!,
              vault: VaultUtils.getVaultByName({
                vaults: engine.vaults,
                vname: mdLink.from.vaultName!,
              })!,
              engine,
            });

            if (note) {
              alias =
                note.title +
                (engine.vaults.length > 1
                  ? ` (${mdLink.from.vaultName!})`
                  : "");
            } else {
              alias = `Unable to find backlinked note ${mdLink.from.fname!}.`;
            }
            return listItem(
              paragraph({
                type: DendronASTTypes.WIKI_LINK,
                value: mdLink.from.fname,
                data: {
                  alias,
                  vaultName: mdLink.from.vaultName!,
                },
                children: [],
              } as WikiLinkNoteV4)
            );
          })
        ) as Content
      );
    }

    // end transformer
  }
  return transformer;
}
Example #11
Source File: dendronPub.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
function plugin(this: Unified.Processor, opts?: PluginOpts): Transformer {
  const proc = this;
  let { overrides, vault } = MDUtilsV4.getDendronData(proc);
  const pOpts = MDUtilsV5.getProcOpts(proc);
  const { mode } = pOpts;
  const pData = MDUtilsV5.getProcData(proc);
  const { dest, fname, config, insideNoteRef } = pData;

  function transformer(tree: Node, _file: VFile) {
    const root = tree as Root;
    const { error: engineError, engine } = MDUtilsV4.getEngineFromProc(proc);
    const insertTitle = !_.isUndefined(overrides?.insertTitle)
      ? overrides?.insertTitle
      : opts?.insertTitle;
    if (mode !== ProcMode.IMPORT && !insideNoteRef && root.children) {
      if (!fname || !vault) {
        // TODO: tmp
        throw new DendronError({
          message: `dendronPub - no fname or vault for node: ${JSON.stringify(
            tree
          )}`,
        });
      }
      let note;

      // Special Logic for 403 Error Static Page:
      if (fname === "403") {
        note = SiteUtils.create403StaticNote({ engine });
      } else {
        note = NoteUtils.getNoteByFnameFromEngine({
          fname,
          vault,
          engine,
        });
      }

      if (!note) {
        throw new DendronError({ message: `no note found for ${fname}` });
      }
      if (insertTitle) {
        const idx = _.findIndex(root.children, (ent) => ent.type !== "yaml");
        root.children.splice(
          idx,
          0,
          u(DendronASTTypes.HEADING, { depth: 1 }, [u("text", note.title)])
        );
      }
    }
    visitParents(tree, (node, ancestors) => {
      const parent = _.last(ancestors);
      if (_.isUndefined(parent) || !RemarkUtils.isParent(parent)) return; // root node
      if (node.type === DendronASTTypes.HASHTAG) {
        const hashtag = node as HashTag;
        const parentIndex = _.findIndex(parent.children, node);
        if (parentIndex === -1) return;
        // For hashtags, convert them to regular links for rendering
        // but not if they are inside of a link, otherwise they break link rendering.
        if (!ancestors.some((node) => RemarkUtils.isLink(node))) {
          node = hashTag2WikiLinkNoteV4(hashtag);
        } else {
          // If they are inside a link, rendering them as wikilinks will break the link rendering. Convert them to regular text.
          node = text(hashtag.value);
        }
        parent.children[parentIndex] = node;
      }
      if (node.type === DendronASTTypes.USERTAG) {
        const userTag = node as UserTag;
        const parentIndex = _.findIndex(parent.children, node);
        if (parentIndex === -1) return;
        // Convert user tags to regular links for rendering
        // but not if they are inside of a link, otherwise they break link rendering.
        if (!ancestors.some((node) => RemarkUtils.isLink(node))) {
          node = userTag2WikiLinkNoteV4(userTag);
        } else {
          node = text(userTag.value);
        }
        parent.children[parentIndex] = node;
      }
      if (
        node.type === DendronASTTypes.WIKI_LINK &&
        dest !== DendronASTDest.MD_ENHANCED_PREVIEW
      ) {
        // If the target is Dendron, no processing of links is needed
        if (dest === DendronASTDest.MD_DENDRON) return;
        const _node = node as WikiLinkNoteV4;
        // @ts-ignore
        let value = node.value as string;
        // we change this later
        const valueOrig = value;
        let isPublished = true;
        const data = _node.data;
        vault = MDUtilsV4.getVault(proc, data.vaultName, {
          vaultMissingBehavior: VaultMissingBehavior.FALLBACK_TO_ORIGINAL_VAULT,
        });
        if (engineError) {
          addError(proc, engineError);
        }

        let error: DendronError | undefined;
        let note: NoteProps | undefined;
        if (mode !== ProcMode.IMPORT) {
          note = NoteUtils.getNoteByFnameFromEngine({
            fname: valueOrig,
            vault,
            engine,
          });

          if (!note) {
            error = new DendronError({ message: `no note found. ${value}` });
          }
        }

        let color: string | undefined;
        if (mode !== ProcMode.IMPORT && value.startsWith(TAGS_HIERARCHY)) {
          const { color: maybeColor, type: colorType } = NoteUtils.color({
            fname: value,
            vault,
            engine,
          });
          const enableRandomlyColoredTagsConfig =
            ConfigUtils.getEnableRandomlyColoredTags(config);
          if (
            colorType === "configured" ||
            (enableRandomlyColoredTagsConfig && !opts?.noRandomlyColoredTags)
          ) {
            color = maybeColor;
          }
        }

        const copts = opts?.wikiLinkOpts;
        if (!note && opts?.transformNoPublish) {
          const code = StatusCodes.FORBIDDEN;
          value = _.toString(code);
          addError(
            proc,
            new DendronError({
              message: "no note",
              code,
              severity: ERROR_SEVERITY.MINOR,
            })
          );
        } else if (note && opts?.transformNoPublish) {
          if (error) {
            value = _.toString(StatusCodes.FORBIDDEN);
            addError(proc, error);
          } else if (!config) {
            const code = StatusCodes.FORBIDDEN;
            value = _.toString(code);
            addError(
              proc,
              new DendronError({
                message: "no config",
                code,
                severity: ERROR_SEVERITY.MINOR,
              })
            );
          } else {
            isPublished = SiteUtils.isPublished({
              note,
              config,
              engine,
            });
            if (!isPublished) {
              value = _.toString(StatusCodes.FORBIDDEN);
            }
          }
        }

        let useId = copts?.useId;
        if (
          useId === undefined &&
          MDUtilsV5.isV5Active(proc) &&
          dest === DendronASTDest.HTML
        ) {
          useId = true;
        }

        if (note && useId && isPublished) {
          if (error) {
            addError(proc, error);
          } else {
            value = note.id;
          }
        }
        const alias = data.alias ? data.alias : value;
        const href = SiteUtils.getSiteUrlPathForNote({
          addPrefix: pOpts.flavor === ProcFlavor.PUBLISHING,
          pathValue: value,
          config,
          pathAnchor: data.anchorHeader,
        });
        const exists = true;
        // for rehype
        //_node.value = newValue;
        //_node.value = alias;

        const { before, after } = linkExtras({ note, config });

        _node.data = {
          vaultName: data.vaultName,
          alias,
          permalink: href,
          exists,
          hName: "a",
          hProperties: {
            className: color ? "color-tag" : undefined,
            style: color ? `--tag-color: ${color};` : undefined,
            href,
          },
          hChildren: [
            ...before,
            {
              type: "text",
              value: alias,
            },
            ...after,
          ],
        } as RehypeLinkData;

        if (value === "403") {
          _node.data = {
            alias,
            hName: "a",
            hProperties: {
              title: "Private",
              href: "https://wiki.dendron.so/notes/hfyvYGJZQiUwQaaxQO27q.html",
              target: "_blank",
              class: "private",
            },
            hChildren: [
              {
                type: "text",
                value: `${alias} (Private)`,
              },
            ],
          } as RehypeLinkData;
        }
      }
      if (node.type === DendronASTTypes.REF_LINK_V2) {
        // If the target is Dendron, no processing of refs is needed
        if (dest === DendronASTDest.MD_DENDRON) return;
        // we have custom compiler for markdown to handle note ref
        const ndata = node.data as NoteRefDataV4;
        const copts: NoteRefsOptsV2 = {
          wikiLinkOpts: opts?.wikiLinkOpts,
        };
        const procOpts = MDUtilsV4.getProcOpts(proc);
        const { data } = convertNoteRefASTV2({
          link: ndata.link,
          proc,
          compilerOpts: copts,
          procOpts,
        });

        if (data) {
          parent.children = replacedUnrenderedRefWithConvertedData(
            data,
            parent.children
          );
        }
      }
      if (node.type === DendronASTTypes.BLOCK_ANCHOR) {
        // no transform
        if (dest !== DendronASTDest.HTML) {
          return;
        }
        const anchorHTML = blockAnchor2html(node as BlockAnchor);
        let target: Node | undefined;
        const grandParent = ancestors[ancestors.length - 2];
        if (
          RemarkUtils.isParagraph(parent) &&
          parent.children.length === 1 &&
          isNotUndefined(grandParent) &&
          RemarkUtils.isRoot(grandParent)
        ) {
          // If the block anchor is at the top level, then it references the block before it
          const parentIndex = _.indexOf(grandParent.children, parent);
          const previous = grandParent.children[parentIndex - 1];
          if (_.isUndefined(previous)) {
            // Block anchor at the very start of the note, just add anchor to the start
            target = grandParent;
          } else {
            // There's an actual block before the anchor
            target = previous;
          }
        } else if (RemarkUtils.isTableRow(grandParent)) {
          // An anchor inside a table references the whole table.
          const greatGrandParent = ancestors[ancestors.length - 3];
          if (
            isNotUndefined(greatGrandParent) &&
            RemarkUtils.isTable(greatGrandParent)
          ) {
            // The table HTML generation drops anything not attached to a cell, so we put this in the first cell instead.
            target = greatGrandParent.children[0]?.children[0];
          }
        } else {
          // Otherwise, it references the block it's inside
          target = parent;
        }

        if (_.isUndefined(target)) return;
        if (RemarkUtils.isList(target)) {
          // Can't install as a child of the list, has to go into a list item
          target = target.children[0];
        }
        if (RemarkUtils.isTable(target)) {
          // Can't install as a child of the table directly, has to go into a table cell
          target = target.children[0].children[0];
        }

        if (RemarkUtils.isParent(target)) {
          // Install the block anchor at the target node
          target.children.unshift(anchorHTML);
        } else if (RemarkUtils.isRoot(target)) {
          // If the anchor is the first thing in the note, anchorHTML goes to the start of the document
          target.children.unshift(anchorHTML);
        } else if (RemarkUtils.isParent(grandParent)) {
          // For some elements (for example code blocks) we can't install the block anchor on them.
          // In that case we at least put a link before the element so that the link will at least work.
          const targetIndex = _.indexOf(grandParent.children, target);
          const targetWrapper = paragraph([
            anchorHTML,
            grandParent.children[targetIndex],
          ]);
          grandParent.children.splice(targetIndex, 1, targetWrapper);
        }
        // Remove the block anchor itself since we install the anchor at the target
        const index = _.indexOf(parent.children, node);
        parent!.children.splice(index, 1);

        // We might be adding and removing siblings here. We must return the index of the next sibling to traverse.
        if (target === parent) {
          // In this case, we removed block anchor but added a node to the start.
          // As a result, the indices match and traversal can continue.
          return;
        } else if (parent.children.length === 0) {
          // After removing the block anchor, there are no siblings left in the parent to traverse.
          return -1;
        } else {
          // Otherwise, the next sibling got shifted down by 1 index, it will be at the same index as the block anchor.
          return index;
        }
      }
      // The url correction needs to happen for both regular and extended images
      if (ImageNodeHandler.match(node, { pData, pOpts })) {
        const { nextAction } = ImageNodeHandler.handle(node as Image, {
          proc,
          parent,
          cOpts: opts,
        });
        if (nextAction) {
          return nextAction;
        }
      }
      if (
        node.type === DendronASTTypes.EXTENDED_IMAGE &&
        dest === DendronASTDest.HTML
      ) {
        const index = _.indexOf(parent.children, node);
        // Replace with the HTML containing the image including custom properties
        parent.children.splice(
          index,
          1,
          extendedImage2html(node as ExtendedImage)
        );
      }
      return; // continue traversal
    });
    return tree;
  }
  return transformer;
}
Example #12
Source File: hierarchies.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
plugin: Plugin = function (this: Unified.Processor, _opts?: PluginOpts) {
  const proc = this;
  const { config } = MDUtilsV5.getProcData(this);
  let hierarchyDisplayTitle = config?.hierarchyDisplayTitle || "Children";
  let hierarchyDisplay = config?.hierarchyDisplay;

  if (MDUtilsV5.shouldApplyPublishingRules(proc)) {
    const hierarchyConfigForPublishing =
      ConfigUtils.getHierarchyDisplayConfigForPublishing(config);
    hierarchyDisplay = hierarchyConfigForPublishing.hierarchyDisplay;
    if (!_.isUndefined(hierarchyConfigForPublishing.hierarchyDisplayTitle)) {
      hierarchyDisplayTitle =
        hierarchyConfigForPublishing.hierarchyDisplayTitle;
    }
  }

  if (hierarchyDisplay === undefined) hierarchyDisplay = true;

  function transformer(tree: Node): void {
    const root = tree as Root;
    const { fname, vault, dest, config, insideNoteRef } =
      MDUtilsV4.getDendronData(proc);
    let addedBreak = false;

    if (dest !== DendronASTDest.HTML) {
      return;
    }
    // TODO: remove
    if (!hierarchyDisplay) {
      return;
    }

    function addBreak() {
      if (addedBreak) return;
      root.children.push({
        type: "thematicBreak",
      });
      addedBreak = true;
    }

    function addFootnotes() {
      /** Maps footnote identifiers to their definitions. */
      const footnotes = new Map(
        RemarkUtils.extractFootnoteDefs(root).map((definition) => [
          definition.identifier,
          definition,
        ])
      );
      /** All footnote definitions that have been referenced in this document. */
      const usedFootnotes = new Set<FootnoteDefinition>();
      visit(
        root,
        [DendronASTTypes.FOOTNOTE_REFERENCE],
        (reference: FootnoteReference, index, parent) => {
          const definition = footnotes.get(reference.identifier);
          if (definition && parent) {
            parent.children[index] = footnote2html(reference);
            usedFootnotes.add(definition);
          }
        }
      );
      if (usedFootnotes.size > 0) {
        addBreak();
        root.children.push(heading(2, text("Footnotes")) as Content);
        const footnoteItems: Node[] = [];
        for (const definition of usedFootnotes) {
          footnoteItems.push(listItem(footnoteDef2html(definition)));
        }
        root.children.push(list("ordered", footnoteItems) as Content);
      }
    }

    if (!fname || insideNoteRef) {
      // Even inside a note ref, render footnotes because we want them in there too
      addFootnotes();
      return;
    }

    const { engine } = MDUtilsV4.getEngineFromProc(proc);
    const note = NoteUtils.getNoteByFnameFromEngine({
      fname,
      engine,
      vault: vault!,
    });

    // check if v5 is active
    if (MDUtilsV5.isV5Active(proc)) {
      const resp = MDUtilsV5.getProcData(proc);
      hierarchyDisplay = ConfigUtils.getEnableChildLinks(resp.config, { note });
    }

    /** Add frontmatter tags, if any, ahead of time. This way wikilink compiler will pick them up and render them. */
    function addTags() {
      const shouldApplyPublishRules =
        MDUtilsV5.shouldApplyPublishingRules(proc);
      const enableFrontmatterTags = ConfigUtils.getEnableFrontmatterTags({
        config,
        shouldApplyPublishRules,
      });
      const enableHashesForFMTags = ConfigUtils.getEnableHashesForFMTags({
        config,
        shouldApplyPublishRules,
      });
      if (
        enableFrontmatterTags !== false &&
        note?.tags &&
        note.tags.length > 0
      ) {
        addBreak();
        root.children.push(heading(2, text("Tags")) as Content);
        const tags = _.isString(note.tags) ? [note.tags] : note.tags;
        const tagLinks = _.sortBy(
          _.map(tags, (tag) =>
            listItem(
              paragraph(
                frontmatterTag2WikiLinkNoteV4(tag, enableHashesForFMTags)
              )
            )
          ),
          ["custom.nav_order", "title"]
        );
        root.children.push(list("ordered", tagLinks) as Content);
      }
    }

    function addChildren() {
      // don't include if collection present
      if (!note || note.children.length <= 0 || note?.custom?.has_collection) {
        return;
      }
      if (
        _.isBoolean(note.custom?.hierarchyDisplay) &&
        !note.custom.hierarchyDisplay
      ) {
        return;
      }
      const children = HierarchyUtils.getChildren({
        skipLevels: note.custom?.skipLevels || 0,
        note,
        notes: engine.notes,
      })
        .filter((note) => SiteUtils.canPublish({ note, engine, config }))
        .filter(
          (note) =>
            _.isUndefined(note.custom?.nav_exclude) || !note.custom?.nav_exclude
        );

      if (!_.isEmpty(children)) {
        addBreak();
        root.children.push(
          u("strong", [{ type: "text", value: hierarchyDisplayTitle }])
        );
        root.children.push(
          list(
            "ordered",
            _.sortBy(children, ["custom.nav_order", "title"]).map((note) => {
              return listItem(
                paragraph({
                  type: DendronASTTypes.WIKI_LINK,
                  value: note.fname,
                  data: {
                    alias: note.title,
                    vaultName: VaultUtils.getName(note.vault),
                  },
                  children: [],
                } as WikiLinkNoteV4)
              );
            })
          ) as Content
        );
      }
    }

    // Will appear on page in this order
    if (hierarchyDisplay) {
      addChildren();
    }
    addTags();
    addFootnotes();

    // end transformer
  }
  return transformer;
}
Example #13
Source File: descriptionFormatter.ts    From prettier-plugin-jsdoc with MIT License 4 votes vote down vote up
/**
 * Trim, make single line with capitalized text. Insert dot if flag for it is
 * set to true and last character is a word character
 *
 * @private
 */
function formatDescription(
  tag: string,
  text: string,
  options: AllOptions,
  formatOptions: FormatOptions,
): string {
  if (!text) return text;

  const { printWidth } = options;
  const { tagStringLength = 0, beginningSpace } = formatOptions;

  /**
   * change list with dash to dot for example:
   * 1- a thing
   *
   * to
   *
   * 1. a thing
   */
  text = text.replace(/^(\d+)[-][\s|]+/g, "$1. "); // Start
  text = text.replace(/\n+(\s*\d+)[-][\s]+/g, "\n$1. ");

  const fencedCodeBlocks = text.matchAll(/```\S*?\n[\s\S]+?```/gm);
  const indentedCodeBlocks = text.matchAll(
    /^\r?\n^(?:(?:(?:[ ]{4}|\t).*(?:\r?\n|$))+)/gm,
  );
  const allCodeBlocks = [...fencedCodeBlocks, ...indentedCodeBlocks];
  const tables: string[] = [];
  text = text.replace(
    /((\n|^)\|[\s\S]*?)((\n[^|])|$)/g,
    (code, _1, _2, _3, _, offs: number) => {
      // If this potential table is inside a code block, don't touch it
      for (const block of allCodeBlocks) {
        if (
          block.index !== undefined &&
          block.index <= offs + 1 &&
          offs + code.length + 1 <= block.index + block[0].length
        ) {
          return code;
        }
      }

      code = _3 ? code.slice(0, -1) : code;

      tables.push(code);
      return `\n\n${TABLE}\n\n${_3 ? _3.slice(1) : ""}`;
    },
  );

  if (
    options.jsdocCapitalizeDescription &&
    !TAGS_PEV_FORMATE_DESCRIPTION.includes(tag)
  ) {
    text = capitalizer(text);
  }

  text = `${tagStringLength ? `${"!".repeat(tagStringLength - 1)}?` : ""}${
    text.startsWith("```") ? "\n" : ""
  }${text}`;

  let tableIndex = 0;

  const rootAst = fromMarkdown(text);

  function stringifyASTWithoutChildren(
    mdAst: Content | Root,
    intention: string,
    parent: Content | Root | null,
  ) {
    if (mdAst.type === "inlineCode") {
      return `\`${mdAst.value}\``;
    }

    if (mdAst.type === "code") {
      let result = mdAst.value || "";
      let _intention = intention;

      if (result) {
        // Remove two space from lines, maybe added previous format
        if (mdAst.lang) {
          const supportParsers = parserSynonyms(mdAst.lang.toLowerCase());
          const parser = supportParsers?.includes(options.parser as any)
            ? options.parser
            : supportParsers?.[0] || mdAst.lang;

          result = formatCode(result, intention, {
            ...options,
            parser,
            jsdocKeepUnParseAbleExampleIndent: true,
          });
        } else if (options.jsdocPreferCodeFences || false) {
          result = formatCode(result, _intention, {
            ...options,
            jsdocKeepUnParseAbleExampleIndent: true,
          });
        } else {
          _intention = intention + " ".repeat(4);

          result = formatCode(result, _intention, {
            ...options,
            jsdocKeepUnParseAbleExampleIndent: true,
          });
        }
      }
      const addFence = options.jsdocPreferCodeFences || !!mdAst.lang;
      result = addFence ? result : result.trimEnd();
      return result
        ? addFence
          ? `\n\n${_intention}\`\`\`${mdAst.lang || ""}${result}\`\`\``
          : `\n${result}`
        : "";
    }

    if ((mdAst as Text).value === TABLE) {
      if (parent) {
        (parent as any).costumeType = TABLE;
      }

      if (tables.length > 0) {
        let result = tables?.[tableIndex] || "";
        tableIndex++;
        if (result) {
          result = format(result, {
            ...options,
            parser: "markdown",
          }).trim();
        }
        return `${
          result
            ? `\n\n${intention}${result.split("\n").join(`\n${intention}`)}`
            : (mdAst as Text).value
        }`;
      }
    }

    if (mdAst.type === "break") {
      return `\\\n`;
    }

    return ((mdAst as Text).value ||
      (mdAst as Link).title ||
      (mdAst as Image).alt ||
      "") as string;
  }

  function stringyfy(
    mdAst: Content | Root,
    intention: string,
    parent: Content | Root | null,
  ): string {
    if (!Array.isArray((mdAst as Root).children)) {
      return stringifyASTWithoutChildren(mdAst, intention, parent);
    }

    return ((mdAst as Root).children as Content[])
      .map((ast, index) => {
        switch (ast.type) {
          case "listItem": {
            let _listCount = `\n${intention}- `;
            // .replace(/((?!(^))\n)/g, "\n" + _intention);
            if (typeof (mdAst as List).start === "number") {
              const count = index + (((mdAst as List).start as number) ?? 1);
              _listCount = `\n${intention}${count}. `;
            }

            const _intention = intention + " ".repeat(_listCount.length - 1);

            const result = stringyfy(ast, _intention, mdAst).trim();

            return `${_listCount}${result}`;
          }

          case "list": {
            let end = "";
            /**
             * Add empty line after list if that is end of description
             * issue: {@link https://github.com/hosseinmd/prettier-plugin-jsdoc/issues/98}
             */
            if (
              tag !== DESCRIPTION &&
              mdAst.type === "root" &&
              index === mdAst.children.length - 1
            ) {
              end = "\n";
            }
            return `\n${stringyfy(ast, intention, mdAst)}${end}`;
          }

          case "paragraph": {
            const paragraph = stringyfy(ast, intention, parent);
            if ((ast as any).costumeType === TABLE) {
              return paragraph;
            }

            return `\n\n${paragraph
              /**
               * Break by backslash\
               * issue: https://github.com/hosseinmd/prettier-plugin-jsdoc/issues/102
               */
              .split("\\\n")
              .map((_paragraph) => {
                const links: string[] = [];
                // Find jsdoc links and remove spaces
                _paragraph = _paragraph.replace(
                  /{@(link|linkcode|linkplain)[\s](([^{}])*)}/g,
                  (_, tag: string, link: string) => {
                    links.push(link);

                    return `{@${tag}${"_".repeat(link.length)}}`;
                  },
                );

                _paragraph = _paragraph.replace(/\s+/g, " "); // Make single line

                if (
                  options.jsdocCapitalizeDescription &&
                  !TAGS_PEV_FORMATE_DESCRIPTION.includes(tag)
                )
                  _paragraph = capitalizer(_paragraph);
                if (options.jsdocDescriptionWithDot)
                  _paragraph = _paragraph.replace(/([\w\p{L}])$/u, "$1."); // Insert dot if needed

                let result = breakDescriptionToLines(
                  _paragraph,
                  printWidth,
                  intention,
                );

                // Replace links
                result = result.replace(
                  /{@(link|linkcode|linkplain)([_]+)}/g,
                  (original: string, tag: string, underline: string) => {
                    const link = links[0];

                    if (link.length === underline.length) {
                      links.shift();
                      return `{@${tag} ${link}}`;
                    }

                    return original;
                  },
                );

                return result;
              })
              .join("\\\n")}`;
          }

          case "strong": {
            return `**${stringyfy(ast, intention, mdAst)}**`;
          }

          case "emphasis": {
            return `_${stringyfy(ast, intention, mdAst)}_`;
          }

          case "heading": {
            return `\n\n${intention}${"#".repeat(ast.depth)} ${stringyfy(
              ast,
              intention,
              mdAst,
            )}`;
          }

          case "link":
          case "image": {
            return `[${stringyfy(ast, intention, mdAst)}](${ast.url})`;
          }

          case "linkReference": {
            return `[${stringyfy(ast, intention, mdAst)}][${ast.label}]`;
          }
          case "definition": {
            return `\n\n[${ast.label}]: ${ast.url}`;
          }

          case "blockquote": {
            const paragraph = stringyfy(ast, "", mdAst);
            return `${intention}> ${paragraph
              .trim()
              .replace(/(\n+)/g, `$1${intention}> `)}`;
          }
        }
        return stringyfy(ast, intention, mdAst);
      })
      .join("");
  }

  let result = stringyfy(rootAst, beginningSpace, null);

  result = result.replace(/^[\s\n]+/g, "");
  result = result.replace(/^([!]+\?)/g, "");

  return result;
}