@tryghost/algolia-fragmenter 0.2.10 → 0.3.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/README.md +7 -3
- package/lib/index.d.mts +16 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +80 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +19 -5
- package/index.js +0 -1
- package/lib/transformer.js +0 -116
package/README.md
CHANGED
|
@@ -19,14 +19,18 @@ pnpm add @tryghost/algolia-fragmenter
|
|
|
19
19
|
Convert Ghost Content API posts, then reduce the resulting records into fragments:
|
|
20
20
|
|
|
21
21
|
```js
|
|
22
|
-
|
|
22
|
+
import {fragmentTransformer, transformToAlgoliaObject} from '@tryghost/algolia-fragmenter';
|
|
23
23
|
|
|
24
|
-
const records =
|
|
25
|
-
const fragments = records.reduce(
|
|
24
|
+
const records = transformToAlgoliaObject(posts);
|
|
25
|
+
const fragments = records.reduce(fragmentTransformer, []);
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
`transformToAlgoliaObject` accepts an optional array of post slugs to exclude as its second argument. `fragmentTransformer` is designed to be passed directly to `Array#reduce`.
|
|
29
29
|
|
|
30
|
+
Both operations are deprecated compatibility wrappers. They remain available with their existing output while a deeper record-building API is introduced separately.
|
|
31
|
+
|
|
32
|
+
This package is ESM-only and requires Node.js 24 or later.
|
|
33
|
+
|
|
30
34
|
## Development
|
|
31
35
|
|
|
32
36
|
Install dependencies from the repository root with `pnpm install`. From the root, run this package's tests and lint checks with:
|
package/lib/index.d.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type GhostContent = Readonly<Record<string, unknown>>;
|
|
2
|
+
export type AlgoliaRecord = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Retained for compatibility while the deep record-building API is introduced.
|
|
5
|
+
*/
|
|
6
|
+
export declare const fragmentTransformer: (recordAccumulator: AlgoliaRecord[], ghostContent: AlgoliaRecord) => AlgoliaRecord[];
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Retained for compatibility while the deep record-building API is introduced.
|
|
9
|
+
*/
|
|
10
|
+
export declare const transformToAlgoliaObject: (posts: readonly GhostContent[], ignoreSlugs?: readonly string[]) => AlgoliaRecord[];
|
|
11
|
+
declare const _default: {
|
|
12
|
+
fragmentTransformer: typeof fragmentTransformer;
|
|
13
|
+
transformToAlgoliaObject: typeof transformToAlgoliaObject;
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAoEpD;;GAEG;AACH,eAAO,MAAM,mBAAmB,sBACT,aAAa,EAAE,gBACpB,aAAa,KAC5B,aAAa,EASf,CAAC;AAqBF;;GAEG;AACH,eAAO,MAAM,wBAAwB,UAC1B,SAAS,YAAY,EAAE,gBAChB,SAAS,MAAM,EAAE,KAChC,aAAa,EAqBf,CAAC"}
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { extract } from '@tryghost/algolia-html-extractor';
|
|
2
|
+
const createLegacyFragment = (fragment) => ({
|
|
3
|
+
html: fragment.html,
|
|
4
|
+
content: fragment.text,
|
|
5
|
+
headings: [...fragment.headingPath],
|
|
6
|
+
anchor: fragment.anchor,
|
|
7
|
+
sourceTag: fragment.sourceTag,
|
|
8
|
+
customRanking: {
|
|
9
|
+
position: fragment.position,
|
|
10
|
+
heading: fragment.headingRank
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
const reduceFragmentsUnderHeadings = (groups, fragment) => {
|
|
14
|
+
const existingGroup = groups.find(group => group.anchor === fragment.anchor);
|
|
15
|
+
if (existingGroup === undefined) {
|
|
16
|
+
groups.push(fragment);
|
|
17
|
+
return groups;
|
|
18
|
+
}
|
|
19
|
+
existingGroup.html += fragment.sourceTag === 'pre' ? ` ${fragment.content}` : fragment.html;
|
|
20
|
+
existingGroup.content += ` ${fragment.content}`;
|
|
21
|
+
return groups;
|
|
22
|
+
};
|
|
23
|
+
const toAlgoliaRecord = (ghostContent, fragment, index) => {
|
|
24
|
+
const { content: _content, sourceTag: _sourceTag, ...groupedFragment } = fragment;
|
|
25
|
+
const url = fragment.anchor === null ? ghostContent.url : `${ghostContent.url}#${fragment.anchor}`;
|
|
26
|
+
return {
|
|
27
|
+
...ghostContent,
|
|
28
|
+
...groupedFragment,
|
|
29
|
+
url,
|
|
30
|
+
objectID: `${ghostContent.objectID}_${index}`
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated Retained for compatibility while the deep record-building API is introduced.
|
|
35
|
+
*/
|
|
36
|
+
export const fragmentTransformer = (recordAccumulator, ghostContent) => {
|
|
37
|
+
const groupedFragments = extract(ghostContent.html)
|
|
38
|
+
.map(createLegacyFragment)
|
|
39
|
+
.reduce(reduceFragmentsUnderHeadings, []);
|
|
40
|
+
const records = groupedFragments.map((fragment, index) => toAlgoliaRecord(ghostContent, fragment, index));
|
|
41
|
+
return [...recordAccumulator, ...records];
|
|
42
|
+
};
|
|
43
|
+
const projectLegacyRelations = (value, fieldName) => {
|
|
44
|
+
const relations = value;
|
|
45
|
+
if (!relations?.length) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
if (typeof relations.forEach !== 'function') {
|
|
49
|
+
throw new TypeError(`post.${fieldName}.forEach is not a function`);
|
|
50
|
+
}
|
|
51
|
+
const projected = [];
|
|
52
|
+
relations.forEach(relation => {
|
|
53
|
+
projected.push({ name: relation.name, slug: relation.slug });
|
|
54
|
+
});
|
|
55
|
+
return projected;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* @deprecated Retained for compatibility while the deep record-building API is introduced.
|
|
59
|
+
*/
|
|
60
|
+
export const transformToAlgoliaObject = (posts, ignoreSlugs) => {
|
|
61
|
+
const algoliaObjects = [];
|
|
62
|
+
for (const post of posts) {
|
|
63
|
+
if (ignoreSlugs?.some(slug => slug === post.slug)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
algoliaObjects.push({
|
|
67
|
+
objectID: post.id,
|
|
68
|
+
slug: post.slug,
|
|
69
|
+
url: post.url,
|
|
70
|
+
html: post.html,
|
|
71
|
+
image: post.feature_image,
|
|
72
|
+
title: post.title,
|
|
73
|
+
tags: projectLegacyRelations(post.tags, 'tags'),
|
|
74
|
+
authors: projectLegacyRelations(post.authors, 'authors')
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return algoliaObjects;
|
|
78
|
+
};
|
|
79
|
+
export default { fragmentTransformer, transformToAlgoliaObject };
|
|
80
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"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","sourcesContent":["import {extract, type ExtractedTagName, type HeadingRank} from '@tryghost/algolia-html-extractor';\n\nexport type GhostContent = Readonly<Record<string, unknown>>;\n\nexport type AlgoliaRecord = Record<string, unknown>;\n\ntype GhostRelation = Readonly<Record<string, unknown>>;\n\ntype LegacyRelationCollection = {\n length: number;\n forEach(callback: (relation: GhostRelation) => void): void;\n};\n\ntype GroupedFragment = {\n html: string;\n headings: string[];\n anchor: string | null;\n customRanking: {\n position: number;\n heading: HeadingRank;\n };\n};\n\ntype LegacyFragment = GroupedFragment & {\n content: string;\n sourceTag: ExtractedTagName;\n};\n\nconst createLegacyFragment = (fragment: ReturnType<typeof extract>[number]): LegacyFragment => ({\n html: fragment.html,\n content: fragment.text,\n headings: [...fragment.headingPath],\n anchor: fragment.anchor,\n sourceTag: fragment.sourceTag,\n customRanking: {\n position: fragment.position,\n heading: fragment.headingRank\n }\n});\n\nconst reduceFragmentsUnderHeadings = (\n groups: LegacyFragment[],\n fragment: LegacyFragment\n): LegacyFragment[] => {\n const existingGroup = groups.find(group => group.anchor === fragment.anchor);\n if (existingGroup === undefined) {\n groups.push(fragment);\n return groups;\n }\n\n existingGroup.html += fragment.sourceTag === 'pre' ? ` ${fragment.content}` : fragment.html;\n existingGroup.content += ` ${fragment.content}`;\n return groups;\n};\n\nconst toAlgoliaRecord = (\n ghostContent: AlgoliaRecord,\n fragment: LegacyFragment,\n index: number\n): AlgoliaRecord => {\n const {content: _content, sourceTag: _sourceTag, ...groupedFragment} = fragment;\n const url =\n fragment.anchor === null ? ghostContent.url : `${ghostContent.url}#${fragment.anchor}`;\n\n return {\n ...ghostContent,\n ...groupedFragment,\n url,\n objectID: `${ghostContent.objectID}_${index}`\n };\n};\n\n/**\n * @deprecated Retained for compatibility while the deep record-building API is introduced.\n */\nexport const fragmentTransformer = (\n recordAccumulator: AlgoliaRecord[],\n ghostContent: AlgoliaRecord\n): AlgoliaRecord[] => {\n const groupedFragments = extract(ghostContent.html as string)\n .map(createLegacyFragment)\n .reduce(reduceFragmentsUnderHeadings, []);\n const records = groupedFragments.map((fragment, index) =>\n toAlgoliaRecord(ghostContent, fragment, index)\n );\n\n return [...recordAccumulator, ...records];\n};\n\nconst projectLegacyRelations = (\n value: unknown,\n fieldName: 'tags' | 'authors'\n): Array<{name: unknown; slug: unknown}> => {\n const relations = value as Partial<LegacyRelationCollection> | null | undefined;\n if (!relations?.length) {\n return [];\n }\n if (typeof relations.forEach !== 'function') {\n throw new TypeError(`post.${fieldName}.forEach is not a function`);\n }\n\n const projected: Array<{name: unknown; slug: unknown}> = [];\n relations.forEach(relation => {\n projected.push({name: relation.name, slug: relation.slug});\n });\n return projected;\n};\n\n/**\n * @deprecated Retained for compatibility while the deep record-building API is introduced.\n */\nexport const transformToAlgoliaObject = (\n posts: readonly GhostContent[],\n ignoreSlugs?: readonly string[]\n): AlgoliaRecord[] => {\n const algoliaObjects: AlgoliaRecord[] = [];\n\n for (const post of posts) {\n if (ignoreSlugs?.some(slug => slug === post.slug)) {\n continue;\n }\n\n algoliaObjects.push({\n objectID: post.id,\n slug: post.slug,\n url: post.url,\n html: post.html,\n image: post.feature_image,\n title: post.title,\n tags: projectLegacyRelations(post.tags, 'tags'),\n authors: projectLegacyRelations(post.authors, 'authors')\n });\n }\n\n return algoliaObjects;\n};\n\nexport default {fragmentTransformer, transformToAlgoliaObject};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryghost/algolia-fragmenter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/TryGhost/algolia.git",
|
|
@@ -11,21 +11,35 @@
|
|
|
11
11
|
"engines": {
|
|
12
12
|
"node": ">=24"
|
|
13
13
|
},
|
|
14
|
-
"
|
|
14
|
+
"type": "module",
|
|
15
|
+
"types": "./lib/index.d.mts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/index.d.mts",
|
|
19
|
+
"import": "./lib/index.mjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
15
22
|
"files": [
|
|
16
|
-
"index.js",
|
|
17
23
|
"lib"
|
|
18
24
|
],
|
|
19
25
|
"publishConfig": {
|
|
20
26
|
"access": "public"
|
|
21
27
|
},
|
|
22
28
|
"dependencies": {
|
|
23
|
-
"algolia-html-extractor": "0.0
|
|
29
|
+
"@tryghost/algolia-html-extractor": "^0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "24.13.3",
|
|
33
|
+
"typescript": "7.0.2"
|
|
24
34
|
},
|
|
25
35
|
"scripts": {
|
|
26
36
|
"dev": "echo \"Implement me!\"",
|
|
37
|
+
"prebuild": "pnpm --filter @tryghost/algolia-html-extractor build",
|
|
38
|
+
"pretest": "pnpm build",
|
|
27
39
|
"test": "NODE_ENV=testing vitest run --root .",
|
|
40
|
+
"typecheck": "tsc --project tsconfig.json",
|
|
41
|
+
"build": "tsc --project tsconfig.build.json",
|
|
28
42
|
"lint": "oxlint --quiet . && oxfmt --check .",
|
|
29
|
-
"posttest": "pnpm lint"
|
|
43
|
+
"posttest": "pnpm typecheck && pnpm lint"
|
|
30
44
|
}
|
|
31
45
|
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require('./lib/transformer');
|
package/lib/transformer.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
const HtmlExtractor = require(`algolia-html-extractor`);
|
|
2
|
-
const Extractor = new HtmlExtractor();
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Utility function, takes the output of HTML Extractor, and reduces it back down
|
|
6
|
-
* So that there is a group of HTML/content per heading
|
|
7
|
-
*
|
|
8
|
-
* @param {Array} accumulator
|
|
9
|
-
* @param {Object} fragment
|
|
10
|
-
*/
|
|
11
|
-
const reduceFragmentsUnderHeadings = (accumulator, fragment) => {
|
|
12
|
-
const existingFragment = accumulator.find(existing => existing.anchor === fragment.anchor);
|
|
13
|
-
|
|
14
|
-
if (existingFragment) {
|
|
15
|
-
// Merge our fragments together
|
|
16
|
-
if (fragment.node && fragment.node.tagName === `PRE`) {
|
|
17
|
-
// For pre-tags, we don't keep all the markup
|
|
18
|
-
existingFragment.html += ` ${fragment.content}`; // keep a space
|
|
19
|
-
existingFragment.content += ` ${fragment.content}`; // keep a space
|
|
20
|
-
} else {
|
|
21
|
-
existingFragment.html += fragment.html;
|
|
22
|
-
existingFragment.content += ` ${fragment.content}`; // keep a space
|
|
23
|
-
}
|
|
24
|
-
} else {
|
|
25
|
-
// If we don't already have a matching fragment with this anchor, add it
|
|
26
|
-
accumulator.push(fragment);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return accumulator;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Fragment Transformer
|
|
34
|
-
* breaks down large HTML strings into sensible fragments based on headings
|
|
35
|
-
*/
|
|
36
|
-
module.exports.fragmentTransformer = (recordAccumulator, node) => {
|
|
37
|
-
let htmlFragments = Extractor
|
|
38
|
-
// These are the top-level HTML elements that we keep - this results in a lot of fragments
|
|
39
|
-
.run(node.html, {cssSelector: `p,pre,td,li`})
|
|
40
|
-
// Use the utility function to merge fragments so that there is one-per-heading
|
|
41
|
-
.reduce(reduceFragmentsUnderHeadings, []);
|
|
42
|
-
|
|
43
|
-
// convert our fragments for this node into valid objects, and merge int the
|
|
44
|
-
const records = htmlFragments.reduce((fragmentAccumulator, fragment, index) => {
|
|
45
|
-
// Don't need a reference to the html node type
|
|
46
|
-
delete fragment.node;
|
|
47
|
-
// For now at least, we're not going to index the content string
|
|
48
|
-
// The HTML string is already very long, and there are size limits
|
|
49
|
-
delete fragment.content;
|
|
50
|
-
// If we have an anchor, change the URL to be a deep link
|
|
51
|
-
if (fragment.anchor) {
|
|
52
|
-
fragment.url = `${node.url}#${fragment.anchor}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let objectID = `${node.objectID}_${index}`;
|
|
56
|
-
|
|
57
|
-
// TODO: switch this on in verbose mode only
|
|
58
|
-
// // If fragments are too long, we need this to see which fragment it was
|
|
59
|
-
|
|
60
|
-
return [...fragmentAccumulator, {...node, ...fragment, objectID: objectID}];
|
|
61
|
-
}, []);
|
|
62
|
-
|
|
63
|
-
return [...recordAccumulator, ...records];
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
module.exports._testReduceFragmentsUnderHeadings = reduceFragmentsUnderHeadings;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Algolia Object Transformer
|
|
70
|
-
* takes a Ghost post and selects the properties needed to send to Algolia
|
|
71
|
-
*
|
|
72
|
-
* @param {Array} posts
|
|
73
|
-
*/
|
|
74
|
-
module.exports.transformToAlgoliaObject = (posts, ignoreSlugs) => {
|
|
75
|
-
const algoliaObjects = [];
|
|
76
|
-
|
|
77
|
-
posts.map(post => {
|
|
78
|
-
// Define the properties we need for Algolia
|
|
79
|
-
const algoliaPost = {
|
|
80
|
-
objectID: post.id,
|
|
81
|
-
slug: post.slug,
|
|
82
|
-
url: post.url,
|
|
83
|
-
html: post.html,
|
|
84
|
-
image: post.feature_image,
|
|
85
|
-
title: post.title,
|
|
86
|
-
tags: [],
|
|
87
|
-
authors: []
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// If we have an array of slugs to ignore, and the current
|
|
91
|
-
// post slug is in that list, skip this loop iteration
|
|
92
|
-
if (ignoreSlugs) {
|
|
93
|
-
if (ignoreSlugs.includes(post.slug)) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (post.tags && post.tags.length) {
|
|
99
|
-
post.tags.forEach(tag => {
|
|
100
|
-
algoliaPost.tags.push({name: tag.name, slug: tag.slug});
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (post.authors && post.authors.length) {
|
|
105
|
-
post.authors.forEach(author => {
|
|
106
|
-
algoliaPost.authors.push({name: author.name, slug: author.slug});
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
algoliaObjects.push(algoliaPost);
|
|
111
|
-
|
|
112
|
-
return algoliaPost;
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return algoliaObjects;
|
|
116
|
-
};
|