角5 +电子+生成的TOC

时间:2017-11-28 22:11:08

标签: javascript angular markdown

我有一个Angular Pipe:

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,这当然不起作用。是否有某种方法来修复hrefs以使其工作或以其他方式。

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

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所在的Side Nav菜单。当我通过此服务将TOC html推送到Side Nav时,似乎没有对HTML执行渲染更新。所以[routerLink]绑定或Angular特定代码永远不会正确更新。

2 个答案:

答案 0 :(得分:0)

所以这就是我要做的事情。 href链接自己只是一种解决方法,因为你可能将html设置为普通字符串,这个字符串无法抓住,因此没有像路由这样的角度特定代码。这并不理想,因为它导致整页重新加载和另一个完整的角度初始化,最终需要花费很多时间。

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

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

现在这种方式,内部HTML完全插入为一个字符串而没有角度知道里面是什么,所以没有角度的东西在这个div内部工作。你可以做的是使用该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()非常相似,我们只需要确保我们不会在同一个<a>标记上多次创建点击侦听器,因为可能会经常调用该函数。所以请记住,我们只想做一次。所以我们需要声明一个布尔值,表明我们是否已经添加了一个监听器。此外,我们应该检查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;
    }
}

答案 1 :(得分:0)

好的,所以我向持有TOC的click添加了<div></div>个事件:

<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中的问题,由于上述问题,滚动到锚点并不容易。