initial commit after project creation
This commit is contained in:
20
tina/collections/API-schema.tsx
Normal file
20
tina/collections/API-schema.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { wrapFieldsWithMeta } from "tinacms";
|
||||
import { JsonFileUploadComponent } from "../customFields/file-upload";
|
||||
|
||||
export const API_Schema_Collection = {
|
||||
name: "apiSchema",
|
||||
label: "API Schema",
|
||||
path: "content/apiSchema",
|
||||
format: "json",
|
||||
|
||||
fields: [
|
||||
{
|
||||
name: "apiSchema",
|
||||
label: "API Schema",
|
||||
type: "string",
|
||||
ui: { component: wrapFieldsWithMeta(JsonFileUploadComponent) },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default API_Schema_Collection;
|
||||
105
tina/collections/docs.tsx
Normal file
105
tina/collections/docs.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import AccordionTemplate, {
|
||||
AccordionBlockTemplate,
|
||||
} from "@/tina/templates/markdown-embeds/accordion.template";
|
||||
import { ApiReferenceTemplate } from "@/tina/templates/markdown-embeds/api-reference.template";
|
||||
import CalloutTemplate from "@/tina/templates/markdown-embeds/callout.template";
|
||||
import CardGridTemplate from "@/tina/templates/markdown-embeds/card-grid.template";
|
||||
import CodeTabsTemplate from "@/tina/templates/markdown-embeds/code-tabs.template";
|
||||
import { FileStructureTemplate } from "@/tina/templates/markdown-embeds/file-structure.template";
|
||||
import RecipeTemplate from "@/tina/templates/markdown-embeds/recipe.template";
|
||||
import ScrollShowcaseTemplate from "@/tina/templates/markdown-embeds/scroll-showcase.template";
|
||||
import { TypeDefinitionTemplate } from "@/tina/templates/markdown-embeds/type-definition.template";
|
||||
import YoutubeTemplate from "@/tina/templates/markdown-embeds/youtube.template";
|
||||
import type { Template } from "tinacms";
|
||||
import SeoInformation from "./seo-information";
|
||||
|
||||
export const docsCollection = {
|
||||
name: "docs",
|
||||
label: "Docs",
|
||||
path: "content/docs",
|
||||
format: "mdx",
|
||||
ui: {
|
||||
beforeSubmit: async ({ values }) => {
|
||||
return {
|
||||
...values,
|
||||
last_edited: new Date().toISOString(),
|
||||
auto_generated: false,
|
||||
};
|
||||
},
|
||||
router: ({ document }) => {
|
||||
if (document._sys.filename === "index") {
|
||||
return "/";
|
||||
}
|
||||
const slug = document._sys.breadcrumbs.join("/");
|
||||
return `/docs/${slug}`;
|
||||
},
|
||||
filename: {
|
||||
slugify: (values) => {
|
||||
return (
|
||||
values?.title
|
||||
?.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, "") // Remove special characters except spaces and dashes
|
||||
.replace(/\s+/g, "-") // Replace spaces with dashes
|
||||
.replace(/-+/g, "-") // Replace multiple dashes with single dash
|
||||
.replace(/^-|-$/g, "") || // Remove leading/trailing dashes
|
||||
""
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
SeoInformation,
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "string",
|
||||
isTitle: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "last_edited",
|
||||
label: "Last Edited",
|
||||
ui: {
|
||||
component: "hidden",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
name: "auto_generated",
|
||||
label: "Auto Generated",
|
||||
description: "Indicates if this document was automatically generated",
|
||||
ui: {
|
||||
component: "hidden",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
name: "tocIsHidden",
|
||||
label: "Hide Table of Contents",
|
||||
description:
|
||||
"Hide the Table of Contents on this page and expand the content window.",
|
||||
},
|
||||
{
|
||||
type: "rich-text",
|
||||
name: "body",
|
||||
label: "Body",
|
||||
isBody: true,
|
||||
templates: [
|
||||
ScrollShowcaseTemplate as Template,
|
||||
CardGridTemplate as Template,
|
||||
RecipeTemplate as Template,
|
||||
AccordionTemplate as Template,
|
||||
AccordionBlockTemplate as Template,
|
||||
ApiReferenceTemplate as Template,
|
||||
YoutubeTemplate as Template,
|
||||
CodeTabsTemplate as Template,
|
||||
CalloutTemplate as Template,
|
||||
TypeDefinitionTemplate as Template,
|
||||
FileStructureTemplate as unknown as Template,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default docsCollection;
|
||||
206
tina/collections/navigation-bar.tsx
Normal file
206
tina/collections/navigation-bar.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import { getBearerAuthHeader } from "@/src/utils/tina/get-bearer-auth-header";
|
||||
import { ApiReferencesSelector } from "../customFields/api-reference-selector";
|
||||
import { itemTemplate } from "../templates/navbar-ui.template";
|
||||
import submenuTemplate from "../templates/submenu.template";
|
||||
|
||||
const docsNavigationBarFields = [
|
||||
{
|
||||
name: "title",
|
||||
label: "Title Label",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "supermenuGroup",
|
||||
label: "Supermenu Group",
|
||||
type: "object",
|
||||
list: true,
|
||||
ui: {
|
||||
itemProps: (item) => ({
|
||||
label: `🗂️ ${item?.title ?? "Unnamed Menu Group"}`,
|
||||
}),
|
||||
},
|
||||
fields: [
|
||||
{ name: "title", label: "Name", type: "string" },
|
||||
{
|
||||
name: "items",
|
||||
label: "Page or Submenu",
|
||||
type: "object",
|
||||
list: true,
|
||||
templates: [submenuTemplate, itemTemplate],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const documentSubMenuTemplate = {
|
||||
name: "documentSubMenu",
|
||||
label: "Document Submenu",
|
||||
fields: [
|
||||
{ name: "title", label: "Name", type: "string" },
|
||||
{
|
||||
name: "items",
|
||||
label: "Items",
|
||||
type: "object",
|
||||
list: true,
|
||||
templates: [itemTemplate],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const groupOfApiReferencesTemplate = {
|
||||
name: "groupOfApiReferences",
|
||||
label: "Group of API References",
|
||||
fields: [
|
||||
{
|
||||
type: "string",
|
||||
name: "apiGroup",
|
||||
label: "API Group",
|
||||
ui: {
|
||||
component: ApiReferencesSelector,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const apiNavigationBarFields = [
|
||||
{
|
||||
name: "title",
|
||||
label: "title",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "supermenuGroup",
|
||||
label: "Supermenu Group",
|
||||
type: "object",
|
||||
list: true,
|
||||
templates: [documentSubMenuTemplate, groupOfApiReferencesTemplate],
|
||||
},
|
||||
];
|
||||
|
||||
const docsTabTemplate = {
|
||||
name: "docsTab",
|
||||
label: "Docs Tab",
|
||||
fields: docsNavigationBarFields,
|
||||
};
|
||||
|
||||
const apiTabTemplate = {
|
||||
name: "apiTab",
|
||||
label: "API Tab",
|
||||
fields: apiNavigationBarFields,
|
||||
};
|
||||
|
||||
export const docsNavigationBarCollection = {
|
||||
name: "navigationBar",
|
||||
label: "Navigation Bar",
|
||||
path: "content/navigation-bar",
|
||||
format: "json",
|
||||
ui: {
|
||||
allowedActions: {
|
||||
create: false,
|
||||
delete: false,
|
||||
},
|
||||
beforeSubmit: async ({ values }: { values: Record<string, any> }) => {
|
||||
try {
|
||||
// Generate .mdx files for API endpoints when navigation is saved
|
||||
const response = await fetch("/api/process-api-docs", {
|
||||
method: "POST",
|
||||
headers: getBearerAuthHeader(),
|
||||
body: JSON.stringify({
|
||||
data: values,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
// Log error but don't block the save operation
|
||||
}
|
||||
|
||||
// Always return the values, don't block the save operation if file generation fails
|
||||
return {
|
||||
...values,
|
||||
};
|
||||
} catch (error) {
|
||||
// Don't block the save operation if file generation fails
|
||||
return {
|
||||
...values,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "lightModeLogo",
|
||||
label: "Light Mode Logo",
|
||||
type: "image",
|
||||
},
|
||||
{
|
||||
name: "darkModeLogo",
|
||||
label: "Dark Mode Logo",
|
||||
type: "image",
|
||||
description: "If your light mode logo fits dark-mode, leave this blank.",
|
||||
},
|
||||
{
|
||||
name: "tabs",
|
||||
label: "Tabs",
|
||||
type: "object",
|
||||
list: true,
|
||||
ui: {
|
||||
itemProps: (item) => ({
|
||||
label: `🗂️ ${item?.title ?? "Unnamed Tab"}`,
|
||||
}),
|
||||
},
|
||||
templates: [docsTabTemplate, apiTabTemplate],
|
||||
},
|
||||
{
|
||||
name: "ctaButtons",
|
||||
label: "CTA Buttons",
|
||||
type: "object",
|
||||
fields: [
|
||||
{
|
||||
name: "button1",
|
||||
label: "Button 1",
|
||||
type: "object",
|
||||
fields: [
|
||||
{ label: "Label", name: "label", type: "string" },
|
||||
{ label: "Link", name: "link", type: "string" },
|
||||
{
|
||||
label: "variant",
|
||||
name: "variant",
|
||||
type: "string",
|
||||
options: [
|
||||
"primary-background",
|
||||
"secondary-background",
|
||||
"primary-outline",
|
||||
"secondary-outline",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "button2",
|
||||
label: "Button 2",
|
||||
type: "object",
|
||||
fields: [
|
||||
{ label: "Label", name: "label", type: "string" },
|
||||
{ label: "Link", name: "link", type: "string" },
|
||||
{
|
||||
label: "variant",
|
||||
name: "variant",
|
||||
type: "string",
|
||||
options: [
|
||||
"primary-background",
|
||||
"secondary-background",
|
||||
"primary-outline",
|
||||
"secondary-outline",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default docsNavigationBarCollection;
|
||||
48
tina/collections/seo-information.tsx
Normal file
48
tina/collections/seo-information.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { TextInputWithCount } from "../customFields/text-input-with-count";
|
||||
|
||||
export const SeoInformation = {
|
||||
type: "object",
|
||||
label: "SEO Values",
|
||||
name: "seo",
|
||||
fields: [
|
||||
{
|
||||
type: "string",
|
||||
label: "Meta - Title",
|
||||
description: "Recommended limit of 70 characters",
|
||||
name: "title",
|
||||
ui: {
|
||||
validate: (value) => {
|
||||
if (value && value.length > 70) {
|
||||
return "Title should be 70 characters or less";
|
||||
}
|
||||
},
|
||||
component: TextInputWithCount(70),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
label: "Meta - Description",
|
||||
description: "Recommended limit of 150 characters",
|
||||
name: "description",
|
||||
component: "textarea",
|
||||
ui: {
|
||||
component: TextInputWithCount(150, true),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
label: "Canonical URL",
|
||||
name: "canonicalUrl",
|
||||
description: "Default URL if no URL is provided",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
label: "Open Graph Image",
|
||||
name: "ogImage",
|
||||
uploadDir: () => "og",
|
||||
description: "Default image if no image is provided",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default SeoInformation;
|
||||
179
tina/collections/settings.tsx
Normal file
179
tina/collections/settings.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import { BROWSER_TAB_THEME_KEY } from "@/src/components/ui/theme-selector";
|
||||
import React from "react";
|
||||
import { RedirectItem } from "../customFields/redirect-item";
|
||||
import { ThemeSelector } from "../customFields/theme-selector";
|
||||
|
||||
export const Settings = {
|
||||
name: "settings",
|
||||
label: "Settings",
|
||||
path: "content/settings",
|
||||
format: "json",
|
||||
ui: {
|
||||
global: true,
|
||||
allowedActions: {
|
||||
create: false,
|
||||
delete: false,
|
||||
},
|
||||
defaultItem: {
|
||||
autoCapitalizeNavigation: true,
|
||||
},
|
||||
beforeSubmit: async ({ values }: { values: Record<string, any> }) => {
|
||||
sessionStorage.setItem(BROWSER_TAB_THEME_KEY, values.selectedTheme);
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "selectedTheme",
|
||||
label: "Selected Theme",
|
||||
description: "Choose your website's visual theme with color previews",
|
||||
type: "string",
|
||||
ui: {
|
||||
component: ThemeSelector,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "redirects",
|
||||
label: "Redirects",
|
||||
type: "object",
|
||||
list: true,
|
||||
ui: {
|
||||
itemProps: (item) => {
|
||||
return {
|
||||
label:
|
||||
item.source && item.destination ? (
|
||||
<RedirectItem
|
||||
source={item.source}
|
||||
destination={item.destination}
|
||||
permanent={item.permanent}
|
||||
/>
|
||||
) : (
|
||||
"Add Redirect"
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "source",
|
||||
label: "Source",
|
||||
type: "string",
|
||||
ui: {
|
||||
validate: (value) => {
|
||||
if (!value?.startsWith("/")) {
|
||||
return "Source path must start with /";
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "destination",
|
||||
label: "Destination",
|
||||
type: "string",
|
||||
ui: {
|
||||
validate: (value) => {
|
||||
if (!value?.startsWith("/")) {
|
||||
return "Destination path must start with /";
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "permanent",
|
||||
label: "Permanent",
|
||||
type: "boolean",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "seoDefaultTitle",
|
||||
label: "SEO Default Title",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "publisher",
|
||||
label: "Publisher",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "applicationName",
|
||||
label: "Application Name",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "siteUrl",
|
||||
label: "Site URL",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "roadmapUrl",
|
||||
label: "Roadmap URL",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "licenseUrl",
|
||||
label: "License URL",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "keywords",
|
||||
label: "Keywords",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "docsHomepage",
|
||||
label: "Docs Homepage",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "autoApiTitles",
|
||||
label: "Auto-Capitalize Titles",
|
||||
description:
|
||||
"Auto-capitalize titles in the navigation bar and generated API pages",
|
||||
type: "boolean",
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: "defaultOGImage",
|
||||
label: "Default OG Image",
|
||||
type: "image",
|
||||
uploadDir: () => "og",
|
||||
},
|
||||
{
|
||||
name: "social",
|
||||
label: "Social",
|
||||
type: "object",
|
||||
fields: [
|
||||
{
|
||||
name: "twitterHandle",
|
||||
label: "Twitter Handle",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "twitter",
|
||||
label: "Twitter",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "github",
|
||||
label: "GitHub",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "forum",
|
||||
label: "Forum",
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
166
tina/collections/site-config.tsx
Normal file
166
tina/collections/site-config.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import { CustomColorToggle } from "@/components/ui/custom-color-toggle";
|
||||
|
||||
export const GlobalSiteConfiguration = {
|
||||
name: "globalSiteConfiguration",
|
||||
label: "Global Site Configuration",
|
||||
ui: {
|
||||
global: true,
|
||||
allowedActions: {
|
||||
create: false,
|
||||
delete: false,
|
||||
},
|
||||
},
|
||||
path: "content/site-config",
|
||||
format: "json",
|
||||
fields: [
|
||||
{
|
||||
name: "docsConfig",
|
||||
label: "Docs Config",
|
||||
type: "object",
|
||||
fields: [
|
||||
{
|
||||
name: "documentationSiteTitle",
|
||||
label: "Documentation Site Title",
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "colorScheme",
|
||||
label: "Color Scheme",
|
||||
type: "object",
|
||||
fields: [
|
||||
{
|
||||
name: "siteColors",
|
||||
label: "Site Colors",
|
||||
type: "object",
|
||||
defaultItem: () => {
|
||||
return {
|
||||
primaryStart: "#f97316",
|
||||
primaryEnd: "#f97316",
|
||||
primaryVia: "#f97316",
|
||||
};
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "primaryStart",
|
||||
label: "Primary Color | Gradient Start",
|
||||
type: "string",
|
||||
description:
|
||||
"This is the start of the primary color gradient ⚠️ If you want a solid color leave the end and via empty ⚠️",
|
||||
ui: {
|
||||
component: "color",
|
||||
colorFormat: "hex",
|
||||
widget: "sketch",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "primaryEnd",
|
||||
label: "Primary Color | Gradient End",
|
||||
type: "string",
|
||||
ui: {
|
||||
component: "color",
|
||||
colorFormat: "hex",
|
||||
widget: "sketch",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "primaryVia",
|
||||
label: "Primary Color | Gradient Via",
|
||||
type: "string",
|
||||
ui: {
|
||||
component: "color",
|
||||
colorFormat: "hex",
|
||||
widget: "sketch",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secondaryStart",
|
||||
label: "Secondary Color | Gradient Start",
|
||||
type: "string",
|
||||
description:
|
||||
"This is the start of the secondary color gradient ⚠️ If you want a solid color leave the end and via empty ⚠️",
|
||||
ui: {
|
||||
component: "color",
|
||||
colorFormat: "hex",
|
||||
widget: "sketch",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secondaryEnd",
|
||||
label: "Secondary Color | Gradient End",
|
||||
type: "string",
|
||||
ui: {
|
||||
component: "color",
|
||||
colorFormat: "hex",
|
||||
widget: "sketch",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secondaryVia",
|
||||
label: "Secondary Color | Gradient Via",
|
||||
type: "string",
|
||||
ui: {
|
||||
component: "color",
|
||||
colorFormat: "hex",
|
||||
widget: "sketch",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rightHandSideActiveColor",
|
||||
label: "Right Hand Side ToC Active Color",
|
||||
type: "string",
|
||||
ui: {
|
||||
component: "color",
|
||||
colorFormat: "hex",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rightHandSideInactiveColor",
|
||||
label: "Right Hand Side ToC Inactive Color",
|
||||
type: "string",
|
||||
ui: {
|
||||
component: "color",
|
||||
colorFormat: "hex",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "customColorToggle",
|
||||
label: "Custom Color Toggle",
|
||||
type: "object",
|
||||
fields: [
|
||||
{
|
||||
name: "disableColor",
|
||||
label: "Tick to use Default Background Color",
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
name: "colorValue",
|
||||
label: "Color Value",
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
ui: {
|
||||
component: CustomColorToggle,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "leftSidebarBackground",
|
||||
label: "Left Sidebar Background",
|
||||
type: "string",
|
||||
description: "This is the background color of the left sidebar",
|
||||
ui: {
|
||||
component: "color",
|
||||
colorFormat: "hex",
|
||||
widget: "sketch",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default GlobalSiteConfiguration;
|
||||
Reference in New Issue
Block a user