-
-
- {versions && versions.length >= 1 && (
-
-
-
- )}
-
-
+
);
diff --git a/src/components/sidebar/productSidebar.tsx b/src/components/sidebar/productSidebar.tsx
index 0425a4a0440c1..d392e63efc59d 100644
--- a/src/components/sidebar/productSidebar.tsx
+++ b/src/components/sidebar/productSidebar.tsx
@@ -1,7 +1,6 @@
import {nodeForPath} from 'sentry-docs/docTree';
import {DynamicNav, toTree} from './dynamicNav';
-import {SidebarLink, SidebarSeparator} from './sidebarLink';
import {NavNode, ProductSidebarProps} from './types';
import {docNodeToNavNode, getNavNodes} from './utils';
@@ -19,7 +18,6 @@ export function ProductSidebar({rootNode, items}: ProductSidebarProps) {
{items.map(item => {
const tree = itemTree(item.root);
-
return (
tree && (
)
);
})}
-
-
+ {/* External links menu removed from here */}
);
}
diff --git a/src/components/sidebar/sidebarLink.tsx b/src/components/sidebar/sidebarLink.tsx
index 10b50564545b0..618130dc0c353 100644
--- a/src/components/sidebar/sidebarLink.tsx
+++ b/src/components/sidebar/sidebarLink.tsx
@@ -13,9 +13,11 @@ export function SidebarLink({
collapsible,
onClick,
topLevel = false,
+ className,
}: {
href: string;
title: string;
+ className?: string;
collapsible?: boolean;
isActive?: boolean;
onClick?: () => void;
@@ -30,7 +32,7 @@ export function SidebarLink({
onClick={onClick}
className={`${styles['sidebar-link']} ${isActive ? 'active' : ''} ${
topLevel ? styles['sidebar-link-top-level'] : ''
- }`}
+ } ${className ?? ''}`}
data-sidebar-link
>
{title}
diff --git a/src/components/sidebar/sidebarNavigation.tsx b/src/components/sidebar/sidebarNavigation.tsx
index fb2502e3de077..30742cad3acc5 100644
--- a/src/components/sidebar/sidebarNavigation.tsx
+++ b/src/components/sidebar/sidebarNavigation.tsx
@@ -9,54 +9,17 @@ import {SidebarSeparator} from './sidebarLink';
import {NavNode} from './types';
import {docNodeToNavNode, getNavNodes} from './utils';
-/** a root of `"some-root"` maps to the `/some-root/` url */
-// todo: we should probably get rid of this
-const productSidebarItems = [
- {
- title: 'Account Settings',
- root: 'account',
- },
- {
- title: 'Organization Settings',
- root: 'organization',
- },
- {
- title: 'Product Walkthroughs',
- root: 'product',
- },
- {
- title: 'Pricing & Billing',
- root: 'pricing',
- },
- {
- title: 'Sentry CLI',
- root: 'cli',
- },
- {
- title: 'Sentry API',
- root: 'api',
- },
- {
- title: 'Security, Legal, & PII',
- root: 'security-legal-pii',
- },
- {
- title: 'Concepts & Reference',
- root: 'concepts',
- },
-];
-
export async function SidebarNavigation({path}: {path: string[]}) {
const rootNode = await getDocsRootNode();
- // product docs and platform-redirect page
- if (
- productSidebarItems.some(el => el.root === path[0]) ||
- path[0] === 'platform-redirect'
- ) {
- return
;
+
+ // Product section: just show the sidebar for /product/ and its children
+ if (path[0] === 'product') {
+ return (
+
+ );
}
- // /platforms/:platformName/guides/:guideName
+ // SDKs/Platforms
if (path[0] === 'platforms') {
const platformName = path[1];
const guideName = path[3];
@@ -72,12 +35,100 @@ export async function SidebarNavigation({path}: {path: string[]}) {
)}
-
);
}
- // contributing pages
+ // Concepts & Reference
+ if (path[0] === 'concepts') {
+ return (
+
+ );
+ }
+
+ // Admin Settings
+ if (path[0] === 'organization' || path[0] === 'account' || path[0] === 'pricing') {
+ const adminItems = [
+ {title: 'Account Settings', root: 'account'},
+ {title: 'Organization Settings', root: 'organization'},
+ {title: 'Pricing & Billing', root: 'pricing'},
+ ];
+ return
;
+ }
+
+ // Security, Legal, & PII
+ if (path[0] === 'security-legal-pii') {
+ return (
+
+ );
+ }
+
+ // API Reference
+ if (path[0] === 'api') {
+ return (
+
+ );
+ }
+
+ // Contributing pages
if (path[0] === 'contributing') {
const contribNode = nodeForPath(rootNode, 'contributing');
if (contribNode) {
@@ -94,6 +145,55 @@ export async function SidebarNavigation({path}: {path: string[]}) {
}
}
+ // Sentry CLI (standalone route)
+ if (path[0] === 'cli') {
+ return (
+
+ );
+ }
+
// This should never happen, all cases need to be handled above
throw new Error(`Unknown path: ${path.join('/')} - cannot render sidebar`);
}
diff --git a/src/components/sidebar/style.module.scss b/src/components/sidebar/style.module.scss
index 2c0b5c169833b..85921e5a19ee2 100644
--- a/src/components/sidebar/style.module.scss
+++ b/src/components/sidebar/style.module.scss
@@ -5,6 +5,7 @@
}
}
.sidebar {
+ margin-top: 0px;
--sidebar-item-bg-hover: var(--accent-purple-light);
--sidebar-item-color: var(--accent-purple);
background-color: var(--gray-1);
@@ -18,7 +19,6 @@
display: none;
flex-shrink: 0;
height: 100vh;
- overflow-y: auto;
@media only screen and (min-width: 768px) {
position: fixed;
@@ -130,3 +130,13 @@
background-color: var(--brandDecoration);
}
}
+
+.sidebar-main {
+ flex: 1;
+ overflow: auto;
+}
+
+.sidebar-external-links {
+ flex: 0 0 auto;
+ padding-bottom: 0;
+}
diff --git a/src/docTree.ts b/src/docTree.ts
index 1857c553eb8e6..b1001fd46ea3d 100644
--- a/src/docTree.ts
+++ b/src/docTree.ts
@@ -91,28 +91,30 @@ function frontmatterToTree(frontmatter: FrontMatter[]): DocNode {
rootNode.children.push(node);
slugMap[slug] = node;
} else {
- const parentSlug = slugParts.slice(0, slugParts.length - 1).join('/');
- let parent: DocNode | undefined = slugMap[parentSlug];
- if (!parent) {
- const grandparentSlug = slugParts.slice(0, slugParts.length - 2).join('/');
- const grandparent = slugMap[grandparentSlug];
- if (!grandparent) {
- throw new Error('missing parent and grandparent: ' + parentSlug);
- }
- parent = {
+ let parent: DocNode | undefined;
+ // Walk up the tree and create missing parents as needed
+ for (let i = slugParts.length - 1; i > 0; i--) {
+ const parentSlug = slugParts.slice(0, i).join('/');
+ parent = slugMap[parentSlug];
+ if (parent) break;
+
+ // Create missing parent node
+ const grandparentSlug = slugParts.slice(0, i - 1).join('/');
+ const grandparent = slugMap[grandparentSlug] || rootNode;
+ const missingParent: DocNode = {
path: parentSlug,
- slug: slugParts[slugParts.length - 2],
+ slug: slugParts[i - 1],
frontmatter: {
- slug: slugParts[slugParts.length - 2],
- // not ideal
+ slug: slugParts[i - 1],
title: '',
},
parent: grandparent,
children: [],
missing: true,
};
- grandparent.children.push(parent);
- slugMap[parentSlug] = parent;
+ grandparent.children.push(missingParent);
+ slugMap[parentSlug] = missingParent;
+ parent = missingParent;
}
const node = {
path: slug,
@@ -123,7 +125,7 @@ function frontmatterToTree(frontmatter: FrontMatter[]): DocNode {
missing: false,
sourcePath: doc.sourcePath,
};
- parent.children.push(node);
+ parent!.children.push(node);
slugMap[slug] = node;
}
});
diff --git a/src/imgs/Linkedin-1128x191.png b/src/imgs/Linkedin-1128x191.png
new file mode 100644
index 0000000000000..f6e404e086083
Binary files /dev/null and b/src/imgs/Linkedin-1128x191.png differ
diff --git a/src/imgs/background-gradient-afternoon.png b/src/imgs/background-gradient-afternoon.png
new file mode 100644
index 0000000000000..dcf5c2ae48036
Binary files /dev/null and b/src/imgs/background-gradient-afternoon.png differ
diff --git a/src/imgs/pink-shape-06.png b/src/imgs/pink-shape-06.png
new file mode 100644
index 0000000000000..ac41a4a1e5151
Binary files /dev/null and b/src/imgs/pink-shape-06.png differ
diff --git a/src/imgs/yellow-shape-05.png b/src/imgs/yellow-shape-05.png
new file mode 100644
index 0000000000000..a7a62885b1efa
Binary files /dev/null and b/src/imgs/yellow-shape-05.png differ
diff --git a/src/imgs/yellow-shape-06.png b/src/imgs/yellow-shape-06.png
new file mode 100644
index 0000000000000..c606603115c3f
Binary files /dev/null and b/src/imgs/yellow-shape-06.png differ
diff --git a/src/imgs/yellow-shape-08.png b/src/imgs/yellow-shape-08.png
new file mode 100644
index 0000000000000..98da0da4d6ff5
Binary files /dev/null and b/src/imgs/yellow-shape-08.png differ
diff --git a/src/imgs/yellow-shape-13.png b/src/imgs/yellow-shape-13.png
new file mode 100644
index 0000000000000..67756a7117d55
Binary files /dev/null and b/src/imgs/yellow-shape-13.png differ
diff --git a/src/mdx.ts b/src/mdx.ts
index f9c363d713a77..748d87018f181 100644
--- a/src/mdx.ts
+++ b/src/mdx.ts
@@ -5,7 +5,7 @@ import yaml from 'js-yaml';
import {bundleMDX} from 'mdx-bundler';
import {access, opendir, readFile} from 'node:fs/promises';
import path from 'node:path';
-import {limitFunction} from 'p-limit';
+import pLimit from 'p-limit';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypePresetMinify from 'rehype-preset-minify';
import rehypePrismDiff from 'rehype-prism-diff';
@@ -145,29 +145,27 @@ export async function getDevDocsFrontMatterUncached(): Promise
{
const folder = 'develop-docs';
const docsPath = path.join(root, folder);
const files = await getAllFilesRecursively(docsPath);
+ const limit = pLimit(FILE_CONCURRENCY_LIMIT);
const frontMatters = (
await Promise.all(
- files.map(
- limitFunction(
- async file => {
- const fileName = file.slice(docsPath.length + 1);
- if (path.extname(fileName) !== '.md' && path.extname(fileName) !== '.mdx') {
- return undefined;
- }
+ files.map(file =>
+ limit(async () => {
+ const fileName = file.slice(docsPath.length + 1);
+ if (path.extname(fileName) !== '.md' && path.extname(fileName) !== '.mdx') {
+ return undefined;
+ }
- const source = await readFile(file, 'utf8');
- const {data: frontmatter} = matter(source);
- return {
- ...(frontmatter as FrontMatter),
- slug: fileName.replace(/\/index.mdx?$/, '').replace(/\.mdx?$/, ''),
- sourcePath: path.join(folder, fileName),
- };
- },
- {concurrency: FILE_CONCURRENCY_LIMIT}
- )
+ const source = await readFile(file, 'utf8');
+ const {data: frontmatter} = matter(source);
+ return {
+ ...(frontmatter as FrontMatter),
+ slug: fileName.replace(/\/index.mdx?$/, '').replace(/\.mdx?$/, ''),
+ sourcePath: path.join(folder, fileName),
+ };
+ })
)
)
- ).filter(isNotNil);
+ ).filter(isNotNil) as FrontMatter[];
return frontMatters;
}
@@ -184,30 +182,28 @@ async function getAllFilesFrontMatter(): Promise {
const docsPath = path.join(root, 'docs');
const files = await getAllFilesRecursively(docsPath);
const allFrontMatter: FrontMatter[] = [];
+ const limit = pLimit(FILE_CONCURRENCY_LIMIT);
await Promise.all(
- files.map(
- limitFunction(
- async file => {
- const fileName = file.slice(docsPath.length + 1);
- if (path.extname(fileName) !== '.md' && path.extname(fileName) !== '.mdx') {
- return;
- }
+ files.map(file =>
+ limit(async () => {
+ const fileName = file.slice(docsPath.length + 1);
+ if (path.extname(fileName) !== '.md' && path.extname(fileName) !== '.mdx') {
+ return;
+ }
- if (fileName.indexOf('/common/') !== -1) {
- return;
- }
+ if (fileName.indexOf('/common/') !== -1) {
+ return;
+ }
- const source = await readFile(file, 'utf8');
- const {data: frontmatter} = matter(source);
- allFrontMatter.push({
- ...(frontmatter as FrontMatter),
- slug: formatSlug(fileName),
- sourcePath: path.join('docs', fileName),
- });
- },
- {concurrency: FILE_CONCURRENCY_LIMIT}
- )
+ const source = await readFile(file, 'utf8');
+ const {data: frontmatter} = matter(source);
+ allFrontMatter.push({
+ ...(frontmatter as FrontMatter),
+ slug: formatSlug(fileName),
+ sourcePath: path.join('docs', fileName),
+ });
+ })
)
);
@@ -244,50 +240,44 @@ async function getAllFilesFrontMatter(): Promise {
);
const commonFiles = await Promise.all(
- commonFileNames.map(
- limitFunction(
- async commonFileName => {
- const source = await readFile(commonFileName, 'utf8');
- const {data: frontmatter} = matter(source);
- return {commonFileName, frontmatter: frontmatter as FrontMatter};
- },
- {concurrency: FILE_CONCURRENCY_LIMIT}
- )
+ commonFileNames.map(commonFileName =>
+ limit(async () => {
+ const source = await readFile(commonFileName, 'utf8');
+ const {data: frontmatter} = matter(source);
+ return {commonFileName, frontmatter: frontmatter as FrontMatter};
+ })
)
);
await Promise.all(
- commonFiles.map(
- limitFunction(
- async f => {
- if (!isSupported(f.frontmatter, platformName)) {
- return;
- }
+ commonFiles.map(f =>
+ limit(async () => {
+ if (!isSupported(f.frontmatter, platformName)) {
+ return;
+ }
- const subpath = f.commonFileName.slice(commonPath.length + 1);
- const slug = f.commonFileName
- .slice(docsPath.length + 1)
- .replace(/\/common\//, '/');
- const noFrontMatter = (
- await Promise.allSettled([
- access(path.join(docsPath, slug)),
- access(path.join(docsPath, slug.replace('/index.mdx', '.mdx'))),
- ])
- ).every(r => r.status === 'rejected');
- if (noFrontMatter) {
- let frontmatter = f.frontmatter;
- if (subpath === 'index.mdx') {
- frontmatter = {...frontmatter, ...platformFrontmatter};
- }
- allFrontMatter.push({
- ...frontmatter,
- slug: formatSlug(slug),
- sourcePath: 'docs/' + f.commonFileName.slice(docsPath.length + 1),
- });
+ const subpath = f.commonFileName.slice(commonPath.length + 1);
+ const slug = f.commonFileName
+ .slice(docsPath.length + 1)
+ .replace(/\/common\//, '/');
+ const noFrontMatter = (
+ await Promise.allSettled([
+ access(path.join(docsPath, slug)),
+ access(path.join(docsPath, slug.replace('/index.mdx', '.mdx'))),
+ ])
+ ).every(r => r.status === 'rejected');
+ if (noFrontMatter) {
+ let frontmatter = f.frontmatter;
+ if (subpath === 'index.mdx') {
+ frontmatter = {...frontmatter, ...platformFrontmatter};
}
- },
- {concurrency: FILE_CONCURRENCY_LIMIT}
- )
+ allFrontMatter.push({
+ ...frontmatter,
+ slug: formatSlug(slug),
+ sourcePath: 'docs/' + f.commonFileName.slice(docsPath.length + 1),
+ });
+ }
+ })
)
);
@@ -317,40 +307,37 @@ async function getAllFilesFrontMatter(): Promise {
}
await Promise.all(
- commonFiles.map(
- limitFunction(
- async f => {
- if (!isSupported(f.frontmatter, platformName, guideName)) {
- return;
- }
-
- const subpath = f.commonFileName.slice(commonPath.length + 1);
- const slug = path.join(
- 'platforms',
- platformName,
- 'guides',
- guideName,
- subpath
- );
- try {
- await access(path.join(docsPath, slug));
- return;
- } catch {
- // pass
- }
-
- let frontmatter = f.frontmatter;
- if (subpath === 'index.mdx') {
- frontmatter = {...frontmatter, ...guideFrontmatter};
- }
- allFrontMatter.push({
- ...frontmatter,
- slug: formatSlug(slug),
- sourcePath: 'docs/' + f.commonFileName.slice(docsPath.length + 1),
- });
- },
- {concurrency: FILE_CONCURRENCY_LIMIT}
- )
+ commonFiles.map(f =>
+ limit(async () => {
+ if (!isSupported(f.frontmatter, platformName, guideName)) {
+ return;
+ }
+
+ const subpath = f.commonFileName.slice(commonPath.length + 1);
+ const slug = path.join(
+ 'platforms',
+ platformName,
+ 'guides',
+ guideName,
+ subpath
+ );
+ try {
+ await access(path.join(docsPath, slug));
+ return;
+ } catch {
+ // pass
+ }
+
+ let frontmatter = f.frontmatter;
+ if (subpath === 'index.mdx') {
+ frontmatter = {...frontmatter, ...guideFrontmatter};
+ }
+ allFrontMatter.push({
+ ...frontmatter,
+ slug: formatSlug(slug),
+ sourcePath: 'docs/' + f.commonFileName.slice(docsPath.length + 1),
+ });
+ })
)
);
}