unist#Node TypeScript Examples

The following examples show how to use unist#Node. 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
/**
   * Recursively check if two given node has identical children.
   * At each level _position_ is omitted as this can change if
   * you are comparing from two different trees.
   * @param a first {@link Node} to compare
   * @param b second {@link Node} to compare
   * @returns boolean
   */
  static hasIdenticalChildren = (a: Node, b: Node): boolean => {
    if (_.isEqual(Object.keys(a).sort(), Object.keys(b).sort())) {
      const aOmit = _.omit(a, ["position", "children"]);
      const bOmit = _.omit(b, ["position", "children"]);
      if (_.isEqual(aOmit, bOmit)) {
        if (_.has(a, "children")) {
          return _.every(
            // @ts-ignore
            a.children as Node[],
            (aChild: Node, aIndex: number) => {
              // @ts-ignore
              const bChild = (b.children as Node[])[aIndex];
              return RemarkUtils.hasIdenticalChildren(aChild, bChild);
            }
          );
        }
        return true;
      } else {
        return false;
      }
    } else {
      return false;
    }
  };
Example #2
Source File: serverSide.ts    From roamjs-com with MIT License 6 votes vote down vote up
debug = (tree: Node) => {
  if (process.env.NODE_ENV === "development") {
    let charcode = 97;
    while (charcode < 123) {
      const fn = `out/${String.fromCharCode(charcode)}.json`;
      if (!fs.existsSync(fn)) {
        fs.writeFileSync(fn, JSON.stringify(tree, null, 2));
      } else {
        charcode++;
      }
    }
  }
}
Example #3
Source File: JoplinNoteHandler.ts    From joplin-utils with MIT License 6 votes vote down vote up
static convertLink(
    node: Node,
    note: CommonNote & { tags: CommonTag[]; resources: CommonResource[] },
    converter: JoplinNoteHandlerLinkConverter,
  ) {
    function getLink() {
      const res: string[] = []
      visit(node, ['link', 'image'], (node: Link) => {
        res.push(node.url)
      })
      return res.filter((link) => link.startsWith(':/')).map((link) => link.slice(2))
    }

    const linkIdList = getLink()
    const resourceMap = note.resources.reduce((res, resource) => {
      res.set(resource.id, resource)
      return res
    }, new Map<string, CommonResource>())
    const idLinkMap = linkIdList.reduce((res, id) => {
      const link = resourceMap.has(id) ? converter.convertResource(resourceMap.get(id)!) : converter.convertNote(id)
      res.set(id, link)
      return res
    }, new Map<string, string>())

    return unistUtilMap(node, (node) => {
      if (node.type !== 'link' && node.type !== 'image') {
        return node
      }
      const link = node as Link
      if (!link.url.startsWith(':/')) {
        return link
      }
      return {
        ...node,
        url: idLinkMap.get(link.url.slice(2)),
      } as Link
    })
  }
Example #4
Source File: stringify.ts    From uniorg with GNU General Public License v3.0 6 votes vote down vote up
function stringifyOne(node: Node | string): string {
  if (typeof node === 'string') {
    return node;
  }

  const org = node as OrgNode & Partial<WithAffiliatedKeywords>;

  const result: string[] = [];

  if (org.affiliated) {
    result.push(stringifyAffiliated(org.affiliated as AffiliatedKeywords));
  }

  result.push(stringifyNode(org));

  return result.join('');
}
Example #5
Source File: transformLinks.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
/**
 * Used from renaming wikilinks
 */
function plugin(this: Unified.Processor, opts: PluginOpts): Transformer {
  // @ts-ignore
  const proc = this;
  function transformer(tree: Node, _file: VFile) {
    visit(tree, (node, _idx, _parent) => {
      if (node.type === DendronASTTypes.WIKI_LINK) {
        let cnode = node as WikiLinkNoteV4;
        if (cnode.value.toLowerCase() === opts.from.fname.toLowerCase()) {
          cnode.value = opts.to.fname;
          // if alias the same, change that to
          if (
            cnode.data.alias.toLowerCase() === opts.from.fname.toLowerCase()
          ) {
            cnode.data.alias = opts.to.fname;
          }
        }
      }
      if (node.type === DendronASTTypes.REF_LINK_V2) {
        let cnode = node as NoteRefNoteV4;
        if (
          cnode.data.link.from.fname.toLowerCase() ===
          opts.from.fname.toLowerCase()
        ) {
          cnode.data.link.from.fname = opts.to.fname;
        }
      }
    });
    return tree;
  }
  return transformer;
}
Example #6
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 #7
Source File: posts.ts    From blog with GNU General Public License v3.0 6 votes vote down vote up
getTOCNodes = (results: TOCRecord[]) => () => (tree) => {
    const nodes: Node[] = tree.children || [];
    const topTOC = {
        href: '/',
        label: 'Root',
        level: 0,
        children: []
    };
    getTOC(nodes, topTOC);

    topTOC.children.forEach(toc => results.push(toc));
}
Example #8
Source File: dendronPreview.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
export function dendronHoverPreview(
  this: Unified.Processor,
  _opts?: PluginOpts
): Transformer {
  const proc = this;
  function transformer(tree: Node, _file: VFile) {
    visit(
      tree,
      [
        DendronASTTypes.FRONTMATTER,
        DendronASTTypes.IMAGE,
        DendronASTTypes.EXTENDED_IMAGE,
        DendronASTTypes.WIKI_LINK,
        DendronASTTypes.USERTAG,
        DendronASTTypes.HASHTAG,
      ],
      (node, index, parent) => {
        // Remove the frontmatter because it will break the output
        if (RemarkUtils.isFrontmatter(node) && parent) {
          // Remove this node
          parent.children.splice(index, 1);
          // Since this removes the frontmatter node, the next node to visit is at the same index.
          return index;
        }
        if (RemarkUtils.isImage(node) || RemarkUtils.isExtendedImage(node)) {
          makeImageUrlFullPath({ proc, node });
        } else if (RemarkUtils.isWikiLink(node)) {
          modifyWikilinkValueToCommandUri({ proc, node });
        } else if (RemarkUtils.isUserTag(node) || RemarkUtils.isHashTag(node)) {
          modifyTagValueToCommandUri({ proc, node });
        }
        return undefined; // continue
      }
    );
  }
  return transformer;
}
Example #9
Source File: posts.ts    From blog with GNU General Public License v3.0 6 votes vote down vote up
htmlParser = () => (tree) => {
    const nodes: Node[] = tree.children || [];
    const images = findNodes(nodes, node => node.tagName === 'img');
    images.forEach((img: NodeElement) => {
        img.properties.loading = 'lazy';
    });

    const preCodeBlocks = findNodes(nodes, node => {
        return node.tagName === 'pre' && (node.children as Node[]).some(child => child.tagName === 'code');
    });

    preCodeBlocks.forEach(pre => {
        const codeEl: NodeElement = (pre.children as any[]).find(child => child.tagName === 'code');
        const codeContent = codeEl.children[0].value || "";
        codeEl.children = [{
            type: 'raw',
            value: hljs.highlightAuto(codeContent).value
        }];
        
        if (!codeEl.properties.className) {
            codeEl.properties.className = [];
        }

        (codeEl.properties.className as string[]).push('hljs');
    });

    const headersElements = findNodes(nodes, node => {
        return ['h1', 'h2', 'h3', 'h4', 'h5'].includes(node.tagName as string);
    });
    headersElements.forEach((header: NodeElement) => {
        const textNode = findNodes((header.children as Node[] || []), (node) => node.type === 'text')[0];
        const text: string = textNode.value as string || '-empty-';
        const id = text.toLowerCase().replace(/\W/g, '-');
        header.properties.id = id;
    });
}
Example #10
Source File: dendronPub.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
/**
 * Returns a new copy of children array where the first un-rendered
 * reference ![[ref]] in children array is replaced with the given `data`. */
function replacedUnrenderedRefWithConvertedData(
  data: Parent[],
  children: Node[]
) {
  if (children.length > 1) {
    const idx = _.findIndex(children, RemarkUtils.isNoteRefV2);
    const processedChildren = children
      .slice(0, idx)
      .concat(data)
      .concat(children.slice(idx + 1));
    return processedChildren;
  } else {
    return data;
  }
}
Example #11
Source File: parsing.ts    From blog with GNU General Public License v3.0 6 votes vote down vote up
parserPlugin = (postPath: string) => async tree => {
    const images = findImages(tree.children as Node[] || []);
    if (images.length === 0) {
        return;
    }
    const s3Urls = await copyImagesToAssets(postPath, images.map(token => decodeURI(token.url as string)));

    images.forEach((token, index) => {
        token.url = `/${s3Urls[index]}`;
    });
}
Example #12
Source File: noteRefsV2.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
function findHeader({
  nodes,
  match,
  slugger,
}: {
  nodes: DendronASTNode["children"];
  match: string;
  slugger: ReturnType<typeof getSlugger>;
}): FindAnchorResult {
  const foundIndex = MdastUtils.findIndex(nodes, (node: Node, idx: number) => {
    if (idx === 0 && match === "*") {
      return false;
    }
    return MdastUtils.matchHeading(node, match, { slugger });
  });
  if (foundIndex < 0) return null;
  return { type: "header", index: foundIndex, anchorType: "header" };
}
Example #13
Source File: rehypeReplaceJoplinUrl.ts    From joplin-utils with MIT License 6 votes vote down vote up
export function rehypeReplaceJoplinUrl(note: RenderNote) {
  return function transformer(tree: Node) {
    visit(tree, 'element', function (node) {
      modifyLink(node, 'href')
      modifyMedia(node, 'src')
    })
  }

  function modifyLink(node: Node, prop: any) {
    if (has(node, prop)) {
      const url = (node.properties as any)[prop]
      ;(node.properties as any)[prop] = replaceUrl(url, note)
      ;(node.properties as any).target = '_blank'
    }
  }

  function modifyMedia(node: Node, prop: any) {
    if (has(node, prop)) {
      const url = (node.properties as any)[prop]
      ;(node.properties as any)[prop] = replaceUrl(url, note)
      ;(node.properties as any).style = 'max-width: 100%;'
    }
  }
}
Example #14
Source File: publishSite.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
/**
 * Used when publishing
 * Rewrite index note
 */
function plugin(this: Unified.Processor, opts: PluginOpts): Transformer {
  const proc = this;
  const { dest, config } = MDUtilsV5.getProcData(proc);
  function transformer(tree: Node, _file: VFile) {
    if (dest !== DendronASTDest.HTML) {
      return;
    }
    visit(tree, (node, _idx, _parent) => {
      if (node.type === DendronASTTypes.WIKI_LINK) {
        const cnode = node as WikiLinkNoteV4;
        const value = cnode.value;
        const href = PublishUtils.getSiteUrl(config);
        if (value === opts.noteIndex.fname) {
          node.data!.hProperties = { href };
        }
      }
    });
    return tree;
  }
  return transformer;
}
Example #15
Source File: JoplinNoteHandler.ts    From joplin-blog with MIT License 5 votes vote down vote up
static format(node: Node) {
    return format(this.md.stringify(node), {
      parser: 'markdown',
      tabWidth: 2,
    } as Options)
  }
Example #16
Source File: stringify.ts    From uniorg with GNU General Public License v3.0 5 votes vote down vote up
export function stringify(org: string | Node | Node[]): string {
  const result = Array.isArray(org)
    ? org.map(stringify).join('')
    : stringifyOne(org);
  return result;
}
Example #17
Source File: toc.ts    From next-cms-ghost with MIT License 5 votes vote down vote up
generateTableOfContents = (htmlAst: Node) => {
  const tags = [`h1`, `h2`, `h3`, `h4`, `h5`, `h6`]

  function headings(node: unknown): node is TocElement {
    return tags.includes((node as TocElement).tagName)
  }

  // recursive walk to visit all children
  const walk = (children: TocElement[], text = ``, depth = 0) => {
    children.forEach((child) => {
      if (child.type === `text`) {
        text = text + child.value
      } else if (child.children && depth < 3) {
        depth = depth + 1
        text = walk(child.children, text, depth)
      }
    })
    return text
  }

  let toc: TOC[] = []
  visit(htmlAst, headings, (node: TocElement) => {
    const text = walk(node.children || [])
    if (text.length > 0) {
      const id = (node.properties as NodeProperties).id || `error-missing-id`
      const level = (node.tagName as string).substr(1, 1)
      toc.push({ level: level, id: id, heading: text, parentIndex: -1, items: [] })
    }
  })

  // Walk up the list to find matching parent
  const findParent = (toc: TOC[], parentIndex: number, level: string) => {
    while (parentIndex >= 0 && level < toc[parentIndex].level) {
      parentIndex = toc[parentIndex].parentIndex
    }
    return parentIndex >= 0 ? toc[parentIndex].parentIndex : -1
  }

  // determine parents
  toc.forEach((node, index) => {
    const prev = toc[index > 0 ? index - 1 : 0]
    node.parentIndex = node.level > prev.level ? (node.parentIndex = index - 1) : prev.parentIndex
    node.parentIndex = node.level < prev.level ? findParent(toc, node.parentIndex, node.level) : node.parentIndex
  })

  // add children to their parent
  toc.forEach((node: TOC) => node.parentIndex >= 0 && (toc[node.parentIndex].items as TOC[]).push(node))

  // make final tree
  let tocTree = toc.filter(({ parentIndex }) => parentIndex === -1)

  const removeProps = ({ id, heading, items }: TOC): IToC => (items.length > 0 ? { id, heading, items: (items as TOC[]).map((item) => removeProps(item)) } : { id, heading })

  return tocTree.map((node) => removeProps(node))
}
Example #18
Source File: index.ts    From uniorg with GNU General Public License v3.0 5 votes vote down vote up
export function uniorgStringify(this: any) {
  this.Compiler = (node: Node) => {
    return stringify(node);
  };
}
Example #19
Source File: JoplinNoteHandler.ts    From joplin-utils with MIT License 5 votes vote down vote up
static format(node: Node) {
    return format(this.md.stringify(node), {
      parser: 'markdown',
      tabWidth: 2,
    } as Options)
  }
Example #20
Source File: index.spec.ts    From uniorg with GNU General Public License v3.0 5 votes vote down vote up
process = (s: string, options?: Options): Node => {
  const processor = unified().use(uniorg).use(uniorgSlug, options);

  const f = new VFile(s);

  return processor.runSync(processor.parse(f), f);
}
Example #21
Source File: remark-img-to-jsx.ts    From portfolio with MIT License 5 votes vote down vote up
export default function remarkImgToJsx() {
  return (tree: Node) => {
    visit(
      tree,
      // only visit p tags that contain an img element
      (node: Parent): node is Parent =>
        node.type === 'paragraph' &&
        node.children.some(n => n.type === 'image'),
      (node: Parent) => {
        const imageNode = node.children.find(
          n => n.type === 'image',
        ) as ImageNode;

        // only local files
        if (fs.existsSync(`${process.cwd()}/public${imageNode.url}`)) {
          const dimensions = sizeOf(`${process.cwd()}/public${imageNode.url}`);

          // Convert original node to next/image
          (imageNode.type = 'mdxJsxFlowElement'),
            (imageNode.name = 'Image'),
            (imageNode.attributes = [
              { type: 'mdxJsxAttribute', name: 'alt', value: imageNode.alt },
              { type: 'mdxJsxAttribute', name: 'src', value: imageNode.url },
              {
                type: 'mdxJsxAttribute',
                name: 'width',
                value: dimensions.width,
              },
              {
                type: 'mdxJsxAttribute',
                name: 'height',
                value: dimensions.height,
              },
            ]);

          // Change node type from p to div to avoid nesting error
          node.type = 'div';
          node.children = [imageNode];
        }
      },
    );
  };
}
Example #22
Source File: serverSide.ts    From roamjs-com with MIT License 5 votes vote down vote up
serialize = (
  s: string
): Promise<MDXRemoteSerializeResult<Record<string, unknown>>> => {
  if (process.env.NODE_ENV === "development")
    fs.readdirSync("./out")
      .filter((s) => s.endsWith(".json"))
      .forEach((s) => fs.unlinkSync(`./out/${s}`));
  return mdxSerialize(s, {
    mdxOptions: {
      rehypePlugins: [
        () => (tree) => {
          debug(tree);
          const expandJsx = async (n: Node) => {
            if (n.type === "jsx") {
              const match = /^<(\w*)((?: \w*={"[\w\d_-]*"})*?)>(.*)$/s.exec(
                n.value as string
              );
              if (match && match.length >= 3) {
                const [, tag, props, rest] = match;
                const content = new RegExp(`^(.*?)(</${tag}>)$`, "s").exec(
                  rest
                )?.[1];
                if (content) {
                  n.type = "element";
                  n.tagName = tag;
                  n.children = await strToHastCompiler
                    .run(strToHastCompiler.parse(content))
                    .then((c) => c.children);
                  n.properties = props
                    ? Object.fromEntries(
                        props
                          .trim()
                          .split(" ")
                          .map((prop) =>
                            /(\w*)={"([\w\d_-]*)"}/.exec(prop.trim())
                          )
                          .filter((prop) => !!prop)
                          .map(([, key, value]) => [key, value])
                      )
                    : {};
                  delete n.value;
                }
              }
            }
            ((n.children as Node[]) || []).forEach(expandJsx);
          };
          debug(tree);
          return Promise.all((tree.children as Node[]).map(expandJsx))
            .then((a) => {
              return a[0];
            })
            .catch((e) => {
              console.error(e);
            });
        },
      ],
    },
  });
}
Example #23
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;
  }
Example #24
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 #25
Source File: index.ts    From gatsby-project-kb with MIT License 4 votes vote down vote up
processWikiLinks = (
  { markdownAST }: { markdownAST: Node },
  options?: { titleToURLPath?: string; stripBrackets?: boolean, stripDefinitionExts?: string[] }
) => {
  const { stripDefinitionExts } = options
  const titleToURL = options?.titleToURLPath
    ? require(options.titleToURLPath)
    : defaultTitleToURLPath

  const definitions: { [identifier: string]: Definition } = {}

  const getLinkInfo = (definition: Definition) => {
    if (typeof definition.identifier !== 'string') return
    let linkUrl = definition.url
    const isExternalLink = /\/\//.test(linkUrl)
    let shouldReplace = !isExternalLink
    if (shouldReplace && stripDefinitionExts) {
      const extname = path.extname(definition.url || '')
      const matchedExtname = stripDefinitionExts.find((n) => extname === n)
      if (matchedExtname) {
        linkUrl = linkUrl.slice(0, linkUrl.length - matchedExtname.length)
      }
    }
    return {
      linkUrl,
      shouldReplace
    }
  }

  visit(markdownAST, `definition`, (node: Definition) => {
    if (!node.identifier || typeof node.identifier !== 'string') {
      return
    }
    definitions[node.identifier] = node
  })
  visit(markdownAST, `linkReference`, (node: LinkReferenceNode, index, parent) => {
    if (node.referenceType !== 'shortcut') {
      return
    }

    const definition = definitions[node.identifier]
    const linkInfo = definition ? getLinkInfo(definition): null
    const linkUrl = linkInfo ? linkInfo.linkUrl: definition?.url
    if ((linkInfo && !linkInfo.shouldReplace)) {
      // console.log('should not replace', definitions, node.identifier)
      return
    }

    const siblings = parent.children
    if (!siblings || !Array.isArray(siblings)) {
      return
    }
    const previous: StaticPhrasingContent = siblings[index - 1] as any
    const next: StaticPhrasingContent = siblings[index + 1] as any

    if (!(previous && next)) {
      return
    }

    if (!('value' in previous && 'value' in next)) {
      return
    }

    const previousValue = previous.value as string
    const nextValue = next.value as string

    if (
      previous.type !== 'text' ||
      previous.value[previousValue.length - 1] !== '[' ||
      next.type !== 'text' ||
      next.value[0] !== ']'
    ) {
      return
    }

    previous.value = previousValue.replace(/\[$/, '')
    next.value = nextValue.replace(/^\]/, '')

    ;(node as any).type = 'link' // cast it to link
    if (definition) {
      node.url = linkUrl
    } else {
      node.url = titleToURL(node.label as string)
    }
    node.title = node.label
    if (!options?.stripBrackets && Array.isArray(node.children)) {
      const firstChild = node.children[0];
      if (firstChild && 'value' in firstChild) {
        firstChild.value = `[[${firstChild.value}]]`
      }
    }
    delete node.label
    delete node.referenceType
    delete node.identifier
  })
}
Example #26
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;
}