@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, query) {
3
- const { q, language, limit, start, tpl } = query;
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.renderSearchPartials(httpContext, {
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
- start,
12
- templateName: tpl,
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, params, labeledBy) {
16
- const documentId = params.childrenOf;
17
- const parentId = parseLNodeId(documentId);
18
- const payload = {
19
- templateName: params.template,
20
- documentId,
21
- offset: params.start,
22
- limit: params.limit,
23
- };
24
- await service.renderChildPartials(httpContext, {
25
- params: payload,
26
- parentDocumentId: parentId,
27
- labeledBy,
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
- start: "string",
8
+ offset: "string",
7
9
  tpl: "string",
10
+ "sorting?": "string|undefined",
8
11
  "+": "reject",
9
- }).pipe((r) => ({
10
- language: r.language,
11
- q: r.q,
12
- limit: Number(r.limit),
13
- start: Number(r.start),
14
- tpl: r.tpl,
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
- start: "string",
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
- start: Number(data.start),
32
+ offset: Number(data.offset),
28
33
  limit: Number(data.limit),
29
- labeledById: data["labeled-t"] ?? undefined,
30
- fieldName: data["labeled-f"] ?? undefined,
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
  }));
@@ -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(({ html }) => {
15
- if (html.includes(`data-effect="paSearchOpener"`) ||
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, termNodeId !== undefined && fieldName
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
+ }