go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/tools/markdown/plugins/default_target.ts (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import MarkdownIt from 'markdown-it';
    16  
    17  const CLOSING_ANCHOR_TAG = '</a>';
    18  
    19  /**
    20   * Set default target for link elements.
    21   */
    22  export function defaultTarget(md: MarkdownIt, defaultTarget: string) {
    23    const renderToken = md.renderer.renderToken.bind(md.renderer);
    24  
    25    // Set default target for link_open tokens.
    26    const existingLinkOpenRule = md.renderer.rules['link_open'] || renderToken;
    27    md.renderer.rules['link_open'] = (tokens, i, ...params) => {
    28      const target = tokens[i].attrGet('target') || defaultTarget;
    29      tokens[i].attrSet('target', target);
    30      return existingLinkOpenRule(tokens, i, ...params);
    31    };
    32  
    33    // Set default target for anchors in html_inline tokens.
    34    const existingHTMLInlineRule =
    35      md.renderer.rules['html_inline'] || renderToken;
    36    md.renderer.rules['html_inline'] = (tokens, i, ...params) => {
    37      const token = tokens[i];
    38      if (/^<a .*>$/i.test(token.content)) {
    39        const template = document.createElement('template');
    40        template.innerHTML = token.content + CLOSING_ANCHOR_TAG;
    41        const anchor = template.content.firstElementChild!;
    42        const target = anchor.getAttribute('target') || defaultTarget;
    43        anchor.setAttribute('target', target);
    44        token.content = anchor.outerHTML.substring(
    45          0,
    46          anchor.outerHTML.length - CLOSING_ANCHOR_TAG.length,
    47        );
    48      }
    49      return existingHTMLInlineRule(tokens, i, ...params);
    50    };
    51  
    52    // Set default target for anchors in html_block tokens.
    53    const existingHTMLBlockRule = md.renderer.rules['html_block'] || renderToken;
    54    md.renderer.rules['html_block'] = (tokens, i, ...params) => {
    55      const token = tokens[i];
    56      const template = document.createElement('template');
    57      template.innerHTML = token.content;
    58      const anchors = template.content.querySelectorAll('a');
    59      anchors.forEach((anchor) => {
    60        const target = anchor.getAttribute('target') || defaultTarget;
    61        anchor.setAttribute('target', target);
    62      });
    63      token.content = template.innerHTML;
    64      return existingHTMLBlockRule(tokens, i, ...params);
    65    };
    66  }