unified#Transformer TypeScript Examples

The following examples show how to use unified#Transformer. 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: Markdown.tsx    From project-loved-web with MIT License 6 votes vote down vote up
function osuWikiLinks(): Transformer {
  return (tree) => {
    visit(tree, ['image', 'link'], (node: any) => {
      if (node.url.startsWith('/wiki/')) {
        node.url = 'https://osu.ppy.sh' + node.url;
      }
    });
  };
}
Example #2
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 #3
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 #4
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 #5
Source File: remark.ts    From vite-plugin-md-preview with MIT License 5 votes vote down vote up
export function remarkVue(options: RemarkVueOptions): Plugin {
  const { file, root, highlighter, remove, update } = options

  const resolve = (...args: string[]) => {
    let ret = path.resolve(path.dirname(file), ...args)
    ret = path.relative(root, ret)
    return `/${ret}`
  }
  function transformer(tree): Transformer {
    const oldBlocks = fileCodeMap.get(file) || []
    const blocks: CodeBlock[] = []
    visit(tree, 'code', (node: Code, i: number, parent: Parent) => {
      const attrs = (node.meta || '').split(' ').reduce((prev, curr) => {
        const [key, value] = curr.split('=')
        if (typeof value === 'undefined') {
          prev[key] = true
        } else {
          prev[key] = value
        }
        return prev
      }, {} as Record<string, string | boolean>)

      if (node.lang === 'vue' && attrs['preview']) {
        const name = `VueCode${md5(file).substr(0, 8)}I${i}`
        const component = typeof attrs['preview'] === 'string' ? attrs['preview'] : 'VueCode'
        const code = highlighter(node.value)
        blocks.push({ name, path: resolve(`./${name}.vue`), code: node.value })
        const demoNode: HTML = {
          type: 'html',
          value: `<${component} source="${encodeURIComponent(code)}">
  <${name} />
</${component}>`,
        }
        parent.children.splice(i, 1, demoNode)
      }
    })
    const names = blocks.map(i => i.name)
    remove(oldBlocks)
    fileCodeMap.set(file, names)
    update(blocks)

    const imports = names.reduce((prev, curr) => {
      return `${prev}import ${curr} from "${resolve(`./${curr}.vue`)}"\n`
    }, '')
    const script = `<script setup>\n${imports}</script>`
    tree.children.splice(0, 0, { type: 'html', value: script })
    return tree
  }

  return transformer
}
Example #6
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 #7
Source File: backlinksHover.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
/**
 * Unified processor for rendering text in the backlinks hover control. This
 * processor returns a transformer that does the following:
 * 1. Highlights the backlink text
 * 2. Changes the backlink node away from a wikilink/noteref to prevent the
 *    backlink text from being altered
 * 3. Adds contextual " --- line # ---" information
 * 4. Removes all elements that lie beyond the contextual lines limit of the
 *    backlink
 * @param this
 * @param _opts
 * @returns
 */
export function backlinksHover(
  this: Unified.Processor,
  _opts?: BacklinkOpts
): Transformer {
  function transformer(tree: Node, _file: VFile) {
    if (!_opts) {
      return;
    }

    const backlinkLineNumber = _opts.location.start.line;

    const lowerLineLimit = backlinkLineNumber - _opts.linesOfContext;
    const upperLineLimit = backlinkLineNumber + _opts.linesOfContext;

    /**
     * The last line of the YAML frontmatter counts as line 0.
     */
    let documentBodyStartLine = 0;
    let documentEndLine = 0;

    // In the first visit, set the beginning and end markers of the document.
    visit(tree, [DendronASTTypes.ROOT], (node, _index, _parent) => {
      if (RemarkUtils.isRoot(node)) {
        documentEndLine = node.position?.end.line ?? 0;

        // Count the last line of YAML as the 0 indexed start of the body of the document
        if (RemarkUtils.isYAML(node.children[0])) {
          documentBodyStartLine = node.children[0].position?.end.line ?? 0;
        }
      }
    });

    // In the second visit, modify the wikilink/ref/candidate that is the
    // backlink to highlight it and to change its node type so that it appears
    // in its text form to the user (we don't want to convert a noteref backlink
    // into its reffed contents for example)
    visit(tree, (node, index, parent) => {
      if (!node.position) {
        return;
      }

      // Remove all elements that fall outside of the context boundary limits
      if (
        node.position.end.line < lowerLineLimit ||
        node.position.start.line > upperLineLimit
      ) {
        if (parent) {
          parent.children.splice(index, 1);
          return index;
        }
      }

      // Make special adjustments for preceding and succeeding code blocks that
      // straddle the context boundaries
      if (node.position && node.position.start.line < lowerLineLimit) {
        if (RemarkUtils.isCode(node)) {
          const lines = node.value.split("\n");
          node.value = lines
            .slice(
              Math.max(0, lowerLineLimit - node.position.start.line - 2), // Adjust an offset to account for the code block ``` lines
              lines.length - 1
            )
            .join("\n");
        }
      } else if (node.position && node.position.end.line > upperLineLimit) {
        if (RemarkUtils.isCode(node)) {
          const lines = node.value.split("\n");
          node.value = lines
            .slice(
              0,
              upperLineLimit - node.position.end.line + 1 // Adjust an offset of 1 to account for the code block ``` line
            )
            .join("\n");
        }
      }

      // Do the node replacement for wikilinks, node refs, and text blocks when
      // it's a candidate link
      if (RemarkUtils.isWikiLink(node)) {
        if (
          backlinkLineNumber === node.position?.start.line &&
          node.position.start.column === _opts.location.start.column
        ) {
          let wiklinkText = `${node.value}`;

          if (node.data.anchorHeader) {
            wiklinkText += `#${node.data.anchorHeader}`;
          }

          (node as Node).type = DendronASTTypes.HTML;
          (node as unknown as HTML).value = getHTMLToHighlightText(
            `[[${wiklinkText}]]`
          );
        }
      } else if (RemarkUtils.isNoteRefV2(node)) {
        if (
          backlinkLineNumber === node.position?.start.line &&
          node.position.start.column === _opts.location.start.column
        ) {
          let noteRefText = `${node.value}`;

          if (node.data.link.data.anchorStart) {
            noteRefText += `#${node.data.link.data.anchorStart}`;
          }

          if (node.data.link.data.anchorEnd) {
            noteRefText += `:#${node.data.link.data.anchorEnd}`;
          }

          (node as Node).type = DendronASTTypes.HTML;
          (node as unknown as HTML).value = getHTMLToHighlightText(
            `![[${noteRefText}]]`
          );
        }
      } else if (RemarkUtils.isText(node)) {
        // If the backlink location falls within the range of this text node,
        // then proceed with formatting. Note: a text node can span multiple
        // lines if it ends with a '\n'
        if (
          backlinkLineNumber === node.position?.start.line &&
          (node.position.end.column > _opts.location.start.column ||
            node.position.end.line > _opts.location.start.line) &&
          (node.position.start.column < _opts.location.end.column ||
            node.position.start.line < _opts.location.end.line)
        ) {
          const contents = node.value;
          const prefix = contents.substring(0, _opts.location.start.column - 1);

          const candidate = contents.substring(
            _opts.location.start.column - 1,
            _opts.location.end.column - 1
          );
          const suffix = contents.substring(
            _opts.location.end.column - 1,
            contents.length
          );

          (node as Node).type = DendronASTTypes.HTML;
          (node as unknown as HTML).value = `${prefix}${getHTMLToHighlightText(
            candidate
          )}${suffix}`;

          return index;
        }
      } else if (RemarkUtils.isHashTag(node) || RemarkUtils.isUserTag(node)) {
        if (
          backlinkLineNumber === node.position?.start.line &&
          node.position.start.column === _opts.location.start.column
        ) {
          (node as Node).type = DendronASTTypes.HTML;
          (node as unknown as HTML).value = getHTMLToHighlightText(node.value);
        }
      }
      return;
    });

    // In the third visit, add the contextual line marker information
    visit(tree, [DendronASTTypes.ROOT], (node, _index, _parent) => {
      if (!RemarkUtils.isRoot(node) || !node.position) {
        return;
      }

      const lowerBoundText =
        lowerLineLimit <= documentBodyStartLine
          ? "Start of Note"
          : `Line ${lowerLineLimit - 1}`;

      const lowerBoundParagraph: Paragraph = {
        type: DendronASTTypes.PARAGRAPH,
        children: [
          {
            type: DendronASTTypes.HTML,
            value: `--- <i>${lowerBoundText}</i> ---`,
          },
        ],
      };

      node.children.unshift(lowerBoundParagraph);

      const upperBoundText =
        upperLineLimit >= documentEndLine
          ? "End of Note"
          : `Line ${upperLineLimit + 1}`;

      const upperBoundParagraph: Paragraph = {
        type: DendronASTTypes.PARAGRAPH,
        children: [
          {
            type: DendronASTTypes.HTML,
            value: `--- <i>${upperBoundText}</i> ---`,
          },
        ],
      };

      node.children.push(upperBoundParagraph);
    });
  }
  return transformer;
}
Example #8
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;
}