【问题标题】:Angular 5 + Electron + Generated TOCAngular 5 + 电子 + 生成的 TOC
【发布时间】:2018-05-12 12:16:24
【问题描述】:

我有一个角管:

import {Pipe, PipeTransform} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import * as Remarkable from 'remarkable';
import * as toc from 'markdown-toc';

@Pipe({
    name: 'MarkdownToc'
})
export class MarkdownTableOfContentsPipe implements PipeTransform {
    constructor(private sanitized: DomSanitizer) {}

    public transform(markdown: string) {
        const toc_opts = {
            linkify: function(tok, text, slug, options) {
                const regex = /(.+\b)(.*)$/

                slug = slug.replace(regex, function(str, g1) { return g1; });
                tok.content = `[${text}](#${slug})`;
                return tok;
             }
        }

        const toc_md = new Remarkable('commonmark')
            .use(toc.plugin(toc_opts))

        const md = new Remarkable('commonmark')

        md.renderer.rules.link_open = function(tokens, idx, options /* env */) {
          var title = tokens[idx].title ? (' title="' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(tokens[idx].title)) + '"') : '';
          var target = options.linkTarget ? (' target="' + options.linkTarget + '"') : '';

          return '<a href="/articles' + Remarkable.utils.escapeHtml(tokens[idx].href) + '"' + title + target + '>';
        };

        const toc_md_text = toc_md.render(markdown);

        console.log(md.render(toc_md_text.content));

        return this.sanitized.bypassSecurityTrustHtml(md.render(toc_md_text.content));
    }
}

它生成一个链接列表(这是一个缩短的列表):

<ul>
  <li><a href="/articles#introduction">Introduction</a></li>
  <li><a href="/articles#downloads">Downloads</a></li>
</uL>

但是,显示的每个链接都是“file:///”+href,这当然是行不通的。有什么方法可以修复 href 以使其正常工作或其他方式。


在我的控制器中,我有这个功能:

private async _show() {
      const db = await this.databaseService.get();
      const id = this.route.snapshot.paramMap.get('id').split('-').join(' ');

      const article$ = db.article
            .findOne(toTitleCase(id))
            .$;

      this.sub = article$.subscribe(async article => {
          this.article = article;
          const attachment = await this.article.getAttachment('index.md');
          this.text = this.markdownPipe.transform(await attachment.getStringData());

          this.intermoduleservice.toc = this.markdownTocPipe.transform(await attachment.getStringData());
          this.zone.run(() => {});
      });
  }

InterModuleService 是一项全球服务,用于将 TOC 推送到我所在的侧导航菜单中。当我通过此服务将 TOC html 推送到 Side Nav 时,似乎没有在 HTML 上执行渲染更新。所以[routerLink] 绑定或 Angular 特定代码永远不会正确更新。

【问题讨论】:

  • 对不起,我不熟悉电子,但是通过使用 href 而不是 routerLink 属性,您没有使用角度路由过程,这将导致整个页面重新加载而不是内部路由。这是故意的吗?
  • 是的,试过了,但是当我将 html 直接插入页面时,链接无法链接。

标签: javascript angular markdown


【解决方案1】:

好的,所以我在持有 TOC 的 &lt;div&gt;&lt;/div&gt; 中添加了一个 click 事件:

<mat-sidenav mode="side" #sidenav id="sidenav" fixedInViewport="fixed" fixedTopGap="65">
        <button  mat-menu-item [routerLink]="['articles']">
            <mat-icon svgIcon="arrow-left"></mat-icon>
            Return to Article List
        </button>
        <div class="sidenav-toc" (click)="onTocClick($event)" [innerHtml]="article | MarkdownToc" id="toc" #tocDiv></div>
</mat-sidenav>

然后在我的 sidenav 组件上我添加了两个函数:

public onTocClick(event: Event) {
    const elem = event.target as Element;
    if (elem.tagName.toLowerCase() == 'a') {
        const frag = elem.getAttribute('data-link');
        const id = this.interModuleService.currentArticle.split(' ').join('-');

        this.goTo(frag);
    }
}

goTo(anchor: string) {
    // TODO - HACK: remove click once https://github.com/angular/angular/issues/6595 is fixed  
    (<HTMLScriptElement>document.querySelector('#'+ anchor)).scrollIntoView();  
}

我曾想过某种动态组件。但是,由于 Angular 中的问题,由于上述问题,滚动到锚点并不容易。

【讨论】:

    【解决方案2】:

    所以这就是我在你的情况下会做的事情。 href 链接本身只是一种解决方法,因为您可能将 html 设置为纯字符串,而 Angular 无法控制该字符串,因此无法像这样注入诸如路由之类的特定于角度的代码。这并不理想,因为它会导致整个页面重新加载和另一个完整的角度初始化,这最终会花费大量时间。

    让我们看看我们是否可以通过尝试实现原生角度路由来使其更快。为了清楚起见,我不知道这是否适用于 Electron。您可以做的是参考您使用@ViewChild() 注入降价的html 标记。我说的是通过 angular 角度组件创建的最后一层 html。假设它是一个div,它有一个innerHTML 属性,你正在使用你的管道。

    <div [innerHTML]="originalMarkdown | MarkdownToc" #markdownDiv></div>
    

    现在通过这种方式,内部 HTML 被完全插入为一个字符串,而 Angular 不知道里面是什么,所以在这个 div 中没有任何 Angular 的东西起作用。但是,您可以做的是使用该 div 上的引用来遍历 innerHTML 创建之后的 HTML 树。

    @ViewChild('markdownDiv') markdownDiv: ElementRef;
    
    // this hook makes sure the html is already available
    ngAfterViewInit() {
        // find a link tag inside your innerHTML
        // this can be any selector, like class, id or tag
        var link = this.markdownDiv.nativeElement.querySelector("a");
        // ... do something with the link
    }
    

    你说你有多个链接,也许你必须使用另一个选择器,甚至在创建 html 时给每个链接一个 ID 并在此处引用。只要您有对链接的引用,您就可以使用它来创建一个 onClick 函数,例如可以使用路由。我们需要一个叫做Renderer2 的东西来帮助我们。它作为正常服务注入。这是您需要的大部分代码,可以放在您的 component.ts 中。

    constructor(private renderer: Renderer2, private router: Router){}
    @ViewChild('markdownDiv') markdownDiv: ElementRef;
    
    // this hook makes sure the html is already available
    ngAfterViewInit() {
        // find a link tag inside your innerHTML
        // this can be any selector, like class, id or tag
        var link = this.markdownDiv.nativeElement.querySelector("a");
        // ... on click
        this.renderer.listen(link, 'click', (event) => {
            this.router.navigateByUrl("/articles#introduction");
        });
    }
    

    现在要做的事情很多,但它可能会通过比您现在尝试做的更快的实现来解决您的问题。如果您有任何问题,请随时提出。

    编辑

    我想这里描述的生命周期钩子ngAfterViewInit() 查询你的元素可能还为时过早,因为在这个阶段 DOM 还没有更新。如果我没看错,您正在调用数据库来创建降价,这需要时间。 Angular 通常只是初始化模板视图,之后不依赖动态 DOM 操作。

    我们要做的可能是挂钩到后面的阶段。这有点棘手,但应该仍然可以管理。我们要使用的是ngAfterViewChecked()

    ngAfterViewChecked() 在 Angular 检查组件的视图和子视图后做出响应。 在 ngAfterViewInit 和每个后续调用之后调用 ngAfterContentChecked().

    它与ngAfterViewInit() 非常相似,我们只需要确保我们不会在同一个&lt;a&gt; 标记上多次创建点击侦听器,因为该函数可能会经常被调用。所以请记住,我们只想做一次。所以我们需要声明一个布尔值来说明我们是否已经添加了一个监听器。此外,我们应该检查 DOM 是否真的存在,因为在注入 HTML 之前,该函数也被调用了几次。这是代码。

    constructor(private renderer: Renderer2, private router: Router){}
    
    @ViewChild('markdownDiv') markdownDiv: ElementRef;
    // this is our boolean that prevents us from multiple addings
    listenerAdded: boolean = false;
    
    // this hook makes sure the html is already available
    ngAfterViewChecked() {
        // check if the div we watch has some innerHTML and if we haven't set the link already
        if(this.markdownDiv.nativeElement.childNodes.length>0 && !listenerAdded) {
            // find a link tag inside your innerHTML
            // this can be any selector, like class, id or tag
            var link = this.markdownDiv.nativeElement.querySelector("a");
            // ... on click
            this.renderer.listen(link, 'click', (event) => {
                this.router.navigateByUrl("/articles#introduction");
            });
            // remember not to do it again
            this.listenerAdded=true;
        }
    }
    

    【讨论】:

    • this.markdownDiv.nativeElement.querySelector("a") 始终为空。
    • 如果您在查询之前放置console.log(this.markdowDiv.nativeElement),它会打印您创建的html吗? ngAfterViewInit() 可能在这个过程中太早了,在 DOM 未更新时被调用。生命周期挂钩仅在初始化时调用一次,但您的创建函数可能会在 这已经发生之后调用。
    • 是的,行得通。我可能必须生成 div 并以这种方式插入它。
    • 这会是我遇到的问题吗:github.com/angular/angular/issues/8563
    • 我不太确定,因为我不太了解那里讨论的内容。这似乎是一个改进 Angular 本身的提议,但我认为它背后的想法解决了一个与你不同的问题,但我不太确定
    猜你喜欢
    • 2018-12-31
    • 2011-08-04
    • 2018-10-02
    • 1970-01-01
    • 2018-06-23
    • 2018-07-28
    • 2017-09-05
    • 1970-01-01
    • 2018-08-17
    相关资源
    最近更新 更多