initial commit after project creation
This commit is contained in:
101
tina/templates/markdown-embeds/accordion.template.tsx
Normal file
101
tina/templates/markdown-embeds/accordion.template.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
const AccordionItemFields = [
|
||||
{
|
||||
name: "heading",
|
||||
label: "Heading",
|
||||
type: "string",
|
||||
description:
|
||||
"The heading text that will be displayed in the collapsed state",
|
||||
},
|
||||
{
|
||||
name: "docText",
|
||||
label: "Body Text",
|
||||
isBody: true,
|
||||
type: "rich-text",
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
label: "image",
|
||||
type: "image",
|
||||
},
|
||||
];
|
||||
|
||||
export const AccordionTemplate = {
|
||||
name: "accordion",
|
||||
label: "Accordion",
|
||||
ui: {
|
||||
defaultItem: {
|
||||
heading: "Click to expand",
|
||||
//TODO: Need to configure this to be a rich text field
|
||||
docText: {
|
||||
type: "root",
|
||||
children: [
|
||||
{
|
||||
type: "p",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Default Text. Edit me!",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
image: "/img/rico-replacement.jpg",
|
||||
fullWidth: false,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
...AccordionItemFields,
|
||||
{
|
||||
name: "fullWidth",
|
||||
label: "Full Width",
|
||||
type: "boolean",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default AccordionTemplate;
|
||||
|
||||
export const AccordionBlockTemplate = {
|
||||
name: "accordionBlock",
|
||||
label: "Accordion Block",
|
||||
fields: [
|
||||
{
|
||||
name: "fullWidth",
|
||||
label: "Full Width",
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
name: "accordionItems",
|
||||
label: "Accordion Items",
|
||||
type: "object",
|
||||
list: true,
|
||||
fields: AccordionItemFields,
|
||||
ui: {
|
||||
itemProps: (item) => {
|
||||
return {
|
||||
label: item.heading ?? "Accordion Item",
|
||||
};
|
||||
},
|
||||
defaultItem: {
|
||||
heading: "Click to expand",
|
||||
docText: {
|
||||
type: "root",
|
||||
children: [
|
||||
{
|
||||
type: "p",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Default Text. Edit me!",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
image: "/img/rico-replacement.jpg",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
343
tina/templates/markdown-embeds/api-reference.template.tsx
Normal file
343
tina/templates/markdown-embeds/api-reference.template.tsx
Normal file
@@ -0,0 +1,343 @@
|
||||
"use client";
|
||||
|
||||
import { CustomDropdown } from "@/src/components/ui/custom-dropdown";
|
||||
import type { DropdownOption } from "@/src/components/ui/custom-dropdown";
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
// Define schema type to match the actual structure from the API
|
||||
interface SchemaFile {
|
||||
id: string;
|
||||
relativePath: string;
|
||||
apiSchema?: string | null;
|
||||
_sys: {
|
||||
filename: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Interface for parsed Swagger/OpenAPI details
|
||||
interface SchemaDetails {
|
||||
title?: string;
|
||||
version?: string;
|
||||
endpointCount: number;
|
||||
endpoints: Endpoint[];
|
||||
}
|
||||
|
||||
// Interface for endpoint details
|
||||
interface Endpoint {
|
||||
path: string;
|
||||
method: string;
|
||||
summary: string;
|
||||
operationId?: string;
|
||||
}
|
||||
|
||||
// Parse Swagger/OpenAPI JSON to extract details
|
||||
const parseSwaggerJson = (jsonContent: string): SchemaDetails => {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonContent);
|
||||
|
||||
// Extract endpoints
|
||||
const endpoints: Endpoint[] = [];
|
||||
if (parsed.paths) {
|
||||
for (const path of Object.keys(parsed.paths)) {
|
||||
const pathObj = parsed.paths[path];
|
||||
for (const method of Object.keys(pathObj)) {
|
||||
const operation = pathObj[method];
|
||||
endpoints.push({
|
||||
path,
|
||||
method: method.toUpperCase(),
|
||||
summary: operation.summary || `${method.toUpperCase()} ${path}`,
|
||||
operationId: operation.operationId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: parsed.info?.title || "Unknown API",
|
||||
version: parsed.info?.version || "Unknown Version",
|
||||
endpointCount: endpoints.length,
|
||||
endpoints,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
title: "Error Parsing Schema",
|
||||
version: "Unknown",
|
||||
endpointCount: 0,
|
||||
endpoints: [],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const getSchemas = async () => {
|
||||
try {
|
||||
const { schemas } = await fetchSchemas();
|
||||
|
||||
if (schemas) {
|
||||
// Convert API response into our simpler SchemaFile interface
|
||||
const schemaFiles: SchemaFile[] = schemas.map((schema) => ({
|
||||
id: schema.id,
|
||||
relativePath: schema.filename,
|
||||
apiSchema: schema.apiSchema,
|
||||
_sys: {
|
||||
filename: schema.displayName,
|
||||
},
|
||||
}));
|
||||
|
||||
return schemaFiles;
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSchemas = async () => {
|
||||
const response = await fetch("/api/list-api-schemas");
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const getSchemaDetails = async (schemaPath: string, schemas: SchemaFile[]) => {
|
||||
try {
|
||||
// Find the selected schema
|
||||
const selectedSchema = schemas.find((s) => s.relativePath === schemaPath);
|
||||
|
||||
if (selectedSchema?.apiSchema) {
|
||||
const details = parseSwaggerJson(selectedSchema.apiSchema);
|
||||
return details;
|
||||
}
|
||||
|
||||
// If the schema content isn't in the current data, fetch it
|
||||
const { schemas: data } = await fetchSchemas();
|
||||
|
||||
if (data?.apiSchema) {
|
||||
const details = parseSwaggerJson(data.apiSchema);
|
||||
return details;
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Custom field for selecting an API schema file
|
||||
const SchemaSelector = (props: any) => {
|
||||
const { input, field } = props;
|
||||
const [schemas, setSchemas] = useState<SchemaFile[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [schemaDetails, setSchemaDetails] = useState<SchemaDetails | null>(
|
||||
null
|
||||
);
|
||||
const [loadingDetails, setLoadingDetails] = useState(false);
|
||||
const [selectedEndpoint, setSelectedEndpoint] = useState<string>("");
|
||||
|
||||
// Fetch schema details when a schema is selected
|
||||
useEffect(() => {
|
||||
if (!input.value) {
|
||||
setSchemaDetails(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = input.value.split("|");
|
||||
if (parts.length > 1) {
|
||||
setSelectedEndpoint(parts[1]);
|
||||
} else {
|
||||
setSelectedEndpoint("");
|
||||
}
|
||||
|
||||
const fetchSchemaDetails = async () => {
|
||||
setLoadingDetails(true);
|
||||
const details = await getSchemaDetails(
|
||||
input.value.split("|")[0],
|
||||
schemas
|
||||
);
|
||||
setSchemaDetails(details);
|
||||
setLoadingDetails(false);
|
||||
};
|
||||
|
||||
fetchSchemaDetails();
|
||||
}, [input.value, schemas]);
|
||||
|
||||
// Fetch available schema files when component mounts
|
||||
useEffect(() => {
|
||||
const fetchSchemas = async () => {
|
||||
setLoading(true);
|
||||
const schemas = await getSchemas();
|
||||
setSchemas(schemas);
|
||||
setLoading(false);
|
||||
};
|
||||
fetchSchemas();
|
||||
}, []);
|
||||
|
||||
const handleSchemaChange = async (schemaPath: string) => {
|
||||
// Reset endpoint selection when schema changes
|
||||
if (!schemaDetails) {
|
||||
setLoadingDetails(true);
|
||||
const details = await getSchemaDetails(
|
||||
input.value.split("|")[0],
|
||||
schemas
|
||||
);
|
||||
setSchemaDetails(details);
|
||||
setLoadingDetails(false);
|
||||
}
|
||||
setSelectedEndpoint("");
|
||||
input.onChange(schemaPath);
|
||||
};
|
||||
|
||||
const handleEndpointChange = (endpoint: string) => {
|
||||
setSelectedEndpoint(endpoint);
|
||||
// Extract just the schema path
|
||||
const schemaPath = input.value.split("|")[0];
|
||||
// Combine schema path and endpoint
|
||||
input.onChange(endpoint ? `${schemaPath}|${endpoint}` : schemaPath);
|
||||
};
|
||||
|
||||
// Helper function to create a unique endpoint identifier
|
||||
const createEndpointId = (endpoint: Endpoint) => {
|
||||
return `${endpoint.method}:${endpoint.path}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-full overflow-x-hidden">
|
||||
<label className="block font-medium text-gray-700 mb-1">
|
||||
{field.label || "Select API Schema"}
|
||||
</label>
|
||||
{loading ? (
|
||||
<div className="py-2 px-3 bg-gray-100 rounded text-gray-500">
|
||||
Loading schemas...
|
||||
</div>
|
||||
) : schemas.length === 0 ? (
|
||||
<div className="max-w-full w-full py-2 px-3 bg-red-50 text-red-500 rounded whitespace-normal">
|
||||
No API schema files found. Please upload one in the Content Manager.
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-w-full w-full overflow-x-hidden">
|
||||
{/* Schema selector dropdown */}
|
||||
<CustomDropdown
|
||||
value={input.value?.split("|")[0]}
|
||||
onChange={handleSchemaChange}
|
||||
options={[
|
||||
{ value: "", label: "Select a schema" },
|
||||
...schemas.map<DropdownOption>((schema) => ({
|
||||
value: schema.relativePath,
|
||||
label: schema._sys.filename,
|
||||
})),
|
||||
]}
|
||||
placeholder="Select a schema"
|
||||
/>
|
||||
|
||||
{input.value && (
|
||||
<div className="mt-3 p-3 bg-blue-50 text-blue-600 rounded text-sm w-full max-w-full overflow-x-hidden">
|
||||
<div className="font-medium mb-1 truncate break-words whitespace-normal max-w-full">
|
||||
Selected schema:{" "}
|
||||
<span className="truncate break-words whitespace-normal max-w-full">
|
||||
{
|
||||
schemas.find(
|
||||
(s) => s.relativePath === input.value.split("|")[0]
|
||||
)?._sys.filename
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{loadingDetails ? (
|
||||
<div className="text-blue-500">Loading schema details...</div>
|
||||
) : schemaDetails ? (
|
||||
<>
|
||||
<div className="grid grid-cols-3 gap-2 mt-2 w-full max-w-full">
|
||||
<div className="bg-blue-100 p-2 rounded w-full max-w-full break-words whitespace-normal">
|
||||
<div className="text-xs text-blue-500 truncate break-words whitespace-normal max-w-full">
|
||||
API Name
|
||||
</div>
|
||||
<div className="font-medium truncate break-words whitespace-normal max-w-full">
|
||||
{schemaDetails.title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-blue-100 p-2 rounded w-full max-w-full break-words whitespace-normal">
|
||||
<div className="text-xs text-blue-500 truncate break-words whitespace-normal max-w-full">
|
||||
Version
|
||||
</div>
|
||||
<div className="font-medium truncate break-words whitespace-normal max-w-full">
|
||||
{schemaDetails.version}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-blue-100 p-2 rounded w-full max-w-full break-words whitespace-normal">
|
||||
<div className="text-xs text-blue-500 truncate break-words whitespace-normal max-w-full">
|
||||
Endpoints
|
||||
</div>
|
||||
<div className="font-medium truncate break-words whitespace-normal max-w-full">
|
||||
{schemaDetails.endpointCount}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Endpoint selector */}
|
||||
{schemaDetails.endpoints.length > 0 && (
|
||||
<div className="mt-4 w-full max-w-full overflow-x-hidden">
|
||||
<label className="block text-blue-700 font-medium mb-1">
|
||||
Select Endpoint (Optional)
|
||||
</label>
|
||||
{/* Endpoint selector dropdown */}
|
||||
<CustomDropdown
|
||||
value={selectedEndpoint}
|
||||
onChange={handleEndpointChange}
|
||||
options={[
|
||||
{ value: "", label: "All Endpoints" },
|
||||
...schemaDetails.endpoints
|
||||
.sort((a, b) => a.path.localeCompare(b.path))
|
||||
.map<DropdownOption>((endpoint) => ({
|
||||
value: createEndpointId(endpoint),
|
||||
label: `${endpoint.method} ${endpoint.path} ${
|
||||
endpoint.summary ? `- ${endpoint.summary}` : ""
|
||||
}`,
|
||||
})),
|
||||
]}
|
||||
placeholder="All Endpoints"
|
||||
contentClassName="bg-white border border-blue-300"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-blue-500">
|
||||
Unable to load schema details
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{field.description && (
|
||||
<p className="mt-1 text-sm text-gray-500 break-words whitespace-normal max-w-full">
|
||||
{field.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="mt-4 p-3 bg-gray-50 text-gray-600 rounded-md text-sm break-words whitespace-normal max-w-full">
|
||||
<p>
|
||||
<strong>Note:</strong> To add more schema files, go to the Content
|
||||
Manager and add files to the API Schema collection.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ApiReferenceTemplate = {
|
||||
name: "apiReference",
|
||||
label: "API Reference",
|
||||
ui: {
|
||||
defaultItem: {
|
||||
schemaFile: "test-doc.json",
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "string",
|
||||
name: "schemaFile",
|
||||
label: "API Schema",
|
||||
description:
|
||||
"Select a Swagger/OpenAPI schema file to display in this component. Optionally select a specific endpoint to display.",
|
||||
ui: {
|
||||
component: SchemaSelector,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
27
tina/templates/markdown-embeds/callout.template.tsx
Normal file
27
tina/templates/markdown-embeds/callout.template.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
export const CalloutTemplate = {
|
||||
name: "Callout",
|
||||
label: "Callout",
|
||||
ui: {
|
||||
defaultItem: {
|
||||
body: "This is a callout",
|
||||
variant: "warning",
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "body",
|
||||
label: "Body",
|
||||
type: "rich-text",
|
||||
isBody: true,
|
||||
},
|
||||
{
|
||||
name: "variant",
|
||||
label: "Variant",
|
||||
type: "string",
|
||||
options: ["warning", "info", "success", "error", "idea", "lock", "api"],
|
||||
defaultValue: "warning",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default CalloutTemplate;
|
||||
66
tina/templates/markdown-embeds/card-grid.template.tsx
Normal file
66
tina/templates/markdown-embeds/card-grid.template.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
export const CardGridTemplate = {
|
||||
name: "cardGrid",
|
||||
label: "Card Grid",
|
||||
ui: {
|
||||
defaultItem: {
|
||||
cards: [
|
||||
{
|
||||
title: "Card Title",
|
||||
description: "Card Description",
|
||||
link: "https://www.google.com",
|
||||
linkText: "Search now",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "cards",
|
||||
label: "Cards",
|
||||
type: "object",
|
||||
list: true,
|
||||
ui: {
|
||||
defaultItem: () => {
|
||||
return {
|
||||
title: "Card Title",
|
||||
description: "Card Description",
|
||||
link: "https://www.google.com",
|
||||
linkText: "Search now",
|
||||
};
|
||||
},
|
||||
itemProps: (item) => {
|
||||
return {
|
||||
label: item.title || "Untitled",
|
||||
};
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "string",
|
||||
ui: {
|
||||
component: "textarea",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "link",
|
||||
label: "Link",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "linkText",
|
||||
label: "Button Text",
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default CardGridTemplate;
|
||||
156
tina/templates/markdown-embeds/code-tabs.template.tsx
Normal file
156
tina/templates/markdown-embeds/code-tabs.template.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import MonacoCodeEditor from "@/tina/customFields/monaco-code-editor";
|
||||
|
||||
export const CodeTabsTemplate = {
|
||||
name: "codeTabs",
|
||||
label: "Code Tabs",
|
||||
ui: {
|
||||
defaultItem: {
|
||||
tabs: [
|
||||
{
|
||||
name: "Query",
|
||||
content: "const CONTENT_MANAGEMENT = 'Optimized';",
|
||||
},
|
||||
{
|
||||
name: "Response",
|
||||
content: "const LLAMAS = '100';",
|
||||
},
|
||||
],
|
||||
initialSelectedIndex: 0,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "object",
|
||||
name: "tabs",
|
||||
label: "Tabs",
|
||||
list: true,
|
||||
ui: {
|
||||
itemProps: (item) => ({
|
||||
label: `🗂️ ${item?.name ?? "Tab"}`,
|
||||
}),
|
||||
defaultItem: {
|
||||
name: "Tab",
|
||||
content: "const CONTENT_MANAGEMENT = 'Optimized';",
|
||||
language: "text",
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "string",
|
||||
name: "name",
|
||||
label: "Name",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "language",
|
||||
label: "Code Highlighting Language",
|
||||
options: [
|
||||
{
|
||||
value: "text",
|
||||
label: "Plain Text",
|
||||
},
|
||||
{
|
||||
value: "javascript",
|
||||
label: "JavaScript",
|
||||
},
|
||||
{
|
||||
value: "typescript",
|
||||
label: "TypeScript",
|
||||
},
|
||||
{
|
||||
value: "python",
|
||||
label: "Python",
|
||||
},
|
||||
{
|
||||
value: "json",
|
||||
label: "JSON",
|
||||
},
|
||||
{
|
||||
value: "html",
|
||||
label: "HTML",
|
||||
},
|
||||
{
|
||||
value: "css",
|
||||
label: "CSS",
|
||||
},
|
||||
{
|
||||
value: "jsx",
|
||||
label: "JSX",
|
||||
},
|
||||
{
|
||||
value: "tsx",
|
||||
label: "TSX",
|
||||
},
|
||||
{
|
||||
value: "markdown",
|
||||
label: "Markdown",
|
||||
},
|
||||
{
|
||||
value: "shell",
|
||||
label: "Shell",
|
||||
},
|
||||
{
|
||||
value: "sql",
|
||||
label: "SQL",
|
||||
},
|
||||
{
|
||||
value: "graphql",
|
||||
label: "GraphQL",
|
||||
},
|
||||
{
|
||||
value: "java",
|
||||
label: "Java",
|
||||
},
|
||||
{
|
||||
value: "php",
|
||||
label: "PHP",
|
||||
},
|
||||
{
|
||||
value: "cpp",
|
||||
label: "C++",
|
||||
},
|
||||
{
|
||||
value: "yaml",
|
||||
label: "YAML",
|
||||
},
|
||||
{
|
||||
value: "xml",
|
||||
label: "XML",
|
||||
},
|
||||
{
|
||||
value: "scss",
|
||||
label: "SCSS",
|
||||
},
|
||||
{
|
||||
value: "vue",
|
||||
label: "Vue",
|
||||
},
|
||||
{
|
||||
value: "svelte",
|
||||
label: "Svelte",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "content",
|
||||
label: "Content",
|
||||
ui: {
|
||||
component: MonacoCodeEditor,
|
||||
format: (val?: string) => val?.replaceAll("<22>", " "),
|
||||
parse: (val?: string) => val?.replaceAll(" ", "<22>"),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "initialSelectedIndex",
|
||||
label: "Initial Selected Index",
|
||||
description:
|
||||
"The index of the tab to select by default, starting from 0.",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default CodeTabsTemplate;
|
||||
45
tina/templates/markdown-embeds/file-structure.template.tsx
Normal file
45
tina/templates/markdown-embeds/file-structure.template.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { FileStructureField } from "@/tina/customFields/file-structure";
|
||||
|
||||
export const FileStructureTemplate = {
|
||||
name: "fileStructure",
|
||||
label: "File Structure",
|
||||
fields: [
|
||||
{
|
||||
type: "object",
|
||||
name: "fileStructure",
|
||||
label: "File Structure",
|
||||
ui: {
|
||||
component: FileStructureField,
|
||||
},
|
||||
list: true,
|
||||
fields: [
|
||||
{
|
||||
type: "string",
|
||||
name: "id",
|
||||
label: "ID",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "name",
|
||||
label: "Name",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "type",
|
||||
label: "Type",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "parentId",
|
||||
label: "Parent ID",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "caption",
|
||||
label: "Caption",
|
||||
description: "Optional caption that appears under the component",
|
||||
},
|
||||
],
|
||||
};
|
||||
67
tina/templates/markdown-embeds/recipe.template.tsx
Normal file
67
tina/templates/markdown-embeds/recipe.template.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import MonacoCodeEditor from "@/tina/customFields/monaco-code-editor";
|
||||
|
||||
export const RecipeTemplate = {
|
||||
name: "recipe",
|
||||
label: "Code Accordion (Recipe)",
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
label: "Heading Title",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "code",
|
||||
label: "Code",
|
||||
ui: {
|
||||
component: MonacoCodeEditor,
|
||||
format: (val?: string) => val?.replaceAll("<22>", " "),
|
||||
parse: (val?: string) => val?.replaceAll(" ", "<22>"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instruction",
|
||||
label: "Instruction",
|
||||
type: "object",
|
||||
list: true,
|
||||
ui: {
|
||||
itemProps: (item) => {
|
||||
return { label: item?.header };
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "header",
|
||||
label: "Header",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "itemDescription",
|
||||
label: "Item Description",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "codeLineStart",
|
||||
label: "Code Line Start",
|
||||
type: "number",
|
||||
description:
|
||||
"Enter negative values to highlight from 0 to your end number",
|
||||
},
|
||||
{
|
||||
name: "codeLineEnd",
|
||||
label: "Code Line End",
|
||||
type: "number",
|
||||
description:
|
||||
"Highlighting will not work if end number is greater than start number",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default RecipeTemplate;
|
||||
62
tina/templates/markdown-embeds/scroll-showcase.template.tsx
Normal file
62
tina/templates/markdown-embeds/scroll-showcase.template.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
export const ScrollShowcaseTemplate = {
|
||||
label: "Scroll Showcase",
|
||||
name: "scrollShowcase",
|
||||
fields: [
|
||||
{
|
||||
type: "object",
|
||||
label: "Showcase Items",
|
||||
name: "showcaseItems",
|
||||
list: true,
|
||||
ui: {
|
||||
defaultItem: {
|
||||
title: "Title",
|
||||
image: "/img/rico-replacement.jpg",
|
||||
content: {
|
||||
type: "root",
|
||||
children: [
|
||||
{
|
||||
type: "p",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Default Text. Edit me!",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
useAsSubsection: false,
|
||||
},
|
||||
itemProps: (item) => {
|
||||
return {
|
||||
label: item.title,
|
||||
};
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "image",
|
||||
label: "Image",
|
||||
name: "image",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
label: "Title",
|
||||
name: "title",
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
label: "Use as Subsection",
|
||||
name: "useAsSubsection",
|
||||
},
|
||||
{
|
||||
type: "rich-text",
|
||||
label: "Content",
|
||||
name: "content",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default ScrollShowcaseTemplate;
|
||||
53
tina/templates/markdown-embeds/type-definition.template.tsx
Normal file
53
tina/templates/markdown-embeds/type-definition.template.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
export const TypeDefinitionTemplate = {
|
||||
name: "typeDefinition",
|
||||
label: "Type Definition",
|
||||
fields: [
|
||||
{
|
||||
type: "object",
|
||||
name: "property",
|
||||
label: "Property",
|
||||
list: true,
|
||||
ui: {
|
||||
itemProps: (item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
};
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "string",
|
||||
name: "name",
|
||||
label: "Name",
|
||||
},
|
||||
{
|
||||
type: "rich-text",
|
||||
name: "description",
|
||||
label: "Description",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "type",
|
||||
label: "Type",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "typeUrl",
|
||||
label: "Type URL",
|
||||
description:
|
||||
"Turns the type into a link for further context. Useful for deeply nested types.",
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
name: "required",
|
||||
label: "Required",
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
name: "experimental",
|
||||
label: "Experimental",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
34
tina/templates/markdown-embeds/youtube.template.tsx
Normal file
34
tina/templates/markdown-embeds/youtube.template.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
export const YoutubeTemplate = {
|
||||
name: "youtube",
|
||||
label: "Youtube Video",
|
||||
ui: {
|
||||
defaultItem: {
|
||||
embedSrc: "https://www.youtube.com/embed/CsCQS7HIBv0?si=os9ona92O2VMOl-V",
|
||||
caption: "Seth goes over the basics of using TinaCMS",
|
||||
minutes: "2",
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "string",
|
||||
name: "embedSrc",
|
||||
label: "Embed URL",
|
||||
description:
|
||||
"⚠︎ Only YouTube embed URLs work - they look like this https://www.youtube.com/embed/Yoh2c5RUTiY",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "caption",
|
||||
label: "Caption",
|
||||
description: "The caption of the video",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "minutes",
|
||||
label: "Minutes",
|
||||
description: "The duration of the video in minutes",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default YoutubeTemplate;
|
||||
35
tina/templates/navbar-ui.template.tsx
Normal file
35
tina/templates/navbar-ui.template.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { Template } from "tinacms";
|
||||
import { titleCase } from "title-case";
|
||||
|
||||
export const itemTemplate: Template = {
|
||||
label: "Item",
|
||||
name: "item",
|
||||
ui: {
|
||||
itemProps: (item) => {
|
||||
return {
|
||||
label: `🔗 ${titleCase(
|
||||
item?.slug?.split("/").at(-1).split(".").at(0).replaceAll("-", " ") ??
|
||||
"Unnamed Menu Item"
|
||||
)}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "slug",
|
||||
label: "Page",
|
||||
type: "reference",
|
||||
collections: ["docs"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const submenusLabel: Pick<Template, "label" | "name" | "ui"> = {
|
||||
label: "Submenu",
|
||||
name: "items",
|
||||
ui: {
|
||||
itemProps: (item) => ({
|
||||
label: `🗂️ ${item?.title ?? "Unnamed Menu Group"}`,
|
||||
}),
|
||||
},
|
||||
};
|
||||
37
tina/templates/submenu.template.tsx
Normal file
37
tina/templates/submenu.template.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { Template } from "tinacms";
|
||||
import { itemTemplate } from "./navbar-ui.template";
|
||||
|
||||
const createMenuTemplate = (
|
||||
templates: Template[],
|
||||
level: number
|
||||
): Template => ({
|
||||
label: "Submenu",
|
||||
name: "items",
|
||||
ui: {
|
||||
itemProps: (item) => {
|
||||
return { label: `🗂️ ${level} | ${item?.title ?? "Unnamed Menu Group"}` };
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{ name: "title", label: "Name", type: "string" },
|
||||
{
|
||||
name: "items",
|
||||
label: "Submenu Items",
|
||||
type: "object",
|
||||
list: true,
|
||||
templates,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const thirdLevelSubmenu: Template = createMenuTemplate([itemTemplate], 3);
|
||||
const secondLevelSubmenu: Template = createMenuTemplate(
|
||||
[thirdLevelSubmenu, itemTemplate],
|
||||
2
|
||||
);
|
||||
export const submenuTemplate: Template = createMenuTemplate(
|
||||
[secondLevelSubmenu, itemTemplate],
|
||||
1
|
||||
);
|
||||
|
||||
export default submenuTemplate;
|
||||
Reference in New Issue
Block a user