Skip to content

Commit

Permalink
Merge pull request #71 from rart/4.0.3
Browse files Browse the repository at this point in the history
## 4.0.3
  • Loading branch information
sumerjabri authored Mar 1, 2023
2 parents c703b6d + 3115ad7 commit c362220
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 34 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# SDK Changelog

## 4.0.3

### All packages
- Switching SDK versioning to follow the CrafterCMS release version

### @craftercms/content
- Update `parseProps` (internally used by `parseDescriptor`) to include `orderDefault_f` (parsed as `orderInNav`) in the resulting ContentInstance.
- Update `parseProps` and `parseDescriptor` options to receive `systemPropMap`, `ignoredProps`, `systemProps` to be used during parsing.
- Note: modifying these props will require you to _open_ the `ContentInstance` interface and extend it accordingly.
- Improve `parseDescriptor` signatures definitions
- Parse values of `orderInNav` and `disabled` to their target data types (float and boolean, respectively).
- Export `extractContent` and `extractChildren` functions.
- Export the default `systemPropMap`, `ignoredProps`, `systemProps`.

## 2.0.7

### @craftercms/content
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@craftercms/sdk",
"version": "2.0.7",
"version": "4.0.3",
"private": true,
"workspaces": [
"packages/*"
Expand Down
114 changes: 84 additions & 30 deletions packages/content/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
* along with this program. If not, see http://www.gnu.org/licenses/.
*/

import { ContentInstance, DescriptorResponse } from '@craftercms/models';
import { ContentInstance, Descriptor, DescriptorResponse, Item } from '@craftercms/models';
import { urlTransform } from './UrlTransformationService';
import { getDescriptor, GetDescriptorConfig } from './ContentStoreService';
import { map, switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs';

const systemPropMap = {
export const systemPropMap = {
guid: 'id',
cmsId: 'id',
objectId: 'id',
Expand All @@ -34,11 +34,11 @@ const systemPropMap = {
content__type: 'contentTypeId',
createdDate_dt: 'dateCreated',
lastModifiedDate_dt: 'dateModified',
disabled: 'disabled'
disabled: 'disabled',
orderDefault_f: 'orderInNav'
};

const ignoreProps = [
'orderDefault_f',
export const ignoredProps = [
'merge-strategy',
'display-template',
'objectGroupId',
Expand All @@ -48,18 +48,37 @@ const ignoreProps = [
'no-template-required'
];

const systemProps = Object.keys(systemPropMap).concat(
Object.values(systemPropMap)
);
export const systemProps = Object.keys(systemPropMap).concat(Object.values(systemPropMap));

export interface ParseDescriptorOptions {
function mixParseDescriptorOptions(options: ParseDescriptorOptions = {}): ParseDescriptorOptions {
return Object.assign({
systemPropMap,
ignoredProps,
systemProps,
parseFieldValueTypes: false
}, options);
}

export type ParseDescriptorOptions = Partial<{
/** Whether to parse the field values to their respective data type based on postfix (e.g. _i number, _b boolean, etc) */
parseFieldValueTypes: boolean;
}
/** Defines the parsed names that go inside the `.craftercms` property of the ContentInstance */
systemPropMap: Record<string, string>;
/** Defines the list of properties that aren't included in the parsed ContentInstance */
ignoredProps: string[];
/** List of all props considered "system" that will be routed to the `.craftercms` prop of the parsed ContentInstances */
systemProps: string[];
}>;

export function parseDescriptor(data: DescriptorResponse, options?: ParseDescriptorOptions): ContentInstance;
export function parseDescriptor(data: DescriptorResponse[], options?: ParseDescriptorOptions): ContentInstance[];
export function parseDescriptor(data: DescriptorResponse | DescriptorResponse[], options?: ParseDescriptorOptions): ContentInstance | ContentInstance[] {
export function parseDescriptor(data: DescriptorResponse): ContentInstance;
export function parseDescriptor(data: DescriptorResponse, options: ParseDescriptorOptions): ContentInstance;
export function parseDescriptor(data: DescriptorResponse[]): ContentInstance[];
export function parseDescriptor(data: DescriptorResponse[], options: ParseDescriptorOptions): ContentInstance[];
export function parseDescriptor(
data: DescriptorResponse | DescriptorResponse[],
options?: ParseDescriptorOptions
): ContentInstance | ContentInstance[] {
options = mixParseDescriptorOptions(options);
if (data == null) {
return null;
} else if (Array.isArray(data)) {
Expand All @@ -85,15 +104,26 @@ export function parseDescriptor(data: DescriptorResponse | DescriptorResponse[],
contentTypeId: null,
dateCreated: null,
dateModified: null,
disabled: false,
sourceMap: {}
}
};
return parseProps(extractContent(data), parsed, options);
}

export function parseProps<Props = object, Target = object>(props: Props, parsed: Target = {} as Target, options: ParseDescriptorOptions = { parseFieldValueTypes: false }): Target {
export function parseProps<Props = object, Target = object>(
props: Props,
parsed: Target = {} as Target,
options?: ParseDescriptorOptions
): Target {
options = mixParseDescriptorOptions(options);
let {
systemPropMap,
ignoredProps,
systemProps
} = options;
Object.entries(props).forEach(([prop, value]) => {
if (ignoreProps.includes(prop)) {
if (ignoredProps.includes(prop)) {
return; // continue, skip prop.
}
if (value?.['crafter-source-content-type-id']) {
Expand All @@ -107,8 +137,20 @@ export function parseProps<Props = object, Target = object>(props: Props, parsed
}
}
if (systemProps.includes(prop)) {
let propName = systemPropMap[prop] ?? prop;
let parsedValue = value;
switch (propName) {
case 'disabled':
parsedValue = value === 'true';
break;
case 'orderInNav':
parsedValue = parseFloat(value);
// Should never happen but just in case the value is not numeric, rollback to original string
if (isNaN(parsedValue)) parsedValue = value;
break;
}
// @ts-ignore
parsed.craftercms[systemPropMap[prop] ?? prop] = value;
parsed.craftercms[propName] = parsedValue;
// Is there a risk prop name that matches what's considered a system prop?
// In that case, here, parsed.craftercms might not be in the target object
// and throw. We could do the below to de-risk but feels this needs assessment.
Expand Down Expand Up @@ -153,8 +195,8 @@ export function parseProps<Props = object, Target = object>(props: Props, parsed
}

export function parseFieldValue(propName: string, propValue: any): number | string | boolean {
let postFix = propName.substr(propName.lastIndexOf('_'));
switch (postFix) {
let suffix = propName.substr(propName.lastIndexOf('_'));
switch (suffix) {
/*
_i For integer number.
_l For long integer number.
Expand All @@ -176,26 +218,34 @@ export function parseFieldValue(propName: string, propValue: any): number | stri
}

export function fetchModelByPath(path: string): Observable<ContentInstance>;
export function fetchModelByPath(path: string, options?: GetDescriptorConfig & ParseDescriptorOptions): Observable<ContentInstance>;
export function fetchModelByPath(path: string, options?: GetDescriptorConfig & ParseDescriptorOptions): Observable<ContentInstance> {
return getDescriptor(path, { flatten: options?.flatten ?? true }).pipe(
map((descriptor) => parseDescriptor(descriptor, { parseFieldValueTypes: options?.parseFieldValueTypes ?? true }))
export function fetchModelByPath(path: string, options: Partial<GetDescriptorConfig & ParseDescriptorOptions>): Observable<ContentInstance>;
export function fetchModelByPath(
path: string,
options?: Partial<GetDescriptorConfig & ParseDescriptorOptions>
): Observable<ContentInstance> {
let pdo = mixParseDescriptorOptions({ parseFieldValueTypes: true, ...options });
return getDescriptor(path, { flatten: true, ...options }).pipe(
map((descriptor) => parseDescriptor(descriptor, pdo))
);
}

export function fetchModelByUrl(webUrl: string): Observable<ContentInstance>;
export function fetchModelByUrl(webUrl: string, options?: GetDescriptorConfig & ParseDescriptorOptions): Observable<ContentInstance>;
export function fetchModelByUrl(webUrl: string, options?: GetDescriptorConfig & ParseDescriptorOptions): Observable<ContentInstance> {
export function fetchModelByUrl(webUrl: string, options: Partial<GetDescriptorConfig & ParseDescriptorOptions>): Observable<ContentInstance>;
export function fetchModelByUrl(
webUrl: string,
options?: Partial<GetDescriptorConfig & ParseDescriptorOptions>
): Observable<ContentInstance> {
let pdo = mixParseDescriptorOptions({ parseFieldValueTypes: true, ...options });
return urlTransform('renderUrlToStoreUrl', webUrl).pipe(
switchMap((path) => getDescriptor(path as string, { flatten: options?.flatten ?? true })),
map((descriptor) => parseDescriptor(descriptor, { parseFieldValueTypes: options?.parseFieldValueTypes ?? true }))
switchMap((path) => getDescriptor(path as string, { flatten: true, ...options })),
map((descriptor) => parseDescriptor(descriptor, pdo))
);
}

/**
* Inspects the data for getItem or getDescriptor responses and returns the inner content object
* */
function extractContent(data) {
*/
export function extractContent(data: Descriptor | Item) {
let output = data;
if (data.descriptorDom) {
return {
Expand All @@ -210,10 +260,14 @@ function extractContent(data) {
return output;
}

export interface ItemWithChildren extends Record<string, any> {
children?: Array<ItemWithChildren>;
}

/**
* Flattens a getChildren response into a flat list of content items
* */
function extractChildren(children) {
*/
export function extractChildren(children: Array<ItemWithChildren>) {
return children.flatMap((child) => {
return child.children ? extractChildren(child.children) : child;
});
Expand Down
4 changes: 3 additions & 1 deletion packages/models/src/ContentInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ export interface ContentInstance {
craftercms: {
id: string
path: string
label: string // Internal name
label: string // "Internal name"
dateCreated: string
dateModified: string
contentTypeId: string
orderInNav?: number // For pages only
disabled: boolean
sourceMap?: { [path: string]: string } // path: contentTypeId
}
[prop: string]: any
Expand Down
2 changes: 1 addition & 1 deletion packages/tsconfig-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"moduleResolution": "node",
"module": "es2015",
"target": "es2015",
"lib": ["es2015", "es2017", "dom"],
"lib": ["dom", "es2019"],
"skipLibCheck": true,
// don't auto-discover @types/node, it results in a ///<reference in the .d.ts output
"types": [],
Expand Down
2 changes: 1 addition & 1 deletion packages/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
},
"rootDir": ".",
"inlineSourceMap": true,
"lib": ["es5", "dom", "es2015", "es2017"],
"lib": ["dom", "es2019"],
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"target": "es5"
Expand Down

0 comments on commit c362220

Please sign in to comment.