scrivito
Version:
Scrivito is a professional, yet easy to use SaaS Enterprise Content Management Service, built for digital agencies and medium to large businesses. It is completely maintenance-free, cost-effective, and has unprecedented performance and security.
351 lines (286 loc) • 9.5 kB
text/typescript
import { BasicField } from 'scrivito_sdk/models';
import {
AttributeTypeWithMandatoryConfig,
BasicTypeInfo,
NormalizedTypeInfo,
} from 'scrivito_sdk/models/type_info';
import {
AppClass,
CmsAttributeType,
Obj,
ObjClass,
Widget,
WidgetClass,
} from 'scrivito_sdk/realm';
export interface ObjClassDefinition {
attributes?: AttributeDefinitions;
extend?: ObjClass;
extractTextAttributes?: readonly string[];
name?: string;
onlyAsRoot?: boolean;
onlyChildren?: readonly string[] | string;
onlyInside?: readonly string[] | string;
validAsRoot?: boolean;
}
interface BaseObjClassDefinition {
extractTextAttributes?: readonly string[];
name?: string;
onlyAsRoot?: boolean;
onlyChildren?: readonly string[] | string;
onlyInside?: readonly string[] | string;
validAsRoot?: boolean;
}
export interface SimpleObjClassDefinition<Attrs extends AttributeDefinitions>
extends BaseObjClassDefinition {
attributes?: Attrs;
}
export interface ExtendObjClassDefinition<
ExtendAttrs extends AttributeDefinitions
> extends BaseObjClassDefinition {
extend?: ObjClass<ExtendAttrs>;
}
export interface MixedObjClassDefinition<
Attrs extends AttributeDefinitions,
ExtendAttrs extends AttributeDefinitions
> extends BaseObjClassDefinition {
attributes?: Attrs;
extend?: ObjClass<ExtendAttrs>;
}
export interface WidgetClassDefinition {
attributes?: AttributeDefinitions;
extend?: WidgetClass;
extractTextAttributes?: readonly string[];
name?: string;
onlyChildren?: undefined;
onlyInside?: readonly string[] | string;
}
interface BaseWidgetClassDefinition {
extractTextAttributes?: readonly string[];
name?: string;
onlyChildren?: undefined;
onlyInside?: readonly string[] | string;
}
export interface SimpleWidgetClassDefinition<Attrs extends AttributeDefinitions>
extends BaseWidgetClassDefinition {
attributes?: Attrs;
}
export interface ExtendWidgetClassDefinition<
ExtendAttrs extends AttributeDefinitions
> extends BaseWidgetClassDefinition {
extend?: WidgetClass<ExtendAttrs>;
}
export interface MixedWidgetClassDefinition<
Attrs extends AttributeDefinitions,
ExtendAttrs extends AttributeDefinitions
> extends BaseWidgetClassDefinition {
attributes?: Attrs;
extend?: WidgetClass<ExtendAttrs>;
}
export interface AttributeDefinitions {
[attributeName: string]: AttributeDefinition;
}
type AttributeDefinition =
| AttributeDefinitionWithoutConfig
| AttributeDefinitionWithConfig;
type AttributeDefinitionWithoutConfig =
| AttributeTypeWithoutConfig
| AttributeTypeWithOmittedConfig;
type AttributeDefinitionWithConfig = {
[Type in keyof AttributeTypeToConfigMapping]: readonly [
Type,
AttributeTypeToConfigMapping[Type]
];
}[keyof AttributeTypeToConfigMapping];
type AttributeTypeWithoutConfig = Exclude<
CmsAttributeType,
keyof AttributeTypeToConfigMapping
>;
type AttributeTypeWithOmittedConfig = Exclude<
CmsAttributeType,
AttributeTypeWithMandatoryConfig | AttributeTypeWithoutConfig
>;
type AttributeTypeToConfigMapping = {
enum: { values: readonly string[] };
multienum: { values: readonly string[] };
reference: { only: string | readonly string[] };
referencelist: { only: string | readonly string[] };
widget: { only: string | readonly string[] };
widgetlist:
| { only: string | readonly string[]; maximum?: number }
| { only?: string | readonly string[]; maximum: number };
};
export interface NormalizedAttributeDefinitions {
[attributeName: string]: NormalizedAttributeDefinition;
}
export type NormalizedAttributeDefinition =
NormalizedTypeInfo<CmsAttributeType>;
export interface BasicAttributeDefinitions {
[attributeName: string]: BasicTypeInfo<CmsAttributeType>;
}
interface BasicObjClassDefinition {
attributes: BasicAttributeDefinitions;
extend?: ObjClass;
extractTextAttributes?: readonly string[];
name?: string;
onlyAsRoot?: boolean;
onlyChildren?: readonly string[];
onlyInside?: readonly string[];
validAsRoot?: boolean;
}
interface BasicWidgetClassDefinition {
attributes: BasicAttributeDefinitions;
extend?: WidgetClass;
extractTextAttributes?: readonly string[];
name?: string;
onlyAsRoot?: boolean;
onlyChildren?: undefined;
onlyInside?: readonly string[];
validAsRoot?: boolean;
}
export type AttributeTypeOf<K extends AttributeDefinition> = K extends
| AttributeTypeWithoutConfig
| AttributeTypeWithOmittedConfig
? K
: K[0];
export class Schema {
static forInstance(model: Obj | Widget): Schema | undefined {
return Schema.forClass(model.constructor as ObjClass | WidgetClass);
}
static forClass(klass: ObjClass | WidgetClass): Schema | undefined {
return klass._scrivitoPrivateSchema;
}
static basicFieldFor<T extends CmsAttributeType>(
model: Obj | Widget,
attributeName: string
): BasicField<T> | null {
const schema = Schema.forInstance(model);
if (!schema) return null;
const typeInfo = schema.attribute(attributeName);
if (!typeInfo) return null;
return new BasicField<T>(
model._scrivitoPrivateContent,
attributeName,
typeInfo as BasicTypeInfo<T>
);
}
private readonly basicClassDefinition:
| BasicObjClassDefinition
| BasicWidgetClassDefinition;
constructor(classDefinition: ObjClassDefinition, parent: ObjClass);
constructor(classDefinition: WidgetClassDefinition, parent: WidgetClass);
constructor(
classDefinition: ObjClassDefinition | WidgetClassDefinition,
readonly parentClass: ObjClass | WidgetClass
) {
const parentSchema = this.parentClass._scrivitoPrivateSchema;
const basicAttributeDefinitions: BasicAttributeDefinitions = parentSchema
? { ...parentSchema.attributes() }
: {};
const {
attributes: rawAttributeDefinitions,
onlyInside: rawOnlyInside,
onlyChildren: rawOnlyChildren,
...restOfRawDefinition
} = classDefinition;
if (rawAttributeDefinitions) {
Object.keys(rawAttributeDefinitions).forEach((name) => {
basicAttributeDefinitions[name] = toBasicAttributeDefinition(
rawAttributeDefinitions[name]
);
});
}
const onlyChildren = toArrayOfStrings(rawOnlyChildren);
const onlyInside = toArrayOfStrings(rawOnlyInside);
if (onlyChildren) {
this.basicClassDefinition = {
...(restOfRawDefinition as ObjClassDefinition),
attributes: basicAttributeDefinitions,
onlyChildren,
onlyInside,
};
} else {
this.basicClassDefinition = {
...restOfRawDefinition,
attributes: basicAttributeDefinitions,
onlyInside,
};
}
}
attributes(): BasicAttributeDefinitions {
return this.basicClassDefinition.attributes;
}
normalizedAttributes(): NormalizedAttributeDefinitions {
const attributes = this.attributes();
const normalizedAttributes: NormalizedAttributeDefinitions = {};
Object.keys(attributes).forEach((name) => {
normalizedAttributes[name] = toNormalizedAttributeDefinition(
attributes[name]
);
});
return normalizedAttributes;
}
extractTextAttributes(): readonly string[] {
return this.basicClassDefinition.extractTextAttributes || [];
}
name(): string | undefined {
return this.basicClassDefinition.name;
}
onlyInside(): readonly string[] | undefined {
return this.basicClassDefinition.onlyInside;
}
onlyChildren(): readonly string[] | undefined {
return this.basicClassDefinition.onlyChildren;
}
validAsRoot(): boolean | undefined {
return this.basicClassDefinition.validAsRoot;
}
onlyAsRoot(): boolean | undefined {
return this.basicClassDefinition.onlyAsRoot;
}
attribute(name: string): BasicTypeInfo<CmsAttributeType> | undefined {
return this.attributes()[name];
}
isBinary() {
const blobDefinition = this.attribute('blob');
if (!blobDefinition) return false;
return blobDefinition[0] === 'binary';
}
parent(): WidgetClass | ObjClass {
return this.parentClass;
}
}
export function isAppClass(object: object): object is AppClass {
return !!(object && (object as AppClass)._scrivitoPrivateSchema);
}
function toBasicAttributeDefinition(
attrDefinition: AttributeDefinition
): BasicTypeInfo<CmsAttributeType> {
if (typeof attrDefinition === 'string') return [attrDefinition];
const type = attrDefinition[0];
if (type === 'enum' || type === 'multienum') return [type, attrDefinition[1]];
const { only, ...otherOptions } = attrDefinition[1];
const validClasses = typeof only === 'string' ? [only] : only;
if (type === 'widgetlist' && !validClasses && attrDefinition[1].maximum) {
return ['widgetlist', { maximum: attrDefinition[1].maximum }];
}
return validClasses ? [type, { ...otherOptions, validClasses }] : [type];
}
function toNormalizedAttributeDefinition(
definition: BasicTypeInfo<CmsAttributeType>
): NormalizedTypeInfo<CmsAttributeType> {
if (definition.length === 1) return [definition[0], {}];
switch (definition[0]) {
case 'reference':
case 'referencelist':
case 'widget':
case 'widgetlist': {
const { validClasses: only, ...otherConfig } = definition[1];
return [definition[0], only ? { only, ...otherConfig } : otherConfig];
}
}
return definition;
}
function toArrayOfStrings(value: string | readonly string[] | undefined) {
if (typeof value === 'string') return [value];
if (value?.length) return value;
}