Giter Site home page Giter Site logo

jfcere / ngx-markdown Goto Github PK

View Code? Open in Web Editor NEW
1.0K 16.0 179.0 22.82 MB

Angular markdown component/directive/pipe/service to parse static, dynamic or remote content to HTML with syntax highlight and more...

Home Page: https://jfcere.github.io/ngx-markdown

License: MIT License

JavaScript 1.36% TypeScript 98.64%
angular ngx markdown md parser marked markedjs prism prismjs syntax-highlighting

ngx-markdown's Introduction

Ngx-Markdown Logo

CircleCI Status Coverage Status NPM Version License
Dependency Status Monthly Downloads

ngx-markdown

ngx-markdown is an Angular library that combines...

Demo available @ https://jfcere.github.io/ngx-markdown
StackBlitz available @ https://stackblitz.com/edit/ngx-markdown

Table of contents

Installation

ngx-markdown

To add ngx-markdown along with the required marked library to your package.json use the following commands.

npm install ngx-markdown marked@^12.0.0 --save

Syntax highlight

๐Ÿ”” Syntax highlight is optional, skip this step if you are not planning to use it

To add Prism.js library to your package.json use the following command.

npm install prismjs@^1.28.0 --save

To activate Prism.js syntax highlight you will need to include...

  • prism.js core library - node_modules/prismjs/prism.js file
  • a highlight css theme - from node_modules/prismjs/themes directory
  • desired code language syntax files - from node_modules/prismjs/components directory

Additional themes can be found by browsing the web such as Prism-Themes or Mokokai for example.

If you are using Angular CLI you can follow the angular.json example below...

"styles": [
  "styles.css",
+ "node_modules/prismjs/themes/prism-okaidia.css"
],
"scripts": [
+ "node_modules/prismjs/prism.js",
+ "node_modules/prismjs/components/prism-csharp.min.js", # c-sharp language syntax
+ "node_modules/prismjs/components/prism-css.min.js" # css language syntax
]

Line Numbers plugin

To use the line numbers plugin that shows line numbers in code blocks, in addition to Prism.js configuration files, you will need to include the following files from prismjs/plugins/line-numbers directory to your application:

  • CSS styling for line numbers - prism-line-numbers.css
  • line numbers plugin script - prism-line-numbers.js

If you are using Angular CLI you can follow the angular.json example below...

"styles": [
  "src/styles.css",
  "node_modules/prismjs/themes/prism-okaidia.css",
+ "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.css"
],
"scripts": [
  "node_modules/prismjs/prism.js",
  "node_modules/prismjs/components/prism-csharp.min.js",
  "node_modules/prismjs/components/prism-css.min.js",
+ "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.js"
]

Using markdown component and/or directive, you will be able to use the lineNumbers property to activate the plugin. The property can be used in combination with either data for variable binding, src for remote content or using transclusion for static markdown.

Additionally, you can use start input property to specify the offset number for the first display line.

<markdown
  lineNumbers
  [start]="5"
  [src]="path/to/file.js">
</markdown>

Line Highlight plugin

To use the line highlight plugin that highlights specific lines and/or line ranges in code blocks, in addition to Prism.js configuration files, you will need to include the following files from prismjs/plugins/line-highlight directory to your application:

  • CSS styling for line highlight - prism-line-highlight.css
  • line highlight plugin script - prism-line-highlight.js

If you are using Angular CLI you can follow the angular.json example below...

"styles": [
  "src/styles.css",
  "node_modules/prismjs/themes/prism-okaidia.css",
+ "node_modules/prismjs/plugins/line-highlight/prism-line-highlight.css"
],
"scripts": [
  "node_modules/prismjs/prism.js",
  "node_modules/prismjs/components/prism-csharp.min.js",
  "node_modules/prismjs/components/prism-css.min.js",
+ "node_modules/prismjs/plugins/line-highlight/prism-line-highlight.js"
]

Using markdown component and/or directive, you will be able to use the lineHighlight property to activate the plugin. The property can be used in combination with either data for variable binding, src for remote content or using transclusion for static markdown.

Use line input property to specify the line(s) to highlight and optionally there is a lineOffset property to specify the starting line of code your snippet represents.

<markdown
  lineHighlight
  [line]="'6, 10-16'"
  [lineOffset]="5"
  [src]="path/to/file.js">
</markdown>

Command Line Plugin

To use the command line plugin that displays a command line with a prompt and, optionally, the output/response from the commands, you will need to include the following files from prismjs/plugins/command-line directory to your application:

  • CSS styling for command line - prism-command-line.css
  • command line plugin script - prism-command-line.js

If you are using Angular CLI you can follow the angular.json example below...

"styles": [
  "src/styles.css",
  "node_modules/prismjs/themes/prism-okaidia.css",
+ "node_modules/prismjs/plugins/command-line/prism-command-line.css"
],
"scripts": [
  "node_modules/prismjs/prism.js",
  "node_modules/prismjs/components/prism-csharp.min.js",
  "node_modules/prismjs/components/prism-css.min.js",
+ "node_modules/prismjs/plugins/command-line/prism-command-line.js"
]

Using markdown component and/or directive, you will be able to use the commandLine property to activate the plugin. The property can be used in combination with either data for variable binding, src for remote content or using transclusion for static markdown.

For a server command line, specify the user and host names using the user and host input properties. The resulting prompt displays a # for the root user and $ for all other users. For any other command line, such as a Windows prompt, you may specify the entire prompt using the prompt input property.

You may also specify the lines to be presented as output (no prompt and no highlighting) through the output property in the following simple format:

  • A single number refers to the line with that number
  • Ranges are denoted by two numbers, separated with a hyphen (-)
  • Multiple line numbers or ranges are separated by commas
  • Whitespace is allowed anywhere and will be stripped off
<markdown
  commandLine
  [user]="'chris'"
  [host]="'remotehost'"
  [output]="'2, 4-8'"
  [src]="'path/to/file.bash'">
</markdown>

Optionally, to automatically present some lines as output without providing the line numbers, you can prefix those lines with any string and specify the prefix using the filterOutput input property. For example, [filterOutput]="'(out)'" will treat lines beginning with (out) as output and remove the prefix.

<markdown
  commandLine
  [prompt]="'PS C:\Users\Chris>'"
  [filterOutput]="'(out)'">
  ```powershell
  Get-Date
  (out)
  (out)Sunday, November 7, 2021 8:19:21 PM
  (out)
  `โ€‹``
</markdown>

Emoji support

๐Ÿ”” Emoji support is optional, skip this step if you are not planning to use it

To add Emoji-Toolkit library to your package.json use the following command.

npm install emoji-toolkit@^8.0.0 --save

To activate Emoji-Toolkit for emoji suppport you will need to include...

  • Emoji-Toolkit library - node_modules/emoji-toolkit/lib/js/joypixels.min.js

If you are using Angular CLI you can follow the angular.json example below...

"scripts": [
+ "node_modules/emoji-toolkit/lib/js/joypixels.min.js",
]

Emoji plugin

Using markdown component and/or directive, you will be able to use the emoji property to activate Emoji-Toolkit plugin that converts emoji shortnames such as :heart: to native unicode emojis.

<markdown emoji>
  I :heart: ngx-markdown
</markdown>

๐Ÿ“˜ You can refer to this Emoji Cheat Sheet for a complete list of shortnames.

Math rendering

๐Ÿ”” Math rendering is optional, skip this step if you are not planning to use it

To add KaTeX library to your package.json use the following command.

npm install katex@^0.16.0 --save

To activate KaTeX math rendering you will need to include...

  • KaTex JavaScript library - node_modules/katex/dist/katex.min.js file
  • KaTex Auto-Render extension - node_modules/katex/dist/contrib/auto-render.min.js, file
  • KaTex CSS customization - node_modules/katex/dist/katex.min.css file

If you are using Angular CLI you can follow the angular.json example below...

"styles": [
  "styles.css",
+ "node_modules/katex/dist/katex.min.css"
],
"scripts": [
+ "node_modules/katex/dist/katex.min.js",
+ "node_modules/katex/dist/contrib/auto-render.min.js",
]

KaTeX plugin

Using markdown component and/or directive, you will be able to use the katex property to activate KaTeX plugin that renders mathematical expression to HTML.

<markdown
  katex
  [src]="path/to/file.md">
</markdown>

Optionally, you can use katexOptions property to specify both the KaTeX options and the KaTeX Auto-Render options.

import { KatexOptions } from 'ngx-markdown';

public options: KatexOptions = {
  displayMode: true,
  throwOnError: false,
  errorColor: '#cc0000',
  delimiters: [...],
  ...
};
<markdown
  katex
  [katexOptions]="options"
  [src]="path/to/file.md">
</markdown>

๐Ÿ“˜ Follow official KaTeX options and KaTeX Auto-Render options documentation for more details on the available options.

Diagrams tool

๐Ÿ”” Diagram support is optional, skip this step if you are not planning to use it

To add Mermaid library to your package.json use the following command.

npm install mermaid@^10.6.0 --save

To activate Mermaid diagramming and charting tool you will need to include...

  • Mermaid JavaScript library - node_modules/mermaid/dist/mermaid.min.js file

If you are using Angular CLI you can follow the angular.json example below...

"scripts": [
+ "node_modules/mermaid/dist/mermaid.min.js",
]

Mermaid plugin

Using markdown component and/or directive, you will be able to use the mermaid property to activate Mermaid plugin that renders Markdown-inspired text definitions to create and modify diagrams dynamically.

<markdown
  mermaid
  [src]="path/to/file.md">
</markdown>

Optionally, you can specify mermaid configuration options using mermaidOptions property.

import { MermaidAPI } from 'ngx-markdown';

public options: MermaidAPI.Config = {
  fontFamily: '"trebuchet ms", verdana, arial, sans-serif',
  logLevel: MermaidAPI.LogLevel.Info,
  theme: MermaidAPI.Theme.Dark,
  ...
};
<markdown
  mermaid
  [mermaidOptions]="options"
  [src]="'path/to/file.md'">
</markdown>

๐Ÿ“˜ Follow official Mermaid documentation for more details on diagrams and charts syntax.

Copy-to-clipboard

๐Ÿ”” Copy-to-clipboard support is optional, skip this step if you are not planning to use it

To add Clipboard library to your package.json use the following command.

npm install clipboard@^2.0.11 --save

To activate Clipboard allowing copy-to-clipboard you will need to include...

  • Clipboard JavaScript library - node_modules/clipboard/dist/clipboard.min.js file

If you are using Angular CLI you can follow the angular.json example below...

"scripts": [
+ "node_modules/clipboard/dist/clipboard.min.js",
]

Clipboard plugin

Using markdown component and/or directive, you will be able to use the clipboard property to activate Clipboard plugin that enable copy-to-clipboard for code block from a single click.

<markdown
  clipboard
  [src]="path/to/file.md">
</markdown>

Default button

The clipboard plugin provide an unstyled default button with a default behavior out of the box if no alternative is used.

To customize the default button styling, use the .markdown-clipboard-button CSS selector in your global styles.css/scss file. You can also customized the "copied" state happening after the button is clicked using the .copied CSS selector.

Using global configuration

You can provide a custom component to use globaly across your application with the clipboardOptions in the MarkdownModuleConfig either with provideMarkdown provide-function for standalone components or MarkdownModule.forRoot() for module configuration.

Using the provideMarkdown function
provideMarkdown({
  clipboardOptions: {
    provide: CLIPBOARD_OPTIONS,
    useValue: {
      buttonComponent: ClipboardButtonComponent,
    },
  },
})
Using the MarkdownModule import
MarkdownModule.forRoot({
  clipboardOptions: {
    provide: CLIPBOARD_OPTIONS,
    useValue: {
      buttonComponent: ClipboardButtonComponent,
    },
  },
}),

Using a component

You can also provide your custom component using the clipboardButtonComponent input property when using the clipboard directive.

import { Component } from '@angular/core';

@Component({
  selector: 'app-clipboard-button',
  template: `<button (click)="onClick()">Copy</button>`,
})
export class ClipboardButtonComponent {
  onClick() {
    alert('Copied to clipboard!');
  }
}
import { ClipboardButtonComponent } from './clipboard-button-component';

@Component({ ... })
export class ExampleComponent {
  readonly clipboardButton = ClipboardButtonComponent;
}
<markdown 
  clipboard 
  [clipboardButtonComponent]="clipboardButton">
</markdown>

Using ng-template

Alternatively, the clipboard directive can be used in conjonction with ng-template to provide a custom button implementation via the clipboardButtonTemplate input property on the markdown component.

<ng-template #buttonTemplate>
  <button (click)="onCopyToClipboard()">...</button>
</ng-template>

<markdown 
  clipboard 
  [clipboardButtonTemplate]="buttonTemplate">
</markdown>

๐Ÿ“˜ Refer to the ngx-markdown clipboard plugin demo for live examples.

Configuration

The ngx-markdown library can be used either with the standalone components or with modules configuration. Please follow the configuration section that matches your application.

Standalone components

Use the provideMarkdown provide-function in your application configuration ApplicationConfig to be able to provide the MarkdownComponent and MarkdownPipe to your standalone components and/or inject the MarkdownService.

import { NgModule } from '@angular/core';
+ import { provideMarkdown } from 'ngx-markdown';

export const appConfig: ApplicationConfig = {
  providers: [
+   provideMarkdown(),
  ],
};

Modules configuration

You must import MarkdownModule inside your main application module (usually named AppModule) with forRoot to be able to use the markdown component, directive, pipe and/or MarkdownService.

import { NgModule } from '@angular/core';
+ import { MarkdownModule } from 'ngx-markdown';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
+   MarkdownModule.forRoot(),
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
})
export class AppModule { }

Use forChild when importing MarkdownModule into other application modules to allow you to use the same parser configuration across your application.

import { NgModule } from '@angular/core';
+ import { MarkdownModule } from 'ngx-markdown';
import { HomeComponent } from './home.component';

@NgModule({
  imports: [
+   MarkdownModule.forChild(),
  ],
  declarations: [HomeComponent],
})
export class HomeModule { }

Remote file configuration

If you want to use the [src] attribute to directly load a remote file, in order to keep only one instance of HttpClient and avoid issues with interceptors, you also have to provide HttpClient:

Using the provideMarkdown function
providers: [
+  provideHttpClient(),
+  provideMarkdown({ loader: HttpClient }),
],
Using the MarkdownModule import
imports: [
+  HttpClientModule,
+  MarkdownModule.forRoot({ loader: HttpClient }),
],

Sanitization

As of ngx-markdown v9.0.0 sanitization is enabled by default and uses Angular DomSanitizer with SecurityContext.HTML to avoid XSS vulnerabilities. The SecurityContext level can be changed using the sanitize property when configuring MarkdownModule.

Using the provideMarkdown function
import { SecurityContext } from '@angular/core';

// enable default sanitization
provideMarkdown()

// turn off sanitization
provideMarkdown({
  sanitize: SecurityContext.NONE
})
Using the MarkdownModule import
import { SecurityContext } from '@angular/core';

// enable default sanitization
MarkdownModule.forRoot()

// turn off sanitization
MarkdownModule.forRoot({
  sanitize: SecurityContext.NONE
})

๐Ÿ“˜ Follow Angular DomSanitizer documentation for more information on sanitization and security contexts.

You can bypass sanitization using the markdown component, directive or pipe using the disableSanitizer option as follow:

<!-- disable sanitizer using markdown component -->
<markdown
  [data]="markdown"
  [disableSanitizer]="true">
</markdown>

<!-- disable sanitizer using markdown directive -->
<div markdown
  [data]="markdown"
  [disableSanitizer]="true">
</div>

<!-- disable sanitizer using markdown pipe -->
<div [innerHTML]="markdown | markdown : { disableSanitizer: true } | async"></div>

MarkedOptions

Optionally, markdown parsing can be configured using MarkedOptions that can be provided with the MARKED_OPTIONS injection token via the markedOptions property of the forRoot method of MarkdownModule.

Using the provideMarkdown function
// imports
import { MARKED_OPTIONS, provideMarkdown } from 'ngx-markdown';

// using default options
provideMarkdown(),

// using specific options with ValueProvider and passing HttpClient
provideMarkdown({
  markedOptions: {
    provide: MARKED_OPTIONS,
    useValue: {
      gfm: true,
      breaks: false,
      pedantic: false,
    },
  },
}),
Using the MarkdownModule import
// imports
import { MarkdownModule, MARKED_OPTIONS } from 'ngx-markdown';

// using default options
MarkdownModule.forRoot(),

// using specific options with ValueProvider and passing HttpClient
MarkdownModule.forRoot({
  loader: HttpClient, // optional, only if you use [src] attribute
  markedOptions: {
    provide: MARKED_OPTIONS,
    useValue: {
      gfm: true,
      breaks: false,
      pedantic: false,
    },
  },
}),

MarkedOptions.renderer

MarkedOptions also exposes the renderer property which allows you to override token rendering for your whole application.

The example uses a factory function and override the default blockquote token rendering by adding a CSS class for custom styling when using Bootstrap CSS:

import { MARKED_OPTIONS, MarkedOptions, MarkedRenderer } from 'ngx-markdown';

// function that returns `MarkedOptions` with renderer override
export function markedOptionsFactory(): MarkedOptions {
  const renderer = new MarkedRenderer();

  renderer.blockquote = (text: string) => {
    return '<blockquote class="blockquote"><p>' + text + '</p></blockquote>';
  };

  return {
    renderer: renderer,
    gfm: true,
    breaks: false,
    pedantic: false,
  };
}
Using the provideMarkdown function
// using specific option with FactoryProvider
provideMarkdown({
  markedOptions: {
    provide: MARKED_OPTIONS,
    useFactory: markedOptionsFactory,
  },
}),
Using the MarkdownModule import
// using specific option with FactoryProvider
MarkdownModule.forRoot({
  markedOptions: {
    provide: MARKED_OPTIONS,
    useFactory: markedOptionsFactory,
  },
}),

Marked extensions

You can provide marked extensions using the markedExtensions property that accepts an array of extensions when configuring MarkdownModule.

Using the provideMarkdown function
import { gfmHeadingId } from 'marked-gfm-heading-id';

providemarkdown({
  markedExtensions: [gfmHeadingId()],
}),
Using the MarkdownModule import
import { gfmHeadingId } from 'marked-gfm-heading-id';

MarkdownModule.forRoot({
  markedExtensions: [gfmHeadingId()],
}),

Usage

ngx-markdown provides different approaches to help you parse markdown to your application depending on your needs.

๐Ÿ’ก As of Angular 6, the template compiler strips whitespace by default. Use ngPreserveWhitespaces directive to preserve whitespaces such as newlines in order for the markdown-formatted content to render as intended.
https://angular.io/api/core/Component#preserveWhitespaces

Component

You can use markdown component to either parse static markdown directly from your HTML markup, load the content from a remote URL using src property or bind a variable to your component using data property. You can get a hook on load complete using load output event property, on loading error using error output event property or when parsing is completed using ready output event property.

<!-- static markdown -->
<markdown ngPreserveWhitespaces>
  # Markdown
</markdown>

<!-- loaded from remote url -->
<markdown
  [src]="'path/to/file.md'"
  (load)="onLoad($event)"
  (error)="onError($event)">
</markdown>

<!-- variable binding -->
<markdown
  [data]="markdown"
  (ready)="onReady()">
</markdown>

<!-- inline parser, omitting rendering top-level paragraph -->
<markdown
  [data]="markdown"
  [inline]="true">
</markdown>

Directive

The same way the component works, you can use markdown directive to accomplish the same thing.

<!-- static markdown -->
<div markdown ngPreserveWhitespaces>
  # Markdown
</div>

<!-- loaded from remote url -->
<div markdown
  [src]="'path/to/file.md'"
  (load)="onLoad($event)"
  (error)="onError($event)">
</div>

<!-- variable binding -->
<div markdown
  [data]="markdown"
  (ready)="onReady()">
</div>

<!-- inline parser, omitting rendering top-level paragraph -->
<div markdown
  [data]="markdown"
  [inline]="true">
</div>

Pipe

Using markdown pipe to transform markdown to HTML allow you to chain pipe transformations and will update the DOM when value changes. It is important to note that, because the marked parsing method returns a Promise, it requires the use of the async pipe.

<!-- chain `language` pipe with `markdown` pipe to convert typescriptMarkdown variable content -->
<div [innerHTML]="typescriptMarkdown | language : 'typescript' | markdown | async"></div>

The markdown pipe allow you to use all the same plugins as the component by providing the options parameters.

<!-- provide options parameters to activate plugins or for configuration -->
<div [innerHTML]="typescriptMarkdown | language : 'typescript' | markdown : { emoji: true, inline: true } | async"></div>

This is the MarkdownPipeOptions parameters interface, those options are the same as the ones available for the markdown component:

export interface MarkdownPipeOptions {
  decodeHtml?: boolean;
  inline?: boolean;
  emoji?: boolean;
  katex?: boolean;
  katexOptions?: KatexOptions;
  mermaid?: boolean;
  mermaidOptions?: MermaidAPI.Config;
  markedOptions?: MarkedOptions;
  disableSanitizer?: boolean;
}

Service

You can use MarkdownService to have access to markdown parsing, rendering and syntax highlight methods.

import { Component, OnInit } from '@angular/core';
import { MarkdownService } from 'ngx-markdown';

@Component({ ... })
export class ExampleComponent implements OnInit {
  constructor(private markdownService: MarkdownService) { }

  ngOnInit() {
    // outputs: <p>I am using <strong>markdown</strong>.</p>
    console.log(this.markdownService.parse('I am using __markdown__.'));
  }
}

Renderer

Tokens can be rendered in a custom manner by either...

  • providing the renderer property with the MarkedOptions when importing MarkdownModule.forRoot() into your main application module (see Configuration section)
  • using MarkdownService exposed renderer

Here is an example of overriding the default heading token rendering through MarkdownService by adding an embedded anchor tag like on GitHub:

import { Component, OnInit } from '@angular/core';
import { MarkdownService } from 'ngx-markdown';

@Component({
  selector: 'app-example',
  template: '<markdown># Heading</markdown>',
})
export class ExampleComponent implements OnInit {
  constructor(private markdownService: MarkdownService) { }

  ngOnInit() {
    this.markdownService.renderer.heading = (text: string, level: number) => {
      const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
      return '<h' + level + '>' +
               '<a name="' + escapedText + '" class="anchor" href="#' + escapedText + '">' +
                 '<span class="header-link"></span>' +
               '</a>' + text +
             '</h' + level + '>';
    };
  }
}

This code will output the following HTML:

<h1>
  <a name="heading" class="anchor" href="#heading">
    <span class="header-link"></span>
  </a>
  Heading
</h1>

๐Ÿ“˜ Follow official marked.renderer documentation for the list of tokens that can be overriden.

Re-render Markdown

In some situations, you might need to re-render markdown after making changes. If you've updated the text this would be done automatically, however if the changes are internal to the library such as rendering options, you will need to inform the MarkdownService that it needs to update.

To do so, inject the MarkdownService and call the reload() function as shown below.

import { MarkdownService } from 'ngx-markdown';

constructor(
  private markdownService: MarkdownService,
) { }

update() {
  this.markdownService.reload();
}

๐Ÿ“˜ Refer to the ngx-markdown re-render demo for a live example.

Syntax highlight

When using static markdown you are responsible to provide the code block with related language.

<markdown ngPreserveWhitespaces>
+  ```typescript
    const myProp: string = 'value';
+  ```
</markdown>

When using remote URL ngx-markdown will use the file extension to automatically resolve the code language.

<!-- will use html highlights -->
<markdown [src]="'path/to/file.html'"></markdown>

<!-- will use php highlights -->
<markdown [src]="'path/to/file.php'"></markdown>

When using variable binding you can optionally use language pipe to specify the language of the variable content (default value is markdown when pipe is not used).

<markdown [data]="markdown | language : 'typescript'"></markdown>

Demo application

A demo is available @ https://jfcere.github.io/ngx-markdown and its source code can be found inside the demo directory.

The following commands will clone the repository, install npm dependencies and serve the application @ http://localhost:4200

git clone https://github.com/jfcere/ngx-markdown.git
npm install
npm start

AoT compilation

Building with AoT is part of the CI and is tested every time a commit occurs so you don't have to worry at all.

Road map

Here is the list of tasks that will be done on this library in the near future ...

  • Add a FAQ section to the README.md
  • Improve flexibily for some options

Contribution

Contributions are always welcome, just make sure that ...

  • Your code style matches with the rest of the project
  • Unit tests pass
  • Linter passes

Support Development

The use of this library is totally free and no donation is required.

As the owner and primary maintainer of this project, I am putting a lot of time and effort beside my job, my family and my private time to bring the best support I can by answering questions, addressing issues and improving the library to provide more and more features over time.

If this project has been useful, that it helped you or your business to save precious time, don't hesitate to give it a star and to consider a donation to support its maintenance and future development.

License

Licensed under MIT.

ngx-markdown's People

Contributors

ajsaraujo avatar ashishpatelcs avatar coolwr avatar dependabot[bot] avatar domidre avatar jfcere avatar json-derulo avatar maxime1992 avatar mistic100 avatar nateradebaugh avatar pshields avatar robertisaac avatar rogov-k avatar shawnostler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ngx-markdown's Issues

Code Syntax Highlighting not Working

Hello, great plugin,

After following the Syntax highlight section of the README the code, as in your examples, is not highlighting:

Inline `code` has `back-ticks around` it.

> ```javascript
> var soop = "JavaScript syntax highlighting";
> alert(s);
> ```
> 
> ```python
> s = "Python syntax highlighting"
> print s
> ```

Any ideas what could be wrong?
Thanks

Feature: add plugins like katex

I would like tu use Katex with ngx-markdown the same way I can use it on StackEdit.io.
Is it planned to have some docs about how to add plugins or how to add new key (in StackEdit, Katex syntax is between $$) to execute some logics ?

## KaTeX

You can render LaTeX mathematical expressions using [KaTeX](https://khan.github.io/KaTeX/):

The *Gamma function* satisfying $\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$ is via the Euler integral

$$
\Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.
$$

I can try to do something and do a PR if you want but I would need some indications.

AOT build failing for 6.0.0

Greetings, I'm not able to build my project with [email protected] and AOT being enabled.

With this CoreModule definition:

@NgModule({
  imports: [
    // ...
    MarkdownModule.forRoot()
  ]
})
export class CoreModule {}

I get this error:

ERROR in Error during template compile of 'CoreModule'
  Function calls are not supported in decorators but 'MarkdownModule' was called.

Omitting .forRoot() leads to this error:

ERROR in : Unexpected value 'MarkdownModule in /path-to-my-project/node_modules/ngx-markdown/src/markdown.module.d.ts'
imported by the module 'CoreModule in /path-to-my-project/src/app/core/core.module.ts'.
Please add a @NgModule annotation.

Can also reproduce with a new angular project:

$ ng new foo
$ cd foo
$ yarn add ngx-markdown

Modify app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { MarkdownModule } from 'ngx-markdown';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    MarkdownModule.forRoot()
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
$ ng build --prod
Date: 2018-05-28T13:49:57.822Z
Hash: 1ae5e85a857959e5cde1
Time: 3569ms
chunk {0} runtime.a66f828dca56eeb90e02.js (runtime) 1.05 kB [entry] [rendered]
chunk {1} styles.34c57ab7888ec1573f9c.css (styles) 0 bytes [initial] [rendered]
chunk {2} polyfills.cdf87a8e5b31fe8a11f1.js (polyfills) 130 bytes [initial] [rendered]
chunk {3} main.a2bc6cab25d0aa41c17a.js (main) 128 bytes [initial] [rendered]

ERROR in Error during template compile of 'AppModule'
  Function calls are not supported in decorators but 'MarkdownModule' was called.

wired things happen in 6.1.0

For a simple example, in HTML I give

<div markdown>
    **TEXT**

    # TITLE
</div>

And it shows excatly as

TEXT # TITLE

Turns out all the content inside one <markdown> are shrinked into one <p>, which I think cause the problem. It happens everywhere just after I install the latest version.

IE support

IE 11 is supported? Im using angular 4 and ngx2-markdown.

Syntax Highlighting - Some languages not rendered correctly

Hey, it's me again ^^

It seems I am having troubles with some languages of the syntax highlighting plugin.

The code below only renders the following result: 'test'

<div class="test">test</div>

And the code below does not render anything:

<?php echo 'test'; ?>

I don't have any issue with the other languages (Java, Python, Javascript, CSS, etc.) which are rendered and highlighted properly.

Thank you.

Rendering is deleting characters and not providing html

Using sample MD text rendering in angular 6 is incomplete:
top is rendered, bottom is 'pre' formatted

Code

default options.

rendered html

Database text:
1. First ordered list item\n2. Another item\n * Unordered sub-list.\n1. Actual numbers don't matter, just that it's a number\n 1. Ordered sub-list\n4. And another item.\n\n You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).\n\n To have a line break without a paragraph, you will need to use two trailing spaces. \n Note that this line is separate, but within the same paragraph. \n (This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)\n\n\n* Unordered list can use asterisks\n- Or minuses\n+ Or pluses

[to fix] AOT: ErrorObservable<T> requires 1 type argument

Build prod with aot:

Starting compilation using the angular compiler.
Angular compilation done, starting webpack bundling.
Error: Error at .../frontend/node_modules/ngx-markdown/dist/lib/markdown.service.d.ts:11:41: Generic type 'ErrorObservable<T>' requires 1 type argument(s).
    at new UserError ( ...\tools\@angular\tsc-wrapped\src\tsc.ts:33:25)
    at check ( ...\tools\@angular\tsc-wrapped\src\tsc.ts:69:11)
    at Tsc.typeCheck ( ...\tools\@angular\tsc-wrapped\src\tsc.ts:152:5)
    at  ...\tools\@angular\tsc-wrapped\src\main.ts:68:11
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
npm ERR! code ELIFECYCLE

file: node_modules /ngx-markdown/dist/lib/markdown.service.d.ts

to fix error you need to
change from:

handleError(error: Response | any): ErrorObservable;

to:

handleError(error: Response | any): ErrorObservable<any>;

Angular 6 wildcards

Angular 6 does not support wildcards anymore, they still support the long form.

Before in .angular-cli.json I had, "app/components/**/*.md". The new version I changed it to
{ "glob": "**/*.md", "input": "src/app/components/", "output": "/" } but it won't work.

Upgrade to support NG 5+

Hi,

I really like this project and it's working perfect in my project!

However, it doesn't "support" angular 5.0+. See warnings:

yarn add v1.3.2
[1/4] ๐Ÿ”  Resolving packages...
[2/4] ๐Ÿšš  Fetching packages...
[3/4] ๐Ÿ”—  Linking dependencies...
warning " > [email protected]" has incorrect peer dependency "@angular/core@^2.4.0 || ^4.0.0".
warning " > [email protected]" has incorrect peer dependency "@angular/http@^2.4.0 || ^4.0.0".
[4/4] ๐Ÿ“ƒ  Building fresh packages...
success Saved lockfile.
success Saved 10 new dependencies.

It will be great if you can release a new version which supports angular 5+

Thanks!

XSS Vulnerability

Hey,

someone pointed out an issue in another Angular markdown library about XSS vulnerability and it seems that this library presents the same issue as well.

Links are not being validated and as such, the following code could be used to execute javascript code:

[Click Me](javascript:alert('Injected!'%29)

Syntax highlighting not working properly

I've been trying to highlight Typescript syntax. The text briefly highlights the syntax, then returns to an unhighlighted state. So the highlighting is working, but not staying highlighted.

I'm using ngx-clipboard@^9.0.0, and it doesn't seem to matter which method I use to load the information; I've tried:

Passing a variable created in my Typescript code, that contains a small amount of Typescript code:

<markdown [data]="base31x | language : 'typescript'"> </markdown>

Passing a variable that observes a HTTP Get:
<markdown [data]="base31 | async | language : 'typescript'"> </markdown>

Referring to a local copy of the file stored as an asset:
<markdown [src]="'assets/base31.ts'"> </markdown>

I get the same results regardless of how I hand the Typescript code to the directive: a very brief display of highlighted code followed by a plain, unhighlighted rendering of the code.

Syntax highlighting for more languages

ngx-markdown only supports a subset of languages from Prismjs as it appears. Any plans to support more? I'm especially missing JSON and YAML for my use cases.

Support Variable Binding

If I have a variable x = "#HelloWorld", I'd like to be able to get that rendered too... Its not possible currently.. I tried this:

<markdown-to-html>
{{ x }}
</markdown-to-html>

Component css styles are not applied?

It appears that any css that I add to my component.css is not applied inside the markdown directive.

<markdown [data]="mydata">
<!-- component css ain't working in here -->
</markdown>

You can verify this by e.g. adding h2 {color: red } in the styles part near the top of app.ts in your plunker, https://plnkr.co/edit/y5LPj7?p=preview

@Component({
  styles: [`
h2 { color: red; /* not working */
    }
  `],
})

marked is not defined

I am receiving a 'marked is not defined' error upon compilation.

The exception is thrown inside the handleRaw method within markdown-to-html.component.ts file.

screen shot 2017-08-21 at 6 35 11 am

If I add import * as marked from 'marked' within the import statements, the exception is not throw and the raw markdown is rendered. I have tried importing marked several places within my application with no success in getting past the error.

Support for xml/html code highlighting

The Angular template parser seems to try to parse the written code e.g.

<test>

and give an error message that the markdown-to-html tag has been closed already.
And with the following code angular states that "test" is not a known element and suggests to insert the NO_ERRORS_SCHEMA into the module.

<test></test>

1.6.0 introduces breaking bug

Same build, successful on Friday, failing on Monday, now with this error:

ERROR in ./src/main.ts Module not found: Error: Can't resolve './$$_gendir/app/app.module.ngfactory' in '/home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/src' 
ERROR in Error: Metadata version mismatch for module /home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/ngx-markdown/dist/lib/language.pipe.d.ts, found version 4, expected 3 
    at StaticSymbolResolver.getModuleMetadata (/home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/@angular/compiler/bundles/compiler.umd.js:25616:34)
    at StaticSymbolResolver._createSymbolsOf (/home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/@angular/compiler/bundles/compiler.umd.js:25404:46)
    at StaticSymbolResolver.getSymbolsOf (/home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/@angular/compiler/bundles/compiler.umd.js:25385:14)
    at /home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/@angular/compiler/bundles/compiler.umd.js:24241:30
    at Array.forEach (native)
    at extractProgramSymbols (/home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/@angular/compiler/bundles/compiler.umd.js:24240:79)
    at AotCompiler.analyzeModulesAsync (/home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/@angular/compiler/bundles/compiler.umd.js:23796:47)
    at CodeGenerator.codegen (/home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/@angular/compiler-cli/src/codegen.js:32:14)
    at Function.NgTools_InternalApi_NG_2.codeGen (/home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/@angular/compiler-cli/src/ngtools_api.js:73:30)
    at _donePromise.Promise.resolve.then (/home/bamboo/bamboo-agent-home/xml-data/build-dir/QPW-PUB20-PUBS3/node_modules/@ngtools/webpack/src/plugin.js:428:58)

Using these packages

{
  "dependencies": {
    "@angular/animations": "^4.4.6",
    "@angular/common": "^4.4.6",
    "@angular/compiler": "^4.4.6",
    "@angular/core": "^4.4.6",
    "@angular/forms": "^4.4.6",
    "@angular/http": "^4.4.6",
    "@angular/platform-browser": "^4.4.6",
    "@angular/platform-browser-dynamic": "^4.4.6",
    "@angular/platform-server": "^4.4.6",
    "@angular/router": "^4.4.6",
    "@angular/service-worker": "^1.0.0-beta.5",
    "core-js": "^2.4.1",
    "font-awesome": "^4.7.0",
    "ng-pwa-tools": "0.0.15",
    "ngx-markdown": "^1.5.1",
    "node-sass": "^4.7.2",
    "rxjs": "^5.5.2",
    "zone.js": "^0.8.14"
  },
  "devDependencies": {
    "@angular/cli": "^1.4.10",
    "@angular/compiler-cli": "^4.4.6",
    "@angular/language-service": "^4.4.5",
    "@types/jasmine": "~2.5.53",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^3.2.2",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.2.0",
    "tslint": "~5.7.0",
    "typescript": "2.4.2"
  }
}

Add pipe usage

Using the markdownToHtml should look like this:
<div [innerHTML] ="content | markdownToHtml"></div>

So that if we want to process or manipulate converted html we can just do it like this:
[innerHTML]="content | markdownToHtml | embedYoutubeLink"

open links in new tab

When embedding a link using markdown it opens in the same tab. Is there any way through which we can open the link in a new tab ?

When I use `{` in code block ,The browser throw `Template parse errors`

source example:

public static void main(String[] args){
//do some thing
// this code can not be parsed since it has contained character "{" or "}"
}

Error code

compiler.js:485 Uncaught Error: Template parse errors:
Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.) ("


</div>
[ERROR ->]"): ng:///UserModule/ProfileComponent.html@97:0
Invalid ICU message. Missing '}'. ("


</div>
[ERROR ->]"): ng:///UserModule/ProfileComponent.html@97:0
    at syntaxError (compiler.js:485)
    at DirectiveNormalizer._preparseLoadedTemplate (compiler.js:3222)
    at eval (compiler.js:3202)
    at Object.then (compiler.js:474)
    at DirectiveNormalizer._preParseTemplate (compiler.js:3202)
    at DirectiveNormalizer.normalizeTemplate (compiler.js:3180)
    at CompileMetadataResolver.loadDirectiveMetadata (compiler.js:14900)
    at eval (compiler.js:34262)
    at Array.forEach (<anonymous>)
    at eval (compiler.js:34261)

code tag not translated the same way

I use your component in 2 ways :

  1. As a markdown to html component for the detail of an article (binded from the attribute of an object in the component)
  2. As a preview feature when editing an article (binded from a FormControl value)

In the first case, a simple :

```php
$test = "value";
```

is translated to :

<pre class=" language-php">
    <code class=" language-php">
        <span class="token variable">$test</span> <span class="token operator">=</span> <span class="token string">"value"</span><span class="token punctuation">;</span>
    </code>
</pre>

which is great.

In the second case, the same markdown code translates to :

<pre>
    <code class="lang-php">$test = "value";</code>
</pre>

It seems that prism is not going over the code. Do you have an idea why ?

Do not import `HttpClientModule`

This might sounds a bit weird.

And I hope someone tells me I'm wrong.

I do have some interceptors in my app.
I do use ngx-markdown within a lazy loaded module, let's call it LazyModule.
From LazyModule, every http request is not caught by any of my interceptors.

I've been digging on why this lazy loaded module especially, because I have others and it works fine with them.

Then I've found that issue:
angular/angular#20575

HTTP_INTERCEPTORS are reset when a lazy loaded module imports another module importing HttpClientModule and yes it's an intended behavior. So it's not going to change and we have to deal with it.

I have no idea how to manage that, but could you somehow remove the imports: [HttpClientModule] on this line?

Otherwise it means that people cannot use ngx-markdown into a lazy loaded module if they rely on some interceptors.

Cannot set `marked` options

I'd like to set custom options as shown in the marked documentation e.g.:

marked.setOptions({
  breaks: false
});

Which makes single line breaks into <br>s instead of the normal md behaviour of just adding a space (i.e. treating this as a line continuation).

Right now, I can hack this by just adding my desired call to setOptions at the top of markdown.component.js, but would prefer to have an option per component instance.

Any plans to add setting of custom marked options? Happy to help with preparing a PR with an attempt if this is useful.

Angular directive in markdownfile

Hi,
i'm working on an application, which loads different pages from markdown files.

The pages shows some html components with descriptions. On some pages, i want to show angular components with some directives on it.
My problem is, that the directives are not working, when they are integrated in the markdown file.

My markdown file looks like:

### Form Inputs
<form>
  <div class="md-form">
    <input type="text" id="normal" class="form-control" mdbActive>
    <label for="normal" >Normales Eingabefeld</label>
  </div>
</form>

The input and label should be wrapped into a container with the class "md-form".

The input field needs the class "form-control" and has the directive mdbActive for activating the floating labels.
 
``` html
<form>
  <div class="md-form">
    <input type="text" id="normal" class="form-control" mdbActive>
    <label for="normal" >Normales Eingabefeld</label>
  </div>
</form

Trying to understand your semver choice of 1.6

version 1.6 seems to completely break some applications.. sounds like I should never use ^ in front of ngx-markdown and freeze at 1.5.2 until I can bring the app up to angular 6.. but shouldn't this have been a 2.0.0 release?

Use [src] to load local .md files in the project

What I want to do is use markdown directive with [src]="..." to load a local .md file inside the project, like I have a my.component.ts and a my.md in the same directory. How can I do that?

I tried importing HttpClient in the module with MarkdownModule.forRoot({ loader: HttpClient }) and using markdownService.getSource() to check it, turns out it always opens an XMLHttpRequest, which cannot get what I want.

Thx~

Type 'ErrorObservable' is not generic.

ERROR
node_modules/ng2-markdown-to-html/src/app/markdown-to-html/markdown-to-html.service.ts (25,39): Type 'ErrorObservable' is not generic.)

i am getting this error with @angular/cli 1.0.0-beta.32.3
while using the ng build -prod --aot --verbose

Code wrong class

when it is first rendered it is 'lang-*'
2017-12-12 4 59 09

after i modify the [data] variable it becomes language-*
2017-12-12 4 59 38

[src] dont take syntax of markdown

Hi! I'm trying to display a markdown in my angular 6 app. I'm using the basic configuration of this plugin and follow the steps in the readme , and it works, but the app shows the text without style. I mean, I want to displays this:

## My header
**my text**

like this :

My header

my text

But I can only show it like this, in plain text:

My header
my text

Also, it doesn't take either the headings or any of the markdown tags. what i'm doing wrong ? this is my code:

  • html
    <markdown ngPreserveWhitespaces [src]="'./assets/md/faqs.md'" ></markdown>

  • module.ts

import { NgModule } from '@angular/core';
import { FaqsComponent } from './faqs.component';
import { routing } from './faqs.routing';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { MarkdownModule, MarkedOptions, MarkedRenderer } from 'ngx-markdown';
@NgModule({
imports: [
    HttpClientModule,
    MarkdownModule.forRoot({
      loader: HttpClient
    }),
    routing
  ],
  declarations: [
    FaqsComponent
  ],
  providers: [],
})
export class FaqsModule {
}

Thanks in advance

Lazy Loading Issue

Hi, I am trying to follow this guide for implementing Server Side Rendering and I keep encountering an issue on using your module. From issues discussed elsewhere, its probably because your module is lazyLoaded... Any suggestions?

Use @angular/common/http instead of @angular/http

What is the expected behaviour?

It should be able to mark @angular/http as an optional dependency and/or maybe use @angular/common/http.

What is the actual behaviour?

Errors are outputted stating that @angular/http is required:
Errors outputted stating that @angular/http is required

Consistent Upgrades

Hi, I am using you package for a tool http://ngDocumentor.github.io . I have plans to maintain this. And I also want to discuss features/open issues (like xss open issue) for consistent upgrades with ngx-markdown for the same. How can we connect offline? :-)

problem in AOT Build

I'm using MarkdownModule in the default way
in the imports

    MarkdownModule.forRoot(),

and when i build my project ng build -- --prod --aot
i'm getting this error

ERROR in Error during template compile of 'AppModule'
Function calls are not supported in decorators but 'MarkdownModule' was called.

i'm using the latest version 6.0.0

npm ERR! code EISGIT

I am trying to install ngx-markdown and getting the following error. I have to add that I have installed angular-font-awesome prior to installing ngx-markdown.

`C:\Users\Ben\node\gcp>npm
install ngx-markdown --save
npm ERR! path C:\Users\Ben\node\gcp\node_modules\angular-font-awesome
npm ERR! code EISGIT
npm ERR! git C:\Users\Ben\node\gcp\node_modules\angular-font-awesome: Appears to be a git repo or submodule.
npm ERR! git C:\Users\Ben\node\gcp\node_modules\angular-font-awesome
npm ERR! git Refusing to remove it. Update manually,
npm ERR! git or move it out of the way first.

npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Ben\AppData\Roaming\npm-cache_logs\2017-12-21T17_23_30_781Z-debug.log`

How can I make this work?

Syntax highlighting inside of markdown block

I have a chunk of markdown I'm pulling from a api that has multiple code blocks inside it with different languages. Is there a way to get syntax highlighting inside of a big chunk of markdown?

<markdown class="markdown" [data]="body"></markdown>

My markdown contains blocks like (closing back ticks don't actually have spaces):

   ```javascript
      .... snip
   ` ` `

v1.4.0 | Repository renamed to ngx-markdown

ngx-markdown

To be more accurate with Angular versions and with the upcoming v5 release, I have renamed the repository to ngx-markdown.

Breaking changes

Module, component and directive names have been adjusted to follow the new repository name.

  • Module has been renamed from MarkdownToHtmlModule to MarkdownModule
  • Component has been renamed from <markdown-to-html> to <markdown>
  • Directive has been renamed from <div markdown-to-html> to <div markdown>

Although the changes shouldn't be too hard I am still sorry for any inconvenience that the renaming could cause. For more information please follow the readme.md or don't hesitate to open an issue.

Codelyzer in tslint.json not found

If I run ng lint with the angular cli version 1.0.1 i get the following error.

Failed to load [pathToProject]/node_modules/ng2-markdown-to-html/tslint.json: 
Could not find custom rule directory: [pathToProject]/node_modules/ng2-markdown-to-html/node_modules/codelyzer

Changing the path to ../codelyzer seems to work around the problem but
maybe you have a better solution.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.