nextpress-core 1.0.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/lib/acf-functions/core/acf-builder.ts +105 -0
- package/lib/acf-functions/core/acf-component-autoloader.ts +33 -0
- package/lib/acf-functions/core/acf-field-group-autoloader.ts +28 -0
- package/lib/acf-functions/services/define-field-group.ts +10 -0
- package/lib/acf-functions/services/define-layout.ts +10 -0
- package/lib/acf-functions/services/map-fields/helpers/map-choice-object.ts +20 -0
- package/lib/acf-functions/services/map-fields/map-fields.ts +359 -0
- package/lib/acf-functions/types/acf-field-group.d.ts +118 -0
- package/lib/acf-functions/types/acf-field.d.ts +2851 -0
- package/lib/acf-functions/types/acf-layout.d.ts +20 -0
- package/lib/acf-functions/types/acf-values.d.ts +4 -0
- package/lib/acf-functions/types/components/field-props.d.ts +189 -0
- package/lib/acf-functions/types/components/nextpress-component.d.ts +11 -0
- package/lib/ambient.d.ts +15 -0
- package/lib/entities/common.d.ts +30 -0
- package/lib/entities/option/option.interface.d.ts +8 -0
- package/lib/entities/option/option.ts +43 -0
- package/lib/entities/post/post.interface.d.ts +72 -0
- package/lib/entities/post/post.ts +209 -0
- package/lib/entities/term/term.interface.d.ts +8 -0
- package/lib/entities/term/term.ts +82 -0
- package/lib/entities/user/user.interface.d.ts +10 -0
- package/lib/entities/user/user.ts +86 -0
- package/lib/globals/entity-loader/entity-loader-base.ts +141 -0
- package/lib/globals/entity-loader/entity-loader.d.ts +50 -0
- package/lib/globals/entity-loader/option-loader.ts +56 -0
- package/lib/globals/entity-loader/post-loader.ts +59 -0
- package/lib/globals/entity-loader/term-loader.ts +59 -0
- package/lib/globals/entity-loader/user-loader.ts +60 -0
- package/lib/globals/get-field/get-field.ts +75 -0
- package/lib/globals/globals.ts +25 -0
- package/lib/globals/nextpress-config/nextpress-config.interface.d.ts +18 -0
- package/lib/globals/nextpress-config/nextpress-config.ts +7 -0
- package/lib/globals/queried-object/queried-object.ts +124 -0
- package/lib/repository/optionquery/option-query-args.d.ts +21 -0
- package/lib/repository/optionquery/option-query.ts +29 -0
- package/lib/repository/postquery/post-query-args.d.ts +108 -0
- package/lib/repository/postquery/post-query.ts +281 -0
- package/lib/repository/termquery/term-query-args.d.ts +61 -0
- package/lib/repository/termquery/term-query.ts +243 -0
- package/lib/repository/userquery/user-query-args.d.ts +75 -0
- package/lib/repository/userquery/user-query.ts +195 -0
- package/lib/router/helpers.ts +20 -0
- package/lib/router/nextpress-layout.tsx +48 -0
- package/lib/router/nextpress-proxy.ts +45 -0
- package/lib/router/nextpress-static-params.ts +62 -0
- package/lib/router/router.tsx +61 -0
- package/lib/router/routes/api/api-get-admin-bar.ts +29 -0
- package/lib/router/routes/api/api-get-draft-mode.ts +47 -0
- package/lib/router/routes/api/api-get-field-groups.ts +29 -0
- package/lib/router/routes/api/api-post-revalidate.ts +30 -0
- package/lib/router/routes/api/helpers.ts +17 -0
- package/lib/router/routes/author-archive.tsx +75 -0
- package/lib/router/routes/not-found-page.tsx +22 -0
- package/lib/router/routes/post-index-page.tsx +58 -0
- package/lib/router/routes/singular-page.tsx +78 -0
- package/lib/router/routes/site-front-page.tsx +51 -0
- package/lib/router/routes/term-archive.tsx +93 -0
- package/lib/router/types.d.ts +9 -0
- package/lib/services/get-menu.ts +104 -0
- package/lib/services/get-theme-mods.ts +16 -0
- package/lib/services/metadata/get-blogname.ts +9 -0
- package/lib/services/metadata/get-favicon-url.ts +13 -0
- package/lib/services/metadata/get-language-attribute.ts +9 -0
- package/lib/services/services.ts +7 -0
- package/lib/services/utilities/capitalise-first-letter.ts +10 -0
- package/lib/services/utilities/esc-html.ts +12 -0
- package/lib/services/utilities/get-date-time-formatter.ts +49 -0
- package/lib/services/utilities/index.ts +0 -0
- package/lib/services/utilities/kses-post.ts +13 -0
- package/lib/services/utilities/process-url.ts +15 -0
- package/lib/template-heirarchy/_autoloader/template-autoloader.ts +54 -0
- package/lib/template-heirarchy/archive/archive.tsx +28 -0
- package/lib/template-heirarchy/archive/author.tsx +28 -0
- package/lib/template-heirarchy/archive/category.tsx +29 -0
- package/lib/template-heirarchy/archive/posttypearchive.tsx +33 -0
- package/lib/template-heirarchy/archive/tag.tsx +30 -0
- package/lib/template-heirarchy/archive/taxonomy.tsx +28 -0
- package/lib/template-heirarchy/home/home.tsx +28 -0
- package/lib/template-heirarchy/index.tsx +30 -0
- package/lib/template-heirarchy/not-found.tsx/not-found.tsx +28 -0
- package/lib/template-heirarchy/page/page.tsx +28 -0
- package/lib/template-heirarchy/page/posttype.tsx +34 -0
- package/lib/template-heirarchy/page/single.tsx +28 -0
- package/lib/template-heirarchy/page/singular.tsx +28 -0
- package/lib/ui/render-attachment-image.tsx +29 -0
- package/lib/ui/render-components.tsx +12 -0
- package/lib/ui/render-the-admin-bar.tsx +79 -0
- package/lib/ui/render-the-logo.tsx +24 -0
- package/lib/wpdb/wpdb.interface.d.ts +171 -0
- package/lib/wpdb/wpdb.ts +36 -0
- package/package.json +38 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type TransformACFLayout<T> = T extends any
|
|
2
|
+
? Omit<T, 'key' | 'sub_fields'> &
|
|
3
|
+
('sub_fields' extends keyof T ? { sub_fields: NextpressField[] } : [])
|
|
4
|
+
: never;
|
|
5
|
+
|
|
6
|
+
/** ACF Layout without key constraints to allow key generation */
|
|
7
|
+
type NextpressLayout = TransformACFLayout<ACFLayout>
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents a layout within an ACF Flexible Content field.
|
|
11
|
+
*/
|
|
12
|
+
type ACFLayout = {
|
|
13
|
+
key: string,
|
|
14
|
+
name: string,
|
|
15
|
+
label: string,
|
|
16
|
+
display: 'table' | 'block' | 'row',
|
|
17
|
+
sub_fields: ACFField[],
|
|
18
|
+
min?: string,
|
|
19
|
+
max?: string
|
|
20
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { IPost } from "@/lib/nextpress/entities/post/post.interface";
|
|
2
|
+
import { ITerm } from "@/lib/nextpress/entities/term/term.interface";
|
|
3
|
+
import { IUser } from "@/lib/nextpress/entities/user/user.interface";
|
|
4
|
+
import { JSX } from "react";
|
|
5
|
+
|
|
6
|
+
type GetFields<T> =
|
|
7
|
+
T extends { fields: readonly any[] } ? T['fields']
|
|
8
|
+
: T extends { sub_fields: readonly any[] } ? T['sub_fields']
|
|
9
|
+
: never;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents the properties passed to a Nextpress ACF component.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The expected type of the mapped ACF fields.
|
|
15
|
+
*/
|
|
16
|
+
type FieldProps<LayoutT> =
|
|
17
|
+
LayoutT extends { fields: readonly any[] } | { sub_fields: readonly any[] }
|
|
18
|
+
? ResolvedFields<GetFields<LayoutT>>
|
|
19
|
+
: never;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An array of ACF field configurations to a strongly-typed object representing the resolved data structure.
|
|
23
|
+
* It iterates over the array, using the `name` property of each field as the object key, and determines the expected value type using the `MapFieldType` utility.
|
|
24
|
+
*
|
|
25
|
+
* @template Fields - A readonly array of ACF field configuration objects.
|
|
26
|
+
*/
|
|
27
|
+
type ResolvedFields<Fields extends readonly any[]> = {
|
|
28
|
+
[F in Fields[number] as F['name']]: MapFieldType<F>
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Maps an array of ACF Flexible Content layouts to a union type representing the resolved layout blocks.
|
|
33
|
+
* For each layout, it checks for the presence of `fields` or `sub_fields`, and resolves to a specific object
|
|
34
|
+
* structure containing the layout's React component and its deeply resolved field content.
|
|
35
|
+
*
|
|
36
|
+
* @template Layouts - A readonly array of ACF Flexible Content layout configuration objects.
|
|
37
|
+
*/
|
|
38
|
+
type ResolvedFlexibleContent<Layouts extends readonly any[]> = {
|
|
39
|
+
[L in Layouts[number] as L['name']]: L extends { name: infer Name } & ({ fields: readonly any[] } | { sub_fields: readonly any[] })
|
|
40
|
+
? {
|
|
41
|
+
Component: () => Promise<JSX.Element>,
|
|
42
|
+
content: ResolvedFields<GetFields<L>>
|
|
43
|
+
}[]
|
|
44
|
+
: never
|
|
45
|
+
}[Layouts[number]['name']];
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
type MapFieldType<Field> =
|
|
49
|
+
Field extends { type: 'color_picker' }
|
|
50
|
+
? string | null
|
|
51
|
+
: Field extends { type: 'date_picker' }
|
|
52
|
+
? string | null
|
|
53
|
+
: Field extends { type: 'date_time_picker' }
|
|
54
|
+
? string | null
|
|
55
|
+
: Field extends { type: 'google_map' }
|
|
56
|
+
? ACFGoogleMapsObject | null
|
|
57
|
+
: Field extends { type: 'icon_picker' }
|
|
58
|
+
? Field extends { return_format: 'string' }
|
|
59
|
+
? string | null
|
|
60
|
+
: Field extends { return_format: 'array' }
|
|
61
|
+
? ACFIconObject | null
|
|
62
|
+
: ACFIconObject | null
|
|
63
|
+
: Field extends { type: 'time_picker' }
|
|
64
|
+
? string | null
|
|
65
|
+
: Field extends { type: 'email' }
|
|
66
|
+
? string | null
|
|
67
|
+
: Field extends { type: 'number' }
|
|
68
|
+
? number | null
|
|
69
|
+
: Field extends { type: 'password' }
|
|
70
|
+
? string | null
|
|
71
|
+
: Field extends { type: 'range' }
|
|
72
|
+
? number | null
|
|
73
|
+
: Field extends { type: 'text' }
|
|
74
|
+
? string | null
|
|
75
|
+
: Field extends { type: 'textarea' }
|
|
76
|
+
? string | null
|
|
77
|
+
: Field extends { type: 'button_group' }
|
|
78
|
+
? Field extends { return_format: 'array' }
|
|
79
|
+
? ACFChoiceObject | null
|
|
80
|
+
: string | null
|
|
81
|
+
: Field extends { type: 'checkbox' }
|
|
82
|
+
? Field extends { return_format: 'array' }
|
|
83
|
+
? ACFChoiceObject[]
|
|
84
|
+
: string[]
|
|
85
|
+
: Field extends { type: 'radio' }
|
|
86
|
+
? Field extends { return_format: 'array' }
|
|
87
|
+
? ACFChoiceObject | null
|
|
88
|
+
: string | null
|
|
89
|
+
: Field extends { type: 'select' }
|
|
90
|
+
? Field extends { multiple: 1 }
|
|
91
|
+
? Field extends { return_format: 'array' }
|
|
92
|
+
? ACFChoiceObject[]
|
|
93
|
+
: string[]
|
|
94
|
+
: Field extends { return_format: 'array' }
|
|
95
|
+
? ACFChoiceObject | null
|
|
96
|
+
: string | null
|
|
97
|
+
: Field extends { type: 'true_false' }
|
|
98
|
+
? boolean
|
|
99
|
+
: Field extends { type: 'file' }
|
|
100
|
+
? number | null
|
|
101
|
+
: Field extends { type: 'gallery' }
|
|
102
|
+
? number[]
|
|
103
|
+
: Field extends { type: 'image' }
|
|
104
|
+
? number | null
|
|
105
|
+
: Field extends { type: 'oembed' }
|
|
106
|
+
? string | null
|
|
107
|
+
: Field extends { type: 'wysiwyg' }
|
|
108
|
+
? string | null
|
|
109
|
+
: Field extends { type: 'link' }
|
|
110
|
+
? Field extends { return_format: 'url' }
|
|
111
|
+
? string | null
|
|
112
|
+
: Field extends { return_format: 'array' }
|
|
113
|
+
? ACFLinkObject | null
|
|
114
|
+
: ACFLinkObject
|
|
115
|
+
: Field extends { type: 'page_link' }
|
|
116
|
+
? Field extends { multiple: 1 }
|
|
117
|
+
? string[]
|
|
118
|
+
: string | null
|
|
119
|
+
: Field extends { type: 'post_object' }
|
|
120
|
+
? Field extends { multiple: 1 }
|
|
121
|
+
? Field extends { return_format: 'object' }
|
|
122
|
+
? IPost[]
|
|
123
|
+
: number[]
|
|
124
|
+
: Field extends { return_format: 'object' }
|
|
125
|
+
? IPost | null
|
|
126
|
+
: number | null
|
|
127
|
+
: Field extends { type: 'relationship' }
|
|
128
|
+
? Field extends { return_format: 'object' }
|
|
129
|
+
? IPost[] | null
|
|
130
|
+
: number[] | null
|
|
131
|
+
: Field extends { type: 'taxonomy' }
|
|
132
|
+
? Field extends { multiple: 1 }
|
|
133
|
+
? Field extends { return_format: 'object' }
|
|
134
|
+
? ITerm[]
|
|
135
|
+
: number[]
|
|
136
|
+
: Field extends { return_format: 'object' }
|
|
137
|
+
? ITerm | null
|
|
138
|
+
: number | null
|
|
139
|
+
: Field extends { type: 'user' }
|
|
140
|
+
? Field extends { multiple: 1 }
|
|
141
|
+
? Field extends { return_format: 'object' }
|
|
142
|
+
? IUser[]
|
|
143
|
+
: number[]
|
|
144
|
+
: Field extends { return_format: 'object' }
|
|
145
|
+
? IUser | null
|
|
146
|
+
: number | null
|
|
147
|
+
: Field extends { type: 'group' } & ({ fields: readonly any[] } | { sub_fields: readonly any[] })
|
|
148
|
+
? ResolvedFields<GetFields<Field>> | null
|
|
149
|
+
: Field extends { type: 'repeater' } & ({ fields: readonly any[] } | { sub_fields: readonly any[] })
|
|
150
|
+
? ResolvedFields<GetFields<Field>>[] | null
|
|
151
|
+
: Field extends { type: 'flexible_content'; layouts: readonly any[] }
|
|
152
|
+
? ResolvedFlexibleContent<Field['layouts']> | null
|
|
153
|
+
: Field extends { fields: readonly any[] } | { sub_fields: readonly any[] }
|
|
154
|
+
? ResolvedFields<GetFields<Field>>
|
|
155
|
+
: never;
|
|
156
|
+
|
|
157
|
+
type ACFIconObject = {
|
|
158
|
+
type: string,
|
|
159
|
+
value: string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type ACFLinkObject = {
|
|
163
|
+
title: string,
|
|
164
|
+
url: string,
|
|
165
|
+
target: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
type ACFChoiceObject = {
|
|
169
|
+
label?: string
|
|
170
|
+
value?: string,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
type ACFGoogleMapsObject = {
|
|
174
|
+
address: string;
|
|
175
|
+
lat: string | number;
|
|
176
|
+
lng: string | number;
|
|
177
|
+
zoom: string | number;
|
|
178
|
+
place_id: string;
|
|
179
|
+
name: string;
|
|
180
|
+
street_number: string | number;
|
|
181
|
+
street_name: string;
|
|
182
|
+
street_name_short: string;
|
|
183
|
+
city: string;
|
|
184
|
+
state: string;
|
|
185
|
+
state_short: string;
|
|
186
|
+
post_code: string | number;
|
|
187
|
+
country: string;
|
|
188
|
+
country_short: string;
|
|
189
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { JSX } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a Next.js React component that consumes Nextpress ACF fields.
|
|
5
|
+
*
|
|
6
|
+
* @template T - The expected type of the mapped ACF fields.
|
|
7
|
+
*/
|
|
8
|
+
type NextpressComponent = {
|
|
9
|
+
layout: NextpressLayout
|
|
10
|
+
Component: () => Promise<JSX.Element>
|
|
11
|
+
}
|
package/lib/ambient.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare module '*nextpress.config' {
|
|
2
|
+
const nextpressConfig: NextpressConfig;
|
|
3
|
+
export default nextpressConfig;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module '@/fonts' {
|
|
7
|
+
type NextFont = import('next/dist/compiled/@next/font').NextFont;
|
|
8
|
+
|
|
9
|
+
const fonts: NextFont[];
|
|
10
|
+
export default fonts;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare module '@/app/_templates/layout' {
|
|
14
|
+
export const LayoutTemplate: ({ children }: Readonly<{children: import('react').React.ReactNode}>) => Promise<import('react').JSX.Element>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents entity with a path.
|
|
3
|
+
*/
|
|
4
|
+
export interface IPath {
|
|
5
|
+
/** Nextpress path to entity. */
|
|
6
|
+
path: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Array of field key-value pairs.
|
|
11
|
+
*/
|
|
12
|
+
type Fields = {
|
|
13
|
+
/** Field key. */
|
|
14
|
+
key: string,
|
|
15
|
+
/** Field value. */
|
|
16
|
+
value: string
|
|
17
|
+
}[];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Represents entity that can provide ACF fields.
|
|
21
|
+
*/
|
|
22
|
+
export interface IFieldLocation {
|
|
23
|
+
/**
|
|
24
|
+
* Retrieves fields matching a name prefix.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} name - Field name prefix to search for.
|
|
27
|
+
* @returns {Promise<Fields>} Promise resolving to array of fields.
|
|
28
|
+
*/
|
|
29
|
+
getFields: (name: string) => Promise<Fields>
|
|
30
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { wpdb } from "@/wpdb/wpdb";
|
|
2
|
+
import { IOption } from "./option.interface";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Implementation of IOption entity.
|
|
6
|
+
*/
|
|
7
|
+
export class Option implements IOption
|
|
8
|
+
{
|
|
9
|
+
constructor(public optionId: number) {};
|
|
10
|
+
|
|
11
|
+
/** Raw option data from database. */
|
|
12
|
+
private optionData?: Record<string, any>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Retrieves array of Option instances by IDs.
|
|
16
|
+
*
|
|
17
|
+
* @param {number[]} ids - Array of option IDs to retrieve.
|
|
18
|
+
* @returns {Promise<Option[]>} Promise resolving to array of Option instances.
|
|
19
|
+
*/
|
|
20
|
+
static async get(ids: number[]): Promise<Option[]> {
|
|
21
|
+
ids = ids.filter(Boolean);
|
|
22
|
+
if (!ids || !ids.length) return [];
|
|
23
|
+
|
|
24
|
+
const optionData = await wpdb
|
|
25
|
+
.selectFrom('wpOptions')
|
|
26
|
+
.where('optionId', 'in', ids)
|
|
27
|
+
.select(['optionId', 'optionName', 'optionValue'])
|
|
28
|
+
.execute();
|
|
29
|
+
|
|
30
|
+
const optionDataMap = new Map(optionData.map(option => [Number(option.optionId), option]));
|
|
31
|
+
|
|
32
|
+
return ids.map(id => {
|
|
33
|
+
const instance = new Option(id);
|
|
34
|
+
|
|
35
|
+
instance.optionData = optionDataMap.get(id);
|
|
36
|
+
|
|
37
|
+
return instance;
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get optionName(): string { return this.optionData?.['optionName'] ?? ''};
|
|
42
|
+
get optionValue(): string { return this.optionData?.['optionValue'] ?? ''};
|
|
43
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Selectable } from "kysely";
|
|
2
|
+
import { WpPost } from "../../wpdb/wpdb.interface";
|
|
3
|
+
import { IFieldLocation, IPath } from "../common";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Attributes for post attachment images.
|
|
7
|
+
*/
|
|
8
|
+
type PostImageAttributes = {
|
|
9
|
+
/** Image source URL. */
|
|
10
|
+
src?: string,
|
|
11
|
+
/** Image alternative text. */
|
|
12
|
+
alt?: string,
|
|
13
|
+
/** Image height. */
|
|
14
|
+
height?: number,
|
|
15
|
+
/** Image width. */
|
|
16
|
+
width?: number,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Attributes for nav menu items.
|
|
21
|
+
*/
|
|
22
|
+
type MenuItemAttributes = {
|
|
23
|
+
/** Menu item label. */
|
|
24
|
+
label: string,
|
|
25
|
+
/** Menu item type. */
|
|
26
|
+
type: 'custom' | 'post_type' | 'taxonomy',
|
|
27
|
+
/** Parent menu item ID. */
|
|
28
|
+
parentId: number,
|
|
29
|
+
/** Target object ID. */
|
|
30
|
+
objectId: number,
|
|
31
|
+
/** Menu item URL. */
|
|
32
|
+
url: string,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Base post properties.
|
|
37
|
+
*/
|
|
38
|
+
interface IBasePost extends Selectable<WpPost> {}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Page post properties.
|
|
42
|
+
*/
|
|
43
|
+
interface IPagePost extends IBasePost, IPath {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Standard post properties.
|
|
47
|
+
*/
|
|
48
|
+
interface IPostPost extends IBasePost, IPath {
|
|
49
|
+
/** Featured image attachment ID. */
|
|
50
|
+
thumbnailId: number,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Attachment post properties.
|
|
55
|
+
*/
|
|
56
|
+
interface IAttachmentPost extends IBasePost {
|
|
57
|
+
/** Image attributes. */
|
|
58
|
+
imageAttributes: PostImageAttributes,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Menu item properties.
|
|
63
|
+
*/
|
|
64
|
+
interface IMenuItem extends IBasePost {
|
|
65
|
+
/** Menu item attributes. */
|
|
66
|
+
menuItemAttributes: MenuItemAttributes | null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Comprehensive post entity encompassing all post types.
|
|
71
|
+
*/
|
|
72
|
+
interface IPost extends IBasePost, IPagePost, IPostPost, IAttachmentPost, IMenuItem, IFieldLocation {};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { unserialize } from "php-serialize";
|
|
2
|
+
import { Fields } from "../common";
|
|
3
|
+
import { IPost, PostImageAttributes, MenuItemAttributes } from "./post.interface";
|
|
4
|
+
import { processURL } from "@/services/utilities/process-url";
|
|
5
|
+
import { wpdb } from "@/wpdb/wpdb";
|
|
6
|
+
|
|
7
|
+
const excerptLength = nextpressConfig.excerptLength ?? 55;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Implementation of IPost entity.
|
|
11
|
+
*/
|
|
12
|
+
export class Post implements IPost {
|
|
13
|
+
constructor(public ID: number) { }
|
|
14
|
+
|
|
15
|
+
/** Raw post data from database. */
|
|
16
|
+
private postData?: Record<string, any>;
|
|
17
|
+
/** Map of post meta key-value pairs. */
|
|
18
|
+
private metaMap?: Map<string, string>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves array of Post instances by IDs.
|
|
22
|
+
*
|
|
23
|
+
* @param {number[]} ids - Array of post IDs to retrieve.
|
|
24
|
+
* @returns {Promise<Post[]>} Promise resolving to array of Post instances.
|
|
25
|
+
*/
|
|
26
|
+
static async get(ids: number[]): Promise<Post[]> {
|
|
27
|
+
ids = ids.filter(Boolean);
|
|
28
|
+
if (!ids || !ids.length) return [];
|
|
29
|
+
|
|
30
|
+
const postsData = await wpdb
|
|
31
|
+
.selectFrom('wpPosts')
|
|
32
|
+
.where('ID', 'in', ids)
|
|
33
|
+
.selectAll()
|
|
34
|
+
.execute();
|
|
35
|
+
|
|
36
|
+
const metaData = await wpdb
|
|
37
|
+
.selectFrom('wpPostmeta')
|
|
38
|
+
.where('postId', 'in', ids)
|
|
39
|
+
.where('metaKey', 'in', [
|
|
40
|
+
'_nextpress_path',
|
|
41
|
+
'_thumbnail_id',
|
|
42
|
+
'_wp_attachment_image_alt',
|
|
43
|
+
'_wp_attachment_metadata',
|
|
44
|
+
'_menu_item_type',
|
|
45
|
+
'_menu_item_object_id',
|
|
46
|
+
'_menu_item_url',
|
|
47
|
+
'_menu_item_menu_item_parent'
|
|
48
|
+
])
|
|
49
|
+
.select(['postId', 'metaKey', 'metaValue'])
|
|
50
|
+
.execute();
|
|
51
|
+
|
|
52
|
+
const metaByPostId = new Map<number, Map<string, string>>();
|
|
53
|
+
for (const row of metaData) {
|
|
54
|
+
const postId = Number(row.postId);
|
|
55
|
+
if (!metaByPostId.has(postId)) {
|
|
56
|
+
metaByPostId.set(postId, new Map());
|
|
57
|
+
}
|
|
58
|
+
metaByPostId.get(postId)?.set(row.metaKey || '', row.metaValue || '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const postDataMap = new Map(postsData.map(post => [Number(post.ID), post]));
|
|
62
|
+
|
|
63
|
+
return ids.map(id => {
|
|
64
|
+
const instance = new Post(id);
|
|
65
|
+
|
|
66
|
+
instance.postData = postDataMap.get(id);
|
|
67
|
+
instance.metaMap = metaByPostId.get(id) || new Map<string, string>();
|
|
68
|
+
|
|
69
|
+
postLoader.prime([instance.thumbnailId]);
|
|
70
|
+
|
|
71
|
+
return instance;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get path(): string {
|
|
76
|
+
return this.metaMap?.get('_nextpress_path') ?? '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get thumbnailId(): number {
|
|
80
|
+
const val = this.metaMap?.get('_thumbnail_id');
|
|
81
|
+
return val ? Number(val) : 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private _imageAttributes?: PostImageAttributes;
|
|
85
|
+
get imageAttributes(): PostImageAttributes {
|
|
86
|
+
if (this._imageAttributes !== undefined) return this._imageAttributes;
|
|
87
|
+
if (!this.metaMap) return {};
|
|
88
|
+
|
|
89
|
+
const alt = this.metaMap.get('_wp_attachment_image_alt') ?? '';
|
|
90
|
+
const rawMetadata = this.metaMap.get('_wp_attachment_metadata');
|
|
91
|
+
|
|
92
|
+
if (!rawMetadata && !alt) {
|
|
93
|
+
this._imageAttributes = {};
|
|
94
|
+
return this._imageAttributes;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const guid = this.guid;
|
|
98
|
+
const index = guid.indexOf('/wp-content');
|
|
99
|
+
const path = index !== -1 ? guid.slice(index) : guid;
|
|
100
|
+
const srcPath = `${process.env.WP_SERVICE_URL}${path}`;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const metadata: { height?: string; width?: string } = unserialize(rawMetadata ?? 'a:0:{}');
|
|
104
|
+
this._imageAttributes = {
|
|
105
|
+
src: srcPath,
|
|
106
|
+
alt: alt,
|
|
107
|
+
height: metadata.height ? Number(metadata.height) : undefined,
|
|
108
|
+
width: metadata.width ? Number(metadata.width) : undefined
|
|
109
|
+
};
|
|
110
|
+
} catch (error: any) {
|
|
111
|
+
console.warn('Error while getting post: Could not unserialize php: ', error.message);
|
|
112
|
+
this._imageAttributes = {};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return this._imageAttributes;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private _menuItemAttributes?: MenuItemAttributes | null
|
|
119
|
+
get menuItemAttributes(): MenuItemAttributes | null {
|
|
120
|
+
if (this._menuItemAttributes !== undefined) return this._menuItemAttributes;
|
|
121
|
+
if (!this.metaMap) return null;
|
|
122
|
+
|
|
123
|
+
const typeMeta = this.metaMap.get('_menu_item_type');
|
|
124
|
+
const type = ['custom', 'post_type', 'taxonomy'].includes(typeMeta ?? '')
|
|
125
|
+
? typeMeta as 'custom' | 'post_type' | 'taxonomy'
|
|
126
|
+
: undefined;
|
|
127
|
+
|
|
128
|
+
let url;
|
|
129
|
+
let objectId;
|
|
130
|
+
if (type === 'custom') {
|
|
131
|
+
const urlMeta = this.metaMap.get('_menu_item_url');
|
|
132
|
+
url = urlMeta ? processURL(urlMeta) : '';
|
|
133
|
+
} else {
|
|
134
|
+
objectId = Number(this.metaMap.get('_menu_item_object_id'));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const parentId = Number(this.metaMap.get('_menu_item_menu_item_parent')) ?? 0;
|
|
138
|
+
|
|
139
|
+
const label = this.postTitle ?? '';
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
label,
|
|
143
|
+
type: type ?? 'custom',
|
|
144
|
+
parentId,
|
|
145
|
+
objectId: objectId ?? 0,
|
|
146
|
+
url: url ?? ''
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private _postExcerpt?: string
|
|
151
|
+
get postExcerpt(): string {
|
|
152
|
+
if (this._postExcerpt !== undefined) return this._postExcerpt;
|
|
153
|
+
const excerpt = this.postData?.['postExcerpt'] ?? '';
|
|
154
|
+
|
|
155
|
+
if (excerpt) return excerpt;
|
|
156
|
+
|
|
157
|
+
const postContent = this.postContent;
|
|
158
|
+
if (!postContent) return '';
|
|
159
|
+
|
|
160
|
+
const plainText = postContent
|
|
161
|
+
.replace(/<[^>]+>/g, ' ') // Strip HTML tags
|
|
162
|
+
.replace(/\s+/g, ' ') // Normalize multiple spaces into a single space
|
|
163
|
+
.trim();
|
|
164
|
+
|
|
165
|
+
const words = plainText.split(' ');
|
|
166
|
+
if (words.length > (excerptLength)) {
|
|
167
|
+
return words.slice(0, excerptLength).join(' ') + '...';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this._postExcerpt = plainText;
|
|
171
|
+
return this._postExcerpt;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
get commentCount(): number { return this.postData?.['commentCount'] ?? 0; }
|
|
175
|
+
get commentStatus(): string { return this.postData?.['commentStatus'] ?? 'closed'; }
|
|
176
|
+
get guid(): string { return this.postData?.['guid'] ?? ''; }
|
|
177
|
+
get menuOrder(): number { return this.postData?.['menuOrder'] ?? 0; }
|
|
178
|
+
get pinged(): string { return this.postData?.['pinged'] ?? ''; }
|
|
179
|
+
get pingStatus(): string { return this.postData?.['pingStatus'] ?? 'closed'; }
|
|
180
|
+
get postAuthor(): number { return this.postData?.['postAuthor'] ?? 0; }
|
|
181
|
+
get postContent(): string { return this.postData?.['postContent'] ?? ''; }
|
|
182
|
+
get postContentFiltered(): string { return this.postData?.['postContentFiltered'] ?? ''; }
|
|
183
|
+
get postDate(): Date { return this.postData?.['postDate'] ?? new Date(); }
|
|
184
|
+
get postDateGmt(): Date { return this.postData?.['postDateGmt'] ?? new Date(); }
|
|
185
|
+
get postMimeType(): string { return this.postData?.['postMimeType'] ?? ''; }
|
|
186
|
+
get postModified(): Date { return this.postData?.['postModified'] ?? new Date(); }
|
|
187
|
+
get postModifiedGmt(): Date { return this.postData?.['postModifiedGmt'] ?? new Date(); }
|
|
188
|
+
get postName(): string { return this.postData?.['postName'] ?? ''; }
|
|
189
|
+
get postParent(): number { return this.postData?.['postParent'] ?? 0; }
|
|
190
|
+
get postPassword(): string { return this.postData?.['postPassword'] ?? ''; }
|
|
191
|
+
get postStatus(): string { return this.postData?.['postStatus'] ?? 'draft'; }
|
|
192
|
+
get postTitle(): string { return this.postData?.['postTitle'] ?? ''; }
|
|
193
|
+
get postType(): string { return this.postData?.['postType'] ?? 'post'; }
|
|
194
|
+
get toPing(): string { return this.postData?.['toPing'] ?? ''; }
|
|
195
|
+
|
|
196
|
+
async getFields(name: string): Promise<Fields> {
|
|
197
|
+
const meta = await wpdb
|
|
198
|
+
.selectFrom('wpPostmeta')
|
|
199
|
+
.where('metaKey', 'like', `${name}%`)
|
|
200
|
+
.where('postId', '=', this.ID)
|
|
201
|
+
.select(['metaKey', 'metaValue'])
|
|
202
|
+
.execute();
|
|
203
|
+
|
|
204
|
+
return meta.map(m => ({
|
|
205
|
+
key: m.metaKey || '',
|
|
206
|
+
value: m.metaValue || ''
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Selectable } from "kysely";
|
|
2
|
+
import { WpTerm, WpTermTaxonomy } from "../../wpdb/wpdb.interface";
|
|
3
|
+
import { IPath } from "../common";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents term entity.
|
|
7
|
+
*/
|
|
8
|
+
export interface ITerm extends Selectable<WpTerm>, Selectable<WpTermTaxonomy>, IPath {}
|