writr 4.1.3 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +170 -50
  2. package/dist/writr.d.ts +132 -18
  3. package/dist/writr.js +212 -57
  4. package/package.json +13 -14
package/README.md CHANGED
@@ -1,21 +1,40 @@
1
1
  ![Writr](site/logo.svg)
2
2
 
3
- ---
4
-
5
- ## Markdown Rendering Simplified
3
+ # Markdown Rendering Simplified
6
4
  [![tests](https://github.com/jaredwray/writr/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/writr/actions/workflows/tests.yml)
7
5
  [![GitHub license](https://img.shields.io/github/license/jaredwray/writr)](https://github.com/jaredwray/writr/blob/master/LICENSE)
8
6
  [![codecov](https://codecov.io/gh/jaredwray/writr/branch/master/graph/badge.svg?token=1YdMesM07X)](https://codecov.io/gh/jaredwray/writr)
9
7
  [![npm](https://img.shields.io/npm/dm/writr)](https://npmjs.com/package/writr)
10
8
  [![npm](https://img.shields.io/npm/v/writr)](https://npmjs.com/package/writr)
11
9
 
12
- ---
13
- ## Table of Contents
10
+ # Table of Contents
14
11
  - [Features](#features)
12
+ - [ESM and Node Version Support](#esm-and-node-version-support)
15
13
  - [Getting Started](#getting-started)
16
- - [License - MIT](#license)
17
-
18
- ## Features
14
+ - [API](#api)
15
+ - [`new Writr(arg?: string | WritrOptions, options?: WritrOptions)`](#new-writrarg-string--writroptions-options-writroptions)
16
+ - [`.content`](#content)
17
+ - [`.body`](#body)
18
+ - [`.options`](#options)
19
+ - [`.frontmatter`](#frontmatter)
20
+ - [`.frontMatterRaw`](#frontmatterraw)
21
+ - [`.cache`](#cache)
22
+ - [`.engine`](#engine)
23
+ - [`.render(options?: RenderOptions): Promise<string>`](#renderoptions-renderoptions-promisestring)
24
+ - [`.renderSync(options?: RenderOptions): string`](#rendersyncoptions-renderoptions-string)
25
+ - [`.renderToFile(filePath: string, options?: RenderOptions): Promise<void>`](#rendertofilefilepath-string-options-renderoptions-promisevoid)
26
+ - [`.renderToFileSync(filePath: string, options?: RenderOptions): void`](#rendertofilesyncfilepath-string-options-renderoptions-void)
27
+ - [`.renderReact(options?: RenderOptions, reactOptions?: HTMLReactParserOptions): Promise<React.JSX.Element />`](#renderreactoptions-renderoptions-reactoptions-htmlreactparseroptions-promise-reactjsxelement-)
28
+ - [`.renderReactSync( options?: RenderOptions, reactOptions?: HTMLReactParserOptions): React.JSX.Element`](#renderreactsync-options-renderoptions-reactoptions-htmlreactparseroptions-reactjsxelement)
29
+ - [`.loadFromFile(filePath: string): Promise<void>`](#loadfromfilefilepath-string-promisevoid)
30
+ - [`.loadFromFileSync(filePath: string): void`](#loadfromfilesyncfilepath-string-void)
31
+ - [`.saveToFile(filePath: string): Promise<void>`](#savetofilefilepath-string-promisevoid)
32
+ - [`.saveToFileSync(filePath: string): void`](#savetofilesyncfilepath-string-void)
33
+ - [Code of Conduct and Contributing](#code-of-conduct-and-contributing)
34
+ - [License](#license)
35
+
36
+
37
+ # Features
19
38
  * Removes the remark / unified complexity and easy to use.
20
39
  * Built in caching 💥 making it render very fast when there isnt a change
21
40
  * Frontmatter support built in by default. :tada:
@@ -29,11 +48,11 @@
29
48
  * Emoji Support (remark-emoji).
30
49
  * MDX Support (remark-mdx).
31
50
 
32
- ## ESM and Node Version Support
51
+ # ESM and Node Version Support
33
52
 
34
53
  This package is ESM only and tested on the current lts version and its previous. Please don't open issues for questions regarding CommonJS / ESM or previous Nodejs versions. To learn more about using ESM please read this from `sindresorhus`: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
35
54
 
36
- ## Getting Started
55
+ # Getting Started
37
56
 
38
57
  ```bash
39
58
  > npm install writr
@@ -64,6 +83,7 @@ An example passing in the options also via the constructor:
64
83
  ```javascript
65
84
  import { Writr, WritrOptions } from 'writr';
66
85
  const writrOptions = {
86
+ throwErrors: true,
67
87
  renderOptions: {
68
88
  emoji: true,
69
89
  toc: true,
@@ -79,15 +99,16 @@ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`, writrOptions)
79
99
  const html = await writr.render(options); // <h1>Hello World ::-):</h1><p>This is a test.</p>
80
100
  ```
81
101
 
82
- ## API
102
+ # API
83
103
 
84
- ### `new Writr(arg?: string | WritrOptions, options?: WritrOptions)`
104
+ ## `new Writr(arg?: string | WritrOptions, options?: WritrOptions)`
85
105
 
86
106
  By default the constructor takes in a markdown `string` or `WritrOptions` in the first parameter. You can also send in nothing and set the markdown via `.content` property. If you want to pass in your markdown and options you can easily do this with `new Writr('## Your Markdown Here', { ...options here})`. You can access the `WritrOptions` from the instance of Writr. Here is an example of WritrOptions.
87
107
 
88
108
  ```javascript
89
109
  import { Writr, WritrOptions } from 'writr';
90
110
  const writrOptions = {
111
+ throwErrors: true,
91
112
  renderOptions: {
92
113
  emoji: true,
93
114
  toc: true,
@@ -102,7 +123,7 @@ const writrOptions = {
102
123
  const writr = new Writr(writrOptions);
103
124
  ```
104
125
 
105
- ### `.content`
126
+ ## `.content`
106
127
 
107
128
  Setting the markdown content for the instance of Writr. This can be set via the constructor or directly on the instance and can even handle `frontmatter`.
108
129
 
@@ -116,60 +137,112 @@ title: Hello World
116
137
  # Hello World ::-):\n\n This is a test.`;
117
138
  ```
118
139
 
119
- ### `.options`
140
+ ## `.body`
120
141
 
121
- Accessing the default options for this instance of Writr.
142
+ gets the body of the markdown content. This is the content without the frontmatter.
122
143
 
123
- ### `.cache`
144
+ ```javascript
145
+ import { Writr } from 'writr';
146
+ const writr = new Writr();
147
+ writr.content = `---
148
+ title: Hello World
149
+ ---
150
+ # Hello World ::-):\n\n This is a test.`;
151
+ console.log(writr.body); // '# Hello World ::-):\n\n This is a test.'
152
+ ```
124
153
 
125
- Accessing the cache for this instance of Writr. By default this is an in memory cache and is enabled by default. You can disable this by setting `caching: false` in the `RenderOptions` of the `WritrOptions` or when calling render passing the `RenderOptions` like here:
154
+ ## `.options`
155
+
156
+ Accessing the default options for this instance of Writr. Here is the default settings for `WritrOptions`.
126
157
 
127
158
  ```javascript
128
- import { Writr } from 'writr';
129
- const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
130
- const options = {
131
- caching: false
159
+ {
160
+ throwErrors: false,
161
+ renderOptions: {
162
+ emoji: true,
163
+ toc: false,
164
+ slug: false,
165
+ highlight: false,
166
+ gfm: true,
167
+ math: false,
168
+ mdx: false,
169
+ caching: false,
170
+ }
132
171
  }
133
- const html = await writr.render(options); // <h1>Hello World ::-):</h1><p>This is a test.</p>
134
172
  ```
135
173
 
136
- If you would like to use a specific storage adapter from https://keyv.org you can pass in the adapter like so:
174
+ ## `.frontmatter`
175
+
176
+ Accessing the frontmatter for this instance of Writr. This is a `Record<string, any>` and can be set via the `.content` property.
177
+
178
+ ```javascript
179
+ import { Writr } from 'writr';
180
+ const writr = new Writr();
181
+ writr.content = `---
182
+ title: Hello World
183
+ ---
184
+ # Hello World ::-):\n\n This is a test.`;
185
+ console.log(writr.frontmatter); // { title: 'Hello World' }
186
+ ```
187
+
188
+ you can also set the front matter directly like this:
189
+
190
+ ```javascript
191
+ import { Writr } from 'writr';
192
+ const writr = new Writr();
193
+ writr.frontmatter = { title: 'Hello World' };
194
+ ```
195
+
196
+ ## `.frontMatterRaw`
197
+
198
+ Accessing the raw frontmatter for this instance of Writr. This is a `string` and can be set via the `.content` property.
199
+
200
+ ```javascript
201
+ import { Writr } from 'writr';
202
+ const writr = new Writr();
203
+ writr.content = `---
204
+ title: Hello World
205
+ ---
206
+ # Hello World ::-):\n\n This is a test.`;
207
+ console.log(writr.frontMatterRaw); // '---\ntitle: Hello World\n---'
208
+ ```
209
+
210
+ ## `.cache`
211
+
212
+ Accessing the cache for this instance of Writr. By default this is an in memory cache and is disabled (set to false) by default. You can enable this by setting `caching: true` in the `RenderOptions` of the `WritrOptions` or when calling render passing the `RenderOptions` like here:
137
213
 
138
214
  ```javascript
139
215
  import { Writr } from 'writr';
140
- import Keyv from '@keyv/redis';
141
- const keyvRedis = new Keyv('redis://user:pass@localhost:6379');
142
216
  const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
143
- writr.cache.setStorageAdapter(keyvRedis);
217
+ const options = {
218
+ caching: true
219
+ }
220
+ const html = await writr.render(options); // <h1>Hello World ::-):</h1><p>This is a test.</p>
144
221
  ```
145
222
 
146
- ### `.engine`
223
+
224
+ ## `.engine`
147
225
 
148
226
  Accessing the underlying engine for this instance of Writr. This is a `Processor<Root, Root, Root, undefined, undefined>` fro the unified `.use()` function. You can use this to add additional plugins to the engine.
149
227
 
150
- ### `.render(options?: RenderOptions): Promise<string>`
228
+ ## `.render(options?: RenderOptions): Promise<string>`
151
229
 
152
230
  Rendering markdown to HTML. the options are based on RenderOptions. Which you can access from the Writr instance.
153
231
 
154
232
  ```javascript
155
- import { Writr, RenderOptions } from 'writr';
233
+ import { Writr } from 'writr';
234
+ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
235
+ const html = await writr.render(); // <h1>Hello World 🙂</h1><p>This is a test.</p>
156
236
 
157
- ## `RenderOptions`
237
+ //passing in with render options
238
+ const options = {
239
+ emoji: false
240
+ }
158
241
 
159
- ```js
160
- type RenderOptions = {
161
- emoji?: boolean; // Emoji support (default: true)
162
- toc?: boolean; // Table of contents generation (default: true)
163
- slug?: boolean; // Slug generation (default: true)
164
- highlight?: boolean; // Code highlighting (default: true)
165
- gfm?: boolean; // Github flavor markdown (default: true)
166
- math?: boolean; // Math support (default: true)
167
- mdx?: boolean; // MDX support (default: true)
168
- caching?: boolean; // Caching (default: true)
169
- };
242
+ const html = await writr.render(options); // <h1>Hello World ::-):</h1><p>This is a test.</p>
170
243
  ```
171
244
 
172
- ### `.renderSync(options?: RenderOptions): string`
245
+ ## `.renderSync(options?: RenderOptions): string`
173
246
 
174
247
  Rendering markdown to HTML synchronously. the options are based on RenderOptions. Which you can access from the Writr instance. The parameters are the same as the `.render()` function.
175
248
 
@@ -179,7 +252,27 @@ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
179
252
  const html = writr.renderSync(); // <h1>Hello World 🙂</h1><p>This is a test.</p>
180
253
  ```
181
254
 
182
- ### '.renderReact(options?: RenderOptions, reactOptions?: HTMLReactParserOptions): Promise<React.JSX.Element />'
255
+ ## '.renderToFile(filePath: string, options?: RenderOptions): Promise<void>'
256
+
257
+ Rendering markdown to a file. The options are based on RenderOptions.
258
+
259
+ ```javascript
260
+ import { Writr } from 'writr';
261
+ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
262
+ await writr.renderToFile('path/to/file.html');
263
+ ```
264
+
265
+ ## '.renderToFileSync(filePath: string, options?: RenderOptions): void'
266
+
267
+ Rendering markdown to a file synchronously. The options are based on RenderOptions.
268
+
269
+ ```javascript
270
+ import { Writr } from 'writr';
271
+ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
272
+ writr.renderToFileSync('path/to/file.html');
273
+ ```
274
+
275
+ ## '.renderReact(options?: RenderOptions, reactOptions?: HTMLReactParserOptions): Promise<React.JSX.Element />'
183
276
 
184
277
  Rendering markdown to React. The options are based on RenderOptions and now HTMLReactParserOptions from `html-react-parser`.
185
278
 
@@ -189,7 +282,7 @@ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
189
282
  const reactElement = await writr.renderReact(); // Will return a React.JSX.Element
190
283
  ```
191
284
 
192
- ### '.renderReactSync( options?: RenderOptions, reactOptions?: HTMLReactParserOptions): React.JSX.Element'
285
+ ## '.renderReactSync( options?: RenderOptions, reactOptions?: HTMLReactParserOptions): React.JSX.Element'
193
286
 
194
287
  Rendering markdown to React. The options are based on RenderOptions and now HTMLReactParserOptions from `html-react-parser`.
195
288
 
@@ -199,7 +292,7 @@ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
199
292
  const reactElement = writr.renderReactSync(); // Will return a React.JSX.Element
200
293
  ```
201
294
 
202
- ### `.loadFromFile(filePath: string): Promise<void>`
295
+ ## `.loadFromFile(filePath: string): Promise<void>`
203
296
 
204
297
  Load your markdown content from a file path.
205
298
 
@@ -209,11 +302,11 @@ const writr = new Writr();
209
302
  await writr.loadFromFile('path/to/file.md');
210
303
  ```
211
304
 
212
- ### `.loadFromFileSync(filePath: string): void`
305
+ ## `.loadFromFileSync(filePath: string): void`
213
306
 
214
307
  Load your markdown content from a file path synchronously.
215
308
 
216
- ### `.saveToFile(filePath: string): Promise<void>`
309
+ ## `.saveToFile(filePath: string): Promise<void>`
217
310
 
218
311
  Save your markdown and frontmatter (if included) content to a file path.
219
312
 
@@ -223,13 +316,40 @@ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
223
316
  await writr.saveToFile('path/to/file.md');
224
317
  ```
225
318
 
226
- ### `.saveToFileSync(filePath: string): void`
319
+ ## `.saveToFileSync(filePath: string): void`
227
320
 
228
321
  Save your markdown and frontmatter (if included) content to a file path synchronously.
229
322
 
230
- ## Code of Conduct and Contributing
323
+ # Caching On Render
324
+
325
+ Caching is built into Writr and is an in-memory cache using `CacheableMemory` from [Cacheable](https://cacheable.org). It is turned off by default and can be enabled by setting `caching: true` in the `RenderOptions` of the `WritrOptions` or when calling render passing the `RenderOptions` like here:
326
+
327
+ ```javascript
328
+ import { Writr } from 'writr';
329
+ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`, { renderOptions: { caching: true } });
330
+ ```
331
+
332
+ or via `RenderOptions` such as:
333
+
334
+ ```javascript
335
+ import { Writr } from 'writr';
336
+ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
337
+ await writr.render({ caching: true});
338
+ ```
339
+
340
+ If you want to set the caching options for the instance of Writr you can do so like this:
341
+
342
+ ```javascript
343
+ // we will set the lruSize of the cache and the default ttl
344
+ import {Writr} from 'writr';
345
+ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`, { renderOptions: { caching: true } });
346
+ writr.cache.store.lruSize = 100;
347
+ writr.cache.store.ttl = '5m'; // setting it to 5 minutes
348
+ ```
349
+
350
+ # Code of Conduct and Contributing
231
351
  [Code of Conduct](CODE_OF_CONDUCT.md) and [Contributing](CONTRIBUTING.md) guidelines.
232
352
 
233
- ## License
353
+ # License
234
354
 
235
355
  MIT © [Jared Wray](https://jaredwray.com)
package/dist/writr.d.ts CHANGED
@@ -3,33 +3,42 @@ import * as hast from 'hast';
3
3
  import * as mdast from 'mdast';
4
4
  import React from 'react';
5
5
  import { HTMLReactParserOptions } from 'html-react-parser';
6
- import { Cacheable, CacheableMemory } from 'cacheable';
7
- import { KeyvStoreAdapter } from 'keyv';
6
+ import { Hookified } from 'hookified';
7
+ import { CacheableMemory } from 'cacheable';
8
8
 
9
9
  declare class WritrCache {
10
- private _markdownStore;
11
- private readonly _markdownStoreSync;
10
+ private readonly _store;
12
11
  private readonly _hashStore;
13
- get markdownStore(): Cacheable;
14
- get markdownStoreSync(): CacheableMemory;
12
+ get store(): CacheableMemory;
15
13
  get hashStore(): CacheableMemory;
16
- getMarkdown(markdown: string, options?: RenderOptions): Promise<string | undefined>;
17
- getMarkdownSync(markdown: string, options?: RenderOptions): string | undefined;
18
- setMarkdown(markdown: string, value: string, options?: RenderOptions): Promise<boolean>;
19
- setMarkdownSync(markdown: string, value: string, options?: RenderOptions): boolean;
20
- get(key: string): Promise<string | undefined>;
21
- getSync(key: string): string | undefined;
22
- set(key: string, value: string): Promise<boolean>;
23
- setSync(key: string, value: string): boolean;
24
- clear(): Promise<void>;
25
- setStorageAdapter(adapter: KeyvStoreAdapter): void;
14
+ get(markdown: string, options?: RenderOptions): string | undefined;
15
+ set(markdown: string, value: string, options?: RenderOptions): void;
16
+ clear(): void;
26
17
  hash(markdown: string, options?: RenderOptions): string;
27
18
  }
28
19
 
20
+ /**
21
+ * Writr options.
22
+ * @typedef {Object} WritrOptions
23
+ * @property {RenderOptions} [renderOptions] - Default render options (default: undefined)
24
+ * @property {boolean} [throwErrors] - Throw error (default: false)
25
+ */
29
26
  type WritrOptions = {
30
- openai?: string;
31
27
  renderOptions?: RenderOptions;
28
+ throwErrors?: boolean;
32
29
  };
30
+ /**
31
+ * Render options.
32
+ * @typedef {Object} RenderOptions
33
+ * @property {boolean} [emoji] - Emoji support (default: true)
34
+ * @property {boolean} [toc] - Table of contents generation (default: true)
35
+ * @property {boolean} [slug] - Slug generation (default: true)
36
+ * @property {boolean} [highlight] - Code highlighting (default: true)
37
+ * @property {boolean} [gfm] - Github flavor markdown (default: true)
38
+ * @property {boolean} [math] - Math support (default: true)
39
+ * @property {boolean} [mdx] - MDX support (default: true)
40
+ * @property {boolean} [caching] - Caching (default: false)
41
+ */
33
42
  type RenderOptions = {
34
43
  emoji?: boolean;
35
44
  toc?: boolean;
@@ -40,32 +49,137 @@ type RenderOptions = {
40
49
  mdx?: boolean;
41
50
  caching?: boolean;
42
51
  };
43
- declare class Writr {
52
+ declare class Writr extends Hookified {
44
53
  engine: unified.Processor<mdast.Root, mdast.Root, hast.Root, hast.Root, string>;
45
54
  private readonly _options;
46
55
  private _content;
47
56
  private readonly _cache;
57
+ /**
58
+ * Initialize Writr. Accepts a string or options object.
59
+ * @param {string | WritrOptions} [arguments1] If you send in a string, it will be used as the markdown content. If you send in an object, it will be used as the options.
60
+ * @param {WritrOptions} [arguments2] This is if you send in the content in the first argument and also want to send in options.
61
+ *
62
+ * @example
63
+ * const writr = new Writr('Hello, world!', {caching: false});
64
+ */
48
65
  constructor(arguments1?: string | WritrOptions, arguments2?: WritrOptions);
66
+ /**
67
+ * Get the options.
68
+ * @type {WritrOptions}
69
+ */
49
70
  get options(): WritrOptions;
71
+ /**
72
+ * Get the Content. This is the markdown content and front matter if it exists.
73
+ * @type {WritrOptions}
74
+ */
50
75
  get content(): string;
76
+ /**
77
+ * Set the Content. This is the markdown content and front matter if it exists.
78
+ * @type {WritrOptions}
79
+ */
51
80
  set content(value: string);
81
+ /**
82
+ * Get the cache.
83
+ * @type {WritrCache}
84
+ */
52
85
  get cache(): WritrCache;
86
+ /**
87
+ * Get the front matter raw content.
88
+ * @type {string} The front matter content including the delimiters.
89
+ */
53
90
  get frontMatterRaw(): string;
91
+ /**
92
+ * Get the body content without the front matter.
93
+ * @type {string} The markdown content without the front matter.
94
+ */
54
95
  get body(): string;
96
+ /**
97
+ * Get the markdown content. This is an alias for the body property.
98
+ * @type {string} The markdown content.
99
+ */
55
100
  get markdown(): string;
101
+ /**
102
+ * Get the front matter content as an object.
103
+ * @type {Record<string, any>} The front matter content as an object.
104
+ */
56
105
  get frontMatter(): Record<string, any>;
106
+ /**
107
+ * Set the front matter content as an object.
108
+ * @type {Record<string, any>} The front matter content as an object.
109
+ */
57
110
  set frontMatter(data: Record<string, any>);
111
+ /**
112
+ * Get the front matter value for a key.
113
+ * @param {string} key The key to get the value for.
114
+ * @returns {T} The value for the key.
115
+ */
58
116
  getFrontMatterValue<T>(key: string): T;
117
+ /**
118
+ * Render the markdown content to HTML.
119
+ * @param {RenderOptions} [options] The render options.
120
+ * @returns {Promise<string>} The rendered HTML content.
121
+ */
59
122
  render(options?: RenderOptions): Promise<string>;
123
+ /**
124
+ * Render the markdown content to HTML synchronously.
125
+ * @param {RenderOptions} [options] The render options.
126
+ * @returns {string} The rendered HTML content.
127
+ */
60
128
  renderSync(options?: RenderOptions): string;
129
+ /**
130
+ * Render the markdown content and save it to a file. If the directory doesn't exist it will be created.
131
+ * @param {string} filePath The file path to save the rendered markdown content to.
132
+ * @param {RenderOptions} [options] the render options.
133
+ */
134
+ renderToFile(filePath: string, options?: RenderOptions): Promise<void>;
135
+ /**
136
+ * Render the markdown content and save it to a file synchronously. If the directory doesn't exist it will be created.
137
+ * @param {string} filePath The file path to save the rendered markdown content to.
138
+ * @param {RenderOptions} [options] the render options.
139
+ */
140
+ renderToFileSync(filePath: string, options?: RenderOptions): void;
141
+ /**
142
+ * Render the markdown content to React.
143
+ * @param {RenderOptions} [options] The render options.
144
+ * @param {HTMLReactParserOptions} [reactParseOptions] The HTML React parser options.
145
+ * @returns {Promise<string | React.JSX.Element | React.JSX.Element[]>} The rendered React content.
146
+ */
61
147
  renderReact(options?: RenderOptions, reactParseOptions?: HTMLReactParserOptions): Promise<string | React.JSX.Element | React.JSX.Element[]>;
148
+ /**
149
+ * Render the markdown content to React synchronously.
150
+ * @param {RenderOptions} [options] The render options.
151
+ * @param {HTMLReactParserOptions} [reactParseOptions] The HTML React parser options.
152
+ * @returns {string | React.JSX.Element | React.JSX.Element[]} The rendered React content.
153
+ */
62
154
  renderReactSync(options?: RenderOptions, reactParseOptions?: HTMLReactParserOptions): string | React.JSX.Element | React.JSX.Element[];
155
+ /**
156
+ * Load markdown content from a file.
157
+ * @param {string} filePath The file path to load the markdown content from.
158
+ * @returns {Promise<void>}
159
+ */
63
160
  loadFromFile(filePath: string): Promise<void>;
161
+ /**
162
+ * Load markdown content from a file synchronously.
163
+ * @param {string} filePath The file path to load the markdown content from.
164
+ * @returns {void}
165
+ */
64
166
  loadFromFileSync(filePath: string): void;
167
+ /**
168
+ * Save the markdown content to a file. If the directory doesn't exist it will be created.
169
+ * @param {string} filePath The file path to save the markdown content to.
170
+ * @returns {Promise<void>}
171
+ */
65
172
  saveToFile(filePath: string): Promise<void>;
173
+ /**
174
+ * Save the markdown content to a file synchronously. If the directory doesn't exist it will be created.
175
+ * @param {string} filePath The file path to save the markdown content to.
176
+ * @returns {void}
177
+ */
66
178
  saveToFileSync(filePath: string): void;
67
179
  private isCacheEnabled;
68
180
  private createProcessor;
181
+ private mergeOptions;
182
+ private mergeRenderOptions;
69
183
  }
70
184
 
71
185
  export { type RenderOptions, Writr, type WritrOptions };
package/dist/writr.js CHANGED
@@ -15,60 +15,31 @@ import remarkEmoji from "remark-emoji";
15
15
  import remarkMDX from "remark-mdx";
16
16
  import parse from "html-react-parser";
17
17
  import * as yaml from "js-yaml";
18
+ import { Hookified } from "hookified";
18
19
 
19
20
  // src/writr-cache.ts
20
- import { Cacheable, CacheableMemory } from "cacheable";
21
+ import { CacheableMemory } from "cacheable";
21
22
  var WritrCache = class {
22
- _markdownStore = new Cacheable();
23
- _markdownStoreSync = new CacheableMemory();
23
+ _store = new CacheableMemory();
24
24
  _hashStore = new CacheableMemory();
25
- get markdownStore() {
26
- return this._markdownStore;
27
- }
28
- get markdownStoreSync() {
29
- return this._markdownStoreSync;
25
+ get store() {
26
+ return this._store;
30
27
  }
31
28
  get hashStore() {
32
29
  return this._hashStore;
33
30
  }
34
- async getMarkdown(markdown, options) {
35
- const key = this.hash(markdown, options);
36
- return this.get(key);
37
- }
38
- getMarkdownSync(markdown, options) {
31
+ get(markdown, options) {
39
32
  const key = this.hash(markdown, options);
40
- return this.getSync(key);
33
+ return this._store.get(key);
41
34
  }
42
- async setMarkdown(markdown, value, options) {
35
+ set(markdown, value, options) {
43
36
  const key = this.hash(markdown, options);
44
- return this.set(key, value);
45
- }
46
- setMarkdownSync(markdown, value, options) {
47
- const key = this.hash(markdown, options);
48
- this.setSync(key, value);
49
- return true;
50
- }
51
- async get(key) {
52
- return this._markdownStore.get(key);
37
+ this._store.set(key, value);
53
38
  }
54
- getSync(key) {
55
- return this._markdownStoreSync.get(key);
56
- }
57
- async set(key, value) {
58
- return this._markdownStore.set(key, value);
59
- }
60
- setSync(key, value) {
61
- this._markdownStoreSync.set(key, value);
62
- return true;
63
- }
64
- async clear() {
65
- await this._markdownStore.clear();
66
- this._markdownStoreSync.clear();
39
+ clear() {
40
+ this._store.clear();
67
41
  this._hashStore.clear();
68
42
  }
69
- setStorageAdapter(adapter) {
70
- this._markdownStore = new Cacheable({ primary: adapter });
71
- }
72
43
  hash(markdown, options) {
73
44
  const content = { markdown, options };
74
45
  const key = JSON.stringify(content);
@@ -83,11 +54,11 @@ var WritrCache = class {
83
54
  };
84
55
 
85
56
  // src/writr.ts
86
- var Writr = class {
57
+ var Writr = class extends Hookified {
87
58
  engine = unified().use(remarkParse).use(remarkGfm).use(remarkToc).use(remarkEmoji).use(remarkRehype).use(rehypeSlug).use(remarkMath).use(rehypeKatex).use(rehypeHighlight).use(remarkMDX).use(rehypeStringify);
88
59
  // Stringify HTML
89
60
  _options = {
90
- openai: void 0,
61
+ throwErrors: false,
91
62
  renderOptions: {
92
63
  emoji: true,
93
64
  toc: true,
@@ -96,39 +67,68 @@ var Writr = class {
96
67
  gfm: true,
97
68
  math: true,
98
69
  mdx: true,
99
- caching: true
70
+ caching: false
100
71
  }
101
72
  };
102
73
  _content = "";
103
74
  _cache = new WritrCache();
75
+ /**
76
+ * Initialize Writr. Accepts a string or options object.
77
+ * @param {string | WritrOptions} [arguments1] If you send in a string, it will be used as the markdown content. If you send in an object, it will be used as the options.
78
+ * @param {WritrOptions} [arguments2] This is if you send in the content in the first argument and also want to send in options.
79
+ *
80
+ * @example
81
+ * const writr = new Writr('Hello, world!', {caching: false});
82
+ */
104
83
  constructor(arguments1, arguments2) {
84
+ super();
105
85
  if (typeof arguments1 === "string") {
106
86
  this._content = arguments1;
107
87
  } else if (arguments1) {
108
- this._options = { ...this._options, ...arguments1 };
88
+ this._options = this.mergeOptions(this._options, arguments1);
109
89
  if (this._options.renderOptions) {
110
90
  this.engine = this.createProcessor(this._options.renderOptions);
111
91
  }
112
92
  }
113
93
  if (arguments2) {
114
- this._options = { ...this._options, ...arguments2 };
94
+ this._options = this.mergeOptions(this._options, arguments2);
115
95
  if (this._options.renderOptions) {
116
96
  this.engine = this.createProcessor(this._options.renderOptions);
117
97
  }
118
98
  }
119
99
  }
100
+ /**
101
+ * Get the options.
102
+ * @type {WritrOptions}
103
+ */
120
104
  get options() {
121
105
  return this._options;
122
106
  }
107
+ /**
108
+ * Get the Content. This is the markdown content and front matter if it exists.
109
+ * @type {WritrOptions}
110
+ */
123
111
  get content() {
124
112
  return this._content;
125
113
  }
114
+ /**
115
+ * Set the Content. This is the markdown content and front matter if it exists.
116
+ * @type {WritrOptions}
117
+ */
126
118
  set content(value) {
127
119
  this._content = value;
128
120
  }
121
+ /**
122
+ * Get the cache.
123
+ * @type {WritrCache}
124
+ */
129
125
  get cache() {
130
126
  return this._cache;
131
127
  }
128
+ /**
129
+ * Get the front matter raw content.
130
+ * @type {string} The front matter content including the delimiters.
131
+ */
132
132
  get frontMatterRaw() {
133
133
  if (!this._content.trimStart().startsWith("---")) {
134
134
  return "";
@@ -140,6 +140,10 @@ var Writr = class {
140
140
  }
141
141
  return this._content.slice(start, end + 5);
142
142
  }
143
+ /**
144
+ * Get the body content without the front matter.
145
+ * @type {string} The markdown content without the front matter.
146
+ */
143
147
  get body() {
144
148
  if (this.frontMatterRaw === "") {
145
149
  return this._content;
@@ -147,17 +151,33 @@ var Writr = class {
147
151
  const end = this._content.indexOf("\n---\n");
148
152
  return this._content.slice(Math.max(0, end + 5)).trim();
149
153
  }
154
+ /**
155
+ * Get the markdown content. This is an alias for the body property.
156
+ * @type {string} The markdown content.
157
+ */
150
158
  get markdown() {
151
159
  return this.body;
152
160
  }
161
+ /**
162
+ * Get the front matter content as an object.
163
+ * @type {Record<string, any>} The front matter content as an object.
164
+ */
153
165
  get frontMatter() {
154
166
  const frontMatter = this.frontMatterRaw;
155
167
  const match = /^---\s*([\s\S]*?)\s*---\s*/.exec(frontMatter);
156
168
  if (match) {
157
- return yaml.load(match[1].trim());
169
+ try {
170
+ return yaml.load(match[1].trim());
171
+ } catch (error) {
172
+ this.emit("error", error);
173
+ }
158
174
  }
159
175
  return {};
160
176
  }
177
+ /**
178
+ * Set the front matter content as an object.
179
+ * @type {Record<string, any>} The front matter content as an object.
180
+ */
161
181
  set frontMatter(data) {
162
182
  const frontMatter = this.frontMatterRaw;
163
183
  const yamlString = yaml.dump(data);
@@ -166,14 +186,24 @@ ${yamlString}---
166
186
  `;
167
187
  this._content = this._content.replace(frontMatter, newFrontMatter);
168
188
  }
189
+ /**
190
+ * Get the front matter value for a key.
191
+ * @param {string} key The key to get the value for.
192
+ * @returns {T} The value for the key.
193
+ */
169
194
  getFrontMatterValue(key) {
170
195
  return this.frontMatter[key];
171
196
  }
197
+ /**
198
+ * Render the markdown content to HTML.
199
+ * @param {RenderOptions} [options] The render options.
200
+ * @returns {Promise<string>} The rendered HTML content.
201
+ */
172
202
  async render(options) {
173
203
  try {
174
204
  let result = "";
175
205
  if (this.isCacheEnabled(options)) {
176
- const cached = await this._cache.getMarkdown(this._content, options);
206
+ const cached = this._cache.get(this._content, options);
177
207
  if (cached) {
178
208
  return cached;
179
209
  }
@@ -186,18 +216,23 @@ ${yamlString}---
186
216
  const file = await engine.process(this.body);
187
217
  result = String(file);
188
218
  if (this.isCacheEnabled(options)) {
189
- await this._cache.setMarkdown(this._content, result, options);
219
+ this._cache.set(this._content, result, options);
190
220
  }
191
221
  return result;
192
222
  } catch (error) {
193
223
  throw new Error(`Failed to render markdown: ${error.message}`);
194
224
  }
195
225
  }
226
+ /**
227
+ * Render the markdown content to HTML synchronously.
228
+ * @param {RenderOptions} [options] The render options.
229
+ * @returns {string} The rendered HTML content.
230
+ */
196
231
  renderSync(options) {
197
232
  try {
198
233
  let result = "";
199
234
  if (this.isCacheEnabled(options)) {
200
- const cached = this._cache.getMarkdownSync(this._content, options);
235
+ const cached = this._cache.get(this._content, options);
201
236
  if (cached) {
202
237
  return cached;
203
238
  }
@@ -210,38 +245,121 @@ ${yamlString}---
210
245
  const file = engine.processSync(this.body);
211
246
  result = String(file);
212
247
  if (this.isCacheEnabled(options)) {
213
- this._cache.setMarkdownSync(this._content, result, options);
248
+ this._cache.set(this._content, result, options);
214
249
  }
215
250
  return result;
216
251
  } catch (error) {
217
252
  throw new Error(`Failed to render markdown: ${error.message}`);
218
253
  }
219
254
  }
255
+ /**
256
+ * Render the markdown content and save it to a file. If the directory doesn't exist it will be created.
257
+ * @param {string} filePath The file path to save the rendered markdown content to.
258
+ * @param {RenderOptions} [options] the render options.
259
+ */
260
+ async renderToFile(filePath, options) {
261
+ try {
262
+ const { writeFile, mkdir } = fs.promises;
263
+ const directoryPath = dirname(filePath);
264
+ const content = await this.render(options);
265
+ await mkdir(directoryPath, { recursive: true });
266
+ await writeFile(filePath, content, "utf8");
267
+ } catch (error) {
268
+ this.emit("error", error);
269
+ if (this._options.throwErrors) {
270
+ throw error;
271
+ }
272
+ }
273
+ }
274
+ /**
275
+ * Render the markdown content and save it to a file synchronously. If the directory doesn't exist it will be created.
276
+ * @param {string} filePath The file path to save the rendered markdown content to.
277
+ * @param {RenderOptions} [options] the render options.
278
+ */
279
+ renderToFileSync(filePath, options) {
280
+ try {
281
+ const directoryPath = dirname(filePath);
282
+ const content = this.renderSync(options);
283
+ fs.mkdirSync(directoryPath, { recursive: true });
284
+ fs.writeFileSync(filePath, content, "utf8");
285
+ } catch (error) {
286
+ this.emit("error", error);
287
+ if (this._options.throwErrors) {
288
+ throw error;
289
+ }
290
+ }
291
+ }
292
+ /**
293
+ * Render the markdown content to React.
294
+ * @param {RenderOptions} [options] The render options.
295
+ * @param {HTMLReactParserOptions} [reactParseOptions] The HTML React parser options.
296
+ * @returns {Promise<string | React.JSX.Element | React.JSX.Element[]>} The rendered React content.
297
+ */
220
298
  async renderReact(options, reactParseOptions) {
221
299
  const html = await this.render(options);
222
300
  return parse(html, reactParseOptions);
223
301
  }
302
+ /**
303
+ * Render the markdown content to React synchronously.
304
+ * @param {RenderOptions} [options] The render options.
305
+ * @param {HTMLReactParserOptions} [reactParseOptions] The HTML React parser options.
306
+ * @returns {string | React.JSX.Element | React.JSX.Element[]} The rendered React content.
307
+ */
224
308
  renderReactSync(options, reactParseOptions) {
225
309
  const html = this.renderSync(options);
226
310
  return parse(html, reactParseOptions);
227
311
  }
312
+ /**
313
+ * Load markdown content from a file.
314
+ * @param {string} filePath The file path to load the markdown content from.
315
+ * @returns {Promise<void>}
316
+ */
228
317
  async loadFromFile(filePath) {
229
318
  const { readFile } = fs.promises;
230
319
  this._content = await readFile(filePath, "utf8");
231
320
  }
321
+ /**
322
+ * Load markdown content from a file synchronously.
323
+ * @param {string} filePath The file path to load the markdown content from.
324
+ * @returns {void}
325
+ */
232
326
  loadFromFileSync(filePath) {
233
327
  this._content = fs.readFileSync(filePath, "utf8");
234
328
  }
329
+ /**
330
+ * Save the markdown content to a file. If the directory doesn't exist it will be created.
331
+ * @param {string} filePath The file path to save the markdown content to.
332
+ * @returns {Promise<void>}
333
+ */
235
334
  async saveToFile(filePath) {
236
- const { writeFile, mkdir } = fs.promises;
237
- const directoryPath = dirname(filePath);
238
- await mkdir(directoryPath, { recursive: true });
239
- await writeFile(filePath, this._content, "utf8");
335
+ try {
336
+ const { writeFile, mkdir } = fs.promises;
337
+ const directoryPath = dirname(filePath);
338
+ await mkdir(directoryPath, { recursive: true });
339
+ await writeFile(filePath, this._content, "utf8");
340
+ } catch (error) {
341
+ this.emit("error", error);
342
+ if (this._options.throwErrors) {
343
+ throw error;
344
+ }
345
+ }
240
346
  }
347
+ /**
348
+ * Save the markdown content to a file synchronously. If the directory doesn't exist it will be created.
349
+ * @param {string} filePath The file path to save the markdown content to.
350
+ * @returns {void}
351
+ */
241
352
  saveToFileSync(filePath) {
242
- const directoryPath = dirname(filePath);
243
- fs.mkdirSync(directoryPath, { recursive: true });
244
- fs.writeFileSync(filePath, this._content, "utf8");
353
+ try {
354
+ const directoryPath = dirname(filePath);
355
+ fs.mkdirSync(directoryPath, { recursive: true });
356
+ fs.writeFileSync(filePath, this._content, "utf8");
357
+ } catch (error) {
358
+ this.emit("error", error);
359
+ if (this._options.throwErrors) {
360
+ throw error;
361
+ }
362
+ }
245
363
  }
246
364
  isCacheEnabled(options) {
247
365
  if (options?.caching !== void 0) {
@@ -276,6 +394,43 @@ ${yamlString}---
276
394
  processor.use(rehypeStringify);
277
395
  return processor;
278
396
  }
397
+ mergeOptions(current, options) {
398
+ if (options.throwErrors !== void 0) {
399
+ current.throwErrors = options.throwErrors;
400
+ }
401
+ if (options.renderOptions) {
402
+ current.renderOptions ??= {};
403
+ this.mergeRenderOptions(current.renderOptions, options.renderOptions);
404
+ }
405
+ return current;
406
+ }
407
+ mergeRenderOptions(current, options) {
408
+ if (options.emoji !== void 0) {
409
+ current.emoji = options.emoji;
410
+ }
411
+ if (options.toc !== void 0) {
412
+ current.toc = options.toc;
413
+ }
414
+ if (options.slug !== void 0) {
415
+ current.slug = options.slug;
416
+ }
417
+ if (options.highlight !== void 0) {
418
+ current.highlight = options.highlight;
419
+ }
420
+ if (options.gfm !== void 0) {
421
+ current.gfm = options.gfm;
422
+ }
423
+ if (options.math !== void 0) {
424
+ current.math = options.math;
425
+ }
426
+ if (options.mdx !== void 0) {
427
+ current.mdx = options.mdx;
428
+ }
429
+ if (options.caching !== void 0) {
430
+ current.caching = options.caching;
431
+ }
432
+ return current;
433
+ }
279
434
  };
280
435
  export {
281
436
  Writr
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "writr",
3
- "version": "4.1.3",
3
+ "version": "4.2.0",
4
4
  "description": "Markdown Rendering Simplified",
5
5
  "type": "module",
6
6
  "main": "./dist/writr.js",
@@ -53,37 +53,36 @@
53
53
  "website:serve": "rimraf ./site/README.md ./site/dist && npx docula serve -s ./site -o ./site/dist"
54
54
  },
55
55
  "dependencies": {
56
- "cacheable": "^1.8.0",
56
+ "cacheable": "^1.8.5",
57
+ "hookified": "^1.5.1",
57
58
  "html-react-parser": "^5.1.18",
58
59
  "js-yaml": "^4.1.0",
59
- "keyv": "^5.1.0",
60
60
  "react": "^18.3.1",
61
- "rehype-highlight": "^7.0.0",
61
+ "rehype-highlight": "^7.0.1",
62
62
  "rehype-katex": "^7.0.1",
63
63
  "rehype-slug": "^6.0.0",
64
64
  "rehype-stringify": "^10.0.1",
65
65
  "remark-emoji": "^5.0.1",
66
66
  "remark-gfm": "^4.0.0",
67
67
  "remark-math": "^6.0.0",
68
- "remark-mdx": "^3.0.1",
68
+ "remark-mdx": "^3.1.0",
69
69
  "remark-parse": "^11.0.0",
70
70
  "remark-rehype": "^11.1.1",
71
71
  "remark-toc": "^9.0.0",
72
72
  "unified": "^11.0.5"
73
73
  },
74
74
  "devDependencies": {
75
- "@keyv/sqlite": "^4.0.1",
76
75
  "@types/js-yaml": "^4.0.9",
77
- "@types/node": "^22.7.5",
78
- "@types/react": "^18.3.11",
79
- "@vitest/coverage-v8": "^2.1.2",
80
- "docula": "^0.9.1",
76
+ "@types/node": "^22.10.1",
77
+ "@types/react": "^18.3.12",
78
+ "@vitest/coverage-v8": "^2.1.6",
79
+ "docula": "^0.9.5",
81
80
  "rimraf": "^6.0.1",
82
81
  "ts-node": "^10.9.2",
83
- "tsup": "^8.3.0",
84
- "typescript": "^5.6.2",
85
- "vitest": "^2.1.2",
86
- "webpack": "^5.95.0",
82
+ "tsup": "^8.3.5",
83
+ "typescript": "^5.7.2",
84
+ "vitest": "^2.1.6",
85
+ "webpack": "^5.96.1",
87
86
  "xo": "^0.59.3"
88
87
  },
89
88
  "xo": {