@hardlydifficult/document-generator 1.1.12 → 1.1.13
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.
- package/README.md +182 -190
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,284 +11,260 @@ npm install @hardlydifficult/document-generator
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import { Document } from
|
|
14
|
+
import { Document } from "@hardlydifficult/document-generator";
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
const doc = new Document({
|
|
17
|
+
header: "Release Notes",
|
|
18
|
+
sections: [
|
|
19
|
+
{ title: "New Features", content: "Ship onboarding flow\nFix retry bug" },
|
|
20
|
+
],
|
|
21
|
+
context: { Network: "mainnet", Status: "active" },
|
|
22
|
+
});
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
console.log(doc.toMarkdown());
|
|
25
|
+
// # Release Notes
|
|
26
|
+
//
|
|
27
|
+
// **New Features**
|
|
28
|
+
// Ship onboarding flow
|
|
29
|
+
// Fix retry bug
|
|
30
|
+
//
|
|
31
|
+
// ---
|
|
32
|
+
//
|
|
33
|
+
// *Network: mainnet, Status: active*
|
|
34
|
+
|
|
35
|
+
console.log(doc.toSlack());
|
|
36
|
+
// *Release Notes*
|
|
37
|
+
//
|
|
38
|
+
// **New Features**
|
|
39
|
+
// Ship onboarding flow
|
|
40
|
+
// Fix retry bug
|
|
41
|
+
//
|
|
42
|
+
// ────────────────
|
|
43
|
+
//
|
|
44
|
+
// Network: mainnet, Status: active
|
|
45
|
+
```
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
console.log(document.toSlackText());
|
|
47
|
+
## Core Document API
|
|
29
48
|
|
|
30
|
-
|
|
31
|
-
console.log(document.toPlainText());
|
|
32
|
-
```
|
|
49
|
+
The `Document` class provides a fluent, chainable builder for structured content.
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
### Constructor
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
Creates a new document, optionally initialized with header, sections, and context.
|
|
37
54
|
|
|
38
55
|
```typescript
|
|
39
|
-
const doc = new Document(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.context("Footer text"); // Italicized context
|
|
56
|
+
const doc = new Document({
|
|
57
|
+
header: "My Report",
|
|
58
|
+
sections: [
|
|
59
|
+
{ title: "Summary", content: "All systems operational" },
|
|
60
|
+
{ content: "No issues found" },
|
|
61
|
+
],
|
|
62
|
+
context: { Network: "mainnet", Status: "active" },
|
|
63
|
+
});
|
|
48
64
|
```
|
|
49
65
|
|
|
50
|
-
###
|
|
66
|
+
### Block Methods
|
|
51
67
|
|
|
52
|
-
|
|
68
|
+
These methods add specific block types and return `this` for chaining.
|
|
53
69
|
|
|
54
70
|
```typescript
|
|
55
|
-
new Document()
|
|
71
|
+
const doc = new Document()
|
|
72
|
+
.header("Title")
|
|
73
|
+
.text("Content")
|
|
74
|
+
.list(["Item 1", "Item 2"])
|
|
75
|
+
.divider()
|
|
76
|
+
.context("Context text")
|
|
77
|
+
.link("Visit Site", "https://example.com")
|
|
78
|
+
.code("const x = 1;")
|
|
79
|
+
.image("https://example.com/image.png", "Alt text");
|
|
56
80
|
```
|
|
57
81
|
|
|
58
|
-
|
|
59
|
-
- **Slack:** Converts to `*bold*`, `_italic_`, `~strike~`
|
|
60
|
-
- **Plain text:** Formatting stripped, text only
|
|
82
|
+
### Convenience Methods
|
|
61
83
|
|
|
62
|
-
|
|
84
|
+
#### `section(title, content?, options?)`
|
|
63
85
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
Add titled sections with optional content (string or list of items):
|
|
86
|
+
Renders section titles as bold with optional content.
|
|
67
87
|
|
|
68
88
|
```typescript
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// With string content
|
|
73
|
-
doc.section("Summary", "All systems operational");
|
|
89
|
+
doc.section("Summary").text("All systems green");
|
|
90
|
+
// Output: **Summary**\nAll systems green
|
|
74
91
|
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
doc.section("Today", ["Ship onboarding", "Fix flaky test"]);
|
|
93
|
+
// Output: **Today**\n- Ship onboarding\n- Fix flaky test
|
|
77
94
|
|
|
78
|
-
// With empty state
|
|
79
95
|
doc.section("Blockers", [], { emptyText: "None." });
|
|
80
|
-
|
|
81
|
-
// Ordered list
|
|
82
|
-
doc.section("Steps", ["First", "Second"], { ordered: true });
|
|
96
|
+
// Output: **Blockers**\n- None.
|
|
83
97
|
```
|
|
84
98
|
|
|
85
|
-
|
|
99
|
+
| Option | Type | Default | Description |
|
|
100
|
+
|--------|------|---------|-------------|
|
|
101
|
+
| `emptyText` | `string` | _none_ | Fallback when content is empty |
|
|
102
|
+
| `ordered` | `boolean` | `false` | Render list as numbered instead of bullets |
|
|
103
|
+
| `divider` | `boolean` | `false` | Insert divider before section output |
|
|
104
|
+
|
|
105
|
+
#### `field(label, value, options?)`
|
|
86
106
|
|
|
87
|
-
|
|
107
|
+
Renders a single key-value pair.
|
|
88
108
|
|
|
89
109
|
```typescript
|
|
90
|
-
// Single field
|
|
91
110
|
doc.field("ETA", "Tomorrow");
|
|
92
|
-
// Output: **ETA
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Output: **Network**: mainnet\n**Status**: active
|
|
97
|
-
|
|
98
|
-
// With styling options
|
|
99
|
-
doc.keyValue(
|
|
100
|
-
{ Name: "Alice", Role: "Admin" },
|
|
101
|
-
{ style: "bullet", separator: " =", bold: false }
|
|
102
|
-
);
|
|
103
|
-
// Output: • Name = Alice\n• Role = Admin
|
|
111
|
+
// Output: **ETA:** Tomorrow
|
|
112
|
+
|
|
113
|
+
doc.field("Blockers", "", { emptyText: "None." });
|
|
114
|
+
// Output: **Blockers:** None.
|
|
104
115
|
```
|
|
105
116
|
|
|
106
|
-
|
|
117
|
+
| Option | Type | Default | Description |
|
|
118
|
+
|--------|------|---------|-------------|
|
|
119
|
+
| `emptyText` | `string` | _none_ | Fallback when value is empty |
|
|
120
|
+
| `separator` | `string` | `":"` | Separator between label and value |
|
|
121
|
+
| `bold` | `boolean` | `true` | Whether to bold the label |
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
#### `keyValue(data, options?)`
|
|
124
|
+
|
|
125
|
+
Formats an object as key-value lines.
|
|
109
126
|
|
|
110
127
|
```typescript
|
|
111
|
-
doc.
|
|
112
|
-
|
|
113
|
-
{ limit: 3 }
|
|
114
|
-
);
|
|
115
|
-
// Output:
|
|
116
|
-
// • Item 1
|
|
117
|
-
// • Item 2
|
|
118
|
-
// • Item 3
|
|
119
|
-
// _... and 2 more_
|
|
128
|
+
doc.keyValue({ Name: "Alice", Role: "Admin" });
|
|
129
|
+
// Output: **Name:** Alice\n**Role:** Admin
|
|
120
130
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
doc.truncatedList(users, {
|
|
124
|
-
limit: 2,
|
|
125
|
-
format: (u) => u.name,
|
|
126
|
-
moreText: (n) => `Plus ${n} others`,
|
|
127
|
-
ordered: true
|
|
128
|
-
});
|
|
131
|
+
doc.keyValue({ A: "1", B: "2" }, { style: "bullet" });
|
|
132
|
+
// Output: • **A**: 1\n• **B**: 2
|
|
129
133
|
```
|
|
130
134
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
doc.timestamp();
|
|
137
|
-
// Output: 🕐 2024-02-04T12:00:00.000Z
|
|
135
|
+
| Option | Type | Default | Description |
|
|
136
|
+
|--------|------|---------|-------------|
|
|
137
|
+
| `style` | `"plain" \| "bullet" \| "numbered"` | `"plain"` | List style |
|
|
138
|
+
| `separator` | `string` | `":"` | Key-value separator |
|
|
139
|
+
| `bold` | `boolean` | `true` | Bold the keys |
|
|
138
140
|
|
|
139
|
-
|
|
140
|
-
// Output: 2024-02-04T12:00:00.000Z
|
|
141
|
+
#### `truncatedList(items, options?)`
|
|
141
142
|
|
|
142
|
-
|
|
143
|
-
// Output: Generated 2024-02-04T12:00:00.000Z
|
|
143
|
+
Renders a list with automatic truncation.
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
```typescript
|
|
146
|
+
doc.truncatedList(["a", "b", "c", "d", "e"], { limit: 3 });
|
|
147
|
+
// Output:
|
|
148
|
+
// • a
|
|
149
|
+
// • b
|
|
150
|
+
// • c
|
|
151
|
+
// _... and 2 more_
|
|
147
152
|
```
|
|
148
153
|
|
|
149
|
-
|
|
154
|
+
| Option | Type | Default | Description |
|
|
155
|
+
|--------|------|---------|-------------|
|
|
156
|
+
| `limit` | `number` | `10` | Maximum items to show |
|
|
157
|
+
| `format` | `(item, index) => string` | `String` | Custom formatter |
|
|
158
|
+
| `moreText` | `(remaining) => string` | `_... and N more_` | Truncation text |
|
|
159
|
+
| `ordered` | `boolean` | `false` | Numbered list |
|
|
160
|
+
|
|
161
|
+
#### `timestamp(options?)`
|
|
150
162
|
|
|
151
|
-
|
|
163
|
+
Adds a timestamp in context format.
|
|
152
164
|
|
|
153
165
|
```typescript
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
doc.toMarkdown(); // # Report\n\nStatus: **active**
|
|
159
|
-
doc.toSlackText(); // *Report*\n\nStatus: *active*
|
|
160
|
-
doc.toSlack(); // Alias for toSlackText()
|
|
161
|
-
doc.toPlainText(); // REPORT\n\nStatus: active
|
|
162
|
-
|
|
163
|
-
// Or use render() with explicit format
|
|
164
|
-
doc.render("markdown"); // Standard markdown
|
|
165
|
-
doc.render("slack"); // Slack mrkdwn
|
|
166
|
-
doc.render("plaintext"); // Plain text
|
|
166
|
+
doc.timestamp(); // 🕐 2024-02-04T12:00:00.000Z
|
|
167
|
+
doc.timestamp({ emoji: false }); // 2024-02-04T12:00:00.000Z
|
|
168
|
+
doc.timestamp({ label: "Generated" }); // Generated 2024-02-04T...
|
|
167
169
|
```
|
|
168
170
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
import { Document } from "@hardlydifficult/document-generator";
|
|
171
|
+
| Option | Type | Default | Description |
|
|
172
|
+
|--------|------|---------|-------------|
|
|
173
|
+
| `date` | `Date` | `new Date()` | Custom date |
|
|
174
|
+
| `emoji` | `boolean` | `true` | Include clock emoji |
|
|
175
|
+
| `label` | `string` | _none_ | Prefix label |
|
|
175
176
|
|
|
176
|
-
|
|
177
|
-
.header("Sprint ENG-533")
|
|
178
|
-
.text("Shipped ENG-533 and reviewed PR#42")
|
|
179
|
-
.code("ENG-533 inside code is untouched")
|
|
180
|
-
.link("PR", "https://github.com/example/pull/42");
|
|
181
|
-
|
|
182
|
-
// Simple function transformer
|
|
183
|
-
doc.linkify((text) => text.replace("ENG-533", "[ENG-533](...)"));
|
|
184
|
-
|
|
185
|
-
// Linker-style object with platform awareness
|
|
186
|
-
doc.linkify(
|
|
187
|
-
{
|
|
188
|
-
linkText: (text, { platform }) => {
|
|
189
|
-
if (text.includes("ENG-")) {
|
|
190
|
-
return `[${text}](https://linear.app/...)`;
|
|
191
|
-
}
|
|
192
|
-
return text;
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
{ platform: "slack" }
|
|
196
|
-
);
|
|
197
|
-
```
|
|
177
|
+
### Utility Methods
|
|
198
178
|
|
|
199
|
-
|
|
179
|
+
#### `linkify(transform, options?)`
|
|
200
180
|
|
|
201
|
-
|
|
181
|
+
Applies transformations to text-bearing blocks (headers, paragraphs, lists, context).
|
|
202
182
|
|
|
203
183
|
```typescript
|
|
204
|
-
|
|
205
|
-
header: "Daily Report",
|
|
206
|
-
sections: [
|
|
207
|
-
{ title: "Summary", content: "All systems operational" },
|
|
208
|
-
{ content: "No blockers" }
|
|
209
|
-
],
|
|
210
|
-
context: { Network: "mainnet", Status: "active" }
|
|
211
|
-
});
|
|
212
|
-
```
|
|
184
|
+
doc.linkify((text) => text.toUpperCase());
|
|
213
185
|
|
|
214
|
-
|
|
186
|
+
// Example with linkText-style transformer:
|
|
187
|
+
doc.linkify({ linkText: (t) => t.replace("ENG-533", "LINKED") }, { platform: "slack" });
|
|
188
|
+
```
|
|
215
189
|
|
|
216
|
-
|
|
190
|
+
#### `isEmpty(): boolean`
|
|
217
191
|
|
|
218
|
-
|
|
192
|
+
Returns `true` if no blocks have been added.
|
|
219
193
|
|
|
220
194
|
```typescript
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
doc.text("Content");
|
|
225
|
-
doc.isEmpty(); // false
|
|
195
|
+
new Document().isEmpty(); // true
|
|
196
|
+
new Document().text("Content").isEmpty(); // false
|
|
226
197
|
```
|
|
227
198
|
|
|
228
|
-
|
|
199
|
+
#### `clone(): Document`
|
|
229
200
|
|
|
230
|
-
|
|
201
|
+
Creates a shallow copy.
|
|
231
202
|
|
|
232
203
|
```typescript
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
copy.text("Added to copy");
|
|
237
|
-
// original still has 2 blocks, copy has 3
|
|
204
|
+
const doc1 = new Document().header("Title");
|
|
205
|
+
const doc2 = doc1.clone();
|
|
206
|
+
doc2.text("Extra"); // does not affect doc1
|
|
238
207
|
```
|
|
239
208
|
|
|
240
|
-
|
|
209
|
+
#### `getBlocks(): Block[]`
|
|
241
210
|
|
|
242
|
-
|
|
211
|
+
Returns the internal blocks array for inspection.
|
|
243
212
|
|
|
244
213
|
```typescript
|
|
245
|
-
const blocks =
|
|
246
|
-
//
|
|
214
|
+
const blocks = new Document().header("Title").getBlocks();
|
|
215
|
+
// [{ type: "header", text: "Title" }]
|
|
247
216
|
```
|
|
248
217
|
|
|
249
|
-
###
|
|
218
|
+
### Output Methods
|
|
250
219
|
|
|
251
|
-
|
|
220
|
+
Render the document to different formats.
|
|
252
221
|
|
|
253
222
|
```typescript
|
|
254
|
-
Document.
|
|
255
|
-
|
|
223
|
+
const doc = new Document().text("Hello **world**");
|
|
224
|
+
|
|
225
|
+
doc.toMarkdown(); // "**world**" preserved
|
|
226
|
+
doc.toSlack(); // "*world*" (italic)
|
|
227
|
+
doc.toPlainText(); // "world" (stripped)
|
|
228
|
+
doc.render("slack"); // same as toSlack()
|
|
256
229
|
```
|
|
257
230
|
|
|
258
231
|
## Direct Outputter Functions
|
|
259
232
|
|
|
260
|
-
|
|
233
|
+
Use the outputters directly without `Document` if you have raw blocks.
|
|
261
234
|
|
|
262
235
|
```typescript
|
|
263
|
-
import { toMarkdown,
|
|
236
|
+
import { toMarkdown, toPlainText, toSlackText } from "@hardlydifficult/document-generator";
|
|
264
237
|
|
|
265
238
|
const blocks = [
|
|
266
|
-
{ type:
|
|
267
|
-
{ type:
|
|
239
|
+
{ type: "header", text: "Title" },
|
|
240
|
+
{ type: "text", content: "Body with **bold**" },
|
|
268
241
|
];
|
|
269
242
|
|
|
270
|
-
toMarkdown(blocks);
|
|
271
|
-
toSlackText(blocks);
|
|
272
|
-
toPlainText(blocks);
|
|
243
|
+
console.log(toMarkdown(blocks)); // # Title\n\nBody with **bold**
|
|
244
|
+
console.log(toSlackText(blocks)); // *Title*\n\nBody with *bold*
|
|
245
|
+
console.log(toPlainText(blocks)); // TITLE\n\nBody with bold
|
|
273
246
|
```
|
|
274
247
|
|
|
275
|
-
## Markdown Conversion
|
|
248
|
+
## Markdown Conversion
|
|
276
249
|
|
|
277
|
-
|
|
250
|
+
### `convertMarkdown(text, platform)`
|
|
251
|
+
|
|
252
|
+
Converts inline markdown formatting to target platform syntax.
|
|
278
253
|
|
|
279
254
|
```typescript
|
|
280
|
-
|
|
255
|
+
convertMarkdown("**bold** and *italic*", "slack"); // "*bold* and _italic_"
|
|
256
|
+
convertMarkdown("~~strike~~", "discord"); // "~~strike~~"
|
|
257
|
+
convertMarkdown("**bold**", "plaintext"); // "bold"
|
|
258
|
+
```
|
|
281
259
|
|
|
282
|
-
|
|
283
|
-
convertMarkdown("**bold** and *italic*", "slack");
|
|
284
|
-
// → "*bold* and _italic_"
|
|
260
|
+
Supported platforms: `"markdown"`, `"slack"`, `"discord"`, `"plaintext"`
|
|
285
261
|
|
|
286
|
-
|
|
287
|
-
// → "**bold** and *italic*"
|
|
262
|
+
### `stripMarkdown(text)`
|
|
288
263
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
264
|
+
Strips all formatting and returns plain text.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
stripMarkdown("**bold** and *italic* and ~~strike~~"); // "bold and italic and strike"
|
|
292
268
|
```
|
|
293
269
|
|
|
294
270
|
## Block Types
|
|
@@ -307,6 +283,22 @@ type Block =
|
|
|
307
283
|
| { type: 'image'; url: string; alt?: string };
|
|
308
284
|
```
|
|
309
285
|
|
|
286
|
+
## Types
|
|
287
|
+
|
|
288
|
+
All exported types are listed below:
|
|
289
|
+
|
|
290
|
+
| Name | Description |
|
|
291
|
+
|--|--|
|
|
292
|
+
| `Block` | Union type of all block types |
|
|
293
|
+
| `HeaderBlock`, `TextBlock`, `ListBlock`, `DividerBlock`, `ContextBlock`, `LinkBlock`, `CodeBlock`, `ImageBlock` | Block structures |
|
|
294
|
+
| `Platform` | `"markdown" \| "slack" \| "discord" \| "plaintext"` |
|
|
295
|
+
| `StringOutputFormat` | `"markdown" \| "slack" \| "plaintext"` |
|
|
296
|
+
| `DocumentOptions`, `DocumentSection` | Constructor options |
|
|
297
|
+
| `SectionOptions`, `FieldOptions`, `KeyValueOptions` | Formatting options |
|
|
298
|
+
| `TruncatedListOptions<T>` | Truncation configuration |
|
|
299
|
+
| `TimestampOptions` | Timestamp configuration |
|
|
300
|
+
| `DocumentLinkifier`, `DocumentLinkTransform`, `DocumentLinkifyOptions` | Link transformation types |
|
|
301
|
+
|
|
310
302
|
## Integration with @hardlydifficult/chat
|
|
311
303
|
|
|
312
304
|
Documents integrate seamlessly with the chat package for Slack and Discord:
|
|
@@ -331,7 +323,7 @@ await channel.postMessage(report);
|
|
|
331
323
|
## Appendix: Platform Differences
|
|
332
324
|
|
|
333
325
|
| Feature | Markdown | Slack | Plain Text |
|
|
334
|
-
|
|
326
|
+
|---------|---------|-------|-----------|
|
|
335
327
|
| **Bold** | `**text**` | `*text*` | text |
|
|
336
328
|
| *Italic* | `*text*` | `_text_` | text |
|
|
337
329
|
| ~~Strike~~ | `~~text~~` | `~text~` | text |
|