@decocms/apps-blog 8.0.0 → 8.1.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/package.json +14 -5
- package/src/README.md +69 -0
- package/src/__tests__/blogRecords.test.ts +270 -0
- package/src/__tests__/categoryTree.test.ts +209 -0
- package/src/__tests__/date.test.ts +134 -0
- package/src/__tests__/handlePosts.test.ts +256 -230
- package/src/__tests__/loaders.test.ts +198 -154
- package/src/__tests__/mod.test.ts +6 -6
- package/src/__tests__/publicationStatus.test.ts +176 -0
- package/src/__tests__/records.test.ts +68 -68
- package/src/__tests__/seoSections.test.ts +245 -0
- package/src/actions/submitRating.ts +36 -0
- package/src/actions/submitReview.ts +95 -0
- package/src/actions/submitView.ts +28 -0
- package/src/client.ts +70 -0
- package/src/core/blogRecords.ts +94 -0
- package/src/core/categoryTree.ts +210 -0
- package/src/core/handlePosts.ts +160 -64
- package/src/core/records.ts +147 -12
- package/src/index.ts +48 -14
- package/src/loaderMap.ts +28 -24
- package/src/loaders/BlogPostItem.ts +15 -5
- package/src/loaders/BlogPostPage.ts +61 -24
- package/src/loaders/BlogRelatedPosts.ts +45 -45
- package/src/loaders/BlogpostList.ts +93 -0
- package/src/loaders/BlogpostListing.ts +151 -85
- package/src/loaders/GetCategories.ts +51 -28
- package/src/loaders/extensions/BlogpostList/ratings.ts +36 -0
- package/src/loaders/extensions/BlogpostList/reviews.ts +36 -0
- package/src/loaders/extensions/BlogpostListing/ratings.ts +38 -0
- package/src/loaders/extensions/BlogpostListing/reviews.ts +38 -0
- package/src/loaders/extensions/BlogpostPage/ratings.ts +24 -0
- package/src/loaders/extensions/BlogpostPage/reviews.ts +31 -0
- package/src/loaders/extensions/types.ts +15 -0
- package/src/manifest.gen.ts +82 -14
- package/src/mod.ts +73 -20
- package/src/registry.ts +5 -5
- package/src/sections/Seo/SeoBlogPost.tsx +77 -0
- package/src/sections/Seo/SeoBlogPostListing.tsx +80 -0
- package/src/sections/Template.tsx +106 -0
- package/src/sections/blocks/BlockImage.tsx +42 -0
- package/src/sections/blocks/Callout.tsx +64 -0
- package/src/sections/blocks/CardGroup.tsx +60 -0
- package/src/sections/blocks/Checklist.tsx +58 -0
- package/src/sections/blocks/Code.tsx +29 -0
- package/src/sections/blocks/Comparison.tsx +77 -0
- package/src/sections/blocks/Cta.tsx +22 -0
- package/src/sections/blocks/Divider.tsx +3 -0
- package/src/sections/blocks/Heading.tsx +28 -0
- package/src/sections/blocks/List.tsx +39 -0
- package/src/sections/blocks/Paragraph.tsx +15 -0
- package/src/sections/blocks/Quote.tsx +32 -0
- package/src/sections/blocks/Stat.tsx +23 -0
- package/src/sections/blocks/StatGroup.tsx +38 -0
- package/src/sections/blocks/Steps.tsx +64 -0
- package/src/sections/blocks/Table.tsx +80 -0
- package/src/sections/blocks/Video.tsx +39 -0
- package/src/static/css.ts +178 -0
- package/src/types.ts +333 -77
- package/src/utils/blocksToSections.ts +175 -0
- package/src/utils/constants.ts +1 -0
- package/src/utils/date.ts +95 -0
- package/src/utils/hardSanitize.ts +106 -0
- package/src/utils/jsonLD.ts +118 -0
- package/src/utils/sanitizeHtml.ts +37 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
export const CSS = `
|
|
2
|
+
.deco-post-preview{
|
|
3
|
+
width: 100%;
|
|
4
|
+
margin-left: auto;
|
|
5
|
+
margin-right: auto;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@media (min-width: 640px){
|
|
9
|
+
.deco-post-preview{
|
|
10
|
+
max-width: 640px
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@media (min-width: 768px){
|
|
15
|
+
.deco-post-preview{
|
|
16
|
+
max-width: 768px
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.deco-post-preview {
|
|
21
|
+
font-family: 'Albert sans', sans-serif;
|
|
22
|
+
color: #66736C;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.deco-post-preview p {
|
|
26
|
+
line-height: 1.75rem;
|
|
27
|
+
margin-bottom: 1rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.deco-post-preview h1,
|
|
31
|
+
.deco-post-preview h2,
|
|
32
|
+
.deco-post-preview h3,
|
|
33
|
+
.deco-post-preview h4,
|
|
34
|
+
.deco-post-preview h5,
|
|
35
|
+
.deco-post-preview h6 {
|
|
36
|
+
color: #161616;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.deco-post-preview h1 {
|
|
40
|
+
font-size: 2.25rem;
|
|
41
|
+
margin-top: 2.5rem;
|
|
42
|
+
margin-bottom: 1rem;
|
|
43
|
+
line-height: 2.5rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.deco-post-preview h2 {
|
|
47
|
+
font-size: 1.5rem;
|
|
48
|
+
margin-top: 2rem;
|
|
49
|
+
margin-bottom: 0.75rem;
|
|
50
|
+
line-height: 2rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.deco-post-preview h3 {
|
|
54
|
+
font-size: 1.25rem;
|
|
55
|
+
margin-top: 1.75rem;
|
|
56
|
+
margin-bottom: 0.5rem;
|
|
57
|
+
line-height: 1.75rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.deco-post-preview h4 {
|
|
61
|
+
font-size: 1rem;
|
|
62
|
+
margin-top: 1.5rem;
|
|
63
|
+
margin-bottom: 0.5rem;
|
|
64
|
+
line-height: 1.5rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.deco-post-preview h5 {
|
|
68
|
+
font-size: 0.875rem;
|
|
69
|
+
margin-top: 1.25rem;
|
|
70
|
+
margin-bottom: 0.25rem;
|
|
71
|
+
line-height: 1.25rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.deco-post-preview h6 {
|
|
75
|
+
font-size: 0.75rem;
|
|
76
|
+
margin-top: 1rem;
|
|
77
|
+
margin-bottom: 0.25rem;
|
|
78
|
+
line-height: 1rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.deco-post-preview time,
|
|
82
|
+
.deco-post-preview address {
|
|
83
|
+
font-size: 0.875rem;
|
|
84
|
+
color: #555;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.deco-post-preview a[rel="tag"] {
|
|
88
|
+
background-color: #f0f0f0;
|
|
89
|
+
color: #333;
|
|
90
|
+
padding: 0.25rem 0.5rem;
|
|
91
|
+
border-radius: 0.25rem;
|
|
92
|
+
font-size: 0.875rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.deco-post-preview a {
|
|
96
|
+
color: #007bff;
|
|
97
|
+
text-decoration: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.deco-post-preview a:hover {
|
|
101
|
+
color: #0056b3;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.deco-post-preview pre {
|
|
105
|
+
background-color: #f4f4f4;
|
|
106
|
+
border: 1px solid #ddd;
|
|
107
|
+
padding: 1rem;
|
|
108
|
+
border-radius: 0.25rem;
|
|
109
|
+
overflow-x: auto;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.deco-post-preview code {
|
|
113
|
+
font-family: 'Courier New', monospace;
|
|
114
|
+
background-color: #f4f4f4;
|
|
115
|
+
padding: 0.25rem 0.5rem;
|
|
116
|
+
border-radius: 0.25rem;
|
|
117
|
+
font-size: 0.875rem;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.deco-post-preview ul,
|
|
121
|
+
.deco-post-preview ol {
|
|
122
|
+
margin-left: 1.5rem;
|
|
123
|
+
margin-bottom: 1rem;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.decopost-preview ul {
|
|
127
|
+
list-style-type: disc;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.deco-post-preview ol {
|
|
131
|
+
list-style-type: decimal;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.deco-post-preview li {
|
|
135
|
+
margin-bottom: 0.25rem;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.deco-post-preview blockquote {
|
|
139
|
+
font-style: italic;
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
padding-left: 1rem;
|
|
142
|
+
color: #555;
|
|
143
|
+
border-left: 4px solid #ddd;
|
|
144
|
+
margin: 0 0 1rem;
|
|
145
|
+
line-height: 1.75rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.deco-post-preview img {
|
|
149
|
+
max-width: 100%;
|
|
150
|
+
height: auto;
|
|
151
|
+
margin-bottom: 1rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.deco-post-preview table {
|
|
155
|
+
border-collapse: collapse;
|
|
156
|
+
width: 100%;
|
|
157
|
+
margin-bottom: 1rem;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.deco-post-preview th,
|
|
161
|
+
.deco-post-preview td {
|
|
162
|
+
text-align: left;
|
|
163
|
+
padding: 0.5rem;
|
|
164
|
+
border: 1px solid #ddd;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.deco-post-preview th {
|
|
168
|
+
background-color: #f4f4f4;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.deco-post-preview hr {
|
|
172
|
+
border: 0;
|
|
173
|
+
height: 1px;
|
|
174
|
+
background: #e1e1e1;
|
|
175
|
+
margin-bottom: 1rem;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
`;
|
package/src/types.ts
CHANGED
|
@@ -1,105 +1,361 @@
|
|
|
1
|
+
import type { PageInfo, Person, Thing } from "@decocms/apps-commerce/types";
|
|
1
2
|
import type { ImageWidget } from "@decocms/apps-website/types";
|
|
3
|
+
import { scheduledTime } from "./utils/date";
|
|
4
|
+
|
|
5
|
+
export type { PageInfo };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Opaque "pick any section" reference, as authored in the decofile:
|
|
9
|
+
* `{ "__resolveType": "site/sections/Foo.tsx", ...props }`.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately `any`, and deliberately named `Section`. `@decocms/blocks-cli`'s
|
|
12
|
+
* schema generator only emits the admin's section-picker widget for a prop whose
|
|
13
|
+
* type is *named* `Section` **and** resolves to `any`/`unknown`
|
|
14
|
+
* (`generate-schema.ts`, `isOpaqueSectionType`) — a concrete object shape is
|
|
15
|
+
* treated as user data and rendered as an inline editable form instead. So this
|
|
16
|
+
* is not laziness: narrowing it would silently downgrade the editor experience.
|
|
17
|
+
*
|
|
18
|
+
* Note this is NOT `Section` from `@decocms/blocks/types`, which is the
|
|
19
|
+
* *resolved* `{ Component, props }` pair produced after block resolution.
|
|
20
|
+
*/
|
|
21
|
+
export type Section = any;
|
|
2
22
|
|
|
3
23
|
/**
|
|
4
24
|
* @titleBy name
|
|
5
25
|
* @widget author
|
|
6
26
|
*/
|
|
7
27
|
export interface Author {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
name: string;
|
|
29
|
+
email: string;
|
|
30
|
+
/**
|
|
31
|
+
* @title Type
|
|
32
|
+
* @description Whether the author is a person or an organization. Emitted as the author @type in the JSON-LD. Defaults to Person.
|
|
33
|
+
* @default Person
|
|
34
|
+
*/
|
|
35
|
+
type?: "Person" | "Organization";
|
|
36
|
+
avatar?: ImageWidget;
|
|
37
|
+
jobTitle?: string;
|
|
38
|
+
company?: string;
|
|
39
|
+
url?: string;
|
|
13
40
|
}
|
|
14
41
|
|
|
15
42
|
export interface Category {
|
|
16
|
-
|
|
17
|
-
|
|
43
|
+
name: string;
|
|
44
|
+
slug: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
/**
|
|
47
|
+
* @title Parent category
|
|
48
|
+
* @description Slug of the parent category. Leave empty for a root category.
|
|
49
|
+
*/
|
|
50
|
+
parentSlug?: string;
|
|
51
|
+
/**
|
|
52
|
+
* @title Sections
|
|
53
|
+
* @label hidden
|
|
54
|
+
* @changeable true
|
|
55
|
+
*/
|
|
56
|
+
sections?: Section[];
|
|
18
57
|
}
|
|
19
58
|
|
|
20
59
|
export interface BlogPost {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
title: string;
|
|
61
|
+
excerpt: string;
|
|
62
|
+
/**
|
|
63
|
+
* @title Main image
|
|
64
|
+
*/
|
|
65
|
+
image?: ImageWidget;
|
|
66
|
+
/**
|
|
67
|
+
* @title Alt text for the image
|
|
68
|
+
*/
|
|
69
|
+
alt?: string;
|
|
70
|
+
/**
|
|
71
|
+
* @title Mobile image
|
|
72
|
+
* @description Optional mobile-optimized version of the main image. Served below 768px. Falls back to the main image when empty.
|
|
73
|
+
*/
|
|
74
|
+
mobileImage?: ImageWidget;
|
|
75
|
+
/**
|
|
76
|
+
* @widget blog
|
|
77
|
+
* @collection authors
|
|
78
|
+
*/
|
|
79
|
+
authors?: Author[];
|
|
80
|
+
/**
|
|
81
|
+
* @widget blog
|
|
82
|
+
* @collection categories
|
|
83
|
+
*/
|
|
84
|
+
categories?: Category[];
|
|
85
|
+
/**
|
|
86
|
+
* @format date
|
|
87
|
+
*/
|
|
88
|
+
date: string;
|
|
89
|
+
/**
|
|
90
|
+
* @title Modified date
|
|
91
|
+
* @format date
|
|
92
|
+
* @description Date of the last relevant content update. Emitted as dateModified in the JSON-LD.
|
|
93
|
+
*/
|
|
94
|
+
dateModified?: string;
|
|
95
|
+
slug: string;
|
|
96
|
+
/**
|
|
97
|
+
* @title Status
|
|
98
|
+
* @description Publication status. Anything other than `published` is kept out of listings and never indexed. Posts with no status are treated as published.
|
|
99
|
+
*/
|
|
100
|
+
status?: PostStatus;
|
|
101
|
+
/**
|
|
102
|
+
* @title Scheduled publication date
|
|
103
|
+
* @format datetime
|
|
104
|
+
* @description Instant this post goes live, honoured only while status is `scheduled`. Deliberately separate from the editorial date shown and sorted on: the two may diverge.
|
|
105
|
+
*/
|
|
106
|
+
scheduledDatetime?: string;
|
|
107
|
+
/**
|
|
108
|
+
* @title Post Content
|
|
109
|
+
* @format rich-text
|
|
110
|
+
*/
|
|
111
|
+
content?: string;
|
|
112
|
+
/**
|
|
113
|
+
* @title Sections
|
|
114
|
+
* @label hidden
|
|
115
|
+
* @changeable true
|
|
116
|
+
*/
|
|
117
|
+
sections?: Section[];
|
|
118
|
+
/**
|
|
119
|
+
* @title Carousel in post content
|
|
120
|
+
* @description add a carousel in the middle of the post. Must be implemented in frontEnd
|
|
121
|
+
*/
|
|
122
|
+
imageCarousel?: ImageCarousel;
|
|
123
|
+
/**
|
|
124
|
+
* @title SEO
|
|
125
|
+
*/
|
|
126
|
+
seo?: Seo;
|
|
127
|
+
/**
|
|
128
|
+
* @title ReadTime in minutes
|
|
129
|
+
*/
|
|
130
|
+
readTime?: number;
|
|
131
|
+
/**
|
|
132
|
+
* @title Extra Props
|
|
133
|
+
*/
|
|
134
|
+
extraProps?: ExtraProps[];
|
|
135
|
+
/** @hide true */
|
|
136
|
+
aggregateRating?: AggregateRating;
|
|
137
|
+
/** @hide true */
|
|
138
|
+
review?: Review[];
|
|
139
|
+
/** @hide true */
|
|
140
|
+
contentRating?: Rating[];
|
|
141
|
+
/** @hide true */
|
|
142
|
+
interactionStatistic?: InteractionCounter;
|
|
143
|
+
id?: string;
|
|
70
144
|
}
|
|
71
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Publication status of a post. `published` (or an absent value, for legacy
|
|
148
|
+
* posts) renders on the live site; `scheduled` renders once its
|
|
149
|
+
* `scheduledDatetime` has passed; every other value keeps the post out of
|
|
150
|
+
* listings and out of the index.
|
|
151
|
+
*
|
|
152
|
+
* `generating` and `awaiting_review` are written by the autonomous-blog agent
|
|
153
|
+
* while a post is still being produced, which is why the checks below are
|
|
154
|
+
* allowlists: a status this app does not recognize is a post the CMS does not
|
|
155
|
+
* consider ready, so it must not leak into a listing.
|
|
156
|
+
*/
|
|
157
|
+
export type PostStatus =
|
|
158
|
+
| "draft"
|
|
159
|
+
| "published"
|
|
160
|
+
| "scheduled"
|
|
161
|
+
| "archived"
|
|
162
|
+
| "generating"
|
|
163
|
+
| "awaiting_review";
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* A post is live when it has no status at all or is explicitly `published`.
|
|
167
|
+
*
|
|
168
|
+
* The absent case is load-bearing: `status` was added long after the first
|
|
169
|
+
* posts were written, so every existing record is missing it. Requiring an
|
|
170
|
+
* explicit `published` would empty every blog in production the moment a site
|
|
171
|
+
* bumps this app.
|
|
172
|
+
*
|
|
173
|
+
* Takes a plain `string` so it can also be applied to a raw CMS record, where
|
|
174
|
+
* the value is only a `PostStatus` by convention.
|
|
175
|
+
*/
|
|
176
|
+
export const isPublishedStatus = (status?: string): boolean => !status || status === "published";
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Whether a post is live *right now* — the read-time half of scheduling.
|
|
180
|
+
*
|
|
181
|
+
* A scheduled post is merged to production ahead of its go-live instant, so
|
|
182
|
+
* nothing rewrites the record when that instant arrives: this comparison is
|
|
183
|
+
* what flips it, on whichever request first evaluates it after the fact.
|
|
184
|
+
*
|
|
185
|
+
* Like `isPublishedStatus`, this is an allowlist and stays one deliberately:
|
|
186
|
+
* only an absent/empty status, an explicit `published`, or a `scheduled` post
|
|
187
|
+
* whose instant has arrived is live. That makes every status added later — by
|
|
188
|
+
* the CMS or by an agent — fail closed on an app version that predates it,
|
|
189
|
+
* hiding the post instead of leaking a half-written one. A `status !== "draft"`
|
|
190
|
+
* blacklist would lose that property permanently.
|
|
191
|
+
*/
|
|
192
|
+
export const isLivePost = (
|
|
193
|
+
post: { status?: string; scheduledDatetime?: string },
|
|
194
|
+
now: number = Date.now(),
|
|
195
|
+
): boolean => {
|
|
196
|
+
if (isPublishedStatus(post.status)) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (post.status !== "scheduled") {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
const goLive = post.scheduledDatetime ? scheduledTime(post.scheduledDatetime) : null;
|
|
203
|
+
// A missing or unreadable instant is rejected rather than compared: this is
|
|
204
|
+
// the fail-closed guarantee, not a redundant null check. `scheduledTime`
|
|
205
|
+
// returns null (not 0) for a bad value precisely so that a real instant which
|
|
206
|
+
// happens to be the epoch is still honoured here.
|
|
207
|
+
return goLive !== null && goLive <= now;
|
|
208
|
+
};
|
|
209
|
+
|
|
72
210
|
export interface ExtraProps {
|
|
73
|
-
|
|
74
|
-
|
|
211
|
+
key: string;
|
|
212
|
+
value: string;
|
|
75
213
|
}
|
|
76
214
|
|
|
77
215
|
export interface Seo {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
216
|
+
title?: string;
|
|
217
|
+
description?: string;
|
|
218
|
+
image?: ImageWidget;
|
|
219
|
+
canonical?: string;
|
|
220
|
+
noIndexing?: boolean;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** @titleBy name */
|
|
224
|
+
export interface Publisher {
|
|
225
|
+
name: string;
|
|
226
|
+
/** @title Logo */
|
|
227
|
+
logo?: ImageWidget;
|
|
228
|
+
url?: string;
|
|
83
229
|
}
|
|
84
230
|
|
|
85
231
|
export interface BlogPostPage {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
232
|
+
"@type": "BlogPostPage";
|
|
233
|
+
post: BlogPost;
|
|
234
|
+
/**
|
|
235
|
+
* @title Category path
|
|
236
|
+
* @description Ancestor chain of the post's primary category, root first.
|
|
237
|
+
*/
|
|
238
|
+
categories?: Category[] | null;
|
|
239
|
+
seo?: Seo | null;
|
|
89
240
|
}
|
|
90
241
|
|
|
91
|
-
export type SortBy =
|
|
242
|
+
export type SortBy =
|
|
243
|
+
| "date_desc"
|
|
244
|
+
| "date_asc"
|
|
245
|
+
| "title_asc"
|
|
246
|
+
| "title_desc"
|
|
247
|
+
| "view_asc"
|
|
248
|
+
| "view_desc";
|
|
92
249
|
|
|
93
|
-
export interface
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
250
|
+
export interface BlogPostListingPage {
|
|
251
|
+
posts: BlogPost[];
|
|
252
|
+
/** @title Active category */
|
|
253
|
+
category?: Category | null;
|
|
254
|
+
/** @title Categories */
|
|
255
|
+
categories?: Category[] | null;
|
|
256
|
+
/**
|
|
257
|
+
* @title Category path
|
|
258
|
+
* @description Ancestor chain of the active category, root first.
|
|
259
|
+
*/
|
|
260
|
+
categoryPath?: Category[] | null;
|
|
261
|
+
pageInfo: PageInfo;
|
|
262
|
+
seo: Seo;
|
|
99
263
|
}
|
|
100
264
|
|
|
101
|
-
export interface
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
265
|
+
export interface ImageCarousel {
|
|
266
|
+
banners?: Banner[];
|
|
267
|
+
description?: string;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface Review {
|
|
271
|
+
"@type": "Review";
|
|
272
|
+
id?: string;
|
|
273
|
+
/** Author of the review */
|
|
274
|
+
author?: Person;
|
|
275
|
+
/** The date that the review was published, in ISO 8601 date format.*/
|
|
276
|
+
datePublished?: string;
|
|
277
|
+
/** The date that the review was modified, in ISO 8601 date format.*/
|
|
278
|
+
dateModified?: string;
|
|
279
|
+
/** The item that is being reviewed/rated. */
|
|
280
|
+
itemReviewed?: string;
|
|
281
|
+
/** Emphasis part of the review */
|
|
282
|
+
reviewHeadline?: string;
|
|
283
|
+
/** The actual body of the review. */
|
|
284
|
+
reviewBody?: string;
|
|
285
|
+
/** Review status */
|
|
286
|
+
additionalType?: string;
|
|
287
|
+
/** Anonymous comment. Not in Schema.org */
|
|
288
|
+
isAnonymous?: boolean;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export interface Rating {
|
|
292
|
+
"@type": "Rating";
|
|
293
|
+
id?: string;
|
|
294
|
+
/** The author of this content or rating. */
|
|
295
|
+
author?: Person;
|
|
296
|
+
/** The item that is being reviewed/rated. */
|
|
297
|
+
itemReviewed?: string;
|
|
298
|
+
/** The highest value allowed in this rating system. */
|
|
299
|
+
bestRating?: number;
|
|
300
|
+
/** The lowest value allowed in this rating system. */
|
|
301
|
+
worstRating?: number;
|
|
302
|
+
/** A short explanation providing background context and other information that led to the conclusion expressed in the rating. */
|
|
303
|
+
ratingValue?: number;
|
|
304
|
+
/** Review status */
|
|
305
|
+
additionalType?: string;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export interface AggregateRating {
|
|
309
|
+
"@type": "AggregateRating";
|
|
310
|
+
/** The count of total number of ratings. */
|
|
311
|
+
ratingCount?: number;
|
|
312
|
+
/** The count of total number of reviews. */
|
|
313
|
+
reviewCount?: number;
|
|
314
|
+
/** The rating for the content. */
|
|
315
|
+
ratingValue?: number;
|
|
316
|
+
/** The highest value allowed in this rating system. */
|
|
317
|
+
bestRating?: number;
|
|
318
|
+
/** The lowest value allowed in this rating system. */
|
|
319
|
+
worstRating?: number;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export interface InteractionCounter extends Omit<Thing, "@type"> {
|
|
323
|
+
"@type": "InteractionCounter";
|
|
324
|
+
/** The number of interactions or views */
|
|
325
|
+
userInteractionCount?: number;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface ViewFromDatabase {
|
|
329
|
+
id: string;
|
|
330
|
+
userInteractionCount?: number;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export interface Ignore {
|
|
334
|
+
/**
|
|
335
|
+
* @title Active
|
|
336
|
+
*/
|
|
337
|
+
active?: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* @title When additionalType is marked with:
|
|
340
|
+
*/
|
|
341
|
+
markedAs?: string[];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export interface BannerItem {
|
|
345
|
+
image?: ImageWidget;
|
|
346
|
+
width?: number;
|
|
347
|
+
height?: number;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export interface Banner {
|
|
351
|
+
/** @description desktop otimized image */
|
|
352
|
+
desktop: BannerItem;
|
|
353
|
+
/** @description mobile otimized image */
|
|
354
|
+
mobile: BannerItem;
|
|
355
|
+
/** @description Image's alt text */
|
|
356
|
+
alt: string;
|
|
357
|
+
action?: {
|
|
358
|
+
/** @description when user clicks on the image, go to this link */
|
|
359
|
+
href: string;
|
|
360
|
+
};
|
|
105
361
|
}
|