16 changed files with 458 additions and 156 deletions
+2 -70
View File
@@ -1,5 +1,6 @@
'use client';
import { useState } from 'react';
import { VERSIONS } from '../constants';
import styles from './Clients.module.css';
export default function Clients({
@@ -7,76 +8,7 @@ export default function Clients({
}: {
version: string;
}) {
const csharpSvg = `
<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 456 456" fill="none">
<rect width="456" height="456" fill="#512BD4"/>
<path d="M81.2738 291.333C78.0496 291.333 75.309 290.259 73.052 288.11C70.795 285.906 69.6665 283.289 69.6665 280.259C69.6665 277.173 70.795 274.529 73.052 272.325C75.309 270.121 78.0496 269.019 81.2738 269.019C84.5518 269.019 87.3193 270.121 89.5763 272.325C91.887 274.529 93.0424 277.173 93.0424 280.259C93.0424 283.289 91.887 285.906 89.5763 288.11C87.3193 290.259 84.5518 291.333 81.2738 291.333Z" fill="white"/>
<path d="M210.167 289.515H189.209L133.994 202.406C132.597 200.202 131.441 197.915 130.528 195.546H130.044C130.474 198.081 130.689 203.508 130.689 211.827V289.515H112.149V171H134.477L187.839 256.043C190.096 259.57 191.547 261.994 192.192 263.316H192.514C191.977 260.176 191.708 254.859 191.708 247.365V171H210.167V289.515Z" fill="white"/>
<path d="M300.449 289.515H235.561V171H297.87V187.695H254.746V221.249H294.485V237.861H254.746V272.903H300.449V289.515Z" fill="white"/>
<path d="M392.667 187.695H359.457V289.515H340.272V187.695H307.143V171H392.667V187.695Z" fill="white"/>
</svg>
`;
const versionClients = {
1: [
{
name: 'C#',
repoLink:
'https://github.com/onspring-technologies/onspring-api-sdk/tree/v1.1',
installCommand:
'dotnet add package Onspring.API.SDK --version 2.1.0',
svg: csharpSvg,
official: true,
},
],
2: [
{
name: 'C#',
repoLink:
'https://github.com/onspring-technologies/onspring-api-sdk',
installCommand:
'dotnet add package Onspring.API.SDK',
svg: csharpSvg,
official: true,
},
{
name: 'Javascript',
repoLink:
'https://github.com/StevanFreeborn/onspring-api-sdk-javascript',
installCommand: 'npm install onspring-api-sdk',
svg: `
<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg" aria-label="JavaScript" viewBox="0 0 512 512" id="javascript">
<rect width="512" height="512" fill="#f7df1e" rx="15%"></rect>
<path d="m324,370c10,17 24,29 47,29 20,0 33,-10 33,-24 0,-16 -13,-22 -35,-32l-12,-5c-35,-15 -58,-33 -58,-72 0,-36 27,-64 70,-64 31,0 53,11 68,39l-37,24c-8,-15 -17,-21 -31,-21 -14,0 -23,9 -23,21 0,14 9,20 30,29l12,5c41,18 64,35 64,76 0,43 -34,67 -80,67 -45,0 -74,-21 -88,-49zm-170,4c8,13 14,25 31,25 16,0 26,-6 26,-30V203h48v164c0,50 -29,72 -72,72 -39,0 -61,-20 -72,-44z"></path>
</svg>
`,
official: false,
},
{
name: 'Python',
repoLink:
'https://github.com/StevanFreeborn/onspring-api-sdk-python',
installCommand: 'pip install OnspringApiSdk',
svg: `
<svg width="30" height="30" viewBox="0 0 256 255" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<path d="M126.916.072c-64.832 0-60.784 28.115-60.784 28.115l.072 29.128h61.868v8.745H41.631S.145 61.355.145 126.77c0 65.417 36.21 63.097 36.21 63.097h21.61v-30.356s-1.165-36.21 35.632-36.21h61.362s34.475.557 34.475-33.319V33.97S194.67.072 126.916.072zM92.802 19.66a11.12 11.12 0 0111.13 11.13 11.12 11.12 0 01-11.13 11.13 11.12 11.12 0 01-11.13-11.13 11.12 11.12 0 0111.13-11.13z" fill="#3372a7"></path>
<path d="M128.757 254.126c64.832 0 60.784-28.115 60.784-28.115l-.072-29.127H127.6v-8.745h86.441s41.486 4.705 41.486-60.712c0-65.416-36.21-63.096-36.21-63.096h-21.61v30.355s1.165 36.21-35.632 36.21h-61.362s-34.475-.557-34.475 33.32v56.013s-5.235 33.897 62.518 33.897zm34.114-19.586a11.12 11.12 0 01-11.13-11.13 11.12 11.12 0 0111.13-11.131 11.12 11.12 0 0111.13 11.13 11.12 11.12 0 01-11.13 11.13z" fill="#ffd235"></path>
</svg>
`,
official: false,
},
],
} as {
[key: string]: {
name: string;
repoLink: string;
installCommand: string;
svg: string;
official: boolean;
}[];
};
const clients = versionClients[version];
const clients = VERSIONS[version].clients;
const [selectedClient, setSelectedClient] = useState(
clients[0]
);
+18 -26
View File
@@ -2,29 +2,17 @@
import Link from 'next/link';
import { useState } from 'react';
import { VERSIONS } from '../constants';
import { themes, useThemeContext } from '../context/theme';
import { DocsStructure } from '../types/types';
import { DocsStructure, Version } from '../types/types';
import styles from './NavBar.module.css';
import SectionDropdown from './SectionDropdown';
function VersionsDropdown({
version,
}: {
version: string;
version: Version;
}) {
const versions = {
version_001: {
name: 'Version 1',
path: '/version-1',
},
version_002: {
name: 'Version 2',
path: '/',
},
} as { [key: string]: { name: string; path: string } };
const currentVersion = versions[version];
const [isDropdownOpen, setIsDropdownOpen] =
useState<boolean>(false);
@@ -38,7 +26,7 @@ function VersionsDropdown({
className={styles.navLink}
onClick={handleDropdownToggle}
>
{currentVersion.name}{' '}
{version.name}{' '}
{isDropdownOpen ? (
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -76,18 +64,18 @@ function VersionsDropdown({
: styles.dropdownMenu
}
>
{Object.keys(versions)
.filter(v => v !== version)
{Object.keys(VERSIONS)
.filter(v => VERSIONS[v].name !== version.name)
.map(v => (
<Link
href={`${versions[v].path}`}
href={`${VERSIONS[v].path}`}
key={v}
className={styles.navLink}
onClick={() => {
setIsDropdownOpen(false);
}}
>
{versions[v].name}
{VERSIONS[v].name}
</Link>
))}
</div>
@@ -96,12 +84,14 @@ function VersionsDropdown({
}
export default function NavBar({
version,
versionDocStructure,
}: {
version: DocsStructure;
versionDocStructure: DocsStructure;
}) {
const { theme } = useThemeContext();
const themeStyles = themes[theme];
const currentVersion =
VERSIONS[versionDocStructure.version];
const [isNavOpen, setIsNavOpen] =
useState<boolean>(false);
@@ -114,7 +104,9 @@ export default function NavBar({
<nav style={themeStyles} className={styles.nav}>
<ul className={styles.navListLeft}>
<li className={styles.sectionDropdown}>
<SectionDropdown version={version} />
<SectionDropdown
versionDocStructure={versionDocStructure}
/>
</li>
</ul>
<div>
@@ -134,18 +126,18 @@ export default function NavBar({
}
>
<li className={styles.navItem}>
<VersionsDropdown version={version.version} />
<VersionsDropdown version={currentVersion} />
</li>
<li className={styles.navItem}>
<Link
href={version.officialDocs}
href={currentVersion.officialDocs}
className={styles.navLink}
target="_blank"
>
Official Docs
</Link>
</li>
{version.hasSwagger ? (
{currentVersion.hasSwagger ? (
<li className={styles.navItem}>
<Link
href="https://api.onspring.com/swagger"
+4 -4
View File
@@ -10,10 +10,10 @@ import styles from './Page.module.css';
import SideBar from './SideBar';
export default function Page({
version,
versionDocStructure,
versionSections,
}: {
version: DocsStructure;
versionDocStructure: DocsStructure;
versionSections: DocSection[];
}) {
const { theme } = useThemeContext();
@@ -56,9 +56,9 @@ export default function Page({
return (
<div className={styles.wrapper} style={themeStyles}>
<SideBar version={version} />
<SideBar versionDocStructure={versionDocStructure} />
<div className={styles.container}>
<NavBar version={version} />
<NavBar versionDocStructure={versionDocStructure} />
<Main versionSections={versionSections} />
</div>
</div>
+3 -3
View File
@@ -4,9 +4,9 @@ import { toKebabCase } from '../utils/stringUtils';
import styles from './SectionDropdown.module.css';
export default function SectionDropdown({
version,
versionDocStructure,
}: {
version: DocsStructure;
versionDocStructure: DocsStructure;
}) {
const { section, setSection } = useSectionContext();
const handleSelectChange = (e: any) => {
@@ -28,7 +28,7 @@ export default function SectionDropdown({
return result;
};
const docs = getAllDocs(version.docs);
const docs = getAllDocs(versionDocStructure.docs);
return (
<select
+3 -3
View File
@@ -107,9 +107,9 @@ function ListTree({ docs }: { docs: Doc[] }) {
}
export default function SideBar({
version,
versionDocStructure,
}: {
version: DocsStructure;
versionDocStructure: DocsStructure;
}) {
const { theme, setTheme } = useThemeContext();
@@ -138,7 +138,7 @@ export default function SideBar({
></span>
</label>
</div>
<ListTree docs={version.docs} />
<ListTree docs={versionDocStructure.docs} />
</div>
);
}
+82
View File
@@ -0,0 +1,82 @@
import path from 'path';
import { Version } from './types/types';
export const DOCS_PATH = path.join(
process.cwd(),
'src/docs'
);
const CSHARP_SVG = `
<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 456 456" fill="none">
<rect width="456" height="456" fill="#512BD4"/>
<path d="M81.2738 291.333C78.0496 291.333 75.309 290.259 73.052 288.11C70.795 285.906 69.6665 283.289 69.6665 280.259C69.6665 277.173 70.795 274.529 73.052 272.325C75.309 270.121 78.0496 269.019 81.2738 269.019C84.5518 269.019 87.3193 270.121 89.5763 272.325C91.887 274.529 93.0424 277.173 93.0424 280.259C93.0424 283.289 91.887 285.906 89.5763 288.11C87.3193 290.259 84.5518 291.333 81.2738 291.333Z" fill="white"/>
<path d="M210.167 289.515H189.209L133.994 202.406C132.597 200.202 131.441 197.915 130.528 195.546H130.044C130.474 198.081 130.689 203.508 130.689 211.827V289.515H112.149V171H134.477L187.839 256.043C190.096 259.57 191.547 261.994 192.192 263.316H192.514C191.977 260.176 191.708 254.859 191.708 247.365V171H210.167V289.515Z" fill="white"/>
<path d="M300.449 289.515H235.561V171H297.87V187.695H254.746V221.249H294.485V237.861H254.746V272.903H300.449V289.515Z" fill="white"/>
<path d="M392.667 187.695H359.457V289.515H340.272V187.695H307.143V171H392.667V187.695Z" fill="white"/>
</svg>
`;
export const VERSIONS: Record<string, Version> = {
version_001: {
name: 'Version 1',
path: '/version-1',
officialDocs:
'https://software.onspring.com/hubfs/Training/Admin%20Guide%20-%20v1%20API.pdf',
hasSwagger: false,
clients: [
{
name: 'C#',
repoLink:
'https://github.com/onspring-technologies/onspring-api-sdk/tree/v1.1',
installCommand:
'dotnet add package Onspring.API.SDK --version 2.1.0',
svg: CSHARP_SVG,
official: true,
},
],
},
version_002: {
name: 'Version 2',
path: '/',
officialDocs:
'https://software.onspring.com/hubfs/Training/Admin%20Guide%20-%20v2%20API.pdf',
hasSwagger: true,
clients: [
{
name: 'C#',
repoLink:
'https://github.com/onspring-technologies/onspring-api-sdk',
installCommand:
'dotnet add package Onspring.API.SDK',
svg: CSHARP_SVG,
official: true,
},
{
name: 'Javascript',
repoLink:
'https://github.com/StevanFreeborn/onspring-api-sdk-javascript',
installCommand: 'npm install onspring-api-sdk',
svg: `
<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg" aria-label="JavaScript" viewBox="0 0 512 512" id="javascript">
<rect width="512" height="512" fill="#f7df1e" rx="15%"></rect>
<path d="m324,370c10,17 24,29 47,29 20,0 33,-10 33,-24 0,-16 -13,-22 -35,-32l-12,-5c-35,-15 -58,-33 -58,-72 0,-36 27,-64 70,-64 31,0 53,11 68,39l-37,24c-8,-15 -17,-21 -31,-21 -14,0 -23,9 -23,21 0,14 9,20 30,29l12,5c41,18 64,35 64,76 0,43 -34,67 -80,67 -45,0 -74,-21 -88,-49zm-170,4c8,13 14,25 31,25 16,0 26,-6 26,-30V203h48v164c0,50 -29,72 -72,72 -39,0 -61,-20 -72,-44z"></path>
</svg>
`,
official: false,
},
{
name: 'Python',
repoLink:
'https://github.com/StevanFreeborn/onspring-api-sdk-python',
installCommand: 'pip install OnspringApiSdk',
svg: `
<svg width="30" height="30" viewBox="0 0 256 255" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<path d="M126.916.072c-64.832 0-60.784 28.115-60.784 28.115l.072 29.128h61.868v8.745H41.631S.145 61.355.145 126.77c0 65.417 36.21 63.097 36.21 63.097h21.61v-30.356s-1.165-36.21 35.632-36.21h61.362s34.475.557 34.475-33.319V33.97S194.67.072 126.916.072zM92.802 19.66a11.12 11.12 0 0111.13 11.13 11.12 11.12 0 01-11.13 11.13 11.12 11.12 0 01-11.13-11.13 11.12 11.12 0 0111.13-11.13z" fill="#3372a7"></path>
<path d="M128.757 254.126c64.832 0 60.784-28.115 60.784-28.115l-.072-29.127H127.6v-8.745h86.441s41.486 4.705 41.486-60.712c0-65.416-36.21-63.096-36.21-63.096h-21.61v30.355s1.165 36.21-35.632 36.21h-61.362s-34.475-.557-34.475 33.32v56.013s-5.235 33.897 62.518 33.897zm34.114-19.586a11.12 11.12 0 01-11.13-11.13 11.12 11.12 0 0111.13-11.131 11.12 11.12 0 0111.13 11.13 11.12 11.12 0 01-11.13 11.13z" fill="#ffd235"></path>
</svg>
`,
official: false,
},
],
},
};
+34 -33
View File
@@ -5,40 +5,10 @@ import React, { ReactNode } from 'react';
import Clients from '../components/Clients';
import CodeSnippet from '../components/CodeSnippet';
import Fence from '../components/Fence';
import { DOCS_PATH } from '../constants';
import { Doc, DocSection } from './../types/types';
const DOCS_PATH = path.join(process.cwd(), 'src/docs');
function getCopyPath(
version: string,
doc: Doc,
docPath: string
): string {
if (doc.copy === undefined) {
return '';
}
return path.join(DOCS_PATH, version, docPath, doc.copy);
}
function getExamplePath(
version: string,
doc: Doc,
docPath: string
): string {
if (doc.example === undefined) {
return '';
}
return path.join(
DOCS_PATH,
version,
docPath,
doc.example
);
}
const markDocConfig = {
export const markDocConfig = {
nodes: {
fence: {
render: 'Fence',
@@ -78,7 +48,9 @@ const markDocConfig = {
},
};
function parseMarkdown(sourcePath: string): ReactNode {
export function parseMarkdown(
sourcePath: string
): ReactNode {
const source = fs.readFileSync(sourcePath, 'utf8');
const ast = Markdoc.parse(source);
const content = Markdoc.transform(
@@ -96,6 +68,35 @@ function parseMarkdown(sourcePath: string): ReactNode {
return children;
}
export function getCopyPath(
version: string,
doc: Doc,
docPath: string
): string {
if (doc.copy === undefined) {
return '';
}
return path.join(DOCS_PATH, version, docPath, doc.copy);
}
export function getExamplePath(
version: string,
doc: Doc,
docPath: string
): string {
if (doc.example === undefined) {
return '';
}
return path.join(
DOCS_PATH,
version,
docPath,
doc.example
);
}
function getCopyChild(
version: string,
doc: Doc,
+1 -1
View File
@@ -10,7 +10,7 @@ export default function VersionTwo() {
return (
<Page
version={versionTwo}
versionDocStructure={versionTwo}
versionSections={versionTwoSections}
/>
);
+16 -2
View File
@@ -2,8 +2,6 @@ import { ReactNode } from 'react';
export type DocsStructure = {
version: string;
officialDocs: string;
hasSwagger: boolean;
docs: Doc[];
};
@@ -20,3 +18,19 @@ export type DocSection = {
copy: ReactNode;
example: ReactNode;
};
export type Version = {
name: string;
path: string;
officialDocs: string;
hasSwagger: boolean;
clients: Client[];
};
export type Client = {
name: string;
repoLink: string;
installCommand: string;
svg: string;
official: boolean;
};
+1 -1
View File
@@ -10,7 +10,7 @@ export default function VersionOne() {
return (
<Page
version={versionOne}
versionDocStructure={versionOne}
versionSections={versionOneSections}
/>
);
-3
View File
@@ -2,9 +2,6 @@ import { DocsStructure } from '@/app/types/types';
export const versionOne: DocsStructure = {
version: 'version_001',
officialDocs:
'https://software.onspring.com/hubfs/Training/Admin%20Guide%20-%20v1%20API.pdf',
hasSwagger: false,
docs: [
{
title: 'Introduction',
+1 -1
View File
@@ -10,5 +10,5 @@ https://api.onspring.com/v1
{% /code %}
{% clients version="1" %}
{% clients version="version_001" %}
{% /clients %}
-3
View File
@@ -2,9 +2,6 @@ import { DocsStructure } from '@/app/types/types';
export const versionTwo: DocsStructure = {
version: 'version_002',
officialDocs:
'https://software.onspring.com/hubfs/Training/Admin%20Guide%20-%20v2%20API.pdf',
hasSwagger: true,
docs: [
{
title: 'Introduction',
+1 -1
View File
@@ -18,5 +18,5 @@ X-Api-Version: 2
{% /code %}
{% clients version="2" %}
{% clients version="version_002" %}
{% /clients %}
-5
View File
@@ -1,5 +0,0 @@
describe('test', () => {
it('should pass', () => {
expect(true).toBe(true);
});
});
+292
View File
@@ -0,0 +1,292 @@
import Clients from '@/app/components/Clients';
import CodeSnippet from '@/app/components/CodeSnippet';
import Fence from '@/app/components/Fence';
import { DOCS_PATH } from '@/app/constants';
import * as MarkdocLibrary from '@/app/lib/markdoc';
import { Doc } from '@/app/types/types';
import { default as Markdoc } from '@markdoc/markdoc';
import fs from 'fs';
import path from 'path';
import React from 'react';
describe('Markdoc lib functions', () => {
describe('getCopyPath', () => {
const version = 'v1.0.0';
const doc: Doc = {
title: 'Title',
folder: 'folder',
copy: 'copy.md',
};
const docPath = 'path/to/doc';
it('returns empty string if doc.copy is undefined', () => {
const doc: Doc = {
title: 'Title',
folder: 'folder',
};
const result = MarkdocLibrary.getCopyPath(
version,
doc,
docPath
);
expect(result).toEqual('');
});
it('returns correct copy path', () => {
const result = MarkdocLibrary.getCopyPath(
version,
doc,
docPath
);
const expected = path.join(
DOCS_PATH,
version,
docPath,
doc.copy!
);
expect(result).toEqual(expected);
});
it('returns correct copy path with leading/trailing slashes in docPath', () => {
const result = MarkdocLibrary.getCopyPath(
version,
doc,
'/path/to/doc/'
);
const expected = path.join(
DOCS_PATH,
version,
docPath,
doc.copy!
);
expect(result).toEqual(expected);
});
it('returns correct copy path with different version', () => {
const result = MarkdocLibrary.getCopyPath(
'v2.0.0',
doc,
docPath
);
const expected = path.join(
DOCS_PATH,
'v2.0.0',
docPath,
doc.copy!
);
expect(result).toEqual(expected);
});
it('returns correct copy path with nested docPath', () => {
const result = MarkdocLibrary.getCopyPath(
version,
doc,
'path/to/nested/doc'
);
const expected = path.join(
DOCS_PATH,
version,
'path/to/nested/doc',
doc.copy!
);
expect(result).toEqual(expected);
});
});
describe('getExamplePath', () => {
const version = 'v1.0.0';
const doc: Doc = {
title: 'Title',
folder: 'folder',
example: 'example.md',
};
const docPath = 'path/to/doc';
it('returns empty string if doc.example is undefined', () => {
const doc: Doc = {
title: 'Title',
folder: 'folder',
};
const result = MarkdocLibrary.getExamplePath(
version,
doc,
docPath
);
expect(result).toEqual('');
});
it('returns correct example path', () => {
const result = MarkdocLibrary.getExamplePath(
version,
doc,
docPath
);
const expected = path.join(
DOCS_PATH,
version,
docPath,
doc.example!
);
expect(result).toEqual(expected);
});
it('returns correct example path with leading/trailing slashes in docPath', () => {
const result = MarkdocLibrary.getExamplePath(
version,
doc,
'/path/to/doc/'
);
const expected = path.join(
DOCS_PATH,
version,
docPath,
doc.example!
);
expect(result).toEqual(expected);
});
it('returns correct example path with different version', () => {
const result = MarkdocLibrary.getExamplePath(
'v2.0.0',
doc,
docPath
);
const expected = path.join(
DOCS_PATH,
'v2.0.0',
docPath,
doc.example!
);
expect(result).toEqual(expected);
});
it('returns correct example path with nested docPath', () => {
const result = MarkdocLibrary.getExamplePath(
version,
doc,
'path/to/nested/doc'
);
const expected = path.join(
DOCS_PATH,
version,
'path/to/nested/doc',
doc.example!
);
expect(result).toEqual(expected);
});
});
describe('parseMarkdown', () => {
const sourcePath = 'test.md';
const sourceContent = '# Test Heading';
beforeAll(() => {
jest
.spyOn(fs, 'readFileSync')
.mockReturnValue(sourceContent);
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should parse markdown file and return react node', () => {
const expected = (
<article>
<h1>Test Heading</h1>
</article>
);
const result =
MarkdocLibrary.parseMarkdown(sourcePath);
expect(result).toEqual(expected);
});
it('should read source file using the correct path', () => {
MarkdocLibrary.parseMarkdown(sourcePath);
expect(fs.readFileSync).toHaveBeenCalledWith(
sourcePath,
'utf8'
);
});
it('should transform the AST using markDocConfig', () => {
const ast = Markdoc.parse(sourceContent);
const spy = jest.spyOn(Markdoc, 'transform');
MarkdocLibrary.parseMarkdown(sourcePath);
expect(spy).toHaveBeenCalledWith(
ast,
MarkdocLibrary.markDocConfig as any
);
});
it('should render react components using Markdoc.renderers', () => {
const ast = Markdoc.parse(sourceContent);
const content = Markdoc.transform(
ast,
MarkdocLibrary.markDocConfig as any
);
const expected = (
<article>
<h1>Test Heading</h1>
</article>
);
const spy = jest.spyOn(Markdoc.renderers, 'react');
const result =
MarkdocLibrary.parseMarkdown(sourcePath);
expect(spy).toHaveBeenCalledWith(content, React, {
components: {
CodeSnippet: CodeSnippet,
Fence: Fence,
Clients: Clients,
},
});
expect(result).toEqual(expected);
});
});
describe('getCopyChild', () => {
it('should return the correct child copy react nodes for the given doc', () => {
const result = MarkdocLibrary.getCopyPath(
'version',
{ title: 'hello', folder: 'hello' },
'hello'
);
expect(result).toEqual('hello');
});
});
});