@andesine/converters 0.9.0 → 0.11.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.
- package/CHANGELOG.md +8 -0
- package/README.md +16 -359
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
- No changes; versioned with the other public Andesine packages.
|
|
6
|
+
|
|
7
|
+
## 0.9.1
|
|
8
|
+
|
|
9
|
+
- No changes; versioned with the other public Andesine packages.
|
|
10
|
+
|
|
3
11
|
## 0.9.0
|
|
4
12
|
|
|
5
13
|
- Images without an asset are placeholders. They export with an empty URL (`![alt]()` in
|
package/README.md
CHANGED
|
@@ -1,376 +1,33 @@
|
|
|
1
1
|
# @andesine/converters
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Converts [Andesine](https://andesine.app) content to and from Markdown, MDX, Markdoc, HTML, and
|
|
4
|
+
plain text.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
6
7
|
|
|
7
8
|
```sh
|
|
8
9
|
npm install @andesine/converters
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
for builds, archive checks, versioning, and npm publishing. This package uses the
|
|
13
|
-
[MIT license](./LICENSE).
|
|
14
|
-
|
|
15
|
-
## Formats
|
|
16
|
-
|
|
17
|
-
| Import | Output | Input |
|
|
18
|
-
| ------------------------------- | ------------ | -------------- |
|
|
19
|
-
| `@andesine/converters/markdown` | `toMarkdown` | `fromMarkdown` |
|
|
20
|
-
| `@andesine/converters/mdx` | `toMDX` | `fromMDX` |
|
|
21
|
-
| `@andesine/converters/markdoc` | `toMarkdoc` | `fromMarkdoc` |
|
|
22
|
-
| `@andesine/converters/html` | `toHTML` | `fromHTML` |
|
|
23
|
-
| `@andesine/converters/text` | `toText` | — |
|
|
24
|
-
|
|
25
|
-
All conversion functions return promises. Callbacks and handlers may return a
|
|
26
|
-
value or a promise. Formats have separate entry points; there is no root export
|
|
27
|
-
that loads every parser. Installation includes all format dependencies.
|
|
28
|
-
|
|
29
|
-
Markdoc's declarations reference its React renderer types, so this package also
|
|
30
|
-
includes `@types/react` for strict TypeScript consumers. No React runtime is
|
|
31
|
-
required or loaded by the converters.
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
import { toMarkdown, fromMarkdown } from "@andesine/converters/markdown";
|
|
35
|
-
|
|
36
|
-
const source = await toMarkdown(document);
|
|
37
|
-
const importedDocument = await fromMarkdown("# Title\n\nHello **world**.");
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Input converters return a `doc` with a title and body content. If no title is
|
|
41
|
-
mapped, the title is empty. Use `{ document: "fragment" }` to return body content
|
|
42
|
-
without a title. The same setting prevents the first heading from becoming a
|
|
43
|
-
title. Use `targetFragment: "body"` to put imported body content in a named
|
|
44
|
-
fragment.
|
|
45
|
-
|
|
46
|
-
## Reusable mapping options
|
|
47
|
-
|
|
48
|
-
```ts
|
|
49
|
-
import { createMDXConverter } from "@andesine/converters/mdx";
|
|
50
|
-
|
|
51
|
-
const converter = createMDXConverter({
|
|
52
|
-
title: { as: "frontmatter", key: "title" },
|
|
53
|
-
properties: { as: "frontmatter" },
|
|
54
|
-
fragments: {
|
|
55
|
-
default: "unwrap",
|
|
56
|
-
named: {
|
|
57
|
-
summary: { as: "element", name: "Fragment" },
|
|
58
|
-
internalNotes: "omit"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const source = await converter.encode(document);
|
|
64
|
-
const restored = await converter.decode(source);
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Factory names are `createMarkdownConverter`, `createMDXConverter`,
|
|
68
|
-
`createMarkdocConverter`, `createHTMLConverter`, and `createTextConverter`.
|
|
69
|
-
Per-call options override saved options; handler maps and named fragment mappings
|
|
70
|
-
merge by key.
|
|
71
|
-
|
|
72
|
-
| Field | Default | Other mappings |
|
|
73
|
-
| ---------- | ------------------- | -------------------------------------------------------------- |
|
|
74
|
-
| Title | First-level heading | `"omit"`, `{ as: "frontmatter", key: "title" }` |
|
|
75
|
-
| Properties | Omit | `{ as: "frontmatter" }`, `{ as: "element", name: "Property" }` |
|
|
76
|
-
| Fragments | `"unwrap"` | `"omit"`, `{ as: "element", name: "Fragment" }` |
|
|
77
|
-
|
|
78
|
-
Unwrap removes the fragment boundary and retains its content in document order.
|
|
79
|
-
Omit removes the content too. The number of fragments does not change the default.
|
|
80
|
-
MDX wrappers become components; Markdoc wrappers become tags; HTML wrappers use
|
|
81
|
-
`data-andesine-element` and JSON `data-andesine-props` on a `div`. Markdown needs
|
|
82
|
-
a custom handler for fragment wrappers. HTML and plain text reject frontmatter
|
|
83
|
-
output; use their AST result or your own surrounding document for metadata.
|
|
84
|
-
|
|
85
|
-
On import, matching named wrappers become fragments. Unwrapped boundaries cannot
|
|
86
|
-
be recovered. With heading mapping, only the first root heading, at level one,
|
|
87
|
-
becomes the title. With frontmatter mapping, the selected field becomes the title.
|
|
88
|
-
|
|
89
|
-
With frontmatter mapping, property labels become frontmatter keys. Duplicate keys,
|
|
90
|
-
including a collision with the title field, cause an error. Frontmatter has no
|
|
91
|
-
position, so imported properties come first.
|
|
92
|
-
|
|
93
|
-
With element mapping, each property becomes a self-closing element in its
|
|
94
|
-
position, for example `<Property name="Status" type="select" value="Draft" />` in
|
|
95
|
-
MDX. `type` is written only when the value doesn't show it. The format must
|
|
96
|
-
support elements, as MDX, Markdoc, and HTML do.
|
|
97
|
-
|
|
98
|
-
Imports with either mapping read both frontmatter and matching elements. Import
|
|
99
|
-
infers text, number, checkbox, and multi-select properties from scalar values and
|
|
100
|
-
string arrays; an element `type` sets date, URL, and select types. Select options
|
|
101
|
-
and schema metadata are not retained. Object-valued properties need custom
|
|
102
|
-
handling. Unmapped frontmatter is ignored.
|
|
103
|
-
|
|
104
|
-
## Heading anchors
|
|
105
|
-
|
|
106
|
-
`getHeadingAnchors(document)` returns `{ path, text, anchor }` for each heading in
|
|
107
|
-
document order. It is exported from the package root and the dependency-free
|
|
108
|
-
`@andesine/converters/anchors` entry point. `path` is the array of child indexes
|
|
109
|
-
from the document root. The same helper supplies search and answer-source anchors.
|
|
110
|
-
|
|
111
|
-
Anchors use NFC-normalized lowercase heading text. Punctuation is removed,
|
|
112
|
-
whitespace and underscores become hyphens, and empty results use `section`.
|
|
113
|
-
Letters, numbers, and combining marks are retained. Collisions receive `-1`,
|
|
114
|
-
`-2`, and later suffixes until the ID is unique, including collisions with names
|
|
115
|
-
that already contain a suffix. All fragments share one counter. Titles and
|
|
116
|
-
properties do not create anchors or consume IDs.
|
|
117
|
-
|
|
118
|
-
HTML headings receive the ID automatically. Markdown and MDX AST headings carry
|
|
119
|
-
it in `data.hProperties.id`; Markdoc AST headings carry it in `attributes.id`.
|
|
120
|
-
String serialization for Markdown, MDX, and Markdoc does not preserve this AST
|
|
121
|
-
metadata. Configure the final renderer to apply these IDs, or use a custom
|
|
122
|
-
heading handler. Do not assume a renderer's own slug rules match Andesine.
|
|
123
|
-
|
|
124
|
-
Custom encode handlers receive `context.anchor` for original heading nodes:
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
const html = await toHTML(document, {
|
|
128
|
-
encode: {
|
|
129
|
-
nodes: {
|
|
130
|
-
heading: async (node, context) => ({
|
|
131
|
-
type: "element",
|
|
132
|
-
tagName: `h${node.attrs?.level || 1}`,
|
|
133
|
-
properties: { id: context.anchor },
|
|
134
|
-
children: await context.children(node)
|
|
135
|
-
})
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
The converter calculates anchors before applying fragment mappings. Omitting a
|
|
142
|
-
fragment does not renumber later headings, but links to omitted headings have no
|
|
143
|
-
target. A renderer that reorders, replaces, or omits content is responsible for
|
|
144
|
-
adapting links. Anchors are stable within a content version, not across edits.
|
|
145
|
-
Plain text does not have link targets. Import does not retain supplied heading IDs;
|
|
146
|
-
anchors are derived again from the imported document.
|
|
147
|
-
|
|
148
|
-
## Images
|
|
149
|
-
|
|
150
|
-
Published content already includes image URLs. Use them directly:
|
|
12
|
+
## Example
|
|
151
13
|
|
|
152
14
|
```ts
|
|
153
15
|
import { publishedContentOptions } from "@andesine/converters";
|
|
154
|
-
import {
|
|
155
|
-
|
|
156
|
-
const page = await client.content.get({ path: "/Docs/Getting started" });
|
|
157
|
-
const source = await toMarkdown(page.content, {
|
|
158
|
-
...publishedContentOptions(page),
|
|
159
|
-
title: { as: "frontmatter" }
|
|
160
|
-
});
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
`publishedContentOptions(page, options?)` returns only an `imageURL` callback.
|
|
164
|
-
It works with single published-content responses and full items from
|
|
165
|
-
`content.listEntries`. It does not change title, property, or fragment mappings.
|
|
166
|
-
The converter package has no SDK dependency; any object with a compatible
|
|
167
|
-
`assets` array works.
|
|
16
|
+
import { toHTML } from "@andesine/converters/html";
|
|
17
|
+
import { fromMarkdown } from "@andesine/converters/markdown";
|
|
168
18
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
Set `{ variant: "thumbnail" }`, or select a variant per image:
|
|
172
|
-
|
|
173
|
-
```ts
|
|
174
|
-
const options = publishedContentOptions(page, {
|
|
175
|
-
variant: async (image) => (image.attrs?.small ? "thumbnail" : "display")
|
|
176
|
-
});
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
The resolver returns the supplied URL unchanged. It does not fetch images, build
|
|
180
|
-
URLs, or fall back to another variant. Missing assets or variants throw
|
|
181
|
-
`ConversionError`. Keep assets from the same entry and snapshot as the content.
|
|
182
|
-
An explicit `imageURL` callback can override this helper through the normal
|
|
183
|
-
converter options. Import still needs your own `imageAssetID` callback.
|
|
184
|
-
|
|
185
|
-
Images need reference resolution in both directions:
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
const source = await toMarkdown(document, {
|
|
189
|
-
imageURL: async (image) => {
|
|
190
|
-
const assetID = String(image.attrs?.assetID);
|
|
191
|
-
return assets.get(assetID)!.url;
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const importedDocument = await fromMarkdown(source, {
|
|
196
|
-
imageAssetID: async ({ url, alt, title }) => resolveAsset(url, alt, title)
|
|
197
|
-
});
|
|
19
|
+
const html = await toHTML(page.content, publishedContentOptions(page));
|
|
20
|
+
const document = await fromMarkdown("# Installation\n\nRun `npm install`.");
|
|
198
21
|
```
|
|
199
22
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
Plain text uses alt text, or the caption when alt text is absent, without resolving
|
|
203
|
-
an image URL. Published snapshot URLs may expire; decide where exported assets
|
|
204
|
-
will live before resolving them.
|
|
205
|
-
|
|
206
|
-
## Custom and wildcard handlers
|
|
207
|
-
|
|
208
|
-
Handlers can return a node, an array of nodes, or `null` to omit content. Exact
|
|
209
|
-
element handlers take precedence over wildcard element handlers. Node and mark
|
|
210
|
-
handlers also support `"*"`. Custom handlers take precedence over built-ins.
|
|
211
|
-
|
|
212
|
-
```ts
|
|
213
|
-
import { createMDXConverter, mdxAdapter } from "@andesine/converters/mdx";
|
|
214
|
-
|
|
215
|
-
const converter = createMDXConverter({
|
|
216
|
-
encode: {
|
|
217
|
-
elements: {
|
|
218
|
-
"Callout": async (node, context) =>
|
|
219
|
-
mdxAdapter.encode(
|
|
220
|
-
{
|
|
221
|
-
...node,
|
|
222
|
-
attrs: { ...node.attrs, name: "Notice" }
|
|
223
|
-
},
|
|
224
|
-
context
|
|
225
|
-
),
|
|
226
|
-
"*": async (node, context) =>
|
|
227
|
-
mdxAdapter.encode(
|
|
228
|
-
{
|
|
229
|
-
...node,
|
|
230
|
-
attrs: { ...node.attrs, name: `Components.${node.attrs?.name}` }
|
|
231
|
-
},
|
|
232
|
-
context
|
|
233
|
-
)
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
The built-in MDX element handler already maps arbitrary Andesine element names
|
|
240
|
-
and static properties to MDX elements. Wildcards let you change that mapping for
|
|
241
|
-
all elements. Decode handlers are independent: define a matching handler when
|
|
242
|
-
custom output should convert back to the original element.
|
|
243
|
-
|
|
244
|
-
```ts
|
|
245
|
-
import { element } from "@andesine/converters";
|
|
246
|
-
|
|
247
|
-
const converter = createMDXConverter({
|
|
248
|
-
decode: {
|
|
249
|
-
elements: {
|
|
250
|
-
Notice: async (node, context) => {
|
|
251
|
-
if (node.type !== "mdxJsxFlowElement") {
|
|
252
|
-
throw new Error("Notice must be a block element");
|
|
253
|
-
}
|
|
254
|
-
return element("Callout", {}, await context.children(node.children));
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
Encoder contexts expose `encode`, `children`, `imageURL`, options, and the content
|
|
262
|
-
path. Decoder contexts expose `decode`, `children`, `image`, options, and the AST
|
|
263
|
-
path. Calling the adapter's built-in handler bypasses custom dispatch; calling
|
|
264
|
-
`context.encode` or `context.decode` uses custom dispatch again. Avoid passing the
|
|
265
|
-
same unchanged node back through dispatch from its own handler.
|
|
266
|
-
|
|
267
|
-
`encode.nodes` is keyed by Andesine node type; `encode.marks` by mark type.
|
|
268
|
-
`decode.nodes` is keyed by native AST type (HTML uses tag names for elements).
|
|
269
|
-
`decode.marks` uses Andesine mark names. `decode.elements` uses component/tag names.
|
|
270
|
-
A node wildcard also matches the document root; delegate roots if your wildcard
|
|
271
|
-
is intended to omit only unknown leaves.
|
|
272
|
-
|
|
273
|
-
## Syntax trees and custom formats
|
|
274
|
-
|
|
275
|
-
Each bidirectional format exports `to…AST` and `from…AST`. They use
|
|
276
|
-
`{ tree, frontmatter }`, so metadata is separate from the format tree. The trees are
|
|
277
|
-
mdast for Markdown/MDX, Markdoc nodes for Markdoc, and hast for HTML. Plain text
|
|
278
|
-
exports `toTextAST` with a small text tree.
|
|
279
|
-
|
|
280
|
-
```ts
|
|
281
|
-
const result = await converter.toAST(document);
|
|
282
|
-
// Inspect or change result.tree and result.frontmatter.
|
|
283
|
-
const restored = await converter.fromAST(result);
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
To add a format, implement `FormatAdapter<T>` and call `createConverter(adapter)`.
|
|
287
|
-
The adapter supplies root creation, parsing, stringification, node/mark encoding,
|
|
288
|
-
decoding, and optional element/mark name lookup. The core supplies async traversal,
|
|
289
|
-
custom handler dispatch, title/property/fragment mapping, and image callbacks.
|
|
290
|
-
It does not require a Markdown tree.
|
|
291
|
-
|
|
292
|
-
For example, this small custom format stores document text as a JSON array. It is
|
|
293
|
-
intentionally lossy and supports only paragraphs and text:
|
|
294
|
-
|
|
295
|
-
```ts
|
|
296
|
-
import { createConverter, ConversionError, type FormatAdapter } from "@andesine/converters";
|
|
297
|
-
|
|
298
|
-
interface ArrayNode {
|
|
299
|
-
type: "root" | "paragraph" | "text";
|
|
300
|
-
value?: string;
|
|
301
|
-
children?: ArrayNode[];
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const adapter: FormatAdapter<ArrayNode> = {
|
|
305
|
-
name: "Paragraph array",
|
|
306
|
-
type: (node) => node.type,
|
|
307
|
-
root: (children) => ({ type: "root", children }),
|
|
308
|
-
parse: (source) => ({
|
|
309
|
-
type: "root",
|
|
310
|
-
children: (JSON.parse(source) as string[]).map((value) => ({
|
|
311
|
-
type: "paragraph",
|
|
312
|
-
children: [{ type: "text", value }]
|
|
313
|
-
}))
|
|
314
|
-
}),
|
|
315
|
-
stringify: (root) =>
|
|
316
|
-
JSON.stringify(
|
|
317
|
-
root.children?.map((node) => node.children?.map((child) => child.value || "").join(""))
|
|
318
|
-
),
|
|
319
|
-
async encode(node, context) {
|
|
320
|
-
if (node.type === "text") return { type: "text", value: node.text };
|
|
321
|
-
if (node.type === "paragraph")
|
|
322
|
-
return { type: "paragraph", children: await context.children(node) };
|
|
323
|
-
throw new ConversionError(`Unsupported node: ${node.type}`);
|
|
324
|
-
},
|
|
325
|
-
mark: (_mark, children) => children,
|
|
326
|
-
async decode(node, context) {
|
|
327
|
-
if (node.type === "text") return { type: "text", text: node.value || "" };
|
|
328
|
-
const content = await context.children(node.children || []);
|
|
329
|
-
return node.type === "root" ? content : { type: "paragraph", content };
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const converter = createConverter(adapter, { title: "omit", document: "fragment" });
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
## Preservation and limits
|
|
337
|
-
|
|
338
|
-
These are content conversions, not document backups. They preserve the supported
|
|
339
|
-
content subset, not exact source text. Node IDs, schema links, inherited-field
|
|
340
|
-
metadata, editor layout, original whitespace, and source syntax choices are not
|
|
341
|
-
preserved. There is no detailed loss report. Unsupported nodes, marks, or syntax
|
|
342
|
-
throw; explicit mappings and the losses below do not.
|
|
343
|
-
|
|
344
|
-
| Feature | Markdown / MDX | Markdoc | HTML | Plain text |
|
|
345
|
-
| ----------------------------------- | ----------------------------------------- | -------------------------------- | --------------------------------- | ------------------- |
|
|
346
|
-
| Paragraphs, headings, quotes, lists | Yes | Yes | Yes | Text and separators |
|
|
347
|
-
| Bold, italic, strike, code, links | Yes | Yes | Yes | Formatting removed |
|
|
348
|
-
| Subscript, superscript, highlight | Custom handlers | Custom handlers | Yes; highlight color omitted | Formatting removed |
|
|
349
|
-
| Task lists | GFM | Custom handlers | Andesine data attributes | Checkbox markers |
|
|
350
|
-
| Code blocks | Language retained | Language retained | Language retained | Code text |
|
|
351
|
-
| Images | URL, alt, caption as title | URL, alt, caption as title | Image and optional figure caption | Alt/caption text |
|
|
352
|
-
| Tables | GFM; single-paragraph cells, no spans | Single-paragraph cells, no spans | Cell spans and block content | Tab-separated cells |
|
|
353
|
-
| Custom elements | MD: custom handler; MDX: static block JSX | Static tags | Andesine data attributes | Custom handler |
|
|
23
|
+
Each format has its own entry point: `@andesine/converters/markdown`, `/mdx`, `/markdoc`, `/html`,
|
|
24
|
+
and `/text`.
|
|
354
25
|
|
|
355
|
-
|
|
356
|
-
link targets/titles, image size, and aspect ratio are not preserved. Empty
|
|
357
|
-
paragraphs can collapse in Markdown-like output. Mark ordering and adjacent text
|
|
358
|
-
node boundaries can change without changing displayed content.
|
|
26
|
+
## Documentation
|
|
359
27
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
references, spreads, imports, exports, or executable code. Elements that stand alone
|
|
363
|
-
on their lines, e.g. one-line `<Step>`s in `<Steps>`, import as blocks. JSX between
|
|
364
|
-
text and JSX fragments require custom handlers. Markdoc variables/functions also require custom
|
|
365
|
-
handlers. No source is evaluated during conversion.
|
|
28
|
+
- [Converters](https://andesine.app/docs/developers/converters/)
|
|
29
|
+
- [SDK](https://andesine.app/docs/developers/sdk/)
|
|
366
30
|
|
|
367
|
-
|
|
368
|
-
task lists, Markdown footnotes, and raw HTML require custom handlers. HTML import supports
|
|
369
|
-
semantic elements, not arbitrary CSS/layout. Whitespace follows basic HTML
|
|
370
|
-
normalization. Scripts, styles, comments, and unknown elements require explicit
|
|
371
|
-
handlers. Built-in HTML output escapes text and rejects unsafe URL schemes; custom
|
|
372
|
-
handlers are responsible for the output they create. This is not an HTML sanitizer.
|
|
31
|
+
## License
|
|
373
32
|
|
|
374
|
-
|
|
375
|
-
that is valid for its destination, including block/inline placement and allowed
|
|
376
|
-
fragment children. If you need a document backup, retain the original JSON.
|
|
33
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andesine/converters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "TypeScript content converters for Andesine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"url": "git+https://github.com/vriteio/andesine.git",
|
|
79
79
|
"directory": "packages/public/converters"
|
|
80
80
|
},
|
|
81
|
-
"homepage": "https://
|
|
81
|
+
"homepage": "https://andesine.app/docs/developers/converters/",
|
|
82
82
|
"bugs": {
|
|
83
83
|
"url": "https://github.com/vriteio/andesine/issues"
|
|
84
84
|
},
|