@nextsparkjs/theme-blog 0.1.0-beta.1
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 +65 -0
- package/about.md +93 -0
- package/api/authors/[username]/route.ts +150 -0
- package/api/authors/route.ts +63 -0
- package/api/posts/public/route.ts +151 -0
- package/components/ExportPostsButton.tsx +102 -0
- package/components/ImportPostsDialog.tsx +284 -0
- package/components/PostsToolbar.tsx +24 -0
- package/components/editor/FeaturedImageUpload.tsx +185 -0
- package/components/editor/WysiwygEditor.tsx +340 -0
- package/components/index.ts +4 -0
- package/components/public/AuthorBio.tsx +105 -0
- package/components/public/AuthorCard.tsx +130 -0
- package/components/public/BlogFooter.tsx +185 -0
- package/components/public/BlogNavbar.tsx +201 -0
- package/components/public/PostCard.tsx +306 -0
- package/components/public/ReadingProgress.tsx +70 -0
- package/components/public/RelatedPosts.tsx +78 -0
- package/config/app.config.ts +200 -0
- package/config/billing.config.ts +146 -0
- package/config/dashboard.config.ts +333 -0
- package/config/dev.config.ts +48 -0
- package/config/features.config.ts +196 -0
- package/config/flows.config.ts +333 -0
- package/config/permissions.config.ts +101 -0
- package/config/theme.config.ts +128 -0
- package/entities/categories/categories.config.ts +60 -0
- package/entities/categories/categories.fields.ts +115 -0
- package/entities/categories/categories.service.ts +333 -0
- package/entities/categories/categories.types.ts +58 -0
- package/entities/categories/messages/en.json +33 -0
- package/entities/categories/messages/es.json +33 -0
- package/entities/posts/messages/en.json +100 -0
- package/entities/posts/messages/es.json +100 -0
- package/entities/posts/migrations/001_posts_table.sql +110 -0
- package/entities/posts/migrations/002_add_featured.sql +19 -0
- package/entities/posts/migrations/003_post_categories_pivot.sql +47 -0
- package/entities/posts/posts.config.ts +61 -0
- package/entities/posts/posts.fields.ts +234 -0
- package/entities/posts/posts.service.ts +464 -0
- package/entities/posts/posts.types.ts +80 -0
- package/lib/selectors.ts +179 -0
- package/messages/en.json +113 -0
- package/messages/es.json +113 -0
- package/migrations/002_author_profile_fields.sql +37 -0
- package/migrations/003_categories_table.sql +90 -0
- package/migrations/999_sample_data.sql +412 -0
- package/migrations/999_theme_sample_data.sql +1070 -0
- package/package.json +18 -0
- package/permissions-matrix.md +63 -0
- package/styles/article.css +333 -0
- package/styles/components.css +204 -0
- package/styles/globals.css +327 -0
- package/styles/theme.css +167 -0
- package/templates/(public)/author/[username]/page.tsx +247 -0
- package/templates/(public)/authors/page.tsx +161 -0
- package/templates/(public)/layout.tsx +44 -0
- package/templates/(public)/page.tsx +276 -0
- package/templates/(public)/posts/[slug]/page.tsx +342 -0
- package/templates/dashboard/(main)/page.tsx +385 -0
- package/templates/dashboard/(main)/posts/[id]/edit/page.tsx +529 -0
- package/templates/dashboard/(main)/posts/[id]/page.tsx +33 -0
- package/templates/dashboard/(main)/posts/create/page.tsx +353 -0
- package/templates/dashboard/(main)/posts/page.tsx +833 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Posts",
|
|
3
|
+
"singular": "Post",
|
|
4
|
+
"plural": "Posts",
|
|
5
|
+
"description": "Gestiona tus posts del blog",
|
|
6
|
+
|
|
7
|
+
"fields": {
|
|
8
|
+
"title": {
|
|
9
|
+
"label": "Título",
|
|
10
|
+
"placeholder": "Escribe un título atractivo...",
|
|
11
|
+
"description": "El título de tu post"
|
|
12
|
+
},
|
|
13
|
+
"slug": {
|
|
14
|
+
"label": "Slug",
|
|
15
|
+
"placeholder": "mi-post-del-blog",
|
|
16
|
+
"description": "Versión amigable para URL del título"
|
|
17
|
+
},
|
|
18
|
+
"excerpt": {
|
|
19
|
+
"label": "Extracto",
|
|
20
|
+
"placeholder": "Escribe un breve resumen de tu post...",
|
|
21
|
+
"description": "Un resumen corto que se muestra en los listados"
|
|
22
|
+
},
|
|
23
|
+
"content": {
|
|
24
|
+
"label": "Contenido",
|
|
25
|
+
"placeholder": "Comienza a escribir tu post...",
|
|
26
|
+
"description": "El contenido principal de tu post"
|
|
27
|
+
},
|
|
28
|
+
"featuredImage": {
|
|
29
|
+
"label": "Imagen Destacada",
|
|
30
|
+
"placeholder": "Sube una imagen...",
|
|
31
|
+
"description": "Imagen principal que se muestra con el post"
|
|
32
|
+
},
|
|
33
|
+
"category": {
|
|
34
|
+
"label": "Categoría",
|
|
35
|
+
"placeholder": "ej., Tecnología, Viajes, Comida",
|
|
36
|
+
"description": "Categoría principal del post"
|
|
37
|
+
},
|
|
38
|
+
"tags": {
|
|
39
|
+
"label": "Etiquetas",
|
|
40
|
+
"placeholder": "Agregar etiquetas...",
|
|
41
|
+
"description": "Palabras clave para ayudar a encontrar este post"
|
|
42
|
+
},
|
|
43
|
+
"status": {
|
|
44
|
+
"label": "Estado",
|
|
45
|
+
"placeholder": "Seleccionar estado...",
|
|
46
|
+
"description": "Estado de publicación",
|
|
47
|
+
"options": {
|
|
48
|
+
"draft": "Borrador",
|
|
49
|
+
"published": "Publicado",
|
|
50
|
+
"scheduled": "Programado"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"publishedAt": {
|
|
54
|
+
"label": "Fecha de Publicación",
|
|
55
|
+
"placeholder": "Seleccionar fecha...",
|
|
56
|
+
"description": "Cuándo se publicó/publicará el post"
|
|
57
|
+
},
|
|
58
|
+
"createdAt": {
|
|
59
|
+
"label": "Creado",
|
|
60
|
+
"description": "Cuándo se creó el post"
|
|
61
|
+
},
|
|
62
|
+
"updatedAt": {
|
|
63
|
+
"label": "Actualizado",
|
|
64
|
+
"description": "Cuándo se modificó por última vez"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
"actions": {
|
|
69
|
+
"create": "Nuevo Post",
|
|
70
|
+
"edit": "Editar Post",
|
|
71
|
+
"delete": "Eliminar Post",
|
|
72
|
+
"publish": "Publicar",
|
|
73
|
+
"unpublish": "Despublicar",
|
|
74
|
+
"saveDraft": "Guardar Borrador",
|
|
75
|
+
"preview": "Vista Previa"
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
"messages": {
|
|
79
|
+
"created": "Post creado exitosamente",
|
|
80
|
+
"updated": "Post actualizado exitosamente",
|
|
81
|
+
"deleted": "Post eliminado exitosamente",
|
|
82
|
+
"published": "Post publicado exitosamente",
|
|
83
|
+
"unpublished": "Post despublicado",
|
|
84
|
+
"confirmDelete": "¿Estás seguro de que quieres eliminar este post? Esta acción no se puede deshacer."
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
"empty": {
|
|
88
|
+
"title": "Aún no hay posts",
|
|
89
|
+
"description": "Comienza escribiendo tu primer post del blog.",
|
|
90
|
+
"action": "Escribe tu primer post"
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
"filters": {
|
|
94
|
+
"all": "Todos los Posts",
|
|
95
|
+
"published": "Publicados",
|
|
96
|
+
"draft": "Borradores",
|
|
97
|
+
"scheduled": "Programados"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Posts Table Migration
|
|
3
|
+
-- Blog theme: Personal blog posts
|
|
4
|
+
-- ============================================================================
|
|
5
|
+
|
|
6
|
+
-- Create posts table
|
|
7
|
+
CREATE TABLE IF NOT EXISTS "posts" (
|
|
8
|
+
"id" TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9
|
+
"title" VARCHAR(255) NOT NULL,
|
|
10
|
+
"slug" VARCHAR(255) NOT NULL,
|
|
11
|
+
"excerpt" TEXT,
|
|
12
|
+
"content" TEXT NOT NULL,
|
|
13
|
+
"featuredImage" VARCHAR(500),
|
|
14
|
+
"category" VARCHAR(100),
|
|
15
|
+
"tags" JSONB DEFAULT '[]'::jsonb,
|
|
16
|
+
"status" VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK ("status" IN ('draft', 'published', 'scheduled')),
|
|
17
|
+
"publishedAt" TIMESTAMPTZ,
|
|
18
|
+
"userId" TEXT NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
|
|
19
|
+
"teamId" TEXT NOT NULL REFERENCES "teams"("id") ON DELETE CASCADE,
|
|
20
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
21
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
-- Create unique index for slug per user
|
|
25
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "posts_slug_userId_idx" ON "posts" ("slug", "userId");
|
|
26
|
+
|
|
27
|
+
-- Index for team isolation queries
|
|
28
|
+
CREATE INDEX IF NOT EXISTS "posts_teamId_idx" ON "posts" ("teamId");
|
|
29
|
+
|
|
30
|
+
-- Index for user queries
|
|
31
|
+
CREATE INDEX IF NOT EXISTS "posts_userId_idx" ON "posts" ("userId");
|
|
32
|
+
|
|
33
|
+
-- Index for public listing (published posts)
|
|
34
|
+
CREATE INDEX IF NOT EXISTS "posts_status_publishedAt_idx" ON "posts" ("status", "publishedAt" DESC)
|
|
35
|
+
WHERE "status" = 'published';
|
|
36
|
+
|
|
37
|
+
-- Index for category filtering
|
|
38
|
+
CREATE INDEX IF NOT EXISTS "posts_category_idx" ON "posts" ("category");
|
|
39
|
+
|
|
40
|
+
-- Enable RLS
|
|
41
|
+
ALTER TABLE "posts" ENABLE ROW LEVEL SECURITY;
|
|
42
|
+
|
|
43
|
+
-- Drop existing policies if any
|
|
44
|
+
DROP POLICY IF EXISTS "posts_select_policy" ON "posts";
|
|
45
|
+
DROP POLICY IF EXISTS "posts_insert_policy" ON "posts";
|
|
46
|
+
DROP POLICY IF EXISTS "posts_update_policy" ON "posts";
|
|
47
|
+
DROP POLICY IF EXISTS "posts_delete_policy" ON "posts";
|
|
48
|
+
DROP POLICY IF EXISTS "posts_public_select_policy" ON "posts";
|
|
49
|
+
|
|
50
|
+
-- Policy: Users can select their own posts (or superadmin can see all)
|
|
51
|
+
CREATE POLICY "posts_select_policy" ON "posts"
|
|
52
|
+
FOR SELECT TO authenticated
|
|
53
|
+
USING (
|
|
54
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
55
|
+
OR public.is_superadmin()
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
-- Policy: Public can see published posts (anonymous access)
|
|
59
|
+
CREATE POLICY "posts_public_select_policy" ON "posts"
|
|
60
|
+
FOR SELECT TO anon
|
|
61
|
+
USING (
|
|
62
|
+
"status" = 'published'
|
|
63
|
+
AND "publishedAt" <= NOW()
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
-- Policy: Users can insert their own posts
|
|
67
|
+
CREATE POLICY "posts_insert_policy" ON "posts"
|
|
68
|
+
FOR INSERT TO authenticated
|
|
69
|
+
WITH CHECK (
|
|
70
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
-- Policy: Users can update their own posts (or superadmin)
|
|
74
|
+
CREATE POLICY "posts_update_policy" ON "posts"
|
|
75
|
+
FOR UPDATE TO authenticated
|
|
76
|
+
USING (
|
|
77
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
78
|
+
OR public.is_superadmin()
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
-- Policy: Users can delete their own posts (or superadmin)
|
|
82
|
+
CREATE POLICY "posts_delete_policy" ON "posts"
|
|
83
|
+
FOR DELETE TO authenticated
|
|
84
|
+
USING (
|
|
85
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
86
|
+
OR public.is_superadmin()
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
-- Trigger for auto-updating updatedAt
|
|
90
|
+
CREATE OR REPLACE FUNCTION update_posts_updated_at()
|
|
91
|
+
RETURNS TRIGGER AS $$
|
|
92
|
+
BEGIN
|
|
93
|
+
NEW."updatedAt" = NOW();
|
|
94
|
+
RETURN NEW;
|
|
95
|
+
END;
|
|
96
|
+
$$ LANGUAGE plpgsql;
|
|
97
|
+
|
|
98
|
+
DROP TRIGGER IF EXISTS posts_updated_at_trigger ON "posts";
|
|
99
|
+
CREATE TRIGGER posts_updated_at_trigger
|
|
100
|
+
BEFORE UPDATE ON "posts"
|
|
101
|
+
FOR EACH ROW
|
|
102
|
+
EXECUTE FUNCTION update_posts_updated_at();
|
|
103
|
+
|
|
104
|
+
-- ============================================================================
|
|
105
|
+
-- Comments
|
|
106
|
+
-- ============================================================================
|
|
107
|
+
COMMENT ON TABLE "posts" IS 'Blog posts for personal blog theme';
|
|
108
|
+
COMMENT ON COLUMN "posts"."slug" IS 'URL-friendly identifier, unique per user';
|
|
109
|
+
COMMENT ON COLUMN "posts"."status" IS 'draft, published, or scheduled';
|
|
110
|
+
COMMENT ON COLUMN "posts"."tags" IS 'JSON array of tag strings';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Posts Migration: Add Featured Column
|
|
3
|
+
-- Blog theme: Allows marking posts as featured for homepage display
|
|
4
|
+
-- ============================================================================
|
|
5
|
+
|
|
6
|
+
-- Add featured column
|
|
7
|
+
ALTER TABLE "posts" ADD COLUMN IF NOT EXISTS "featured" BOOLEAN NOT NULL DEFAULT false;
|
|
8
|
+
|
|
9
|
+
-- Create index for featured posts queries
|
|
10
|
+
CREATE INDEX IF NOT EXISTS "posts_featured_idx" ON "posts" ("featured") WHERE "featured" = true;
|
|
11
|
+
|
|
12
|
+
-- Create composite index for featured + published posts
|
|
13
|
+
CREATE INDEX IF NOT EXISTS "posts_featured_published_idx" ON "posts" ("featured", "status", "publishedAt" DESC)
|
|
14
|
+
WHERE "featured" = true AND "status" = 'published';
|
|
15
|
+
|
|
16
|
+
-- ============================================================================
|
|
17
|
+
-- Comments
|
|
18
|
+
-- ============================================================================
|
|
19
|
+
COMMENT ON COLUMN "posts"."featured" IS 'Whether this post should be featured on the homepage';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Post Categories Pivot Table Migration
|
|
3
|
+
-- Blog theme: Many-to-many relationship between posts and categories
|
|
4
|
+
-- ============================================================================
|
|
5
|
+
|
|
6
|
+
-- Create pivot table
|
|
7
|
+
CREATE TABLE IF NOT EXISTS "post_categories" (
|
|
8
|
+
"postId" TEXT NOT NULL REFERENCES "posts"("id") ON DELETE CASCADE,
|
|
9
|
+
"categoryId" TEXT NOT NULL REFERENCES "categories"("id") ON DELETE CASCADE,
|
|
10
|
+
PRIMARY KEY ("postId", "categoryId")
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
-- Indexes for efficient queries
|
|
14
|
+
CREATE INDEX IF NOT EXISTS "post_categories_postId_idx" ON "post_categories" ("postId");
|
|
15
|
+
CREATE INDEX IF NOT EXISTS "post_categories_categoryId_idx" ON "post_categories" ("categoryId");
|
|
16
|
+
|
|
17
|
+
-- Enable RLS
|
|
18
|
+
ALTER TABLE "post_categories" ENABLE ROW LEVEL SECURITY;
|
|
19
|
+
|
|
20
|
+
-- Drop existing policies if any
|
|
21
|
+
DROP POLICY IF EXISTS "post_categories_all_policy" ON "post_categories";
|
|
22
|
+
DROP POLICY IF EXISTS "post_categories_public_select_policy" ON "post_categories";
|
|
23
|
+
|
|
24
|
+
-- Policies follow post ownership (if user owns the post, they can manage its categories)
|
|
25
|
+
CREATE POLICY "post_categories_all_policy" ON "post_categories"
|
|
26
|
+
FOR ALL TO authenticated
|
|
27
|
+
USING (
|
|
28
|
+
EXISTS (
|
|
29
|
+
SELECT 1 FROM "posts" p
|
|
30
|
+
WHERE p.id = "postId"
|
|
31
|
+
AND p."teamId" = ANY(public.get_user_team_ids())
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
-- Public read access for category pages
|
|
36
|
+
CREATE POLICY "post_categories_public_select_policy" ON "post_categories"
|
|
37
|
+
FOR SELECT TO anon
|
|
38
|
+
USING (
|
|
39
|
+
EXISTS (
|
|
40
|
+
SELECT 1 FROM "posts" p
|
|
41
|
+
WHERE p.id = "postId"
|
|
42
|
+
AND p."status" = 'published'
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- Comments
|
|
47
|
+
COMMENT ON TABLE "post_categories" IS 'Many-to-many relationship between posts and categories';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Posts Entity Configuration
|
|
3
|
+
*
|
|
4
|
+
* Blog posts entity for the personal blog theme.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FileText } from 'lucide-react'
|
|
8
|
+
import type { EntityConfig } from '@nextsparkjs/core/lib/entities/types'
|
|
9
|
+
import { postFields } from './posts.fields'
|
|
10
|
+
|
|
11
|
+
export const postEntityConfig: EntityConfig = {
|
|
12
|
+
// ==========================================
|
|
13
|
+
// 1. BASIC IDENTIFICATION
|
|
14
|
+
// ==========================================
|
|
15
|
+
slug: 'posts',
|
|
16
|
+
enabled: true,
|
|
17
|
+
names: {
|
|
18
|
+
singular: 'post',
|
|
19
|
+
plural: 'Posts'
|
|
20
|
+
},
|
|
21
|
+
icon: FileText,
|
|
22
|
+
|
|
23
|
+
// ==========================================
|
|
24
|
+
// 2. ACCESS AND SCOPE CONFIGURATION
|
|
25
|
+
// ==========================================
|
|
26
|
+
access: {
|
|
27
|
+
public: true, // Published posts are publicly accessible
|
|
28
|
+
api: true, // API access enabled
|
|
29
|
+
metadata: true, // Supports metadata (SEO, custom fields)
|
|
30
|
+
shared: false // Personal posts, filtered by userId
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
// ==========================================
|
|
34
|
+
// 3. UI/UX FEATURES
|
|
35
|
+
// ==========================================
|
|
36
|
+
ui: {
|
|
37
|
+
dashboard: {
|
|
38
|
+
showInMenu: true,
|
|
39
|
+
showInTopbar: true
|
|
40
|
+
},
|
|
41
|
+
public: {
|
|
42
|
+
hasArchivePage: true, // /posts - list of all published posts
|
|
43
|
+
hasSinglePage: true // /posts/[slug] - individual post page
|
|
44
|
+
},
|
|
45
|
+
features: {
|
|
46
|
+
searchable: true,
|
|
47
|
+
sortable: true,
|
|
48
|
+
filterable: true,
|
|
49
|
+
bulkOperations: true,
|
|
50
|
+
importExport: true // Export/Import feature enabled
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// ==========================================
|
|
55
|
+
// FIELDS
|
|
56
|
+
// ==========================================
|
|
57
|
+
fields: postFields,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default postEntityConfig
|
|
61
|
+
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Posts Entity Fields Configuration
|
|
3
|
+
*
|
|
4
|
+
* Complete fields for a blog post including SEO and categorization.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EntityField } from '@nextsparkjs/core/lib/entities/types'
|
|
8
|
+
|
|
9
|
+
export const postFields: EntityField[] = [
|
|
10
|
+
// ==========================================
|
|
11
|
+
// MAIN CONTENT
|
|
12
|
+
// ==========================================
|
|
13
|
+
{
|
|
14
|
+
name: 'title',
|
|
15
|
+
type: 'text',
|
|
16
|
+
required: true,
|
|
17
|
+
display: {
|
|
18
|
+
label: 'Title',
|
|
19
|
+
description: 'The title of your blog post',
|
|
20
|
+
placeholder: 'Enter a compelling title...',
|
|
21
|
+
showInList: true,
|
|
22
|
+
showInDetail: true,
|
|
23
|
+
showInForm: true,
|
|
24
|
+
order: 1,
|
|
25
|
+
columnWidth: 12,
|
|
26
|
+
},
|
|
27
|
+
api: {
|
|
28
|
+
readOnly: false,
|
|
29
|
+
searchable: true,
|
|
30
|
+
sortable: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'slug',
|
|
35
|
+
type: 'text',
|
|
36
|
+
required: true,
|
|
37
|
+
display: {
|
|
38
|
+
label: 'Slug',
|
|
39
|
+
description: 'URL-friendly version of the title (auto-generated)',
|
|
40
|
+
placeholder: 'my-blog-post',
|
|
41
|
+
showInList: false,
|
|
42
|
+
showInDetail: true,
|
|
43
|
+
showInForm: true,
|
|
44
|
+
order: 2,
|
|
45
|
+
columnWidth: 12,
|
|
46
|
+
},
|
|
47
|
+
api: {
|
|
48
|
+
readOnly: false,
|
|
49
|
+
searchable: false,
|
|
50
|
+
sortable: false,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'excerpt',
|
|
55
|
+
type: 'textarea',
|
|
56
|
+
required: false,
|
|
57
|
+
display: {
|
|
58
|
+
label: 'Excerpt',
|
|
59
|
+
description: 'A short summary shown in post listings',
|
|
60
|
+
placeholder: 'Write a brief summary of your post...',
|
|
61
|
+
showInList: true,
|
|
62
|
+
showInDetail: true,
|
|
63
|
+
showInForm: true,
|
|
64
|
+
order: 3,
|
|
65
|
+
columnWidth: 12,
|
|
66
|
+
},
|
|
67
|
+
api: {
|
|
68
|
+
readOnly: false,
|
|
69
|
+
searchable: true,
|
|
70
|
+
sortable: false,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'content',
|
|
75
|
+
type: 'richtext',
|
|
76
|
+
required: true,
|
|
77
|
+
display: {
|
|
78
|
+
label: 'Content',
|
|
79
|
+
description: 'The main content of your blog post',
|
|
80
|
+
placeholder: 'Start writing your post...',
|
|
81
|
+
showInList: false,
|
|
82
|
+
showInDetail: true,
|
|
83
|
+
showInForm: true,
|
|
84
|
+
order: 4,
|
|
85
|
+
columnWidth: 12,
|
|
86
|
+
},
|
|
87
|
+
api: {
|
|
88
|
+
readOnly: false,
|
|
89
|
+
searchable: true,
|
|
90
|
+
sortable: false,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// ==========================================
|
|
95
|
+
// MEDIA
|
|
96
|
+
// ==========================================
|
|
97
|
+
{
|
|
98
|
+
name: 'featuredImage',
|
|
99
|
+
type: 'image',
|
|
100
|
+
required: false,
|
|
101
|
+
display: {
|
|
102
|
+
label: 'Featured Image',
|
|
103
|
+
description: 'Main image displayed with the post',
|
|
104
|
+
placeholder: 'Upload an image...',
|
|
105
|
+
showInList: true,
|
|
106
|
+
showInDetail: true,
|
|
107
|
+
showInForm: true,
|
|
108
|
+
order: 5,
|
|
109
|
+
columnWidth: 12,
|
|
110
|
+
},
|
|
111
|
+
api: {
|
|
112
|
+
readOnly: false,
|
|
113
|
+
searchable: false,
|
|
114
|
+
sortable: false,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
// ==========================================
|
|
120
|
+
// FEATURING
|
|
121
|
+
// ==========================================
|
|
122
|
+
{
|
|
123
|
+
name: 'featured',
|
|
124
|
+
type: 'boolean',
|
|
125
|
+
required: false,
|
|
126
|
+
defaultValue: false,
|
|
127
|
+
display: {
|
|
128
|
+
label: 'Featured',
|
|
129
|
+
description: 'Show this post in the featured section on the homepage',
|
|
130
|
+
showInList: true,
|
|
131
|
+
showInDetail: true,
|
|
132
|
+
showInForm: true,
|
|
133
|
+
order: 6,
|
|
134
|
+
columnWidth: 4,
|
|
135
|
+
},
|
|
136
|
+
api: {
|
|
137
|
+
readOnly: false,
|
|
138
|
+
searchable: false,
|
|
139
|
+
sortable: true,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// ==========================================
|
|
144
|
+
// PUBLISHING
|
|
145
|
+
// ==========================================
|
|
146
|
+
{
|
|
147
|
+
name: 'status',
|
|
148
|
+
type: 'select',
|
|
149
|
+
required: true,
|
|
150
|
+
defaultValue: 'draft',
|
|
151
|
+
options: [
|
|
152
|
+
{ value: 'draft', label: 'Draft' },
|
|
153
|
+
{ value: 'published', label: 'Published' },
|
|
154
|
+
{ value: 'scheduled', label: 'Scheduled' },
|
|
155
|
+
],
|
|
156
|
+
display: {
|
|
157
|
+
label: 'Status',
|
|
158
|
+
description: 'Publication status',
|
|
159
|
+
placeholder: 'Select status...',
|
|
160
|
+
showInList: true,
|
|
161
|
+
showInDetail: true,
|
|
162
|
+
showInForm: true,
|
|
163
|
+
order: 8,
|
|
164
|
+
columnWidth: 4,
|
|
165
|
+
},
|
|
166
|
+
api: {
|
|
167
|
+
readOnly: false,
|
|
168
|
+
searchable: false,
|
|
169
|
+
sortable: true,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'publishedAt',
|
|
174
|
+
type: 'datetime',
|
|
175
|
+
required: false,
|
|
176
|
+
display: {
|
|
177
|
+
label: 'Publish Date',
|
|
178
|
+
description: 'When the post was/will be published',
|
|
179
|
+
placeholder: 'Select date...',
|
|
180
|
+
showInList: true,
|
|
181
|
+
showInDetail: true,
|
|
182
|
+
showInForm: true,
|
|
183
|
+
order: 9,
|
|
184
|
+
columnWidth: 4,
|
|
185
|
+
},
|
|
186
|
+
api: {
|
|
187
|
+
readOnly: false,
|
|
188
|
+
searchable: false,
|
|
189
|
+
sortable: true,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// ==========================================
|
|
194
|
+
// TIMESTAMPS
|
|
195
|
+
// ==========================================
|
|
196
|
+
{
|
|
197
|
+
name: 'createdAt',
|
|
198
|
+
type: 'datetime',
|
|
199
|
+
required: false,
|
|
200
|
+
display: {
|
|
201
|
+
label: 'Created At',
|
|
202
|
+
description: 'When the post was created',
|
|
203
|
+
showInList: false,
|
|
204
|
+
showInDetail: true,
|
|
205
|
+
showInForm: false,
|
|
206
|
+
order: 98,
|
|
207
|
+
columnWidth: 4,
|
|
208
|
+
},
|
|
209
|
+
api: {
|
|
210
|
+
readOnly: true,
|
|
211
|
+
searchable: false,
|
|
212
|
+
sortable: true,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'updatedAt',
|
|
217
|
+
type: 'datetime',
|
|
218
|
+
required: false,
|
|
219
|
+
display: {
|
|
220
|
+
label: 'Updated At',
|
|
221
|
+
description: 'When the post was last modified',
|
|
222
|
+
showInList: false,
|
|
223
|
+
showInDetail: true,
|
|
224
|
+
showInForm: false,
|
|
225
|
+
order: 99,
|
|
226
|
+
},
|
|
227
|
+
api: {
|
|
228
|
+
readOnly: true,
|
|
229
|
+
searchable: false,
|
|
230
|
+
sortable: true,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
]
|
|
234
|
+
|