@dosgato/templating 0.0.1 → 0.0.2
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/component.d.ts +119 -0
- package/dist/component.js +115 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +17 -0
- package/{src/links.ts → dist/links.d.ts} +25 -31
- package/dist/links.js +2 -0
- package/dist/page.d.ts +13 -0
- package/dist/page.js +15 -0
- package/dist/provider.d.ts +79 -0
- package/dist/provider.js +72 -0
- package/dist/template.d.ts +78 -0
- package/dist/template.js +2 -0
- package/package.json +5 -2
- package/.eslintrc.json +0 -15
- package/src/component.ts +0 -167
- package/src/index.ts +0 -5
- package/src/page.ts +0 -23
- package/src/provider.ts +0 -72
- package/src/template.ts +0 -88
- package/tsconfig.eslint.json +0 -7
- package/tsconfig.json +0 -11
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Page } from './page';
|
|
2
|
+
import { ResourceProvider } from './provider';
|
|
3
|
+
/**
|
|
4
|
+
* This is the primary templating class to build your templates. Subclass it and provide
|
|
5
|
+
* at least a render function.
|
|
6
|
+
*
|
|
7
|
+
* During rendering, it will be "hydrated" - placed into a full page structure with its
|
|
8
|
+
* parent and child components linked.
|
|
9
|
+
*/
|
|
10
|
+
export declare abstract class Component<DataType extends ComponentData = any, FetchedType = any, RenderContextType extends ContextBase = any> extends ResourceProvider {
|
|
11
|
+
static templateKey: string;
|
|
12
|
+
static templateName: string;
|
|
13
|
+
areas: Map<string, Component<any, any, any>[]>;
|
|
14
|
+
data: Omit<DataType, 'areas'>;
|
|
15
|
+
fetched: FetchedType;
|
|
16
|
+
renderCtx: RenderContextType;
|
|
17
|
+
path: string;
|
|
18
|
+
parent?: Component;
|
|
19
|
+
page?: Page;
|
|
20
|
+
hadError: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The first phase of rendering a component is the fetch phase. Each component may
|
|
23
|
+
* provide a fetch method that looks up data it needs from external sources. This step
|
|
24
|
+
* is FLAT - it will be executed concurrently for all the components on the page for
|
|
25
|
+
* maximum speed.
|
|
26
|
+
*
|
|
27
|
+
* Note that this.page will be available, along with its ancestors property containing
|
|
28
|
+
* all the data from ancestor pages, in case there is a need for inheritance. It is
|
|
29
|
+
* recommended to copy any needed data into the return object, as future phases will not
|
|
30
|
+
* want to resolve the inheritance again.
|
|
31
|
+
*/
|
|
32
|
+
fetch(editMode: boolean): Promise<FetchedType>;
|
|
33
|
+
/**
|
|
34
|
+
* The second phase of rendering a component is the context phase. This step is TOP-DOWN,
|
|
35
|
+
* each component will receive the parent component's context, modify it as desired,
|
|
36
|
+
* and then pass context to its children.
|
|
37
|
+
*
|
|
38
|
+
* This is useful for rendering logic that is sensitive to where the component exists in
|
|
39
|
+
* the hierarchy of the page. For instance, if a parent component has used an h2 header
|
|
40
|
+
* already, it will want to inform its children so that they can use h3 next, and they inform
|
|
41
|
+
* their children that h4 is next, and so on. (Header level tracking is actually required in
|
|
42
|
+
* dosgato CMS.)
|
|
43
|
+
*
|
|
44
|
+
* This function may return a promise in case you need to do something asynchronous based on
|
|
45
|
+
* the context received from the parent, but use it sparingly since it will stall the process.
|
|
46
|
+
* Try to do all asynchronous work in the fetch phase.
|
|
47
|
+
*/
|
|
48
|
+
setContext(renderCtxFromParent: RenderContextType, editMode: boolean): RenderContextType | Promise<RenderContextType>;
|
|
49
|
+
/**
|
|
50
|
+
* The final phase of rendering a component is the render phase. This step is BOTTOM-UP -
|
|
51
|
+
* components at the bottom of the hierarchy will be rendered first, and the result of the
|
|
52
|
+
* render will be passed to parent components so that the HTML can be included during the
|
|
53
|
+
* render of the parent component.
|
|
54
|
+
*/
|
|
55
|
+
abstract render(renderedAreas: Map<string, string[]>, editMode: boolean): string;
|
|
56
|
+
/**
|
|
57
|
+
* Sometimes pages are requested with an alternate extension like .rss or .ics. In these
|
|
58
|
+
* situations, each component should consider whether it should output anything. For
|
|
59
|
+
* instance, if the extension is .rss and a component represents an article, it should
|
|
60
|
+
* probably output an RSS item. If you don't recognize the extension, just return
|
|
61
|
+
* super.renderVariation(extension, renderedAreas) to give your child components a chance to
|
|
62
|
+
* respond, or return empty string if you want your child components to be silent in all
|
|
63
|
+
* cases.
|
|
64
|
+
*
|
|
65
|
+
* This function will be run after the fetch phase. The context and html rendering phases
|
|
66
|
+
* will be skipped.
|
|
67
|
+
*/
|
|
68
|
+
renderVariation(extension: string, renderedAreas: Map<string, string>): string;
|
|
69
|
+
constructor(data: DataType, path: string, parent: Component | undefined);
|
|
70
|
+
/**
|
|
71
|
+
* For logging errors during rendering without crashing the render. If your fetch, setContext,
|
|
72
|
+
* render, or renderVariation functions throw, the error will be logged but the page render will
|
|
73
|
+
* continue. You generally do not need to use this function, just throw when appropriate.
|
|
74
|
+
*/
|
|
75
|
+
logError(e: Error): void;
|
|
76
|
+
protected passError(e: Error, path: string): void;
|
|
77
|
+
/**
|
|
78
|
+
* During rendering, each component should determine the CSS blocks that it needs. This may
|
|
79
|
+
* change depending on the data. For instance, if you need some CSS to style up an image, but
|
|
80
|
+
* only when the editor uploaded an image, you can check whether the image is present during
|
|
81
|
+
* the execution of this function.
|
|
82
|
+
*
|
|
83
|
+
* This is evaluated after the fetch and context phases but before the rendering phase. If you
|
|
84
|
+
* need any async data to make this determination, be sure to fetch it during the fetch phase.
|
|
85
|
+
*/
|
|
86
|
+
cssBlocks(): string[];
|
|
87
|
+
/**
|
|
88
|
+
* Same as cssBlocks() but for javascript.
|
|
89
|
+
*/
|
|
90
|
+
jsBlocks(): string[];
|
|
91
|
+
}
|
|
92
|
+
export interface PageRecord<DataType extends PageData = PageData> {
|
|
93
|
+
id: string;
|
|
94
|
+
linkId: string;
|
|
95
|
+
path: string;
|
|
96
|
+
data: DataType;
|
|
97
|
+
}
|
|
98
|
+
export interface PageWithAncestors<DataType extends PageData = PageData> extends PageRecord<DataType> {
|
|
99
|
+
ancestors: PageRecord<PageData>[];
|
|
100
|
+
}
|
|
101
|
+
export interface ComponentData {
|
|
102
|
+
templateKey: string;
|
|
103
|
+
areas: Record<string, ComponentData[]>;
|
|
104
|
+
}
|
|
105
|
+
export interface PageData extends ComponentData {
|
|
106
|
+
savedAtVersion: Date;
|
|
107
|
+
}
|
|
108
|
+
export interface ContextBase {
|
|
109
|
+
/**
|
|
110
|
+
* For accessibility, every component should consider whether it is creating headers
|
|
111
|
+
* using h1-h6 tags, and set the context for its children so that they will use the
|
|
112
|
+
* next higher number. For example, a page component might use h1 for the page title,
|
|
113
|
+
* in which case it should set headerLevel: 2 so that its child components will use
|
|
114
|
+
* h2 next. Those components in turn can increment headerLevel for their children.
|
|
115
|
+
*
|
|
116
|
+
* This way every page will have a perfect header tree and avoid complaints from WAVE.
|
|
117
|
+
*/
|
|
118
|
+
headerLevel: number;
|
|
119
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Component = void 0;
|
|
4
|
+
const page_1 = require("./page");
|
|
5
|
+
const provider_1 = require("./provider");
|
|
6
|
+
/**
|
|
7
|
+
* This is the primary templating class to build your templates. Subclass it and provide
|
|
8
|
+
* at least a render function.
|
|
9
|
+
*
|
|
10
|
+
* During rendering, it will be "hydrated" - placed into a full page structure with its
|
|
11
|
+
* parent and child components linked.
|
|
12
|
+
*/
|
|
13
|
+
class Component extends provider_1.ResourceProvider {
|
|
14
|
+
// the constructor is part of the recursive hydration mechanism: constructing
|
|
15
|
+
// a Component will also construct/hydrate all its child components
|
|
16
|
+
constructor(data, path, parent) {
|
|
17
|
+
var _a;
|
|
18
|
+
super();
|
|
19
|
+
// properties for use during hydration, you do not have to provide these when
|
|
20
|
+
// building a template, but you can use them in the functions you do provide
|
|
21
|
+
this.areas = new Map();
|
|
22
|
+
this.parent = parent;
|
|
23
|
+
const { areas, ...ownData } = data;
|
|
24
|
+
this.data = ownData;
|
|
25
|
+
this.path = path;
|
|
26
|
+
this.hadError = false;
|
|
27
|
+
let tmpParent = (_a = this.parent) !== null && _a !== void 0 ? _a : this;
|
|
28
|
+
while (!(tmpParent instanceof page_1.Page) && tmpParent.parent)
|
|
29
|
+
tmpParent = tmpParent.parent;
|
|
30
|
+
if (!(tmpParent instanceof page_1.Page))
|
|
31
|
+
throw new Error('Hydration failed, could not map component back to its page.');
|
|
32
|
+
this.page = tmpParent;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The first phase of rendering a component is the fetch phase. Each component may
|
|
36
|
+
* provide a fetch method that looks up data it needs from external sources. This step
|
|
37
|
+
* is FLAT - it will be executed concurrently for all the components on the page for
|
|
38
|
+
* maximum speed.
|
|
39
|
+
*
|
|
40
|
+
* Note that this.page will be available, along with its ancestors property containing
|
|
41
|
+
* all the data from ancestor pages, in case there is a need for inheritance. It is
|
|
42
|
+
* recommended to copy any needed data into the return object, as future phases will not
|
|
43
|
+
* want to resolve the inheritance again.
|
|
44
|
+
*/
|
|
45
|
+
async fetch(editMode) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The second phase of rendering a component is the context phase. This step is TOP-DOWN,
|
|
50
|
+
* each component will receive the parent component's context, modify it as desired,
|
|
51
|
+
* and then pass context to its children.
|
|
52
|
+
*
|
|
53
|
+
* This is useful for rendering logic that is sensitive to where the component exists in
|
|
54
|
+
* the hierarchy of the page. For instance, if a parent component has used an h2 header
|
|
55
|
+
* already, it will want to inform its children so that they can use h3 next, and they inform
|
|
56
|
+
* their children that h4 is next, and so on. (Header level tracking is actually required in
|
|
57
|
+
* dosgato CMS.)
|
|
58
|
+
*
|
|
59
|
+
* This function may return a promise in case you need to do something asynchronous based on
|
|
60
|
+
* the context received from the parent, but use it sparingly since it will stall the process.
|
|
61
|
+
* Try to do all asynchronous work in the fetch phase.
|
|
62
|
+
*/
|
|
63
|
+
setContext(renderCtxFromParent, editMode) {
|
|
64
|
+
return renderCtxFromParent;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Sometimes pages are requested with an alternate extension like .rss or .ics. In these
|
|
68
|
+
* situations, each component should consider whether it should output anything. For
|
|
69
|
+
* instance, if the extension is .rss and a component represents an article, it should
|
|
70
|
+
* probably output an RSS item. If you don't recognize the extension, just return
|
|
71
|
+
* super.renderVariation(extension, renderedAreas) to give your child components a chance to
|
|
72
|
+
* respond, or return empty string if you want your child components to be silent in all
|
|
73
|
+
* cases.
|
|
74
|
+
*
|
|
75
|
+
* This function will be run after the fetch phase. The context and html rendering phases
|
|
76
|
+
* will be skipped.
|
|
77
|
+
*/
|
|
78
|
+
renderVariation(extension, renderedAreas) {
|
|
79
|
+
return Array.from(renderedAreas.values()).join('');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* For logging errors during rendering without crashing the render. If your fetch, setContext,
|
|
83
|
+
* render, or renderVariation functions throw, the error will be logged but the page render will
|
|
84
|
+
* continue. You generally do not need to use this function, just throw when appropriate.
|
|
85
|
+
*/
|
|
86
|
+
logError(e) {
|
|
87
|
+
var _a;
|
|
88
|
+
this.hadError = true;
|
|
89
|
+
(_a = this.parent) === null || _a === void 0 ? void 0 : _a.passError(e, this.path);
|
|
90
|
+
}
|
|
91
|
+
// helper function for recursively passing the error up until it reaches the page
|
|
92
|
+
passError(e, path) {
|
|
93
|
+
var _a;
|
|
94
|
+
(_a = this.parent) === null || _a === void 0 ? void 0 : _a.passError(e, path);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* During rendering, each component should determine the CSS blocks that it needs. This may
|
|
98
|
+
* change depending on the data. For instance, if you need some CSS to style up an image, but
|
|
99
|
+
* only when the editor uploaded an image, you can check whether the image is present during
|
|
100
|
+
* the execution of this function.
|
|
101
|
+
*
|
|
102
|
+
* This is evaluated after the fetch and context phases but before the rendering phase. If you
|
|
103
|
+
* need any async data to make this determination, be sure to fetch it during the fetch phase.
|
|
104
|
+
*/
|
|
105
|
+
cssBlocks() {
|
|
106
|
+
return this.constructor.cssBlocks().keys();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Same as cssBlocks() but for javascript.
|
|
110
|
+
*/
|
|
111
|
+
jsBlocks() {
|
|
112
|
+
return this.constructor.jsBlocks().keys();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.Component = Component;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
__exportStar(require("./component"), exports);
|
|
14
|
+
__exportStar(require("./links"), exports);
|
|
15
|
+
__exportStar(require("./page"), exports);
|
|
16
|
+
__exportStar(require("./provider"), exports);
|
|
17
|
+
__exportStar(require("./template"), exports);
|
|
@@ -4,36 +4,33 @@
|
|
|
4
4
|
* the link target anyway.
|
|
5
5
|
*/
|
|
6
6
|
export interface AssetLink {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
type: 'asset';
|
|
8
|
+
source: string;
|
|
9
|
+
id: string;
|
|
10
|
+
siteId: string;
|
|
11
|
+
path: string;
|
|
12
|
+
checksum: string;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
14
|
/**
|
|
16
15
|
* Some components (e.g. document list) can point at a folder instead of individual
|
|
17
16
|
* assets, so we will want to track asset folders through moves, renames, and copies.
|
|
18
17
|
* This link format supports all that.
|
|
19
18
|
*/
|
|
20
19
|
export interface AssetFolderLink {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
type: 'assetfolder';
|
|
21
|
+
id: string;
|
|
22
|
+
siteId: string;
|
|
23
|
+
path: string;
|
|
25
24
|
}
|
|
26
|
-
|
|
27
25
|
/**
|
|
28
26
|
* A page link always points at the same pagetree as the page the link is on.
|
|
29
27
|
*/
|
|
30
28
|
export interface PageLink {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
type: 'page';
|
|
30
|
+
linkId: string;
|
|
31
|
+
siteId: string;
|
|
32
|
+
path: string;
|
|
35
33
|
}
|
|
36
|
-
|
|
37
34
|
/**
|
|
38
35
|
* The link format for external webpages. This format seems a little extra since
|
|
39
36
|
* it's just a URL string. Why does it need to be an object with a type? However,
|
|
@@ -42,31 +39,28 @@ export interface PageLink {
|
|
|
42
39
|
* the data a lot easier.
|
|
43
40
|
*/
|
|
44
41
|
export interface WebLink {
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
type: 'url';
|
|
43
|
+
url: string;
|
|
47
44
|
}
|
|
48
|
-
|
|
49
45
|
/**
|
|
50
46
|
* Many components will point at data records. That's the whole idea. Site id is
|
|
51
47
|
* required for all data links, it just might be null when the data being pointed at is
|
|
52
48
|
* global data.
|
|
53
49
|
*/
|
|
54
50
|
export interface DataLink {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
type: 'data';
|
|
52
|
+
id: string;
|
|
53
|
+
siteId: string | null;
|
|
54
|
+
path: string;
|
|
59
55
|
}
|
|
60
|
-
|
|
61
56
|
/**
|
|
62
57
|
* Just like with asset folders, we may have components that point at data folders. We
|
|
63
58
|
* would like to keep the links working through moves, renames, and copies.
|
|
64
59
|
*/
|
|
65
60
|
export interface DataFolderLink {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
type: 'datafolder';
|
|
62
|
+
id: string;
|
|
63
|
+
siteId?: string;
|
|
64
|
+
path: string;
|
|
70
65
|
}
|
|
71
|
-
|
|
72
|
-
export type LinkDefinition = AssetLink | AssetFolderLink | PageLink | WebLink | DataLink | DataFolderLink
|
|
66
|
+
export declare type LinkDefinition = AssetLink | AssetFolderLink | PageLink | WebLink | DataLink | DataFolderLink;
|
package/dist/links.js
ADDED
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PageData, ContextBase, Component, PageRecord, PageWithAncestors } from './component';
|
|
2
|
+
export declare abstract class Page<DataType extends PageData = any, FetchedType = any, RenderContextType extends ContextBase = any> extends Component<DataType, FetchedType, RenderContextType> {
|
|
3
|
+
pagePath: string;
|
|
4
|
+
ancestors: PageRecord[];
|
|
5
|
+
/**
|
|
6
|
+
* we will fill this before rendering, stuff that dosgato knows needs to be added to
|
|
7
|
+
* the <head> element
|
|
8
|
+
* the page's render function must include it
|
|
9
|
+
*/
|
|
10
|
+
headContent: string;
|
|
11
|
+
protected passError(e: Error, path: string): void;
|
|
12
|
+
constructor(page: PageWithAncestors<DataType>);
|
|
13
|
+
}
|
package/dist/page.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Page = void 0;
|
|
4
|
+
const component_1 = require("./component");
|
|
5
|
+
class Page extends component_1.Component {
|
|
6
|
+
constructor(page) {
|
|
7
|
+
super(page.data, '/', undefined);
|
|
8
|
+
this.pagePath = page.path;
|
|
9
|
+
this.ancestors = page.ancestors;
|
|
10
|
+
}
|
|
11
|
+
passError(e, path) {
|
|
12
|
+
console.warn(`Recoverable issue occured during render of ${this.pagePath}. Component at ${path} threw the following error:`, e);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.Page = Page;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This class is a parent class for Component, but it can also be used as a standalone
|
|
3
|
+
* if you are creating a set of templates with shared resources. This will be fairly
|
|
4
|
+
* typical for each entity running an instance and creating their own local templates. You'll
|
|
5
|
+
* probably want one place to set up very common resources like fontawesome or jquery, instead
|
|
6
|
+
* of having each and every component template provide them again and again.
|
|
7
|
+
*
|
|
8
|
+
* If you do this, don't forget to register the provider along with your templates!
|
|
9
|
+
*/
|
|
10
|
+
export declare abstract class ResourceProvider {
|
|
11
|
+
/**
|
|
12
|
+
* Each template should provide a map of CSS blocks where the map key is the unique name for
|
|
13
|
+
* the CSS and the value is the CSS itself. For instance, if a template needs CSS from a
|
|
14
|
+
* standard library like jquery-ui, it could include the full CSS for jquery-ui with 'jquery-ui'
|
|
15
|
+
* as the key. Other templates that depend on jquery-ui would also provide the CSS, but
|
|
16
|
+
* a page with both components would only include the CSS once, because they both called it
|
|
17
|
+
* 'jquery-ui'.
|
|
18
|
+
*
|
|
19
|
+
* A version string (e.g. '1.2.5') may be provided for each block. The block with the highest
|
|
20
|
+
* version number of any given name will be used. Other versions of that name will be ignored.
|
|
21
|
+
*
|
|
22
|
+
* For convenience you can either provide the `css` property with the CSS as a string, or the
|
|
23
|
+
* `path` property with the full server path to a CSS file (node's __dirname function will
|
|
24
|
+
* help you determine it). You MUST provide one or the other.
|
|
25
|
+
*/
|
|
26
|
+
static cssBlocks: Map<string, {
|
|
27
|
+
css?: string;
|
|
28
|
+
path?: string;
|
|
29
|
+
version?: string;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Same as cssBlocks() but for javascript.
|
|
33
|
+
*/
|
|
34
|
+
static jsBlocks: Map<string, {
|
|
35
|
+
js?: string;
|
|
36
|
+
path?: string;
|
|
37
|
+
version?: string;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* If your template needs to serve any files, like fonts or images, you can provide
|
|
41
|
+
* a filesystem path in this static property and the files will be served by the rendering
|
|
42
|
+
* server. Use the provided `webpaths` map to obtain the proper resource URLs. They will be
|
|
43
|
+
* available as soon as your template has been registered to the rendering server's templateRegistry.
|
|
44
|
+
*
|
|
45
|
+
* Typically you will set this to something like `${__dirname}/static` so that the path will be relative
|
|
46
|
+
* to where you are writing your template class.
|
|
47
|
+
*
|
|
48
|
+
* The map name you pick should be globally unique and only collide with other templates as
|
|
49
|
+
* intended. For instance, the fontawesome font only needs to be provided once, even though
|
|
50
|
+
* several templates might depend on it. Setting the name as 'fontawesome5' on all three
|
|
51
|
+
* templates would ensure that the file would only be served once. Including the major version
|
|
52
|
+
* number is a good idea only if the major versions can coexist.
|
|
53
|
+
*
|
|
54
|
+
* Include a version number if applicable for the file you've included with your source. If
|
|
55
|
+
* multiple templates have a common file, the one that provides the highest version number will
|
|
56
|
+
* have its file served, while the others will be ignored.
|
|
57
|
+
*
|
|
58
|
+
* DO NOT change the mime type without changing the name. Other templates could end up with
|
|
59
|
+
* the wrong file extension.
|
|
60
|
+
*/
|
|
61
|
+
static files: Map<string, {
|
|
62
|
+
path: string;
|
|
63
|
+
version?: string;
|
|
64
|
+
mime: string;
|
|
65
|
+
}>;
|
|
66
|
+
/**
|
|
67
|
+
* Template code will need to generate HTML and CSS that points at the static files
|
|
68
|
+
* provided above. In order to do so, we need information from the template registry (since
|
|
69
|
+
* we have to deduplicate with other registered templates at startup time).
|
|
70
|
+
*
|
|
71
|
+
* In order to avoid an ES6 dependency on the registry, we will have the registry write
|
|
72
|
+
* back to this map as templates are registered.
|
|
73
|
+
*
|
|
74
|
+
* Now when a template needs a web path to a resource to put into its HTML, it can do
|
|
75
|
+
* `<img src="${TemplateClass.webpath('keyname')}">`
|
|
76
|
+
*/
|
|
77
|
+
static webpaths: Map<string, string>;
|
|
78
|
+
static webpath(name: string): string | undefined;
|
|
79
|
+
}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-extraneous-class */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ResourceProvider = void 0;
|
|
5
|
+
/**
|
|
6
|
+
* This class is a parent class for Component, but it can also be used as a standalone
|
|
7
|
+
* if you are creating a set of templates with shared resources. This will be fairly
|
|
8
|
+
* typical for each entity running an instance and creating their own local templates. You'll
|
|
9
|
+
* probably want one place to set up very common resources like fontawesome or jquery, instead
|
|
10
|
+
* of having each and every component template provide them again and again.
|
|
11
|
+
*
|
|
12
|
+
* If you do this, don't forget to register the provider along with your templates!
|
|
13
|
+
*/
|
|
14
|
+
class ResourceProvider {
|
|
15
|
+
static webpath(name) { return this.webpaths.get(name); }
|
|
16
|
+
}
|
|
17
|
+
exports.ResourceProvider = ResourceProvider;
|
|
18
|
+
/**
|
|
19
|
+
* Each template should provide a map of CSS blocks where the map key is the unique name for
|
|
20
|
+
* the CSS and the value is the CSS itself. For instance, if a template needs CSS from a
|
|
21
|
+
* standard library like jquery-ui, it could include the full CSS for jquery-ui with 'jquery-ui'
|
|
22
|
+
* as the key. Other templates that depend on jquery-ui would also provide the CSS, but
|
|
23
|
+
* a page with both components would only include the CSS once, because they both called it
|
|
24
|
+
* 'jquery-ui'.
|
|
25
|
+
*
|
|
26
|
+
* A version string (e.g. '1.2.5') may be provided for each block. The block with the highest
|
|
27
|
+
* version number of any given name will be used. Other versions of that name will be ignored.
|
|
28
|
+
*
|
|
29
|
+
* For convenience you can either provide the `css` property with the CSS as a string, or the
|
|
30
|
+
* `path` property with the full server path to a CSS file (node's __dirname function will
|
|
31
|
+
* help you determine it). You MUST provide one or the other.
|
|
32
|
+
*/
|
|
33
|
+
ResourceProvider.cssBlocks = new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Same as cssBlocks() but for javascript.
|
|
36
|
+
*/
|
|
37
|
+
ResourceProvider.jsBlocks = new Map();
|
|
38
|
+
/**
|
|
39
|
+
* If your template needs to serve any files, like fonts or images, you can provide
|
|
40
|
+
* a filesystem path in this static property and the files will be served by the rendering
|
|
41
|
+
* server. Use the provided `webpaths` map to obtain the proper resource URLs. They will be
|
|
42
|
+
* available as soon as your template has been registered to the rendering server's templateRegistry.
|
|
43
|
+
*
|
|
44
|
+
* Typically you will set this to something like `${__dirname}/static` so that the path will be relative
|
|
45
|
+
* to where you are writing your template class.
|
|
46
|
+
*
|
|
47
|
+
* The map name you pick should be globally unique and only collide with other templates as
|
|
48
|
+
* intended. For instance, the fontawesome font only needs to be provided once, even though
|
|
49
|
+
* several templates might depend on it. Setting the name as 'fontawesome5' on all three
|
|
50
|
+
* templates would ensure that the file would only be served once. Including the major version
|
|
51
|
+
* number is a good idea only if the major versions can coexist.
|
|
52
|
+
*
|
|
53
|
+
* Include a version number if applicable for the file you've included with your source. If
|
|
54
|
+
* multiple templates have a common file, the one that provides the highest version number will
|
|
55
|
+
* have its file served, while the others will be ignored.
|
|
56
|
+
*
|
|
57
|
+
* DO NOT change the mime type without changing the name. Other templates could end up with
|
|
58
|
+
* the wrong file extension.
|
|
59
|
+
*/
|
|
60
|
+
ResourceProvider.files = new Map();
|
|
61
|
+
/**
|
|
62
|
+
* Template code will need to generate HTML and CSS that points at the static files
|
|
63
|
+
* provided above. In order to do so, we need information from the template registry (since
|
|
64
|
+
* we have to deduplicate with other registered templates at startup time).
|
|
65
|
+
*
|
|
66
|
+
* In order to avoid an ES6 dependency on the registry, we will have the registry write
|
|
67
|
+
* back to this map as templates are registered.
|
|
68
|
+
*
|
|
69
|
+
* Now when a template needs a web path to a resource to put into its HTML, it can do
|
|
70
|
+
* `<img src="${TemplateClass.webpath('keyname')}">`
|
|
71
|
+
*/
|
|
72
|
+
ResourceProvider.webpaths = new Map();
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { PageWithAncestors, ComponentData } from './component';
|
|
2
|
+
import { LinkDefinition } from './links';
|
|
3
|
+
export declare type TemplateType = 'page' | 'component' | 'data';
|
|
4
|
+
/**
|
|
5
|
+
* This interface lays out the structure the API needs for each template in the system.
|
|
6
|
+
*/
|
|
7
|
+
export interface Template {
|
|
8
|
+
type: TemplateType;
|
|
9
|
+
/**
|
|
10
|
+
* A unique string to globally identify this template across installations. Namespacing like
|
|
11
|
+
* edu.txstate.RichTextEditor could be useful but no special format is required.
|
|
12
|
+
*/
|
|
13
|
+
templateKey: string;
|
|
14
|
+
/**
|
|
15
|
+
* Each template must declare its areas and the template keys of components that will be
|
|
16
|
+
* permitted inside each area. The list of allowed component templates can be updated beyond
|
|
17
|
+
* the list provided here. See templateRegistry.addAvailableComponent's comment for info on why.
|
|
18
|
+
*/
|
|
19
|
+
areas: Record<string, string[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Each template must provide a list of migrations for upgrading the data schema over time.
|
|
22
|
+
* Typically this will start as an empty array and migrations will be added as the template
|
|
23
|
+
* gets refactored.
|
|
24
|
+
*/
|
|
25
|
+
migrations: Migration[];
|
|
26
|
+
/**
|
|
27
|
+
* Each template must provide a function that returns links from its data so that they
|
|
28
|
+
* can be indexed. Only fields that are links need to be returned. Links inside rich editor
|
|
29
|
+
* text will be extracted automatically from any text returned by getFulltext (see below)
|
|
30
|
+
*/
|
|
31
|
+
getLinks: LinkGatheringFn;
|
|
32
|
+
/**
|
|
33
|
+
* Each template must provide the text from any text or rich editor data it possesses, so that
|
|
34
|
+
* the text can be decomposed into words and indexed for fulltext searches. Any text returned
|
|
35
|
+
* by this function will also be scanned for links.
|
|
36
|
+
*/
|
|
37
|
+
getFulltext: FulltextGatheringFn;
|
|
38
|
+
/**
|
|
39
|
+
* Each template must provide a validation function so that the API can enforce its data is
|
|
40
|
+
* shaped properly. If there are no issues, it should return an empty object {}, otherwise it
|
|
41
|
+
* should return an object with keys that reference the path to the error and values that
|
|
42
|
+
* are an array of error messages pertaining to that path.
|
|
43
|
+
*
|
|
44
|
+
* For instance, if name is required and the user didn't provide one, you would return:
|
|
45
|
+
* { name: ['A name is required.'] }
|
|
46
|
+
*
|
|
47
|
+
* This method is async so that you can do things like look in the database for conflicting
|
|
48
|
+
* names.
|
|
49
|
+
*/
|
|
50
|
+
validate: (data: any) => Promise<Record<string, string[]>>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* In dosgato CMS, the data in the database is not altered except during user activity. This
|
|
54
|
+
* means that older records could have been saved when the schema expected by component
|
|
55
|
+
* rendering code was different than the date it's being rendered. To handle this, each
|
|
56
|
+
* page and component template is required to provide migrations responsible for
|
|
57
|
+
* transforming the data to the needed schema version.
|
|
58
|
+
*
|
|
59
|
+
* In order to support backwards compatibility, each API client will specify the date
|
|
60
|
+
* when the code was written, so that their assumptions about the schema will be
|
|
61
|
+
* frozen in time. This system means that migrations need to run backward as well as forward
|
|
62
|
+
* in time.
|
|
63
|
+
*
|
|
64
|
+
* The `up` method is for changing data from an older schema to a newer one. The
|
|
65
|
+
* `down` method is for changing data back from the newer schema to the older one.
|
|
66
|
+
* If a `down` method cannot be provided, the migration is considered to be a breaking
|
|
67
|
+
* change and anyone asking to rewind time to before the migration will receive an error.
|
|
68
|
+
*
|
|
69
|
+
* Your `up` and `down` methods will be applied to components in bottom-up fashion, so you
|
|
70
|
+
* can assume that any components inside one of your areas has already been processed.
|
|
71
|
+
*/
|
|
72
|
+
export interface Migration {
|
|
73
|
+
createdAt: Date;
|
|
74
|
+
up: (data: ComponentData, page: PageWithAncestors) => ComponentData | Promise<ComponentData>;
|
|
75
|
+
down: (data: ComponentData, page: PageWithAncestors) => ComponentData | Promise<ComponentData>;
|
|
76
|
+
}
|
|
77
|
+
export declare type LinkGatheringFn = (data: any) => LinkDefinition[];
|
|
78
|
+
export declare type FulltextGatheringFn = (data: any) => string[];
|
package/dist/template.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dosgato/templating",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "A library to support building templates for dosgato CMS.",
|
|
5
5
|
"exports": {
|
|
6
6
|
"require": "./dist/index.js",
|
|
@@ -30,5 +30,8 @@
|
|
|
30
30
|
"bugs": {
|
|
31
31
|
"url": "https://github.com/txstate-etc/dosgato-templating/issues"
|
|
32
32
|
},
|
|
33
|
-
"homepage": "https://github.com/txstate-etc/dosgato-templating#readme"
|
|
33
|
+
"homepage": "https://github.com/txstate-etc/dosgato-templating#readme",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist", "dist-esm"
|
|
36
|
+
]
|
|
34
37
|
}
|
package/.eslintrc.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "standard-with-typescript",
|
|
3
|
-
"parserOptions": {
|
|
4
|
-
"project": "./tsconfig.eslint.json"
|
|
5
|
-
},
|
|
6
|
-
"rules": {
|
|
7
|
-
"@typescript-eslint/explicit-function-return-type": "off", // useless boilerplate
|
|
8
|
-
"@typescript-eslint/array-type": "off", // forces you to use Array<CustomType> instead of CustomType[], silly
|
|
9
|
-
"@typescript-eslint/no-non-null-assertion": "off", // disables using ! to mark something as non-null,
|
|
10
|
-
// generally not avoidable without wasting cpu cycles on a check
|
|
11
|
-
"@typescript-eslint/no-unused-vars": "off", // typescript already reports this and VSCode darkens the variable
|
|
12
|
-
"@typescript-eslint/return-await": ["error", "always"], // allows you to accidentally break async stacktraces in node 14+
|
|
13
|
-
"@typescript-eslint/strict-boolean-expressions": "off" // we know how truthiness works, annoying to have to avoid
|
|
14
|
-
}
|
|
15
|
-
}
|
package/src/component.ts
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { Page } from './page'
|
|
2
|
-
import { ResourceProvider } from './provider'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* This is the primary templating class to build your templates. Subclass it and provide
|
|
6
|
-
* at least a render function.
|
|
7
|
-
*
|
|
8
|
-
* During rendering, it will be "hydrated" - placed into a full page structure with its
|
|
9
|
-
* parent and child components linked.
|
|
10
|
-
*/
|
|
11
|
-
export abstract class Component<DataType extends ComponentData = any, FetchedType = any, RenderContextType extends ContextBase = any> extends ResourceProvider {
|
|
12
|
-
// properties each template should provide
|
|
13
|
-
static templateKey: string
|
|
14
|
-
static templateName: string
|
|
15
|
-
|
|
16
|
-
// properties for use during hydration, you do not have to provide these when
|
|
17
|
-
// building a template, but you can use them in the functions you do provide
|
|
18
|
-
areas = new Map<string, Component[]>()
|
|
19
|
-
data: Omit<DataType, 'areas'>
|
|
20
|
-
fetched!: FetchedType
|
|
21
|
-
renderCtx!: RenderContextType
|
|
22
|
-
path: string
|
|
23
|
-
parent?: Component
|
|
24
|
-
page?: Page
|
|
25
|
-
hadError: boolean
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The first phase of rendering a component is the fetch phase. Each component may
|
|
29
|
-
* provide a fetch method that looks up data it needs from external sources. This step
|
|
30
|
-
* is FLAT - it will be executed concurrently for all the components on the page for
|
|
31
|
-
* maximum speed.
|
|
32
|
-
*
|
|
33
|
-
* Note that this.page will be available, along with its ancestors property containing
|
|
34
|
-
* all the data from ancestor pages, in case there is a need for inheritance. It is
|
|
35
|
-
* recommended to copy any needed data into the return object, as future phases will not
|
|
36
|
-
* want to resolve the inheritance again.
|
|
37
|
-
*/
|
|
38
|
-
async fetch (editMode: boolean) {
|
|
39
|
-
return undefined as unknown as FetchedType
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* The second phase of rendering a component is the context phase. This step is TOP-DOWN,
|
|
44
|
-
* each component will receive the parent component's context, modify it as desired,
|
|
45
|
-
* and then pass context to its children.
|
|
46
|
-
*
|
|
47
|
-
* This is useful for rendering logic that is sensitive to where the component exists in
|
|
48
|
-
* the hierarchy of the page. For instance, if a parent component has used an h2 header
|
|
49
|
-
* already, it will want to inform its children so that they can use h3 next, and they inform
|
|
50
|
-
* their children that h4 is next, and so on. (Header level tracking is actually required in
|
|
51
|
-
* dosgato CMS.)
|
|
52
|
-
*
|
|
53
|
-
* This function may return a promise in case you need to do something asynchronous based on
|
|
54
|
-
* the context received from the parent, but use it sparingly since it will stall the process.
|
|
55
|
-
* Try to do all asynchronous work in the fetch phase.
|
|
56
|
-
*/
|
|
57
|
-
setContext (renderCtxFromParent: RenderContextType, editMode: boolean): RenderContextType|Promise<RenderContextType> {
|
|
58
|
-
return renderCtxFromParent
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* The final phase of rendering a component is the render phase. This step is BOTTOM-UP -
|
|
63
|
-
* components at the bottom of the hierarchy will be rendered first, and the result of the
|
|
64
|
-
* render will be passed to parent components so that the HTML can be included during the
|
|
65
|
-
* render of the parent component.
|
|
66
|
-
*/
|
|
67
|
-
abstract render (renderedAreas: Map<string, string[]>, editMode: boolean): string
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Sometimes pages are requested with an alternate extension like .rss or .ics. In these
|
|
71
|
-
* situations, each component should consider whether it should output anything. For
|
|
72
|
-
* instance, if the extension is .rss and a component represents an article, it should
|
|
73
|
-
* probably output an RSS item. If you don't recognize the extension, just return
|
|
74
|
-
* super.renderVariation(extension, renderedAreas) to give your child components a chance to
|
|
75
|
-
* respond, or return empty string if you want your child components to be silent in all
|
|
76
|
-
* cases.
|
|
77
|
-
*
|
|
78
|
-
* This function will be run after the fetch phase. The context and html rendering phases
|
|
79
|
-
* will be skipped.
|
|
80
|
-
*/
|
|
81
|
-
renderVariation (extension: string, renderedAreas: Map<string, string>) {
|
|
82
|
-
return Array.from(renderedAreas.values()).join('')
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// the constructor is part of the recursive hydration mechanism: constructing
|
|
86
|
-
// a Component will also construct/hydrate all its child components
|
|
87
|
-
constructor (data: DataType, path: string, parent: Component|undefined) {
|
|
88
|
-
super()
|
|
89
|
-
this.parent = parent
|
|
90
|
-
const { areas, ...ownData } = data
|
|
91
|
-
this.data = ownData
|
|
92
|
-
this.path = path
|
|
93
|
-
this.hadError = false
|
|
94
|
-
let tmpParent = this.parent ?? this
|
|
95
|
-
while (!(tmpParent instanceof Page) && tmpParent.parent) tmpParent = tmpParent.parent
|
|
96
|
-
if (!(tmpParent instanceof Page)) throw new Error('Hydration failed, could not map component back to its page.')
|
|
97
|
-
this.page = tmpParent
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* For logging errors during rendering without crashing the render. If your fetch, setContext,
|
|
102
|
-
* render, or renderVariation functions throw, the error will be logged but the page render will
|
|
103
|
-
* continue. You generally do not need to use this function, just throw when appropriate.
|
|
104
|
-
*/
|
|
105
|
-
logError (e: Error) {
|
|
106
|
-
this.hadError = true
|
|
107
|
-
this.parent?.passError(e, this.path)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// helper function for recursively passing the error up until it reaches the page
|
|
111
|
-
protected passError (e: Error, path: string) {
|
|
112
|
-
this.parent?.passError(e, path)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* During rendering, each component should determine the CSS blocks that it needs. This may
|
|
117
|
-
* change depending on the data. For instance, if you need some CSS to style up an image, but
|
|
118
|
-
* only when the editor uploaded an image, you can check whether the image is present during
|
|
119
|
-
* the execution of this function.
|
|
120
|
-
*
|
|
121
|
-
* This is evaluated after the fetch and context phases but before the rendering phase. If you
|
|
122
|
-
* need any async data to make this determination, be sure to fetch it during the fetch phase.
|
|
123
|
-
*/
|
|
124
|
-
cssBlocks (): string[] {
|
|
125
|
-
return (this.constructor as any).cssBlocks().keys()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Same as cssBlocks() but for javascript.
|
|
130
|
-
*/
|
|
131
|
-
jsBlocks (): string[] {
|
|
132
|
-
return (this.constructor as any).jsBlocks().keys()
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export interface PageRecord<DataType extends PageData = PageData> {
|
|
137
|
-
id: string
|
|
138
|
-
linkId: string
|
|
139
|
-
path: string
|
|
140
|
-
data: DataType
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export interface PageWithAncestors<DataType extends PageData = PageData> extends PageRecord<DataType> {
|
|
144
|
-
ancestors: PageRecord<PageData>[]
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export interface ComponentData {
|
|
148
|
-
templateKey: string
|
|
149
|
-
areas: Record<string, ComponentData[]>
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export interface PageData extends ComponentData {
|
|
153
|
-
savedAtVersion: Date
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export interface ContextBase {
|
|
157
|
-
/**
|
|
158
|
-
* For accessibility, every component should consider whether it is creating headers
|
|
159
|
-
* using h1-h6 tags, and set the context for its children so that they will use the
|
|
160
|
-
* next higher number. For example, a page component might use h1 for the page title,
|
|
161
|
-
* in which case it should set headerLevel: 2 so that its child components will use
|
|
162
|
-
* h2 next. Those components in turn can increment headerLevel for their children.
|
|
163
|
-
*
|
|
164
|
-
* This way every page will have a perfect header tree and avoid complaints from WAVE.
|
|
165
|
-
*/
|
|
166
|
-
headerLevel: number
|
|
167
|
-
}
|
package/src/index.ts
DELETED
package/src/page.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { PageData, ContextBase, Component, PageRecord, PageWithAncestors } from './component'
|
|
2
|
-
|
|
3
|
-
export abstract class Page<DataType extends PageData = any, FetchedType = any, RenderContextType extends ContextBase = any> extends Component<DataType, FetchedType, RenderContextType> {
|
|
4
|
-
pagePath: string
|
|
5
|
-
ancestors: PageRecord[]
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* we will fill this before rendering, stuff that dosgato knows needs to be added to
|
|
9
|
-
* the <head> element
|
|
10
|
-
* the page's render function must include it
|
|
11
|
-
*/
|
|
12
|
-
headContent!: string
|
|
13
|
-
|
|
14
|
-
protected passError (e: Error, path: string) {
|
|
15
|
-
console.warn(`Recoverable issue occured during render of ${this.pagePath}. Component at ${path} threw the following error:`, e)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
constructor (page: PageWithAncestors<DataType>) {
|
|
19
|
-
super(page.data, '/', undefined)
|
|
20
|
-
this.pagePath = page.path
|
|
21
|
-
this.ancestors = page.ancestors
|
|
22
|
-
}
|
|
23
|
-
}
|
package/src/provider.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-extraneous-class */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* This class is a parent class for Component, but it can also be used as a standalone
|
|
5
|
-
* if you are creating a set of templates with shared resources. This will be fairly
|
|
6
|
-
* typical for each entity running an instance and creating their own local templates. You'll
|
|
7
|
-
* probably want one place to set up very common resources like fontawesome or jquery, instead
|
|
8
|
-
* of having each and every component template provide them again and again.
|
|
9
|
-
*
|
|
10
|
-
* If you do this, don't forget to register the provider along with your templates!
|
|
11
|
-
*/
|
|
12
|
-
export abstract class ResourceProvider {
|
|
13
|
-
/**
|
|
14
|
-
* Each template should provide a map of CSS blocks where the map key is the unique name for
|
|
15
|
-
* the CSS and the value is the CSS itself. For instance, if a template needs CSS from a
|
|
16
|
-
* standard library like jquery-ui, it could include the full CSS for jquery-ui with 'jquery-ui'
|
|
17
|
-
* as the key. Other templates that depend on jquery-ui would also provide the CSS, but
|
|
18
|
-
* a page with both components would only include the CSS once, because they both called it
|
|
19
|
-
* 'jquery-ui'.
|
|
20
|
-
*
|
|
21
|
-
* A version string (e.g. '1.2.5') may be provided for each block. The block with the highest
|
|
22
|
-
* version number of any given name will be used. Other versions of that name will be ignored.
|
|
23
|
-
*
|
|
24
|
-
* For convenience you can either provide the `css` property with the CSS as a string, or the
|
|
25
|
-
* `path` property with the full server path to a CSS file (node's __dirname function will
|
|
26
|
-
* help you determine it). You MUST provide one or the other.
|
|
27
|
-
*/
|
|
28
|
-
static cssBlocks: Map<string, { css?: string, path?: string, version?: string }> = new Map()
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Same as cssBlocks() but for javascript.
|
|
32
|
-
*/
|
|
33
|
-
static jsBlocks: Map<string, { js?: string, path?: string, version?: string }> = new Map()
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* If your template needs to serve any files, like fonts or images, you can provide
|
|
37
|
-
* a filesystem path in this static property and the files will be served by the rendering
|
|
38
|
-
* server. Use the provided `webpaths` map to obtain the proper resource URLs. They will be
|
|
39
|
-
* available as soon as your template has been registered to the rendering server's templateRegistry.
|
|
40
|
-
*
|
|
41
|
-
* Typically you will set this to something like `${__dirname}/static` so that the path will be relative
|
|
42
|
-
* to where you are writing your template class.
|
|
43
|
-
*
|
|
44
|
-
* The map name you pick should be globally unique and only collide with other templates as
|
|
45
|
-
* intended. For instance, the fontawesome font only needs to be provided once, even though
|
|
46
|
-
* several templates might depend on it. Setting the name as 'fontawesome5' on all three
|
|
47
|
-
* templates would ensure that the file would only be served once. Including the major version
|
|
48
|
-
* number is a good idea only if the major versions can coexist.
|
|
49
|
-
*
|
|
50
|
-
* Include a version number if applicable for the file you've included with your source. If
|
|
51
|
-
* multiple templates have a common file, the one that provides the highest version number will
|
|
52
|
-
* have its file served, while the others will be ignored.
|
|
53
|
-
*
|
|
54
|
-
* DO NOT change the mime type without changing the name. Other templates could end up with
|
|
55
|
-
* the wrong file extension.
|
|
56
|
-
*/
|
|
57
|
-
static files: Map<string, { path: string, version?: string, mime: string }> = new Map()
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Template code will need to generate HTML and CSS that points at the static files
|
|
61
|
-
* provided above. In order to do so, we need information from the template registry (since
|
|
62
|
-
* we have to deduplicate with other registered templates at startup time).
|
|
63
|
-
*
|
|
64
|
-
* In order to avoid an ES6 dependency on the registry, we will have the registry write
|
|
65
|
-
* back to this map as templates are registered.
|
|
66
|
-
*
|
|
67
|
-
* Now when a template needs a web path to a resource to put into its HTML, it can do
|
|
68
|
-
* `<img src="${TemplateClass.webpath('keyname')}">`
|
|
69
|
-
*/
|
|
70
|
-
static webpaths: Map<string, string> = new Map()
|
|
71
|
-
static webpath (name: string) { return this.webpaths.get(name) }
|
|
72
|
-
}
|
package/src/template.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { PageWithAncestors, ComponentData } from './component'
|
|
2
|
-
import { LinkDefinition } from './links'
|
|
3
|
-
|
|
4
|
-
export type TemplateType = 'page'|'component'|'data'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* This interface lays out the structure the API needs for each template in the system.
|
|
8
|
-
*/
|
|
9
|
-
export interface Template {
|
|
10
|
-
type: TemplateType
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A unique string to globally identify this template across installations. Namespacing like
|
|
14
|
-
* edu.txstate.RichTextEditor could be useful but no special format is required.
|
|
15
|
-
*/
|
|
16
|
-
templateKey: string
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Each template must declare its areas and the template keys of components that will be
|
|
20
|
-
* permitted inside each area. The list of allowed component templates can be updated beyond
|
|
21
|
-
* the list provided here. See templateRegistry.addAvailableComponent's comment for info on why.
|
|
22
|
-
*/
|
|
23
|
-
areas: Record<string, string[]>
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Each template must provide a list of migrations for upgrading the data schema over time.
|
|
27
|
-
* Typically this will start as an empty array and migrations will be added as the template
|
|
28
|
-
* gets refactored.
|
|
29
|
-
*/
|
|
30
|
-
migrations: Migration[]
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Each template must provide a function that returns links from its data so that they
|
|
34
|
-
* can be indexed. Only fields that are links need to be returned. Links inside rich editor
|
|
35
|
-
* text will be extracted automatically from any text returned by getFulltext (see below)
|
|
36
|
-
*/
|
|
37
|
-
getLinks: LinkGatheringFn
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Each template must provide the text from any text or rich editor data it possesses, so that
|
|
41
|
-
* the text can be decomposed into words and indexed for fulltext searches. Any text returned
|
|
42
|
-
* by this function will also be scanned for links.
|
|
43
|
-
*/
|
|
44
|
-
getFulltext: FulltextGatheringFn
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Each template must provide a validation function so that the API can enforce its data is
|
|
48
|
-
* shaped properly. If there are no issues, it should return an empty object {}, otherwise it
|
|
49
|
-
* should return an object with keys that reference the path to the error and values that
|
|
50
|
-
* are an array of error messages pertaining to that path.
|
|
51
|
-
*
|
|
52
|
-
* For instance, if name is required and the user didn't provide one, you would return:
|
|
53
|
-
* { name: ['A name is required.'] }
|
|
54
|
-
*
|
|
55
|
-
* This method is async so that you can do things like look in the database for conflicting
|
|
56
|
-
* names.
|
|
57
|
-
*/
|
|
58
|
-
validate: (data: any) => Promise<Record<string, string[]>>
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* In dosgato CMS, the data in the database is not altered except during user activity. This
|
|
63
|
-
* means that older records could have been saved when the schema expected by component
|
|
64
|
-
* rendering code was different than the date it's being rendered. To handle this, each
|
|
65
|
-
* page and component template is required to provide migrations responsible for
|
|
66
|
-
* transforming the data to the needed schema version.
|
|
67
|
-
*
|
|
68
|
-
* In order to support backwards compatibility, each API client will specify the date
|
|
69
|
-
* when the code was written, so that their assumptions about the schema will be
|
|
70
|
-
* frozen in time. This system means that migrations need to run backward as well as forward
|
|
71
|
-
* in time.
|
|
72
|
-
*
|
|
73
|
-
* The `up` method is for changing data from an older schema to a newer one. The
|
|
74
|
-
* `down` method is for changing data back from the newer schema to the older one.
|
|
75
|
-
* If a `down` method cannot be provided, the migration is considered to be a breaking
|
|
76
|
-
* change and anyone asking to rewind time to before the migration will receive an error.
|
|
77
|
-
*
|
|
78
|
-
* Your `up` and `down` methods will be applied to components in bottom-up fashion, so you
|
|
79
|
-
* can assume that any components inside one of your areas has already been processed.
|
|
80
|
-
*/
|
|
81
|
-
export interface Migration {
|
|
82
|
-
createdAt: Date
|
|
83
|
-
up: (data: ComponentData, page: PageWithAncestors) => ComponentData|Promise<ComponentData>
|
|
84
|
-
down: (data: ComponentData, page: PageWithAncestors) => ComponentData|Promise<ComponentData>
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export type LinkGatheringFn = (data: any) => LinkDefinition[]
|
|
88
|
-
export type FulltextGatheringFn = (data: any) => string[]
|
package/tsconfig.eslint.json
DELETED