Added Sidenote Component
This commit is contained in:
13
src/keystatic/collections/meta/article.ts
Normal file
13
src/keystatic/collections/meta/article.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { collection } from '@keystatic/core';
|
||||
|
||||
import { createArticleField } from '@/keystatic/fields/article';
|
||||
|
||||
export default collection({
|
||||
label: 'Meta - Posts',
|
||||
slugField: 'title',
|
||||
path: 'content/meta/*',
|
||||
format: { contentField: 'content' },
|
||||
schema: {
|
||||
...createArticleField('meta'),
|
||||
},
|
||||
});
|
||||
46
src/keystatic/collections/taxonomy/authors.ts
Normal file
46
src/keystatic/collections/taxonomy/authors.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { collection, fields } from '@keystatic/core';
|
||||
|
||||
export default collection({
|
||||
label: 'Authors',
|
||||
slugField: 'name',
|
||||
path: 'content/taxonomy/authors/*',
|
||||
format: { data: 'json' },
|
||||
schema: {
|
||||
name: fields.slug({ name: { label: 'Name' } }),
|
||||
avatar: fields.image({
|
||||
label: 'Avatar',
|
||||
directory: 'public/images/authors',
|
||||
publicPath: '/images/authors',
|
||||
}),
|
||||
description: fields.text({ label: 'Description', multiline: true }),
|
||||
contacts: fields.array(
|
||||
fields.object({
|
||||
type: fields.select({
|
||||
label: 'Contact Type',
|
||||
defaultValue: 'email',
|
||||
options: [
|
||||
{
|
||||
value: 'email',
|
||||
label: 'E-Mail',
|
||||
},
|
||||
{
|
||||
value: 'discord',
|
||||
label: 'Discord',
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
label: 'Other',
|
||||
},
|
||||
],
|
||||
}),
|
||||
url: fields.text({
|
||||
label: 'URL',
|
||||
}),
|
||||
}),
|
||||
{
|
||||
label: 'Contact Type',
|
||||
itemLabel: (props) => `${props.fields.type.value}`,
|
||||
}
|
||||
),
|
||||
},
|
||||
});
|
||||
41
src/keystatic/collections/taxonomy/tags.ts
Normal file
41
src/keystatic/collections/taxonomy/tags.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { collection, fields } from '@keystatic/core';
|
||||
|
||||
export default collection({
|
||||
label: 'Tags',
|
||||
slugField: 'name',
|
||||
path: 'content/taxonomy/tags/*',
|
||||
format: { data: 'json' },
|
||||
schema: {
|
||||
name: fields.slug({ name: { label: 'Name' } }),
|
||||
icon: fields.conditional(
|
||||
fields.select({
|
||||
label: 'Icon Type',
|
||||
defaultValue: 'none',
|
||||
options: [
|
||||
{
|
||||
value: 'none',
|
||||
label: 'None',
|
||||
},
|
||||
{
|
||||
value: 'glyph',
|
||||
label: 'Glyph',
|
||||
},
|
||||
{
|
||||
value: 'img',
|
||||
label: 'Image',
|
||||
},
|
||||
],
|
||||
}),
|
||||
{
|
||||
none: fields.empty(),
|
||||
glyph: fields.text({ label: 'Glyph' }),
|
||||
img: fields.image({
|
||||
label: 'icon',
|
||||
publicPath: '/images/icons',
|
||||
directory: 'public/images/icons',
|
||||
}),
|
||||
}
|
||||
),
|
||||
description: fields.text({ label: 'Description', multiline: true }),
|
||||
},
|
||||
});
|
||||
61
src/keystatic/components/general/grid.ts
Normal file
61
src/keystatic/components/general/grid.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { grid2X2Icon } from '@keystar/ui/icon/icons/grid2X2Icon';
|
||||
import { fields } from '@keystatic/core';
|
||||
import { repeating, wrapper } from '@keystatic/core/content-components';
|
||||
|
||||
export const gridComponents = {
|
||||
Row: repeating({
|
||||
label: 'Row',
|
||||
icon: grid2X2Icon,
|
||||
children: ['Column'], // This defines what can go inside
|
||||
schema: {
|
||||
style: fields.object({
|
||||
cols: fields.integer({
|
||||
label: 'Columns',
|
||||
defaultValue: 2,
|
||||
validation: { min: 2, max: 12 },
|
||||
}),
|
||||
gap: fields.text({
|
||||
label: 'Gap',
|
||||
description: 'Enter a variable or value',
|
||||
}),
|
||||
align: fields.select({
|
||||
label: 'Align Items',
|
||||
defaultValue: 'flex-start',
|
||||
options: [
|
||||
{ label: 'Start', value: 'flex-start' },
|
||||
{ label: 'End', value: 'flex-end' },
|
||||
{ label: 'Center', value: 'center' },
|
||||
{ label: 'Baseline', value: 'baseline' },
|
||||
{ label: 'Stretch', value: 'stretch' },
|
||||
],
|
||||
}),
|
||||
justify: fields.select({
|
||||
label: 'Justify Content',
|
||||
defaultValue: 'flex-start',
|
||||
options: [
|
||||
{ label: 'Start', value: 'flex-start' },
|
||||
{ label: 'End', value: 'flex-end' },
|
||||
{ label: 'Center', value: 'center' },
|
||||
{ label: 'Space Between', value: 'space-between' },
|
||||
{ label: 'Space Around', value: 'space-around' },
|
||||
{ label: 'Evenly', value: 'evenly' },
|
||||
{ label: 'Stretch', value: 'stretch' },
|
||||
{ label: 'Baseline', value: 'baseline' },
|
||||
],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
Column: wrapper({
|
||||
label: 'Column',
|
||||
forSpecificLocations: true,
|
||||
schema: {
|
||||
style: fields.object({
|
||||
colspan: fields.integer({
|
||||
label: 'Colspan',
|
||||
defaultValue: 1,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
4
src/keystatic/components/general/index.ts
Normal file
4
src/keystatic/components/general/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { gridComponents } from '@/keystatic/components/general/grid';
|
||||
import { sidenoteComponents } from '@/keystatic/components/general/sidenote';
|
||||
|
||||
export const generalComponents = { ...gridComponents, ...sidenoteComponents };
|
||||
59
src/keystatic/components/general/sidenote.ts
Normal file
59
src/keystatic/components/general/sidenote.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { panelRightDashedIcon } from '@keystar/ui/icon/icons/panelRightDashedIcon';
|
||||
import { fields } from '@keystatic/core';
|
||||
import { inline } from '@keystatic/core/content-components';
|
||||
|
||||
export const sidenoteComponents = {
|
||||
Sidenote: inline({
|
||||
label: 'Sidenote',
|
||||
icon: panelRightDashedIcon,
|
||||
schema: {
|
||||
id: fields.text({
|
||||
label: 'ID',
|
||||
description: 'Unique Identifier (Auto-generated)',
|
||||
}),
|
||||
marker: fields.text({
|
||||
label: 'Marker',
|
||||
description: 'Number or Glyph for reference',
|
||||
defaultValue: '⋄',
|
||||
validation: {
|
||||
length: {
|
||||
min: 1,
|
||||
max: 3,
|
||||
},
|
||||
},
|
||||
}),
|
||||
content: fields.text({
|
||||
label: 'Note Content',
|
||||
multiline: true,
|
||||
validation: {
|
||||
length: {
|
||||
min: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
type: fields.select({
|
||||
label: 'Type',
|
||||
description: 'Visual Style',
|
||||
options: [
|
||||
{
|
||||
label: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
label: 'Lore',
|
||||
value: 'lore',
|
||||
},
|
||||
{
|
||||
label: 'Crunch',
|
||||
value: 'crunch',
|
||||
},
|
||||
{
|
||||
label: 'Example',
|
||||
value: 'example',
|
||||
},
|
||||
],
|
||||
defaultValue: 'default',
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
26
src/keystatic/fields/article.ts
Normal file
26
src/keystatic/fields/article.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { fields } from '@keystatic/core';
|
||||
|
||||
import { createContentField } from '@/keystatic/fields/content';
|
||||
import { createSEOField } from '@/keystatic/fields/seo';
|
||||
import { createMetaField } from '@/keystatic/fields/meta';
|
||||
|
||||
export const createArticleField = (imageSubfolder: string) => ({
|
||||
title: fields.slug({ name: { label: 'Title' } }),
|
||||
summary: fields.text({ label: 'Summary', multiline: true }),
|
||||
cover: fields.object({
|
||||
src: fields.image({
|
||||
label: 'Cover Image',
|
||||
directory: `public/images/covers/${imageSubfolder}`,
|
||||
publicPath: `/images/covers/${imageSubfolder}`,
|
||||
}),
|
||||
alt: fields.text({ label: 'Alt' }),
|
||||
caption: fields.text({ label: 'Caption', multiline: true }),
|
||||
showInHeader: fields.checkbox({
|
||||
label: 'Show in Header',
|
||||
defaultValue: false,
|
||||
}),
|
||||
}),
|
||||
meta: createMetaField(),
|
||||
seo: createSEOField(),
|
||||
content: createContentField(imageSubfolder),
|
||||
});
|
||||
22
src/keystatic/fields/content.ts
Normal file
22
src/keystatic/fields/content.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { fields } from '@keystatic/core';
|
||||
import type { ContentComponent } from '@keystatic/core/content-components';
|
||||
|
||||
import { generalComponents } from '@/keystatic/components/general';
|
||||
|
||||
export const createContentField = (
|
||||
imageSubfolder: string,
|
||||
additionalComponents?: Record<string, ContentComponent>
|
||||
) =>
|
||||
fields.markdoc({
|
||||
label: 'Content',
|
||||
options: {
|
||||
image: {
|
||||
directory: `public/images/content/${imageSubfolder}`,
|
||||
publicPath: `/images/content/${imageSubfolder}`,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
...generalComponents,
|
||||
...additionalComponents,
|
||||
},
|
||||
});
|
||||
44
src/keystatic/fields/meta.ts
Normal file
44
src/keystatic/fields/meta.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { ComponentSchema } from '@keystatic/core';
|
||||
import { fields } from '@keystatic/core';
|
||||
|
||||
export const createMetaField = (): ComponentSchema =>
|
||||
fields.object(
|
||||
{
|
||||
publicationDate: fields.datetime({
|
||||
label: 'Publication Date',
|
||||
defaultValue: { kind: 'now' },
|
||||
}),
|
||||
updateDate: fields.datetime({ label: 'Update Date' }),
|
||||
status: fields.select({
|
||||
label: 'Status',
|
||||
defaultValue: 'draft',
|
||||
options: [
|
||||
{ label: 'Draft', value: 'draft' },
|
||||
{ label: 'Published', value: 'published' },
|
||||
{ label: 'Archived', value: 'archived' },
|
||||
],
|
||||
}),
|
||||
isFeatured: fields.checkbox({
|
||||
label: 'Featured',
|
||||
description: 'Show on Homepage',
|
||||
}),
|
||||
tags: fields.array(
|
||||
fields.relationship({
|
||||
label: 'Tags',
|
||||
collection: 'tags',
|
||||
}),
|
||||
{
|
||||
label: 'Tags',
|
||||
itemLabel: (props) => props.value || 'Select Tag',
|
||||
}
|
||||
),
|
||||
author: fields.relationship({
|
||||
label: 'Author',
|
||||
collection: 'authors',
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Meta Information',
|
||||
layout: [4, 4, 4, 12, 12, 12],
|
||||
}
|
||||
);
|
||||
24
src/keystatic/fields/seo.ts
Normal file
24
src/keystatic/fields/seo.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { ComponentSchema } from '@keystatic/core';
|
||||
import { fields } from '@keystatic/core';
|
||||
|
||||
export const createSEOField = (): ComponentSchema =>
|
||||
fields.object(
|
||||
{
|
||||
title: fields.text({
|
||||
label: 'SEO Title',
|
||||
validation: { length: { max: 60 } },
|
||||
}),
|
||||
description: fields.text({
|
||||
label: 'SEO Description',
|
||||
multiline: true,
|
||||
validation: { length: { max: 160 } },
|
||||
}),
|
||||
noIndex: fields.checkbox({
|
||||
label: 'No Index',
|
||||
description: 'Prevent search engines from indexing',
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'SEO Settings',
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user