@paroicms/content-loading-plugin 0.26.1 → 0.27.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.
|
@@ -1,29 +1,50 @@
|
|
|
1
1
|
import { parseLNodeId } from "@paroicms/public-anywhere-lib";
|
|
2
|
-
export async function makeSearch(service, httpContext,
|
|
3
|
-
const { q, language, limit,
|
|
2
|
+
export async function makeSearch(service, httpContext, input) {
|
|
3
|
+
const { q, language, limit, offset, template, sorting } = input;
|
|
4
4
|
const words = q.split(/\s+/).filter((word) => word.length >= 2);
|
|
5
5
|
if (words.length === 0)
|
|
6
6
|
return { total: 0 };
|
|
7
|
-
await service.
|
|
7
|
+
const { total, documents } = await service.loadDocuments({
|
|
8
|
+
load: "list",
|
|
9
|
+
nodeKind: "document",
|
|
10
|
+
descriptorName: "search",
|
|
8
11
|
language,
|
|
9
12
|
words,
|
|
10
13
|
limit,
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
offset,
|
|
15
|
+
sorting,
|
|
16
|
+
}, { withTotal: true });
|
|
17
|
+
const result = [];
|
|
18
|
+
for (const document of documents) {
|
|
19
|
+
result.push(await service.renderDocument(template, document));
|
|
20
|
+
}
|
|
21
|
+
await service.serve(httpContext, {
|
|
22
|
+
content: JSON.stringify({
|
|
23
|
+
total,
|
|
24
|
+
html: result.length > 0 ? result.join("\n") : undefined,
|
|
25
|
+
}),
|
|
26
|
+
contentType: "application/json; charset=utf-8",
|
|
13
27
|
});
|
|
14
28
|
}
|
|
15
|
-
export async function getPartials(service, httpContext,
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
export async function getPartials(service, httpContext, input) {
|
|
30
|
+
const { childrenOf, labeledWith, offset, limit, sorting, template } = input;
|
|
31
|
+
const parentDocumentId = parseLNodeId(childrenOf);
|
|
32
|
+
const documents = await service.loadDocuments({
|
|
33
|
+
load: "list",
|
|
34
|
+
nodeKind: "document",
|
|
35
|
+
descriptorName: "children",
|
|
36
|
+
parentDocumentId,
|
|
37
|
+
labeledWith,
|
|
38
|
+
offset,
|
|
39
|
+
limit,
|
|
40
|
+
sorting,
|
|
41
|
+
});
|
|
42
|
+
const result = [];
|
|
43
|
+
for (const document of documents) {
|
|
44
|
+
result.push(await service.renderDocument(template, document));
|
|
45
|
+
}
|
|
46
|
+
await service.serve(httpContext, {
|
|
47
|
+
content: result.join("\n"),
|
|
48
|
+
contentType: "text/plain; charset=utf-8",
|
|
28
49
|
});
|
|
29
50
|
}
|
|
@@ -1,31 +1,41 @@
|
|
|
1
|
+
import { extractNodeId } from "@paroicms/public-anywhere-lib";
|
|
2
|
+
import { parseSorting } from "@paroicms/public-server-lib";
|
|
1
3
|
import { type } from "arktype";
|
|
2
4
|
export const SearchTextOptionsUrlQueryAT = type({
|
|
3
5
|
language: "string",
|
|
4
6
|
q: "string",
|
|
5
7
|
limit: "string",
|
|
6
|
-
|
|
8
|
+
offset: "string",
|
|
7
9
|
tpl: "string",
|
|
10
|
+
"sorting?": "string|undefined",
|
|
8
11
|
"+": "reject",
|
|
9
|
-
}).pipe((
|
|
10
|
-
language:
|
|
11
|
-
q:
|
|
12
|
-
limit: Number(
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
}).pipe((data) => ({
|
|
13
|
+
language: data.language,
|
|
14
|
+
q: data.q,
|
|
15
|
+
limit: Number(data.limit),
|
|
16
|
+
offset: Number(data.offset),
|
|
17
|
+
template: data.tpl,
|
|
18
|
+
sorting: data.sorting ? parseSorting(data.sorting) : undefined,
|
|
15
19
|
}));
|
|
16
20
|
export const PartialsQueryAT = type({
|
|
17
21
|
template: "string",
|
|
18
22
|
"children-of": "string",
|
|
19
|
-
|
|
23
|
+
offset: "string",
|
|
20
24
|
limit: "string",
|
|
21
|
-
"labeled-t?": "string|undefined",
|
|
22
25
|
"labeled-f?": "string|undefined",
|
|
26
|
+
"labeled-t?": "string|undefined",
|
|
27
|
+
"sorting?": "string|undefined",
|
|
23
28
|
"+": "reject",
|
|
24
29
|
}).pipe((data) => ({
|
|
25
30
|
template: data.template,
|
|
26
31
|
childrenOf: data["children-of"],
|
|
27
|
-
|
|
32
|
+
offset: Number(data.offset),
|
|
28
33
|
limit: Number(data.limit),
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
labeledWith: data["labeled-f"] && data["labeled-t"]
|
|
35
|
+
? {
|
|
36
|
+
fieldName: data["labeled-f"],
|
|
37
|
+
termNodeId: extractNodeId(data["labeled-t"]),
|
|
38
|
+
}
|
|
39
|
+
: undefined,
|
|
40
|
+
sorting: data.sorting ? parseSorting(data.sorting) : undefined,
|
|
31
41
|
}));
|
package/backend/dist/index.js
CHANGED
|
@@ -1,26 +1,118 @@
|
|
|
1
|
-
import { isDef, parseLNodeId } from "@paroicms/public-anywhere-lib";
|
|
2
|
-
import { escapeHtml, makeStylesheetLinkAsyncTag, } from "@paroicms/public-server-lib";
|
|
1
|
+
import { encodeLNodeId, isDef, parseLNodeId } from "@paroicms/public-anywhere-lib";
|
|
2
|
+
import { escapeHtml, extractDocumentsLoadDescriptor, makeStylesheetLinkAsyncTag, } from "@paroicms/public-server-lib";
|
|
3
3
|
import { esmDirName, extractPackageNameAndVersionSync } from "@paroicms/script-lib";
|
|
4
|
+
import { type } from "arktype";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
5
6
|
import { getPartials, makeSearch } from "./controller.js";
|
|
6
7
|
import { PartialsQueryAT, SearchTextOptionsUrlQueryAT } from "./formatters.js";
|
|
7
8
|
const projectDir = dirname(esmDirName(import.meta.url));
|
|
8
9
|
const packageDir = dirname(projectDir);
|
|
9
10
|
const { version } = extractPackageNameAndVersionSync(packageDir);
|
|
11
|
+
const SearchOpenerAT = type({
|
|
12
|
+
url: "string",
|
|
13
|
+
"iconColor?": "string|undefined",
|
|
14
|
+
"class?": "string|undefined",
|
|
15
|
+
"+": "reject",
|
|
16
|
+
});
|
|
17
|
+
const SearchAppAT = type({
|
|
18
|
+
template: "string",
|
|
19
|
+
by: "number|string",
|
|
20
|
+
"class?": "string|undefined",
|
|
21
|
+
"+": "reject",
|
|
22
|
+
}).pipe((v) => ({
|
|
23
|
+
template: v.template,
|
|
24
|
+
by: Number(v.by),
|
|
25
|
+
class: v.class,
|
|
26
|
+
}));
|
|
27
|
+
const InfiniteLoadingAT = type({
|
|
28
|
+
template: "string",
|
|
29
|
+
"paginatedDocs?": "object|undefined",
|
|
30
|
+
"by?": "number|string|undefined",
|
|
31
|
+
"class?": "string|undefined",
|
|
32
|
+
"+": "reject",
|
|
33
|
+
}).pipe((v) => ({
|
|
34
|
+
template: v.template,
|
|
35
|
+
paginatedDocs: v.paginatedDocs,
|
|
36
|
+
by: isDef(v.by) ? Number(v.by) : undefined,
|
|
37
|
+
class: v.class,
|
|
38
|
+
}));
|
|
10
39
|
const plugin = {
|
|
11
40
|
version,
|
|
12
41
|
async siteInit(service) {
|
|
13
42
|
service.setPublicAssetsDirectory(join(packageDir, "frontend", "dist"));
|
|
14
|
-
service.registerHeadTags(({
|
|
15
|
-
if (
|
|
16
|
-
html.includes(`data-effect="paSearchApp"`) ||
|
|
17
|
-
html.includes(`data-effect="paInfiniteLoading"`)) {
|
|
43
|
+
service.registerHeadTags(({ state }) => {
|
|
44
|
+
if (state.get("searchOpener") || state.get("searchApp") || state.get("infiniteLoading")) {
|
|
18
45
|
return [
|
|
19
46
|
makeStylesheetLinkAsyncTag(`${service.pluginAssetsUrl}/content-loading-plugin.css`),
|
|
20
47
|
`<script type="module" src="${escapeHtml(`${service.pluginAssetsUrl}/content-loading-plugin.mjs`)}" async></script>`,
|
|
21
48
|
];
|
|
22
49
|
}
|
|
23
50
|
});
|
|
51
|
+
service.registerOutLiquidTagFunction("searchOpener", (service, { namedParameters }) => {
|
|
52
|
+
service.setRenderState("searchOpener", true);
|
|
53
|
+
const { iconColor, url, class: className } = SearchOpenerAT.assert(namedParameters);
|
|
54
|
+
const attributes = [`data-effect="paSearchOpener"`, `data-search-url="${url}"`];
|
|
55
|
+
if (iconColor) {
|
|
56
|
+
attributes.push(`data-icon-color="${escapeHtml(iconColor)}"`);
|
|
57
|
+
}
|
|
58
|
+
if (className) {
|
|
59
|
+
attributes.push(`class="${escapeHtml(className)}"`);
|
|
60
|
+
}
|
|
61
|
+
return `<span ${attributes.join(" ")}></span>`;
|
|
62
|
+
});
|
|
63
|
+
service.registerOutLiquidTagFunction("searchApp", (service, { namedParameters }) => {
|
|
64
|
+
service.setRenderState("searchApp", true);
|
|
65
|
+
const { template, by, class: className } = SearchAppAT.assert(namedParameters);
|
|
66
|
+
if (!template.endsWith(".public") && !template.endsWith(".public.liquid")) {
|
|
67
|
+
throw new Error(`Invalid template name: "${template}" (should ends with .public)`);
|
|
68
|
+
}
|
|
69
|
+
const attributes = [
|
|
70
|
+
`data-effect="paSearchApp"`,
|
|
71
|
+
`data-template="${escapeHtml(template)}"`,
|
|
72
|
+
`data-limit="${by}"`,
|
|
73
|
+
];
|
|
74
|
+
if (className) {
|
|
75
|
+
attributes.push(`class="${escapeHtml(className)}"`);
|
|
76
|
+
}
|
|
77
|
+
return `<div ${attributes.join(" ")}></div>`;
|
|
78
|
+
});
|
|
79
|
+
service.registerOutLiquidTagFunction("infiniteLoading", async (service, { namedParameters }) => {
|
|
80
|
+
service.setRenderState("infiniteLoading", true);
|
|
81
|
+
const { template, paginatedDocs, by, class: className, } = InfiniteLoadingAT.assert(namedParameters);
|
|
82
|
+
if (!template.endsWith(".public") && !template.endsWith(".public.liquid")) {
|
|
83
|
+
throw new Error(`Invalid template name: "${template}" (should ends with .public)`);
|
|
84
|
+
}
|
|
85
|
+
if (!paginatedDocs)
|
|
86
|
+
return "";
|
|
87
|
+
const loadDescriptor = extractDocumentsLoadDescriptor(paginatedDocs);
|
|
88
|
+
if (!loadDescriptor || loadDescriptor.descriptorName !== "children") {
|
|
89
|
+
throw new Error("[infiniteLoading] Invalid paginatedDocs parameter");
|
|
90
|
+
}
|
|
91
|
+
const attributes = [
|
|
92
|
+
`data-effect="paInfiniteLoading"`,
|
|
93
|
+
`data-template="${escapeHtml(template)}"`,
|
|
94
|
+
`data-limit="${by ?? paginatedDocs.pageSize}"`,
|
|
95
|
+
`data-offset="${paginatedDocs.pageSize}"`,
|
|
96
|
+
`data-total="${paginatedDocs.total}"`,
|
|
97
|
+
`data-parent-document-id="${encodeLNodeId(loadDescriptor.parentDocumentId)}"`,
|
|
98
|
+
];
|
|
99
|
+
if (isDef(loadDescriptor.sorting)) {
|
|
100
|
+
attributes.push(`data-sorting="${escapeHtml(encodeSorting(loadDescriptor.sorting))}"`);
|
|
101
|
+
}
|
|
102
|
+
if (isDef(loadDescriptor.labeledWith)) {
|
|
103
|
+
attributes.push(`data-labeled-with="${escapeHtml(loadDescriptor.labeledWith.fieldName)}"`);
|
|
104
|
+
attributes.push(`data-term-node-id="${escapeHtml(loadDescriptor.labeledWith.termNodeId)}"`);
|
|
105
|
+
}
|
|
106
|
+
if (className) {
|
|
107
|
+
attributes.push(`class="${escapeHtml(className)}"`);
|
|
108
|
+
}
|
|
109
|
+
const rendered = [];
|
|
110
|
+
for (const item of paginatedDocs.items) {
|
|
111
|
+
const html = await service.renderDocument(template, item);
|
|
112
|
+
rendered.push(html);
|
|
113
|
+
}
|
|
114
|
+
return `<div ${attributes.join(" ")}>${rendered.join("\n")}</div>`;
|
|
115
|
+
});
|
|
24
116
|
service.setPublicApiHandler(async (service, httpContext, relativePath) => {
|
|
25
117
|
const { req, res } = httpContext;
|
|
26
118
|
if (req.method === "GET" && relativePath === "/search") {
|
|
@@ -35,19 +127,12 @@ const plugin = {
|
|
|
35
127
|
}
|
|
36
128
|
if (req.method === "GET" && relativePath === "/partials") {
|
|
37
129
|
const input = PartialsQueryAT.assert(req.query);
|
|
38
|
-
const { labeledById, fieldName } = input;
|
|
39
|
-
const termNodeId = isDef(labeledById) ? toNodeId(labeledById) : undefined;
|
|
40
130
|
const { language } = parseLNodeId(input.childrenOf);
|
|
41
131
|
const renderService = await service.openRenderingService({
|
|
42
132
|
language,
|
|
43
133
|
urlLike: relativePath,
|
|
44
134
|
});
|
|
45
|
-
await getPartials(renderService, httpContext, input
|
|
46
|
-
? {
|
|
47
|
-
fieldName,
|
|
48
|
-
termNodeId,
|
|
49
|
-
}
|
|
50
|
-
: undefined);
|
|
135
|
+
await getPartials(renderService, httpContext, input);
|
|
51
136
|
await renderService.close();
|
|
52
137
|
return;
|
|
53
138
|
}
|
|
@@ -57,7 +142,9 @@ const plugin = {
|
|
|
57
142
|
});
|
|
58
143
|
},
|
|
59
144
|
};
|
|
60
|
-
function toNodeId(nodeOrLNodeId) {
|
|
61
|
-
return nodeOrLNodeId.indexOf(":") !== -1 ? parseLNodeId(nodeOrLNodeId).nodeId : nodeOrLNodeId;
|
|
62
|
-
}
|
|
63
145
|
export default plugin;
|
|
146
|
+
function encodeSorting(sorting) {
|
|
147
|
+
if (sorting === "manual")
|
|
148
|
+
return "manual";
|
|
149
|
+
return sorting.map(({ fieldName, direction }) => `${fieldName} ${direction}`).join(",");
|
|
150
|
+
}
|