diff --git a/types/remarkable/index.d.ts b/types/remarkable/index.d.ts new file mode 100644 index 0000000000..31a5e7d44b --- /dev/null +++ b/types/remarkable/index.d.ts @@ -0,0 +1,8 @@ +// Type definitions for remarkable 1.7 +// Project: https://github.com/jonschlinkert/remarkable +// Definitions by: makepost +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +import Remarkable = require("./lib"); + +export = Remarkable; diff --git a/types/remarkable/lib/common/utils.d.ts b/types/remarkable/lib/common/utils.d.ts new file mode 100644 index 0000000000..0feaaac0ce --- /dev/null +++ b/types/remarkable/lib/common/utils.d.ts @@ -0,0 +1,15 @@ +export function isString(obj?: any): boolean; + +export function has(object: any, key: string): boolean; + +export function assign(target: any, ...sources: any[]): any; + +export function unescapeMd(str: string): string; + +export function isValidEntityCode(c: number): boolean; + +export function fromCodePoint(c: number): string; + +export function replaceEntities(str: string): string; + +export function escapeHtml(str: string): string; diff --git a/types/remarkable/lib/index.d.ts b/types/remarkable/lib/index.d.ts new file mode 100644 index 0000000000..ba29c7cf5c --- /dev/null +++ b/types/remarkable/lib/index.d.ts @@ -0,0 +1,347 @@ +import * as Utils from "./common/utils"; +import Renderer = require("./renderer"); +import Ruler = require("./ruler"); + +export = Remarkable; + +declare class Remarkable { + /** + * Useful helper functions for custom rendering. + */ + static utils: typeof Utils; + + inline: { ruler: Ruler }; + + block: { ruler: Ruler }; + + core: { ruler: Ruler }; + + renderer: Renderer; + + /** + * Markdown parser, done right. + */ + constructor(options?: Remarkable.Options); + + /** + * Remarkable offers some "presets" as a convenience to quickly enable/disable + * active syntax rules and options for common use cases. + */ + constructor(preset: "commonmark" | "full" | "remarkable", options?: Remarkable.Options); + + /** + * `"# Remarkable rulezz!"` => `"

Remarkable rulezz!

"` + */ + render(markdown: string, env?: Remarkable.Env): string; + + /** + * Define options. + * + * Note: To achieve the best possible performance, don't modify a Remarkable instance + * on the fly. If you need multiple configurations, create multiple instances and + * initialize each with a configuration that is ideal for that instance. + */ + set(options: Remarkable.Options): void; + + /** + * Use a plugin. + */ + use(plugin: Remarkable.Plugin, options?: any): Remarkable; + + /** + * Batch loader for components rules states, and options. + */ + configure(presets: Remarkable.Presets): void; + + /** + * Parse the input `string` and return a tokens array. + * Modifies `env` with definitions data. + */ + parse(str: string, env: Remarkable.Env): Remarkable.Token[]; + + /** + * Parse the given content `string` as a single string. + */ + parseInline(str: string, env: Remarkable.Env): Remarkable.Token[]; + + /** + * Render a single content `string`, without wrapping it + * to paragraphs. + */ + renderInline(str: string, env?: Remarkable.Env): string; +} + +declare namespace Remarkable { + interface Env { + [key: string]: any; + } + + type GetBreak = (tokens: Token[], idx: number) => "" | "\n"; + + interface Options { + /** + * Enable HTML tags in source. + */ + html?: boolean; + + /** + * Use "/" to close single tags (
). + */ + xhtmlOut?: boolean; + + /** + * Convert "\n" in paragraphs into
. + */ + breaks?: boolean; + + /** + * CSS language prefix for fenced blocks. + */ + langPrefix?: string; + + /** + * Autoconvert URL-like text to links. + */ + linkify?: boolean; + + /** + * Enable some language-neutral replacement + quotes beautification. + */ + typographer?: boolean; + + /** + * Double + single quotes replacement pairs, when typographer enabled, + * and smartquotes on. Set doubles to "«»" for Russian, "„“" for German. + */ + quotes?: string; + + /** + * Highlighter function. Should return escaped HTML, or "" if the source + * string is not changed. + */ + highlight?(str: string, lang: string): string; + } + + type Plugin = (md: Remarkable, options?: any) => void; + + interface Presets { + components: { + [name: string]: { + rules: Rules, + }, + }; + + options: Options; + } + + type Rule = ( + /** + * The list of tokens currently being processed. + */ + tokens: Token[], + + /** + * The index of the token currently being processed. + */ + idx: number, + + /** + * The options given to remarkable. + */ + options: Options, + + /** + * The key-value store created by the parsing rules. + */ + env: Env, + ) => string; + + interface Rules { + [name: string]: Rule; + "blockquote_open": Rule; + "blockquote_close": Rule; + "code": Rule; + "fence": Rule; + "fence_custom": Rule; + "heading_open": Rule; + "heading_close": Rule; + "hr": Rule; + "bullet_list_open": Rule; + "bullet_list_close": Rule; + "list_item_open": Rule; + "list_item_close": Rule; + "ordered_list_open": Rule; + "ordered_list_close": Rule; + "paragraph_open": Rule; + "paragraph_close": Rule; + "link_open": Rule; + "link_close": Rule; + "image": Rule; + "table_open": Rule; + "table_close": Rule; + "thead_open": Rule; + "thead_close": Rule; + "tbody_open": Rule; + "tbody_close": Rule; + "tr_open": Rule; + "tr_close": Rule; + "th_open": Rule; + "th_close": Rule; + "td_open": Rule; + "td_close": Rule; + "strong_open": Rule; + "strong_close": Rule; + "em_open": Rule; + "em_close": Rule; + "del_open": Rule; + "del_close": Rule; + "ins_open": Rule; + "ins_close": Rule; + "mark_open": Rule; + "mark_close": Rule; + "sub": Rule; + "sup": Rule; + "hardbreak": Rule; + "softbreak": Rule; + "text": Rule; + "htmlblock": Rule; + "htmltag": Rule; + "abbr_open": Rule; + "abbr_close": Rule; + "footnote_ref": Rule; + "footnote_block_open": Rule; + "footnote_block_close": Rule; + "footnote_open": Rule; + "footnote_close": Rule; + "footnote_anchor": Rule; + "dl_open": Rule; + "dt_open": Rule; + "dd_open": Rule; + "dl_close": Rule; + "dt_close": Rule; + "dd_close": Rule; + + /** + * Check to see if `\n` is needed before the next token. + */ + getBreak: GetBreak; + } + + interface BaseToken { + /** + * The nesting level of the associated markdown structure in the source. + */ + level: number; + + /** + * Tokens generated by block parsing rules also include a `lines` + * property which is a 2 elements array marking the first and last line of the + * `src` used to generate the token. + */ + lines?: [number, number]; + } + + interface TagToken extends BaseToken { + /** + * Tag tokens have a variety of types and each is a name of a + * rendering rule. + */ + type: string; + } + + interface ContentToken extends BaseToken { + /** + * A text token has a `content` property containing the text it represents. + */ + content: string; + + /** + * The type of the token. + */ + type: "text"; + } + + interface BlockContentToken extends BaseToken { + /** + * The content of the block. This might include inline mardown syntax + * which may need further processing by the inline rules. + */ + content: string; + + /** + * This is initialized with an empty array (`[]`) and will be filled + * with the inline parser tokens as the inline parsing rules are applied. + */ + children: Token[]; + + /** + * The type of the token. + */ + type: "inline"; + } + + interface MiscTokenProps { + /** + * Image alt. + */ + alt?: string; + + /** + * Code: `true` if block, `false` if inline. + */ + block?: boolean; + + /** + * Footnote label. + */ + label?: any; + + /** + * Heading level. + */ + hLevel?: number; + + /** + * Link url. + */ + href?: string; + + /** + * Footnote id. + */ + id?: number; + + /** + * Ordered list marker value. + */ + order?: number; + + /** + * Fenced block params. + */ + params?: any[]; + + /** + * Image url. + */ + src?: string; + + /** + * Footnote sub id. + */ + subId?: number; + + /** + * Absence of empty line before current tag: `true` if absent, `false` + * if present. List is tight if any list item is tight. + */ + tight?: boolean; + + /** + * Abbreviation title. + */ + title?: string; + } + + type Token = (TagToken | ContentToken | BlockContentToken) & MiscTokenProps; +} diff --git a/types/remarkable/lib/renderer.d.ts b/types/remarkable/lib/renderer.d.ts new file mode 100644 index 0000000000..f90e4187cf --- /dev/null +++ b/types/remarkable/lib/renderer.d.ts @@ -0,0 +1,28 @@ +import Remarkable = require("."); + +export = Renderer; + +/** + * Renderer class. Renders HTML and exposes `rules` to allow + * local modifications. + */ +declare class Renderer { + rules: Remarkable.Rules; + + /** + * Exported helper, for custom rules only. + */ + getBreak: Remarkable.GetBreak; + + /** + * Render a string of inline HTML with the given `tokens` and + * `options`. + */ + renderInline(tokens: Remarkable.Token[], options: Remarkable.Options, env: Remarkable.Env): string; + + /** + * Render a string of HTML with the given `tokens` and + * `options`. + */ + render(tokens: Remarkable.Token[], options: Remarkable.Options, env: Remarkable.Env): string; +} diff --git a/types/remarkable/lib/ruler.d.ts b/types/remarkable/lib/ruler.d.ts new file mode 100644 index 0000000000..20c781f9b0 --- /dev/null +++ b/types/remarkable/lib/ruler.d.ts @@ -0,0 +1,52 @@ +import Remarkable = require("."); + +export = Ruler; + +/** + * Ruler is a helper class for building responsibility chains from + * parse rules. It allows: + * + * - easy stack rules chains + * - getting main chain and named chains content (as arrays of functions) + */ +declare class Ruler { + /** + * Replace the rule `ruleName` with a new rule. + */ + at(ruleName: string, fn: Remarkable.Rule, options: Remarkable.Options): void; + + /** + * Add a rule to the chain before given the `ruleName`. + */ + before(beforeName: string, ruleName: string, fn: Remarkable.Rule, options: Remarkable.Options): void; + + /** + * Add a rule to the chain after the given `ruleName`. + */ + after(afterName: string, ruleName: string, fn: Remarkable.Rule, options: Remarkable.Options): void; + + /** + * Add a rule to the end of chain. + */ + push(ruleName: string, fn: Remarkable.Rule, options: Remarkable.Options): void; + + /** + * Enable a rule or list of rules. + * + * @param list Name or array of rule names to enable. + * @param strict If `true`, all non listed rules will be disabled. + */ + enable(list: string | string[], strict?: boolean): void; + + /** + * Disable a rule or list of rules. + * + * @param list Name or array of rule names to disable. + */ + disable(list: string | string[]): void; + + /** + * Get a rules list as an array of functions. + */ + getRules(chainName: string): Remarkable.Rule[]; +} diff --git a/types/remarkable/remarkable-tests.ts b/types/remarkable/remarkable-tests.ts new file mode 100644 index 0000000000..9c12632e69 --- /dev/null +++ b/types/remarkable/remarkable-tests.ts @@ -0,0 +1,355 @@ +// This code does not run, but it is type-checked. + +import hljs = require("highlight.js"); +import Remarkable = require("remarkable"); + +/** + * Examples from README. + */ +export class RemarkableTest { + usage() { + const md = new Remarkable(); + md.render("# Remarkable rulezz!"); + } + + defineOptionsInContructor() { + const md = new Remarkable({ + html: false, + xhtmlOut: false, + breaks: false, + langPrefix: "language-", + linkify: false, + typographer: false, + quotes: "“”‘’", + highlight(/*str, lang*/) { return ""; }, + }); + + md.render("# Remarkable rulezz!"); + } + + defineOptions() { + const md = new Remarkable(); + + md.set({ + html: true, + breaks: true, + }); + } + + enableStrict() { + const md = new Remarkable("commonmark"); + + md.render("# Remarkable rulezz!"); + } + + enableAllRules() { + let md = new Remarkable("full"); + + // Or with options: + md = new Remarkable("full", { + html: true, + linkify: true, + typographer: true, + }); + + md.render("# Remarkable rulezz!"); + } + + highlightFencedCodeBlocks() { + const md = new Remarkable({ + highlight(str, lang) { + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(lang, str).value; + } catch (err) { } + } + + try { + return hljs.highlightAuto(str).value; + } catch (err) { } + + return ""; + }, + }); + + md.render("# Remarkable rulezz!"); + } + + manageRules() { + let md = new Remarkable(); + md.inline.ruler.enable(["ins", "mark"]); + md.block.ruler.disable(["table", "footnote"]); + + // Enable everything + md = new Remarkable("full", { + html: true, + linkify: true, + typographer: true, + }); + } + + enableRulesManually() { + const md = new Remarkable(); + md.core.ruler.enable([ + "abbr", + ]); + md.block.ruler.enable([ + "footnote", + "deflist", + ]); + md.inline.ruler.enable([ + "footnote_inline", + "ins", + "mark", + "sub", + "sup", + ]); + } + + typographer() { + const md = new Remarkable({ + typographer: true, + quotes: "“”‘’", + }); + + // Disable rules at all: + md.core.ruler.disable(["replacements", "smartquotes"]); + } + + loadPlugins() { + const md = new Remarkable(); + const noop = () => { }; + const plugin1: Remarkable.Plugin = noop as (md: Remarkable) => void; + const plugin2: Remarkable.Plugin = noop as (md: Remarkable, options: {}) => void; + const plugin3: Remarkable.Plugin = noop as (md: Remarkable) => void; + const opts: any = undefined; + + md.use(plugin1) + .use(plugin2, opts) + .use(plugin3); + } + + touchParserAndRenderer() { + const md = new Remarkable(); + md.core; + md.core.ruler; + md.block; + md.block.ruler; + md.inline; + md.inline.ruler; + md.renderer; + md.renderer.rules; + } +} + +/** + * Various tokens copied from source. + */ +export class TokenTest { + blockRules() { + const tokens: Remarkable.Token[] = []; + const state = { + level: 0, + line: 0, + tokens, + }; + + const lines: [number, number] = [0, 0]; + const startLine = 0; + + state.tokens.push({ + type: "blockquote_open", + lines, + level: state.level++, + }); + + state.tokens.push({ + type: "blockquote_close", + level: --state.level, + }); + + state.tokens.push({ + type: "code", + content: "", + block: true, + lines, + level: state.level, + }); + + state.tokens.push({ + type: "inline", + content: "", + level: state.level + 1, + lines, + children: [], + }); + + state.tokens.push({ + type: "fence", + params: [], + content: "", + lines: [startLine, state.line], + level: state.level, + }); + + state.tokens.push({ + type: "footnote_reference_open", + label: "", + level: state.level++, + }); + + state.tokens.push({ type: "heading_close", hLevel: 1, level: state.level }); + + state.tokens.push({ + type: "ordered_list_open", + order: 0, + lines: [startLine, 0], + level: state.level++, + }); + + state.tokens.push({ + type: "paragraph_open", + tight: false, + lines: [startLine, state.line], + level: state.level, + }); + } + + coreRules() { + const env: Remarkable.Env = {}; + const nodes: Remarkable.Token[] = []; + const tokens = nodes; + const state = { env, src: "", tokens }; + const m = ["", "", ""]; + let level = 0; + + nodes.push({ + type: "text", + content: "", + level, + }); + + nodes.push({ + type: "abbr_open", + title: state.env["abbreviations"][":" + m[2]], + level: level++, + }); + + nodes.push({ + type: "text", + content: m[2], + level, + }); + + nodes.push({ + type: "abbr_close", + level: --level, + }); + + state.tokens.push({ + type: "inline", + content: state.src.replace(/\n/g, " ").trim(), + level: 0, + lines: [0, 1], + children: [], + }); + + tokens.push({ + type: "inline", + content: "", + level, + children: [], + }); + } + + inlineRules() { + const state: Remarkable.Token[] = []; + let level = 0; + + state.push({ + type: "link_open", + href: "", + level, + }); + + state.push({ + type: "code", + content: "", + block: false, + level, + }); + + state.push({ + type: "footnote_ref", + id: 1, + level, + }); + + state.push({ + type: "footnote_ref", + id: 1, + subId: 1, + level, + }); + + state.push({ + type: "image", + src: "", + title: "", + alt: "", + level, + }); + + state.push({ + type: "link_open", + href: "", + title: "", + level: level++, + }); + } +} + +/** + * Helper function usage. + */ +export class UtilsTest { + isString() { + Remarkable.utils.isString("foo"); + Remarkable.utils.isString(1); + Remarkable.utils.isString({}); + Remarkable.utils.isString(); + } + + has() { + Remarkable.utils.has({ foo: "bar" }, "foo"); + Remarkable.utils.has({}, "foo"); + } + + assign() { + Remarkable.utils.assign({}, { foo: "bar" }, { baz: "qux" }); + } + + unescapeMd() { + Remarkable.utils.unescapeMd("\\"); + } + + isValidEntityCode() { + Remarkable.utils.isValidEntityCode(0xD800); + Remarkable.utils.isValidEntityCode(0xD7FF); + Remarkable.utils.isValidEntityCode(1000); + } + + fromCodePoint() { + Remarkable.utils.fromCodePoint(0xffff + 1); + Remarkable.utils.fromCodePoint(0xffff); + } + + replaceEntities() { + Remarkable.utils.replaceEntities(""); + Remarkable.utils.replaceEntities(" "); + } + + escapeHtml() { + Remarkable.utils.replaceEntities(''); + } +} diff --git a/types/remarkable/tsconfig.json b/types/remarkable/tsconfig.json new file mode 100644 index 0000000000..48f5c8f145 --- /dev/null +++ b/types/remarkable/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "lib/common/utils.d.ts", + "lib/index.d.ts", + "lib/renderer.d.ts", + "lib/ruler.d.ts", + "index.d.ts", + "remarkable-tests.ts" + ] +} diff --git a/types/remarkable/tslint.json b/types/remarkable/tslint.json new file mode 100644 index 0000000000..f93cf8562a --- /dev/null +++ b/types/remarkable/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "dtslint/dt.json" +}