@plasmicpkgs/plasmic-sanity-io 1.0.154 → 1.0.155

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/dist/index.mjs ADDED
@@ -0,0 +1,496 @@
1
+ // src/index.tsx
2
+ import registerComponent from "@plasmicapp/host/registerComponent";
3
+ import registerGlobalContext from "@plasmicapp/host/registerGlobalContext";
4
+
5
+ // src/sanity.tsx
6
+ import {
7
+ DataProvider,
8
+ repeatedElement,
9
+ useSelector
10
+ } from "@plasmicapp/host";
11
+ import { usePlasmicQueryData } from "@plasmicapp/query";
12
+ import { createClient } from "@sanity/client";
13
+ import imageUrlBuilder from "@sanity/image-url";
14
+ import { pascalCase } from "change-case";
15
+ import get from "dlv";
16
+ import React, { useContext } from "react";
17
+
18
+ // src/utils.ts
19
+ var filterParameters = [
20
+ {
21
+ value: "==",
22
+ label: "Is"
23
+ },
24
+ {
25
+ value: "!=",
26
+ label: "Is not"
27
+ },
28
+ {
29
+ value: ">",
30
+ label: "Greater than"
31
+ },
32
+ {
33
+ value: "<",
34
+ label: "Less than"
35
+ },
36
+ {
37
+ value: "<=",
38
+ label: "Less than or equal"
39
+ },
40
+ {
41
+ value: ">=",
42
+ label: "Greater than or equal "
43
+ }
44
+ ];
45
+
46
+ // src/sanity.tsx
47
+ function ensure(x) {
48
+ if (x === null || x === void 0) {
49
+ throw new Error(`Value must not be undefined or null`);
50
+ } else {
51
+ return x;
52
+ }
53
+ }
54
+ var modulePath = "@plasmicpkgs/plasmic-sanity-io";
55
+ var makeDataProviderName = (docType) => `currentSanity${pascalCase(docType)}Item`;
56
+ function makeSanityClient(creds) {
57
+ const sanity = createClient({
58
+ projectId: creds.projectId,
59
+ dataset: creds.dataset,
60
+ apiVersion: creds.apiVersion ? creds.apiVersion : "v1",
61
+ token: creds.token,
62
+ useCdn: creds.useCdn
63
+ });
64
+ return sanity;
65
+ }
66
+ var CredentialsContext = React.createContext(void 0);
67
+ var sanityCredentialsProviderMeta = {
68
+ name: "SanityCredentialsProvider",
69
+ displayName: "Sanity Credentials Provider",
70
+ description: `Get your project ID, dataset, and token [here](https://www.sanity.io/manage).
71
+
72
+ Add 'https://host.plasmicdev.com' (or your app host origin) as an authorized host in the CORS origins section of your Sanity project.
73
+
74
+ [See tutorial video](https://www.youtube.com/watch?v=dLeu7I4RsYg).`,
75
+ importName: "SanityCredentialsProvider",
76
+ importPath: modulePath,
77
+ props: {
78
+ projectId: {
79
+ type: "string",
80
+ displayName: "Project ID",
81
+ defaultValueHint: "b2gfz67v",
82
+ defaultValue: "b2gfz67v",
83
+ description: "The ID of the project to use."
84
+ },
85
+ dataset: {
86
+ type: "string",
87
+ displayName: "Dataset",
88
+ defaultValueHint: "production",
89
+ defaultValue: "production",
90
+ description: "The dataset to use."
91
+ },
92
+ apiVersion: {
93
+ type: "string",
94
+ displayName: "API Version",
95
+ defaultValueHint: "v1",
96
+ description: "The API version to use (if not set, 'v1' will be used) - see https://www.sanity.io/docs/js-client#specifying-api-version."
97
+ },
98
+ token: {
99
+ type: "string",
100
+ displayName: "Token",
101
+ description: "The token to use (or leave blank for unauthenticated usage) - you can create tokens in the API section of your project (i.e. https://www.sanity.io/manage/personal/project/PROJECT_ID/api#tokens)."
102
+ },
103
+ useCdn: {
104
+ type: "boolean",
105
+ displayName: "Use CDN?",
106
+ defaultValueHint: false,
107
+ description: "Whether you want to use CDN ('false' if you want to ensure fresh data)."
108
+ }
109
+ }
110
+ };
111
+ function SanityCredentialsProvider({
112
+ projectId,
113
+ dataset,
114
+ apiVersion,
115
+ token,
116
+ useCdn,
117
+ children
118
+ }) {
119
+ return /* @__PURE__ */ React.createElement(
120
+ CredentialsContext.Provider,
121
+ {
122
+ value: { projectId, dataset, apiVersion, token, useCdn }
123
+ },
124
+ children
125
+ );
126
+ }
127
+ var sanityFetcherMeta = {
128
+ name: "SanityFetcher",
129
+ displayName: "Sanity Fetcher",
130
+ importName: "SanityFetcher",
131
+ importPath: modulePath,
132
+ providesData: true,
133
+ description: `Fetches Sanity data of a given collection, and repeats \`children\` slot content for each row fetched.
134
+
135
+ [See tutorial video](https://www.youtube.com/watch?v=1SLoVY3hkQ4) and [GROQ cheat sheet](https://www.sanity.io/docs/query-cheat-sheet).`,
136
+ defaultStyles: {
137
+ display: "grid",
138
+ gridTemplateColumns: "1fr 1fr 1fr 1fr",
139
+ gridRowGap: "8px",
140
+ gridColumnGap: "8px",
141
+ padding: "8px",
142
+ maxWidth: "100%"
143
+ },
144
+ props: {
145
+ children: {
146
+ type: "slot",
147
+ defaultValue: {
148
+ type: "vbox",
149
+ styles: {
150
+ padding: "8px"
151
+ },
152
+ children: {
153
+ type: "component",
154
+ name: "SanityField"
155
+ }
156
+ }
157
+ },
158
+ groq: {
159
+ type: "string",
160
+ displayName: "GROQ",
161
+ description: "Query in GROQ.",
162
+ defaultValueHint: "*[_type == 'movie']",
163
+ // Hide this if there's no groq, AND there's docType, so we're in
164
+ // "docType" mode
165
+ hidden: (props) => !props.groq && !!props.docType
166
+ },
167
+ docType: {
168
+ type: "choice",
169
+ options: (props, ctx) => {
170
+ var _a;
171
+ return (_a = ctx == null ? void 0 : ctx.docTypes) != null ? _a : [];
172
+ },
173
+ displayName: "Document type",
174
+ description: "Document type to be queried (*[_type == DOC_TYPE] shortcut).",
175
+ // Hide this if groq is specified, as groq always takes precedence
176
+ hidden: (props) => !!props.groq
177
+ },
178
+ filterField: {
179
+ type: "choice",
180
+ displayName: "Filter field",
181
+ description: "Field (from Collection) to filter by",
182
+ options: (props, ctx) => {
183
+ var _a;
184
+ return (_a = ctx == null ? void 0 : ctx.sanityFields) != null ? _a : [];
185
+ },
186
+ // Hide this if there's groq (so we're just using groq), or if there's
187
+ // no docType selected yet
188
+ hidden: (props, ctx) => !!props.groq || !props.docType
189
+ },
190
+ filterParameter: {
191
+ type: "choice",
192
+ displayName: "Filter Operation",
193
+ description: "Filter Option to filter by. Read more (https://www.sanity.io/docs/groq-operators#3b7211e976f6)",
194
+ options: (props, ctx) => {
195
+ var _a;
196
+ return (_a = ctx == null ? void 0 : ctx.queryOptions) != null ? _a : [];
197
+ },
198
+ // Hide if in groq mode, or if no filter field is selected yet
199
+ hidden: (props, ctx) => !!props.groq || !props.filterField
200
+ },
201
+ filterValue: {
202
+ type: "string",
203
+ displayName: "Filter value",
204
+ description: "Value to filter by, should be of filter field type",
205
+ // Hide if in groq mode, or if no filter field is selected yet
206
+ hidden: (props, ctx) => !!props.groq || !props.filterField
207
+ },
208
+ limit: {
209
+ type: "string",
210
+ displayName: "Limit",
211
+ description: "Limit",
212
+ // Hide if in groq mode
213
+ hidden: (props) => !!props.groq || !props.docType
214
+ },
215
+ noAutoRepeat: {
216
+ type: "boolean",
217
+ displayName: "No auto-repeat",
218
+ description: "Do not automatically repeat children for every category.",
219
+ defaultValue: false
220
+ },
221
+ noLayout: {
222
+ type: "boolean",
223
+ displayName: "No layout",
224
+ description: "When set, Sanity Fetcher will not layout its children; instead, the layout set on its parent element will be used. Useful if you want to set flex gap or control container tag type.",
225
+ defaultValue: false
226
+ }
227
+ }
228
+ };
229
+ function SanityFetcher({
230
+ groq,
231
+ docType,
232
+ filterField,
233
+ filterValue,
234
+ filterParameter,
235
+ limit,
236
+ noAutoRepeat,
237
+ children,
238
+ className,
239
+ noLayout,
240
+ setControlContextData
241
+ }) {
242
+ var _a;
243
+ const projectIdRegex = new RegExp(/^[-a-z0-9]+$/i);
244
+ const datasetRegex = new RegExp(
245
+ /^(~[a-z0-9]{1}[-\w]{0,63}|[a-z0-9]{1}[-\w]{0,63})$/
246
+ );
247
+ const dateRegex = new RegExp(/^\d{4}-\d{2}-\d{2}$/);
248
+ const creds = ensure(useContext(CredentialsContext));
249
+ if (!creds.projectId || !projectIdRegex.test(creds.projectId)) {
250
+ return /* @__PURE__ */ React.createElement("div", { className }, "Please specify a valid projectId, it can only contain only a-z, 0-9 and dashes.");
251
+ } else if (!creds.dataset || !datasetRegex.test(creds.dataset)) {
252
+ return /* @__PURE__ */ React.createElement("div", { className }, "Please specify a valid dataset, they can only contain lowercase characters, numbers, underscores and dashes, and start with tilde, and be maximum 64 characters.");
253
+ } else if (creds.apiVersion) {
254
+ if (creds.apiVersion !== "v1" && creds.apiVersion !== "1" && creds.apiVersion !== "X") {
255
+ const date = new Date(creds.apiVersion);
256
+ if (!(dateRegex.test(creds.apiVersion) && date instanceof Date && date.getTime() > 0)) {
257
+ return /* @__PURE__ */ React.createElement("div", { className }, "Please specify a valid API version, expected `v1`, `1` or date in format `YYYY-MM-DD`.");
258
+ }
259
+ }
260
+ }
261
+ const filterUniqueDocTypes = (records) => records.map((record) => record._type).reduce((acc, type) => {
262
+ if (!acc.includes(type)) {
263
+ acc.push(type);
264
+ }
265
+ return acc;
266
+ }, []);
267
+ const allDataTypes = usePlasmicQueryData(
268
+ JSON.stringify(creds) + "/SANITY_DOCTYPES",
269
+ async () => {
270
+ const sanity2 = makeSanityClient(creds);
271
+ const resp = await sanity2.fetch("*{_type}").then(filterUniqueDocTypes);
272
+ return resp;
273
+ }
274
+ );
275
+ const docTypes = (_a = allDataTypes.data) != null ? _a : false;
276
+ const hasFilter = !!docType && !!filterField && !!filterParameter && !!filterValue;
277
+ const generateUnfilteredGroq = () => {
278
+ if (groq) {
279
+ console.log("ORIG GROQ", groq);
280
+ return groq;
281
+ } else if (docType) {
282
+ let query = `*[_type=='${docType}']`;
283
+ if (hasFilter) {
284
+ query += `[0...10]`;
285
+ } else if (limit) {
286
+ query += `[0...${limit}]`;
287
+ }
288
+ console.log("UNFILTERED GROQ", query);
289
+ return query;
290
+ } else {
291
+ return null;
292
+ }
293
+ };
294
+ const unfilteredQuery = generateUnfilteredGroq();
295
+ const sanity = makeSanityClient(creds);
296
+ const { data: unfilteredData } = usePlasmicQueryData(
297
+ unfilteredQuery ? JSON.stringify({ fullQuery: unfilteredQuery, creds }) : null,
298
+ async () => {
299
+ return sanity.fetch(unfilteredQuery);
300
+ }
301
+ );
302
+ const generateFilteredQuery = () => {
303
+ if (!hasFilter || !unfilteredData) {
304
+ return null;
305
+ }
306
+ const fieldValues = Object.values(unfilteredData).flatMap((model) => Array.isArray(model) ? model : [model]).map((item) => {
307
+ const field = Object.entries(item).find((el) => el[0] === filterField);
308
+ return field == null ? void 0 : field[1];
309
+ });
310
+ let query = `*[_type=='${docType}'`;
311
+ if (fieldValues.some((v) => typeof v === "string")) {
312
+ query = `${query} && ${filterField} ${filterParameter} "${filterValue}"`;
313
+ } else {
314
+ query = `${query} && ${filterField} ${filterParameter} ${filterValue}`;
315
+ }
316
+ if (limit) {
317
+ query = `${query}][0...${limit}]`;
318
+ } else {
319
+ query = `${query}]`;
320
+ }
321
+ console.log("FILTERED GROQ", query);
322
+ return query;
323
+ };
324
+ const filteredQuery = generateFilteredQuery();
325
+ const { data: filteredData } = usePlasmicQueryData(
326
+ filteredQuery ? JSON.stringify({ filteredQuery, creds }) : null,
327
+ async () => {
328
+ const resp = await sanity.fetch(filteredQuery);
329
+ return resp;
330
+ }
331
+ );
332
+ if (!docTypes) {
333
+ return /* @__PURE__ */ React.createElement("div", { className }, "Please configure the Sanity provider with a valid projectId, dataset, and token (if necessary). Don't forget to add 'https://host.plasmicdev.com' as an authorized host on the CORS origins section of your project.");
334
+ }
335
+ setControlContextData == null ? void 0 : setControlContextData({
336
+ docTypes
337
+ });
338
+ if (!groq && !docType) {
339
+ return /* @__PURE__ */ React.createElement("div", { className }, "Please specify a valid GROQ query or select a Document type.");
340
+ }
341
+ if (!unfilteredData) {
342
+ return /* @__PURE__ */ React.createElement("div", { className }, "Loading...");
343
+ }
344
+ let sanityFields = unfilteredData.map((item) => {
345
+ const fields = Object.keys(item).filter((field) => {
346
+ const value = get(item, field);
347
+ return typeof value !== "object" && value._type !== "image" && typeof value === "number" || typeof value === "string" && !value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
348
+ });
349
+ return fields;
350
+ });
351
+ let operators;
352
+ const matchedFields = Object.values(unfilteredData).flatMap((model) => Array.isArray(model) ? model : [model]).map((item) => {
353
+ const fields = Object.entries(item).find((el) => el[0] === filterField);
354
+ return fields;
355
+ });
356
+ Object.values(matchedFields).map((model) => Array.isArray(model) ? model : [model]).map((item) => {
357
+ if (typeof item[1] === "number" && typeof item[1] !== "object") {
358
+ operators = filterParameters;
359
+ } else if (typeof item[1] !== "number" && typeof item[1] !== "object" && typeof item[1] === "string") {
360
+ operators = [
361
+ {
362
+ value: "==",
363
+ label: "Equals"
364
+ },
365
+ {
366
+ value: "!=",
367
+ label: "Not equals"
368
+ }
369
+ ];
370
+ }
371
+ });
372
+ setControlContextData == null ? void 0 : setControlContextData({
373
+ queryOptions: operators,
374
+ docTypes,
375
+ sanityFields: sanityFields[0]
376
+ });
377
+ if (hasFilter) {
378
+ if (!filterParameter) {
379
+ return /* @__PURE__ */ React.createElement("div", { className }, "Please specify a filter operation");
380
+ }
381
+ if (!filterValue) {
382
+ return /* @__PURE__ */ React.createElement("div", { className }, "Please specify a filter value");
383
+ }
384
+ }
385
+ if (hasFilter && !filteredData) {
386
+ return /* @__PURE__ */ React.createElement("div", { className }, "Loading...");
387
+ }
388
+ const resultData = hasFilter ? filteredData : unfilteredData;
389
+ const imageBuilder = imageUrlBuilder(sanity);
390
+ const repElements = noAutoRepeat ? children : resultData.map((item, index) => {
391
+ Object.keys(item).forEach((field) => {
392
+ if (item[field]._type === "image") {
393
+ item[field].imgUrl = imageBuilder.image(item[field]).ignoreImageParams().toString();
394
+ }
395
+ });
396
+ return docType ? /* @__PURE__ */ React.createElement(
397
+ DataProvider,
398
+ {
399
+ key: item._id,
400
+ name: "sanityItem",
401
+ data: item,
402
+ hidden: true
403
+ },
404
+ /* @__PURE__ */ React.createElement(DataProvider, { name: makeDataProviderName(docType), data: item }, repeatedElement(index, children))
405
+ ) : /* @__PURE__ */ React.createElement(DataProvider, { key: item._id, name: "sanityItem", data: item }, repeatedElement(index, children));
406
+ });
407
+ return /* @__PURE__ */ React.createElement(DataProvider, { name: "sanityItems", data: resultData }, noLayout ? /* @__PURE__ */ React.createElement(React.Fragment, null, " ", repElements, " ") : /* @__PURE__ */ React.createElement("div", { className }, " ", repElements, " "));
408
+ }
409
+ var sanityFieldMeta = {
410
+ name: "SanityField",
411
+ displayName: "Sanity Field",
412
+ importName: "SanityField",
413
+ importPath: modulePath,
414
+ props: {
415
+ path: {
416
+ type: "string",
417
+ displayName: "Path",
418
+ description: "Field path - see https://www.sanity.io/docs/ids.",
419
+ defaultValueHint: "castMembers.0._key"
420
+ },
421
+ field: {
422
+ type: "choice",
423
+ options: (props, ctx) => {
424
+ var _a;
425
+ return (_a = ctx == null ? void 0 : ctx.fields) != null ? _a : [];
426
+ },
427
+ displayName: "Field",
428
+ description: "Field to be displayed."
429
+ }
430
+ }
431
+ };
432
+ function SanityField({
433
+ className,
434
+ path,
435
+ field,
436
+ setControlContextData
437
+ }) {
438
+ const item = useSelector("sanityItem");
439
+ if (!item) {
440
+ return /* @__PURE__ */ React.createElement("div", null, "SanityField must be used within a SanityFetcher");
441
+ }
442
+ const displayableFields = Object.keys(item).filter((field2) => {
443
+ const value = get(item, field2);
444
+ return typeof value !== "object" || value._type === "image";
445
+ });
446
+ setControlContextData == null ? void 0 : setControlContextData({
447
+ fields: displayableFields,
448
+ isImage: false
449
+ });
450
+ if (!path && !field) {
451
+ return /* @__PURE__ */ React.createElement("div", null, "Please specify a valid path or select a field.");
452
+ }
453
+ if (!path) {
454
+ path = field;
455
+ }
456
+ const data = get(item, path);
457
+ setControlContextData == null ? void 0 : setControlContextData({
458
+ fields: displayableFields,
459
+ isImage: (data == null ? void 0 : data._type) == "image"
460
+ });
461
+ if (!data) {
462
+ return /* @__PURE__ */ React.createElement("div", null, "Please specify a valid path.");
463
+ } else if ((data == null ? void 0 : data._type) === "image") {
464
+ return /* @__PURE__ */ React.createElement("img", { className, src: data.imgUrl });
465
+ } else {
466
+ return /* @__PURE__ */ React.createElement("div", { className }, data);
467
+ }
468
+ }
469
+
470
+ // src/index.tsx
471
+ function registerAll(loader) {
472
+ const _registerComponent = (Component, defaultMeta) => {
473
+ if (loader) {
474
+ loader.registerComponent(Component, defaultMeta);
475
+ } else {
476
+ registerComponent(Component, defaultMeta);
477
+ }
478
+ };
479
+ if (loader) {
480
+ loader.registerGlobalContext(SanityCredentialsProvider, sanityCredentialsProviderMeta);
481
+ } else {
482
+ registerGlobalContext(SanityCredentialsProvider, sanityCredentialsProviderMeta);
483
+ }
484
+ _registerComponent(SanityFetcher, sanityFetcherMeta);
485
+ _registerComponent(SanityField, sanityFieldMeta);
486
+ }
487
+ export {
488
+ SanityCredentialsProvider,
489
+ SanityFetcher,
490
+ SanityField,
491
+ ensure,
492
+ registerAll,
493
+ sanityCredentialsProviderMeta,
494
+ sanityFetcherMeta,
495
+ sanityFieldMeta
496
+ };
package/package.json CHANGED
@@ -1,28 +1,26 @@
1
1
  {
2
2
  "name": "@plasmicpkgs/plasmic-sanity-io",
3
- "version": "1.0.154",
3
+ "version": "1.0.155",
4
4
  "description": "Plasmic Sanity.io components.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
- "module": "dist/plasmic-sanity-io.esm.js",
7
+ "module": "dist/index.mjs",
8
8
  "files": [
9
9
  "dist"
10
10
  ],
11
11
  "size-limit": [
12
12
  {
13
- "path": "dist/plasmic-sanity-io.cjs.production.min.js",
13
+ "path": "dist/index.js",
14
14
  "limit": "10 KB"
15
15
  },
16
16
  {
17
- "path": "dist/plasmic-sanity-io.esm.js",
17
+ "path": "dist/index.mjs",
18
18
  "limit": "10 KB"
19
19
  }
20
20
  ],
21
21
  "scripts": {
22
- "build": "tsdx build",
23
- "start": "tsdx watch",
22
+ "build": "tsup-node src/index.tsx --dts --format esm,cjs --target es2019",
24
23
  "test": "yarn --cwd=../.. test --passWithNoTests",
25
- "lint": "tsdx lint",
26
24
  "prepare": "if-env PREPARE_NO_BUILD=true || yarn build",
27
25
  "size": "size-limit",
28
26
  "analyze": "size-limit --why"
@@ -35,8 +33,9 @@
35
33
  "@types/node": "^17.0.14",
36
34
  "@types/react": "^18.2.33",
37
35
  "size-limit": "^7.0.8",
38
- "tsdx": "^0.14.1",
39
- "tslib": "^2.3.1"
36
+ "tslib": "^2.3.1",
37
+ "tsup": "^7.2.0",
38
+ "typescript": "^5.2.2"
40
39
  },
41
40
  "dependencies": {
42
41
  "@sanity/client": "^6.2.0",
@@ -48,5 +47,5 @@
48
47
  "react": ">=16.8.0",
49
48
  "react-dom": ">=16.8.0"
50
49
  },
51
- "gitHead": "6db50a6b3e9ffd747e4a6521b42fcc0a8f32e97c"
50
+ "gitHead": "d387b9e838764794b8513b5c19288f37c7981dc4"
52
51
  }