chore: reorganize docs apps (#7228)
* reorganize docs apps * add README * fix directory * add condition for old docs
This commit is contained in:
1 parent
224ebb2154
commit
4fe28f5a95
6187 files changed
+601447
-598226
No files matched your search
@@ -0,0 +1,9 @@
|
||||
const path = require("path")
|
||||
|
||||
module.exports = {
|
||||
extends: ["docs"],
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: path.join(__dirname, "..", ".."),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "build-scripts",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"module": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn clean && tsc",
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"remark-rehype-plugins": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.20",
|
||||
"eslint-config-docs": "*",
|
||||
"rimraf": "^5.0.5",
|
||||
"tsconfig": "*",
|
||||
"types": "*",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import type { RawSidebarItemType } from "types"
|
||||
import findPageHeading from "./utils/find-page-heading.js"
|
||||
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "fs"
|
||||
import path from "path"
|
||||
import { sidebarAttachHrefCommonOptions } from "./index.js"
|
||||
import { getFrontMatterUtil } from "remark-rehype-plugins"
|
||||
import findMetadataTitle from "./utils/find-metadata-title.js"
|
||||
|
||||
type ItemsToAdd = RawSidebarItemType & {
|
||||
sidebar_position?: number
|
||||
}
|
||||
|
||||
async function getSidebarItems(
|
||||
dir: string,
|
||||
nested = false
|
||||
): Promise<ItemsToAdd[]> {
|
||||
const isRefDir = dir.startsWith("/references")
|
||||
const basePath = isRefDir ? path.resolve() : path.resolve("app")
|
||||
const fullPath = path.resolve(basePath, dir.replace(/^\//, ""))
|
||||
const items: ItemsToAdd[] = []
|
||||
|
||||
const files = readdirSync(fullPath)
|
||||
let mainPageIndex = -1
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(fullPath, file)
|
||||
const fileBasename = path.basename(file)
|
||||
if (fileBasename.startsWith("_")) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (fileBasename !== "page.mdx" && statSync(filePath).isDirectory()) {
|
||||
const newItems = await getSidebarItems(
|
||||
filePath.replace(basePath, ""),
|
||||
true
|
||||
)
|
||||
if (nested && newItems.length > 1) {
|
||||
items.push(
|
||||
...sidebarAttachHrefCommonOptions([
|
||||
{
|
||||
title:
|
||||
fileBasename.charAt(0).toUpperCase() +
|
||||
fileBasename.substring(1),
|
||||
hasTitleStyling: true,
|
||||
children: newItems,
|
||||
},
|
||||
])
|
||||
)
|
||||
} else {
|
||||
items.push(...sidebarAttachHrefCommonOptions(newItems))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const frontmatter = await getFrontMatterUtil(filePath)
|
||||
if (frontmatter.sidebar_autogenerate_exclude) {
|
||||
continue
|
||||
}
|
||||
|
||||
const fileContent = frontmatter.sidebar_label
|
||||
? ""
|
||||
: readFileSync(filePath, "utf-8")
|
||||
|
||||
const newItem = sidebarAttachHrefCommonOptions([
|
||||
{
|
||||
path:
|
||||
frontmatter.slug ||
|
||||
filePath.replace(basePath, "").replace(`/${fileBasename}`, ""),
|
||||
title:
|
||||
frontmatter.sidebar_label ||
|
||||
findMetadataTitle(fileContent) ||
|
||||
findPageHeading(fileContent) ||
|
||||
"",
|
||||
},
|
||||
])[0]
|
||||
|
||||
items.push({
|
||||
...newItem,
|
||||
sidebar_position: frontmatter.sidebar_position,
|
||||
})
|
||||
|
||||
mainPageIndex = items.length - 1
|
||||
}
|
||||
|
||||
if (mainPageIndex !== -1 && items.length > 1) {
|
||||
// push all other items to be children of that page.
|
||||
const mainPageChildren = [
|
||||
...items.splice(0, mainPageIndex),
|
||||
...items.splice(1),
|
||||
]
|
||||
mainPageChildren.sort(sortItems)
|
||||
items[0].children = mainPageChildren
|
||||
} else if (items.length > 1) {
|
||||
items.sort(sortItems)
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
async function checkItem(
|
||||
item: RawSidebarItemType
|
||||
): Promise<RawSidebarItemType> {
|
||||
if (item.autogenerate_path) {
|
||||
item.children = (await getSidebarItems(item.autogenerate_path)).map(
|
||||
(child) => {
|
||||
delete child.sidebar_position
|
||||
|
||||
return child
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (item.children) {
|
||||
item.children = await Promise.all(
|
||||
item.children.map(async (childItem) => await checkItem(childItem))
|
||||
)
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
export async function generateSidebar(sidebar: RawSidebarItemType[]) {
|
||||
const path = await import("path")
|
||||
const { writeFileSync } = await import("fs")
|
||||
|
||||
const normalizedSidebar = await Promise.all(
|
||||
sidebar.map(async (item) => await checkItem(item))
|
||||
)
|
||||
|
||||
const generatedDirPath = path.resolve("generated")
|
||||
|
||||
if (!existsSync(generatedDirPath)) {
|
||||
mkdirSync(generatedDirPath)
|
||||
}
|
||||
|
||||
// write normalized sidebar
|
||||
writeFileSync(
|
||||
path.resolve(generatedDirPath, "sidebar.mjs"),
|
||||
`export const generatedSidebar = ${JSON.stringify(
|
||||
normalizedSidebar,
|
||||
null,
|
||||
2
|
||||
)}`,
|
||||
{
|
||||
flag: "w",
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function sortItems(itemA: ItemsToAdd, itemB: ItemsToAdd): number {
|
||||
const itemASidebarPosition = itemA.sidebar_position || 0
|
||||
const itemBSidebarPosition = itemB.sidebar_position || 0
|
||||
|
||||
if (itemASidebarPosition > itemBSidebarPosition) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return itemASidebarPosition < itemBSidebarPosition ? -1 : 0
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./generate-sidebar.js"
|
||||
|
||||
export * from "./utils/find-metadata-title.js"
|
||||
export * from "./utils/find-page-heading.js"
|
||||
export * from "./utils/sidebar-attach-href-common-options.js"
|
||||
@@ -0,0 +1,7 @@
|
||||
const REGEX = /export const metadata = {[\s\S]*title: `(?<title>.*)`/
|
||||
|
||||
export default function findMetadataTitle(content: string): string | undefined {
|
||||
const headingMatch = REGEX.exec(content)
|
||||
|
||||
return headingMatch?.groups?.title
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const HEADING_REGEX = /# (?<title>.*)/
|
||||
|
||||
export default function findPageHeading(content: string): string | undefined {
|
||||
const headingMatch = HEADING_REGEX.exec(content)
|
||||
|
||||
return headingMatch?.groups?.title
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { RawSidebarItemType } from "types"
|
||||
|
||||
const commonOptions: Partial<RawSidebarItemType> = {
|
||||
loaded: true,
|
||||
isPathHref: true,
|
||||
}
|
||||
|
||||
export function sidebarAttachHrefCommonOptions(
|
||||
sidebar: RawSidebarItemType[]
|
||||
): RawSidebarItemType[] {
|
||||
return sidebar.map((item) => ({
|
||||
...commonOptions,
|
||||
...item,
|
||||
children: sidebarAttachHrefCommonOptions(item.children || []),
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": ["tsconfig/base.json"],
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo-client",
|
||||
"noEmit": false,
|
||||
"lib": ["es2022"],
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -65,6 +65,7 @@
|
||||
"@segment/analytics-next": "^1.55.0",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"algoliasearch": "^4.20.0",
|
||||
"copy-text-to-clipboard": "^3.2.0",
|
||||
"mermaid": "^10.9.0",
|
||||
"npm-to-yarn": "^2.1.0",
|
||||
"prism-react-renderer": "2.3.1",
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
SearchSuggestionType,
|
||||
SearchHitGroupName,
|
||||
Tooltip,
|
||||
LegacyLink,
|
||||
Link,
|
||||
} from "@/components"
|
||||
import { useAiAssistant, useSearch } from "@/providers"
|
||||
import { ArrowUturnLeft, XMarkMini } from "@medusajs/icons"
|
||||
@@ -382,14 +382,10 @@ export const AiAssistant = () => {
|
||||
tooltipChildren={
|
||||
<>
|
||||
This site is protected by reCAPTCHA and the{" "}
|
||||
{/* TODO replace with Link once we move away from Docusaurus */}
|
||||
<LegacyLink href="https://policies.google.com/privacy">
|
||||
<Link href="https://policies.google.com/privacy">
|
||||
Google Privacy Policy
|
||||
</LegacyLink>{" "}
|
||||
and{" "}
|
||||
<LegacyLink href="https://policies.google.com/terms">
|
||||
ToS
|
||||
</LegacyLink>{" "}
|
||||
</Link>{" "}
|
||||
and <Link href="https://policies.google.com/terms">ToS</Link>{" "}
|
||||
apply
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react"
|
||||
import { ArrowUpRightOnBox } from "@medusajs/icons"
|
||||
import clsx from "clsx"
|
||||
import { LegacyLink } from "@/components"
|
||||
import { Link } from "@/components"
|
||||
|
||||
export type CardProps = {
|
||||
startIcon?: React.ReactNode
|
||||
@@ -57,8 +57,7 @@ export const Card = ({
|
||||
{showLinkIcon && (
|
||||
<ArrowUpRightOnBox className="text-medusa-fg-subtle min-w-[20px]" />
|
||||
)}
|
||||
{/* TODO replace with Link once we move away from Docusaurus */}
|
||||
<LegacyLink
|
||||
<Link
|
||||
href={href}
|
||||
className="absolute left-0 top-0 h-full w-full rounded"
|
||||
/>
|
||||
|
||||
@@ -46,11 +46,15 @@ export const ApiRunner = ({
|
||||
[apiTestingOptions]
|
||||
)
|
||||
const [responseLogs, setResponseLogs] = useState<string[]>([])
|
||||
const [responseCode, setResponseCode] = useState<string | undefined>()
|
||||
const pushMessage = (...message: string[]) =>
|
||||
setResponseLogs((prev) => [...prev, ...message])
|
||||
const { runRequest } = useRequestRunner({
|
||||
pushLog: pushMessage,
|
||||
onFinish: () => setIsRunning(false),
|
||||
onFinish: (_message, statusCode) => {
|
||||
setIsRunning(false)
|
||||
setResponseCode(statusCode)
|
||||
},
|
||||
replaceLog: (message) => setResponseLogs([message]),
|
||||
})
|
||||
|
||||
@@ -149,6 +153,14 @@ export const ApiRunner = ({
|
||||
collapsed={true}
|
||||
blockStyle="subtle"
|
||||
noReport={true}
|
||||
badgeLabel={responseCode}
|
||||
badgeColor={
|
||||
!responseCode
|
||||
? undefined
|
||||
: responseCode.startsWith("2")
|
||||
? "green"
|
||||
: "red"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { useMemo, useState } from "react"
|
||||
import clsx from "clsx"
|
||||
import { HighlightProps, Highlight, themes } from "prism-react-renderer"
|
||||
import { CopyButton, Tooltip, LegacyLink } from "@/components"
|
||||
import { CopyButton, Tooltip, Link } from "@/components"
|
||||
import { useColorMode } from "@/providers"
|
||||
import { ExclamationCircle, PlaySolid, SquareTwoStack } from "@medusajs/icons"
|
||||
import { CodeBlockHeader, CodeBlockHeaderMeta } from "./Header"
|
||||
@@ -269,8 +269,7 @@ export const CodeBlock = ({
|
||||
tokens.length > 1 && "px-[6px] pb-[6px]"
|
||||
)}
|
||||
>
|
||||
{/* TODO replace with Link once we move away from Docusaurus */}
|
||||
<LegacyLink
|
||||
<Link
|
||||
href={`${GITHUB_ISSUES_PREFIX}&title=${encodeURIComponent(
|
||||
`Docs(Code Issue): `
|
||||
)}`}
|
||||
@@ -284,7 +283,7 @@ export const CodeBlock = ({
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ExclamationCircle className={clsx(iconColor)} />
|
||||
</LegacyLink>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!noCopy && (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react"
|
||||
import { Badge, LegacyLink, Tooltip } from "@/components"
|
||||
import { Badge, Link, Tooltip } from "@/components"
|
||||
|
||||
export type ExpandableNoticeProps = {
|
||||
type: "request" | "method"
|
||||
@@ -20,11 +20,8 @@ export const ExpandableNotice = ({
|
||||
<>
|
||||
If this {type} accepts an <code>expand</code>{" "}
|
||||
{type === "request" ? "parameter" : "property or option"},
|
||||
{/* TODO replace with Link once we move away from Docusaurus */}
|
||||
<br /> this field can be <LegacyLink href={link}>
|
||||
expanded
|
||||
</LegacyLink>{" "}
|
||||
into an object.
|
||||
<br /> this field can be <Link href={link}>expanded</Link> into an
|
||||
object.
|
||||
</>
|
||||
}
|
||||
clickable
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react"
|
||||
import { Badge, LegacyLink, Tooltip } from "@/components"
|
||||
import { Badge, Link, Tooltip } from "@/components"
|
||||
|
||||
export type FeatureFlagNoticeProps = {
|
||||
featureFlag: string
|
||||
@@ -22,13 +22,12 @@ export const FeatureFlagNotice = ({
|
||||
<span className={tooltipTextClassName}>
|
||||
To use this {type}, make sure to
|
||||
<br />
|
||||
{/* TODO replace with Link once we move away from Docusaurus */}
|
||||
<LegacyLink
|
||||
<Link
|
||||
href="https://docs.medusajs.com/development/feature-flags/toggle"
|
||||
target="__blank"
|
||||
>
|
||||
enable its feature flag: <code>{featureFlag}</code>
|
||||
</LegacyLink>
|
||||
</Link>
|
||||
</span>
|
||||
}
|
||||
clickable
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { request } from "@octokit/request"
|
||||
import { LegacyLink } from "@/components"
|
||||
import { Link } from "@/components"
|
||||
|
||||
export type SolutionsProps = {
|
||||
feedback: boolean
|
||||
@@ -69,14 +69,9 @@ export const Solutions = ({ feedback, message }: SolutionsProps) => {
|
||||
<ul>
|
||||
{possibleSolutions.map((solution) => (
|
||||
<li key={solution.url} className="mb-docs_0.5 last:mb-0">
|
||||
{/* TODO replace with Link once we move away from Docusaurus */}
|
||||
<LegacyLink
|
||||
href={solution.html_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Link href={solution.html_url} target="_blank" rel="noreferrer">
|
||||
{solution.title}
|
||||
</LegacyLink>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
|
||||
export type LegacyLinkProps = {
|
||||
href?: string
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
} & React.AllHTMLAttributes<HTMLAnchorElement>
|
||||
|
||||
export const LegacyLink = ({
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: LegacyLinkProps) => {
|
||||
return (
|
||||
<a
|
||||
href={href || ""}
|
||||
{...rest}
|
||||
className={clsx(
|
||||
"text-medusa-fg-interactive hover:text-medusa-fg-interactive-hover",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
Note,
|
||||
Card,
|
||||
CardList,
|
||||
LegacyLink,
|
||||
DetailsSummary,
|
||||
DetailsProps,
|
||||
ZoomImg,
|
||||
Link,
|
||||
} from "@/components"
|
||||
import clsx from "clsx"
|
||||
import { Text } from "@medusajs/ui"
|
||||
@@ -54,13 +54,12 @@ export const MDXComponents: MDXComponentsType = {
|
||||
>
|
||||
{children}
|
||||
{props.id && (
|
||||
// TODO replace with Link once we move away from Docusaurus
|
||||
<LegacyLink
|
||||
<Link
|
||||
href={`#${props.id}`}
|
||||
className="opacity-0 group-hover/h2:opacity-100 transition-opacity ml-docs_0.5 inline-block"
|
||||
>
|
||||
#
|
||||
</LegacyLink>
|
||||
</Link>
|
||||
)}
|
||||
</h2>
|
||||
)
|
||||
@@ -81,13 +80,12 @@ export const MDXComponents: MDXComponentsType = {
|
||||
>
|
||||
{children}
|
||||
{props.id && (
|
||||
// TODO replace with Link once we move away from Docusaurus
|
||||
<LegacyLink
|
||||
<Link
|
||||
href={`#${props.id}`}
|
||||
className="opacity-0 group-hover/h3:opacity-100 transition-opacity ml-docs_0.5 inline-block"
|
||||
>
|
||||
#
|
||||
</LegacyLink>
|
||||
</Link>
|
||||
)}
|
||||
</h3>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
import { ReactMarkdownOptions } from "react-markdown/lib/react-markdown"
|
||||
import { MDXComponents, LegacyLink } from "@/components"
|
||||
import { MDXComponents, Link } from "@/components"
|
||||
import clsx from "clsx"
|
||||
import { NormalComponents } from "react-markdown/lib/complex-types"
|
||||
import { SpecialComponents } from "react-markdown/lib/ast-to-react"
|
||||
@@ -33,8 +33,7 @@ export const MarkdownContent = ({
|
||||
</pre>
|
||||
)
|
||||
},
|
||||
// TODO replace with Link once we move away from Docusaurus
|
||||
a: LegacyLink,
|
||||
a: Link,
|
||||
}
|
||||
}
|
||||
{...props}
|
||||
|
||||
@@ -48,8 +48,6 @@ export const MermaidDiagram = ({ diagramContent }: MermaidDiagramProps) => {
|
||||
setIsZoomed(shouldZoom)
|
||||
}, [])
|
||||
|
||||
console.log(matchedRegex, result?.svg)
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<ControlledZoom
|
||||
|
||||
@@ -18,6 +18,7 @@ export const NoteLayout = ({
|
||||
type === "error" ||
|
||||
type === "check"
|
||||
const isWarningStyle = type === "warning"
|
||||
const isSoonStyle = type === "soon"
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -26,6 +27,7 @@ export const NoteLayout = ({
|
||||
isDefaultStyle &&
|
||||
"bg-medusa-tag-neutral-bg border-medusa-tag-neutral-border",
|
||||
isWarningStyle && "bg-medusa-tag-red-bg border-medusa-tag-red-border",
|
||||
isSoonStyle && "bg-medusa-tag-blue-bg border-medusa-tag-blue-border",
|
||||
"[&_a]:no-underline [&_a]:text-medusa-fg-interactive hover:[&_a]:text-medusa-fg-interactive-hover ",
|
||||
"mb-docs_2 alert"
|
||||
)}
|
||||
@@ -36,6 +38,7 @@ export const NoteLayout = ({
|
||||
className={clsx(
|
||||
isDefaultStyle && "text-medusa-tag-neutral-text",
|
||||
isWarningStyle && "text-medusa-tag-red-text",
|
||||
isSoonStyle && "text-medusa-tag-blue-text",
|
||||
"text-medium flex-1 [&>*:last-child]:mb-0",
|
||||
"[&>p>code]:px-docs_0.5 [&>p>code]:text-code-label"
|
||||
)}
|
||||
@@ -45,7 +48,8 @@ export const NoteLayout = ({
|
||||
className={clsx(
|
||||
"text-compact-medium-plus block mb-docs_0.125",
|
||||
isDefaultStyle && "text-medusa-fg-base",
|
||||
isWarningStyle && "text-medusa-tag-red-text"
|
||||
isWarningStyle && "text-medusa-tag-red-text",
|
||||
isSoonStyle && "text-medusa-tag-blue-text"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from "react"
|
||||
import { NoteProps } from ".."
|
||||
import { NoteLayout } from "../Layout"
|
||||
import { LightBulb } from "@medusajs/icons"
|
||||
import clsx from "clsx"
|
||||
|
||||
export const SoonNote = ({
|
||||
title = "Coming soon",
|
||||
icon,
|
||||
...props
|
||||
}: NoteProps) => {
|
||||
return (
|
||||
<NoteLayout
|
||||
title={title}
|
||||
icon={
|
||||
icon || (
|
||||
<LightBulb
|
||||
className={clsx(
|
||||
"inline-block mr-docs_0.125 text-medusa-tag-blue-icon"
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import { DefaultNote } from "./Types/default"
|
||||
import { SuccessNote } from "./Types/sucess"
|
||||
import { ErrorNote } from "./Types/error"
|
||||
import { CheckNote } from "./Types/checks"
|
||||
import { SoonNote } from "./Types/soon"
|
||||
|
||||
export type NoteProps = {
|
||||
type?: "default" | "warning" | "success" | "error" | "check"
|
||||
type?: "default" | "warning" | "success" | "error" | "check" | "soon"
|
||||
title?: string
|
||||
children?: React.ReactNode
|
||||
icon?: React.ReactNode
|
||||
@@ -22,6 +23,8 @@ export const Note = ({ type = "default", ...props }: NoteProps) => {
|
||||
return <ErrorNote type={type} {...props} />
|
||||
case "check":
|
||||
return <CheckNote type={type} {...props} />
|
||||
case "soon":
|
||||
return <SoonNote type={type} {...props} />
|
||||
default:
|
||||
return <DefaultNote type={type} {...props} />
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export const Pagination = () => {
|
||||
startIcon={<ChevronLeft />}
|
||||
showLinkIcon={false}
|
||||
href={previousPage.link}
|
||||
className="max-w-[45%] ml-0 mr-auto"
|
||||
className="max-w-[45%] ml-0 mr-auto items-center"
|
||||
/>
|
||||
)}
|
||||
{nextPage && (
|
||||
@@ -25,7 +25,7 @@ export const Pagination = () => {
|
||||
endIcon={<ChevronRight />}
|
||||
showLinkIcon={false}
|
||||
href={nextPage.link}
|
||||
className="max-w-[45%] mr-0 ml-auto"
|
||||
className="max-w-[45%] mr-0 ml-auto items-center"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { SearchNoResult } from "../NoResults"
|
||||
import { SearchHitGroupName } from "./GroupName"
|
||||
import { useSearch } from "@/providers"
|
||||
import { LegacyLink } from "@/components"
|
||||
import { Link } from "@/components"
|
||||
|
||||
export type Hierarchy = "lvl0" | "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5"
|
||||
|
||||
@@ -205,8 +205,7 @@ export const SearchHits = ({
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
{/* TODO replace with Link once we move away from Docusaurus */}
|
||||
<LegacyLink
|
||||
<Link
|
||||
href={item.url}
|
||||
className="absolute top-0 left-0 h-full w-full"
|
||||
target="_self"
|
||||
|
||||
@@ -4,23 +4,40 @@ import React from "react"
|
||||
import { Tabs as UiTabs } from "@medusajs/ui"
|
||||
import { ComponentProps } from "react"
|
||||
import clsx from "clsx"
|
||||
import { Key } from "types"
|
||||
|
||||
export const Tabs = (props: ComponentProps<typeof UiTabs> & Key) => (
|
||||
<UiTabs {...props} />
|
||||
type TabsProps = ComponentProps<typeof UiTabs> & {
|
||||
layoutType?: "horizontal" | "vertical"
|
||||
}
|
||||
|
||||
export const Tabs = ({
|
||||
layoutType = "horizontal",
|
||||
className,
|
||||
...props
|
||||
}: TabsProps) => (
|
||||
<UiTabs
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
layoutType === "vertical" && [
|
||||
"flex gap-docs_1",
|
||||
"[&_[role=tablist]]:flex-col [&_[role=tablist]]:items-start",
|
||||
"[&_[role=tablist]+*]:flex-1 [&_[role=tablist]+*]:!mt-0",
|
||||
]
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
||||
export const TabsList = ({
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof UiTabs.List> & Key) => (
|
||||
}: ComponentProps<typeof UiTabs.List>) => (
|
||||
<UiTabs.List {...props} className={clsx(className, "gap-docs_0.5")} />
|
||||
)
|
||||
|
||||
export const TabsTrigger = ({
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof UiTabs.Trigger> & Key) => (
|
||||
}: ComponentProps<typeof UiTabs.Trigger>) => (
|
||||
<UiTabs.Trigger
|
||||
{...props}
|
||||
className={clsx(
|
||||
@@ -43,6 +60,6 @@ export const TabsContentWrapper = ({
|
||||
<div {...props} className={clsx(className, "mt-docs_0.5")} />
|
||||
)
|
||||
|
||||
export const TabsContent = (
|
||||
props: ComponentProps<typeof UiTabs.Content> & Key
|
||||
) => <UiTabs.Content {...props} />
|
||||
export const TabsContent = (props: ComponentProps<typeof UiTabs.Content>) => (
|
||||
<UiTabs.Content {...props} />
|
||||
)
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Switch } from "@medusajs/ui"
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import { Key } from "types"
|
||||
|
||||
export const Toggle = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Switch> & Key) => {
|
||||
}: React.ComponentProps<typeof Switch>) => {
|
||||
return (
|
||||
<Switch
|
||||
{...props}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
TriangleRightMini,
|
||||
} from "@medusajs/icons"
|
||||
import { decodeStr, isInView } from "@/utils"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useIsBrowser } from "../../.."
|
||||
|
||||
type CommonProps = ParentCommonProps & {
|
||||
@@ -39,9 +40,9 @@ const TypeListItem = ({
|
||||
elementKey,
|
||||
sectionTitle,
|
||||
siteUrl = "",
|
||||
pathname,
|
||||
}: TypeListItemProps) => {
|
||||
const isBrowser = useIsBrowser()
|
||||
const pathname = usePathname()
|
||||
|
||||
const groupName = useMemo(() => {
|
||||
switch (level) {
|
||||
|
||||
@@ -6,8 +6,6 @@ export type CommonProps = {
|
||||
expandUrl?: string
|
||||
sectionTitle?: string
|
||||
siteUrl?: string
|
||||
// TODO replace this with inferring pathname when using Next.js only
|
||||
pathname?: string
|
||||
}
|
||||
|
||||
export type Type = {
|
||||
@@ -35,7 +33,6 @@ export const TypeList = ({
|
||||
siteUrl,
|
||||
sectionTitle,
|
||||
expandUrl,
|
||||
pathname,
|
||||
...props
|
||||
}: ParameterTypesType) => {
|
||||
return (
|
||||
@@ -52,7 +49,6 @@ export const TypeList = ({
|
||||
expandUrl={expandUrl}
|
||||
sectionTitle={sectionTitle}
|
||||
siteUrl={siteUrl}
|
||||
pathname={pathname}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,6 @@ export * from "./Kbd"
|
||||
export * from "./Label"
|
||||
export * from "./LearningPath"
|
||||
export * from "./LegacyCodeTabs"
|
||||
export * from "./LegacyLink"
|
||||
export * from "./Link"
|
||||
export * from "./Loading"
|
||||
export * from "./Loading/Dots"
|
||||
|
||||
@@ -12,6 +12,12 @@ export const navbarItems: NavbarLinkProps[] = [
|
||||
rel: "noreferrer",
|
||||
href: `/`,
|
||||
},
|
||||
{
|
||||
label: "Resources",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/resources`,
|
||||
},
|
||||
{
|
||||
label: "User Guide",
|
||||
target: "_blank",
|
||||
@@ -38,6 +44,94 @@ export const navbarItems: NavbarLinkProps[] = [
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* TODO Uncomment for V2
|
||||
*
|
||||
* export const navbarItems: NavbarLinkProps[] = [
|
||||
{
|
||||
label: "Docs",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/`,
|
||||
activeValuePattern: /^\/(?!api\/|ui\/|user-guide\/|resources\/)/,
|
||||
},
|
||||
{
|
||||
label: "User Guide",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/user-guide`,
|
||||
},
|
||||
{
|
||||
label: "Store API",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/api/store`,
|
||||
},
|
||||
{
|
||||
label: "Admin API",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/api/admin`,
|
||||
},
|
||||
{
|
||||
label: "UI",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/ui`,
|
||||
activeValuePattern: /^\/ui\//,
|
||||
},
|
||||
{
|
||||
label: "V2 Docs",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/v2`,
|
||||
},
|
||||
]
|
||||
|
||||
export const navbarItemsV2: NavbarLinkProps[] = [
|
||||
{
|
||||
label: "Docs",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/v2`,
|
||||
activeValuePattern: /^\/v2\/(?!api\/|ui\/|user-guide\/|resources\/)/,
|
||||
},
|
||||
{
|
||||
label: "Resources",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/v2/resources`,
|
||||
activeValuePattern: /^\/v2\/resources\//,
|
||||
},
|
||||
{
|
||||
label: "User Guide",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/v2/user-guide`,
|
||||
activeValuePattern: /^\/v2\/user-guide/,
|
||||
},
|
||||
{
|
||||
label: "Store API",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/api/store`,
|
||||
},
|
||||
{
|
||||
label: "Admin API",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/api/admin`,
|
||||
},
|
||||
{
|
||||
label: "UI",
|
||||
target: "_blank",
|
||||
rel: "noreferrer",
|
||||
href: `/ui`,
|
||||
activeValuePattern: /^\/ui\//,
|
||||
},
|
||||
]
|
||||
*/
|
||||
|
||||
export const mobileSidebarItems: SidebarItemType[] = [
|
||||
{
|
||||
title: "Docs",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ApiTestingOptions } from "types"
|
||||
|
||||
export type useRequestRunnerProps = {
|
||||
pushLog: (...message: string[]) => void
|
||||
onFinish: (message: string) => void
|
||||
onFinish: (message: string, statusCode: string) => void
|
||||
replaceLog?: (message: string) => void
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ export const useRequestRunner = ({
|
||||
).toString()}`
|
||||
}
|
||||
|
||||
let responseCode = ""
|
||||
|
||||
fetch(requestUrl, {
|
||||
method: apiRequestOptions.method,
|
||||
headers: {
|
||||
@@ -37,7 +39,10 @@ export const useRequestRunner = ({
|
||||
? JSON.stringify(apiRequestOptions.bodyData)
|
||||
: undefined,
|
||||
})
|
||||
.then(async (response) => response.json())
|
||||
.then(async (response) => {
|
||||
responseCode = `${response.status}`
|
||||
return response.json()
|
||||
})
|
||||
.then((data) => {
|
||||
const stringifiedData = JSON.stringify(data, undefined, 2)
|
||||
replaceLog ? replaceLog(stringifiedData) : pushLog(stringifiedData)
|
||||
@@ -50,7 +55,7 @@ export const useRequestRunner = ({
|
||||
`ADMIN_CORS=http://localhost:7001,https://docs.medusajs.com`
|
||||
)
|
||||
})
|
||||
.finally(() => onFinish(`Finished running request.`))
|
||||
.finally(() => onFinish(`Finished running request.`, responseCode))
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"use client"
|
||||
|
||||
import React, { createContext, useContext, useState } from "react"
|
||||
import { DocsConfig } from "types"
|
||||
|
||||
export type SiteConfigContextType = {
|
||||
config: DocsConfig
|
||||
setConfig: React.Dispatch<React.SetStateAction<DocsConfig>>
|
||||
}
|
||||
|
||||
const SiteConfigContext = createContext<SiteConfigContextType | null>(null)
|
||||
|
||||
export type SiteConfigProviderProps = {
|
||||
config?: DocsConfig
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const SiteConfigProvider = ({
|
||||
config: initConfig,
|
||||
children,
|
||||
}: SiteConfigProviderProps) => {
|
||||
const [config, setConfig] = useState<DocsConfig>(
|
||||
initConfig || {
|
||||
baseUrl: "",
|
||||
sidebar: {
|
||||
top: [],
|
||||
bottom: [],
|
||||
mobile: [],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<SiteConfigContext.Provider
|
||||
value={{
|
||||
config,
|
||||
setConfig,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SiteConfigContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useSiteConfig = (): SiteConfigContextType => {
|
||||
const context = useContext(SiteConfigContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useSiteConfig must be used inside a SiteConfigProvider")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -10,3 +10,4 @@ export * from "./PageLoading"
|
||||
export * from "./Pagination"
|
||||
export * from "./Search"
|
||||
export * from "./Sidebar"
|
||||
export * from "./SiteConifg"
|
||||
@@ -513,344 +513,6 @@ const paths: LearningPathType[] = [
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO: Eventually remove these learning paths
|
||||
{
|
||||
name: "rbac",
|
||||
label: "Role-based access control (RBAC)",
|
||||
description: "Implement roles and permissions for admin users in Medusa",
|
||||
steps: [
|
||||
{
|
||||
title: "Create Role and Permission Entities",
|
||||
path: "/development/entities/create",
|
||||
description:
|
||||
"When implementing RBAC, you typically require the availability of roles and permissions, both of which would require new entities. A role would include different permissions, such as the ability to access the products’ route, and it can be assigned to one or more users.",
|
||||
},
|
||||
{
|
||||
title: "Extend Entities",
|
||||
path: "/development/entities/extend-entity",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
To associate roles with users, you need to extend the{" "}
|
||||
<code>User</code> entity to add the relation between it and the new{" "}
|
||||
<code>Role</code> entity. You can also extend other entities that
|
||||
are associated with your custom one, such as the <code>Store</code>{" "}
|
||||
entity.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create Guard Middleware",
|
||||
path: "/development/api-routes/add-middleware",
|
||||
description:
|
||||
"To ensure that users who have the privilege can access an API Route, you must create a middleware that guards admin routes. This middleware will run on all authenticated admin requests to ensure that only allowed users can access an API Route.",
|
||||
},
|
||||
{
|
||||
title: "Create Services",
|
||||
path: "/development/services/create-service",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
For every entity you create, such as the <code>Role</code> and{" "}
|
||||
<code>Permission</code> entities, you must create a service that
|
||||
provides create, read, update, and delete (CRUD) functionalities at
|
||||
the very least.
|
||||
<br />
|
||||
If you also extended entities, such as the <code>User</code> entity,
|
||||
you may need to{" "}
|
||||
<Link href="/development/services/extend-service">
|
||||
extend its core service
|
||||
</Link>{" "}
|
||||
<code>UserService</code> as well to perform custom functionalities
|
||||
related to your implementation.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Create API Routes",
|
||||
path: "/development/api-routes/create",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
To manage the roles and permissions, you’ll need to create custom
|
||||
API Routes, typically for Create, Read, Update, and Delete (CRUD)
|
||||
operations.
|
||||
<br />
|
||||
After creating the API Routes, you may test adding roles and
|
||||
permissions, and how they provide different access for different
|
||||
roles and users.
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on implementing RBAC!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_rbac",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "entity-and-api",
|
||||
label: "Create Entity and Expose it with API Routes",
|
||||
description:
|
||||
"Learn how to create a new table in your database, then create API Routes to expose and manipulate its data.",
|
||||
steps: [
|
||||
{
|
||||
title: "Create entity",
|
||||
path: "/development/entities/create",
|
||||
description: "Create your entity, its migration, and repository.",
|
||||
},
|
||||
{
|
||||
title: "Create service",
|
||||
path: "/development/services/create-service",
|
||||
description:
|
||||
"A service is a class that defines helper methods for your entity. The service will be used by the API Routes to access or modify the entity's data.",
|
||||
},
|
||||
{
|
||||
title: "Create API Routes",
|
||||
path: "/development/api-routes/create",
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on creating your entity and API Routes!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_entity-and-api",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "storefront",
|
||||
label: "Create a Custom Storefront",
|
||||
description:
|
||||
"Learn how to create a custom storefront with your preferred language or framework.",
|
||||
steps: [
|
||||
{
|
||||
title: "Choose your client",
|
||||
path: "/medusa-react/overview",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
As your storefront connect to the Medusa backend, you need a way to
|
||||
interact with the backend's REST APIs. There are three ways to
|
||||
do so, based on your type of project:
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/medusa-react/overview">Medusa React</Link>: Can be
|
||||
used in any React-based project. For example, in a Next.js
|
||||
storefront.
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/js-client/overview">Medusa JS Client</Link>: Can be
|
||||
used in any JavaScript and NPM based project. For example, in a
|
||||
Nuxt storefront.
|
||||
</li>
|
||||
<li>
|
||||
<Link href={`https://docs.medusajs.com/api/store`}>
|
||||
Store REST APIs
|
||||
</Link>
|
||||
: You can send requests directly to the API Routes without using
|
||||
Medusa's clients.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Set CORS configurations in Backend",
|
||||
path: "/development/backend/configurations#admin_cors-and-store_cors",
|
||||
description:
|
||||
"To ensure your storefront can connect to the backend, make sure to configure the backend's CORS configuration based on your storefront's local or remote URL.",
|
||||
},
|
||||
{
|
||||
title: "Create a Publishable API Key",
|
||||
path: "/user-guide/settings/publishable-api-keys",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
A publishable API key allows you to associate a key with a sales
|
||||
channel. Then, you can include that key in the headers of all your
|
||||
requests.
|
||||
<br />
|
||||
You can create the publishable API key from the dashboard.
|
||||
Alternatively, you can create it using the{" "}
|
||||
<Link href="/development/publishable-api-keys/admin/manage-publishable-api-keys">
|
||||
Admin APIs
|
||||
</Link>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Use Publishable API Key",
|
||||
path: "/development/publishable-api-keys/storefront/use-in-requests",
|
||||
description:
|
||||
"After creating the publishable API key and associating it with sales channels, you can pass it in the header of your requests to Store API Routes.",
|
||||
},
|
||||
{
|
||||
title: "Add Region Selection",
|
||||
path: "/modules/regions-and-currencies/storefront/use-regions",
|
||||
description:
|
||||
"In your storefront, you can allow your customers to view available regions and select their current region. This can affect the prices, discounts, and shipping and payment providers available to the customer.",
|
||||
},
|
||||
{
|
||||
title: "Display Products",
|
||||
path: "/modules/products/storefront/show-products",
|
||||
description: "Display products to your customers in the storefront.",
|
||||
},
|
||||
{
|
||||
title: "Implement Cart Functionalities",
|
||||
path: "/modules/carts-and-checkout/storefront/implement-cart",
|
||||
description:
|
||||
"Allow your customers to add items to their cart, update them, and more in preparation for checkout.",
|
||||
},
|
||||
{
|
||||
title: "Implement Checkout Flow",
|
||||
path: "/modules/carts-and-checkout/storefront/implement-checkout-flow",
|
||||
description:
|
||||
"Implement the checkout flow that allows customers to handle shipping and payment, then place their orders.",
|
||||
},
|
||||
{
|
||||
title: "Implement Customer Profiles",
|
||||
path: "/modules/customers/storefront/implement-customer-profiles",
|
||||
description:
|
||||
"Allow customers to register, login, edit their profile information, and more.",
|
||||
},
|
||||
{
|
||||
title: "More Commerce Functionalities",
|
||||
path: "/modules/overview",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
This recipe guided you to create a storefront with basic
|
||||
functionalities. You can add more functionalities to your storefront
|
||||
based on your use case.
|
||||
<ul>
|
||||
<li>
|
||||
The <Link href="/modules/overview">Commerce Modules</Link>{" "}
|
||||
documentation holds various storefront-related how-to guides to
|
||||
help you implement different features.
|
||||
</li>
|
||||
<li>
|
||||
You can also checkout the{" "}
|
||||
<Link href={`https://docs.medusajs.com/api/store`}>
|
||||
Store REST APIs
|
||||
</Link>{" "}
|
||||
for a full list of available REST APIs.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on creating your storefront!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_storefront",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "plugin",
|
||||
label: "Create a Plugin",
|
||||
description:
|
||||
"Learn how to create a plugin that can be re-used across Medusa backends.",
|
||||
steps: [
|
||||
{
|
||||
title: "Setup plugin project",
|
||||
path: "/development/backend/install",
|
||||
description:
|
||||
"A plugin is initially a Medusa backend with customizations. If you don't have a project ready, you can create one using Medusa's CLI tool.",
|
||||
},
|
||||
{
|
||||
title: "Implement Customizations",
|
||||
path: "/development/entities/create",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Your plugin can hold backend and admin customizations. Those
|
||||
include:
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/development/entities/create">Create Entity</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/development/services/create-service">
|
||||
Create Service
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/development/api-routes/create">
|
||||
Create an API Route
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/development/events/create-subscriber">
|
||||
Create Subscriber
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/admin/widgets">Create Admin Widgets</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/admin/routes">Create Admin Routes</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/admin/setting-pages">
|
||||
Create Admin Setting Pages
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/development/search/create">
|
||||
Create Search Service
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/development/file-service/create-file-service">
|
||||
Create File Service
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/development/notification/create-notification-provider">
|
||||
Create Notification Service
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
If you've already made your custom development, you can skip to
|
||||
the next step.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Change your package.json",
|
||||
path: "/development/plugins/create#changes-to-packagejson",
|
||||
descriptionJSX: (
|
||||
<>
|
||||
Once you're done making your customizations and you're
|
||||
ready to publish your plugin, make changes to your{" "}
|
||||
<code>package.json</code> in preparation for publishing.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Optionally test locally",
|
||||
path: "/development/plugins/create#test-your-plugin",
|
||||
description:
|
||||
"If necessary, you can test your plugin in a separate local Medusa backend. It's recommended, however, to do your plugin testing within the plugin project.",
|
||||
},
|
||||
{
|
||||
title: "Publish plugin",
|
||||
path: "/development/plugins/publish",
|
||||
description: "Publish your plugin on NPM.",
|
||||
},
|
||||
],
|
||||
finish: {
|
||||
type: "rating",
|
||||
step: {
|
||||
title: "Congratulations on creating your plugin!",
|
||||
description: "Please rate your experience using this recipe.",
|
||||
eventName: "rating_path_plugin",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// get a shallow copy
|
||||
|
||||
@@ -17,8 +17,8 @@ module.exports = {
|
||||
},
|
||||
plugins: ["prettier", "markdown"],
|
||||
ignorePatterns: [
|
||||
"**/content/references/**",
|
||||
"**/content/**/events-list.md"
|
||||
"**/references/**",
|
||||
"**/events-reference/**"
|
||||
],
|
||||
rules: {
|
||||
"no-undef": "off",
|
||||
@@ -32,7 +32,9 @@ module.exports = {
|
||||
camelcase: "off",
|
||||
"no-invalid-this": "off",
|
||||
"max-len": [
|
||||
"error",
|
||||
// TODO change back to error once we figure out
|
||||
// a way to use HTML comments in new MDX
|
||||
"warn",
|
||||
{
|
||||
code: 64,
|
||||
},
|
||||
@@ -75,7 +77,7 @@ module.exports = {
|
||||
"space-infix-ops": "off",
|
||||
"eol-last": ["error", "always"],
|
||||
"react/prop-types": "off",
|
||||
"react/jsx-no-undef": "off"
|
||||
"react/jsx-no-undef": "off",
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
@@ -106,6 +108,9 @@ module.exports = {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"prefer-rest-params": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-non-null-asserted-optional-chain": "off"
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -6,7 +6,8 @@ module.exports = {
|
||||
],
|
||||
"rules": {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"@typescript-eslint/prefer-ts-expect-error": "off"
|
||||
"@typescript-eslint/prefer-ts-expect-error": "off",
|
||||
"valid-jsdoc": "off"
|
||||
},
|
||||
ignorePatterns: [
|
||||
"next.config.js",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const path = require("path")
|
||||
|
||||
module.exports = {
|
||||
extends: ["docs"],
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: path.join(__dirname, "..", ".."),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "remark-rehype-plugins",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"module": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn clean && tsc",
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudinary/url-gen": "^1.17.0",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"to-vfile": "^8.0.0",
|
||||
"unified": "^11.0.4",
|
||||
"unist-builder": "3.0.0",
|
||||
"unist-util-visit": "4.1.2",
|
||||
"vfile-matter": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.20",
|
||||
"docs-ui": "*",
|
||||
"eslint-config-docs": "*",
|
||||
"rimraf": "^5.0.5",
|
||||
"tsconfig": "*",
|
||||
"types": "*",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { existsSync } from "fs"
|
||||
import path from "path"
|
||||
import type { Transformer } from "unified"
|
||||
import type { UnistNode, UnistTree } from "./types/index.js"
|
||||
|
||||
export function brokenLinkCheckerPlugin(): Transformer {
|
||||
return async (tree, file) => {
|
||||
const { visit } = await import("unist-util-visit")
|
||||
|
||||
const currentPageFilePath = file.history[0].replace(
|
||||
`/${path.basename(file.history[0])}`,
|
||||
""
|
||||
)
|
||||
|
||||
visit(tree as UnistTree, "element", (node: UnistNode) => {
|
||||
if (node.tagName !== "a" || !node.properties?.href?.match(/page\.mdx?/)) {
|
||||
return
|
||||
}
|
||||
// get absolute path of the URL
|
||||
const linkedFilePath = path
|
||||
.resolve(currentPageFilePath, node.properties.href)
|
||||
.replace(/#.*$/, "")
|
||||
// check if the file exists
|
||||
if (!existsSync(linkedFilePath)) {
|
||||
throw new Error(
|
||||
`Broken link found! ${node.properties.href} linked in ${file.history[0]}`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { Transformer } from "unified"
|
||||
import type { CloudinaryConfig, UnistNode, UnistTree } from "./types/index.js"
|
||||
|
||||
const cloudinaryImageRegex =
|
||||
/^https:\/\/res.cloudinary.com\/.*\/upload\/v[0-9]+\/(?<imageId>.*)$/
|
||||
|
||||
export function cloudinaryImgRehypePlugin({
|
||||
cloudinaryConfig,
|
||||
}: {
|
||||
cloudinaryConfig: CloudinaryConfig
|
||||
}): Transformer {
|
||||
return async (tree) => {
|
||||
if (!cloudinaryConfig) {
|
||||
return
|
||||
}
|
||||
const { visit } = await import("unist-util-visit")
|
||||
const { Cloudinary } = await import("@cloudinary/url-gen")
|
||||
const { byRadius } = await import(
|
||||
"@cloudinary/url-gen/actions/roundCorners"
|
||||
)
|
||||
|
||||
const {
|
||||
pad,
|
||||
imaggaScale,
|
||||
imaggaCrop,
|
||||
crop,
|
||||
fit,
|
||||
minimumPad,
|
||||
fill,
|
||||
scale,
|
||||
limitFit,
|
||||
thumbnail,
|
||||
limitFill,
|
||||
minimumFit,
|
||||
limitPad,
|
||||
fillPad,
|
||||
} = await import("@cloudinary/url-gen/actions/resize")
|
||||
|
||||
const resizeActions = {
|
||||
pad,
|
||||
imaggaScale,
|
||||
imaggaCrop,
|
||||
crop,
|
||||
fit,
|
||||
minimumPad,
|
||||
fill,
|
||||
scale,
|
||||
limitFit,
|
||||
thumbnail,
|
||||
limitFill,
|
||||
minimumFit,
|
||||
limitPad,
|
||||
fillPad,
|
||||
}
|
||||
|
||||
visit(tree as UnistTree, (node: UnistNode) => {
|
||||
const matchingRegex = node.properties?.src?.match(cloudinaryImageRegex)
|
||||
|
||||
if (node.tagName !== "img" || !matchingRegex?.groups?.imageId) {
|
||||
return
|
||||
}
|
||||
|
||||
const cloudinary = new Cloudinary({
|
||||
cloud: {
|
||||
cloudName: cloudinaryConfig.cloudName,
|
||||
},
|
||||
})
|
||||
const image = cloudinary.image(
|
||||
matchingRegex.groups.imageId.replaceAll("%20", " ")
|
||||
)
|
||||
|
||||
cloudinaryConfig.flags?.forEach((flag) => image.addTransformation(flag))
|
||||
|
||||
if (cloudinaryConfig.roundCorners) {
|
||||
image.roundCorners(byRadius(cloudinaryConfig.roundCorners))
|
||||
}
|
||||
|
||||
if (cloudinaryConfig.resize) {
|
||||
const action =
|
||||
resizeActions[
|
||||
cloudinaryConfig.resize.action as keyof typeof resizeActions
|
||||
]
|
||||
let resizeAction = action()
|
||||
if (node.properties?.width || node.properties?.height) {
|
||||
if (node.properties?.width) {
|
||||
resizeAction = resizeAction.width(node.properties.width)
|
||||
}
|
||||
|
||||
if (node.properties?.height) {
|
||||
resizeAction = resizeAction.height(node.properties.height)
|
||||
}
|
||||
} else if (cloudinaryConfig.resize.aspectRatio) {
|
||||
resizeAction = resizeAction.aspectRatio(
|
||||
cloudinaryConfig.resize.aspectRatio
|
||||
)
|
||||
} else {
|
||||
if (cloudinaryConfig.resize.width) {
|
||||
resizeAction = resizeAction.width(cloudinaryConfig.resize.width)
|
||||
}
|
||||
|
||||
if (cloudinaryConfig.resize.height) {
|
||||
resizeAction = resizeAction.height(cloudinaryConfig.resize.height)
|
||||
}
|
||||
}
|
||||
|
||||
image.resize(resizeAction)
|
||||
}
|
||||
|
||||
node.properties!.src = image.toURL()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Transformer } from "unified"
|
||||
import type {
|
||||
CrossProjectLinksOptions,
|
||||
UnistNode,
|
||||
UnistTree,
|
||||
} from "./types/index.js"
|
||||
|
||||
const PROJECT_REGEX = /^!(?<area>[\w-]+)!/
|
||||
|
||||
export function crossProjectLinksPlugin({
|
||||
baseUrl,
|
||||
projectUrls,
|
||||
}: CrossProjectLinksOptions): Transformer {
|
||||
return async (tree) => {
|
||||
const { visit } = await import("unist-util-visit")
|
||||
visit(tree as UnistTree, "element", (node: UnistNode) => {
|
||||
if (node.tagName !== "a" || !node.properties?.href) {
|
||||
return
|
||||
}
|
||||
|
||||
const projectArea = PROJECT_REGEX.exec(node.properties.href)
|
||||
|
||||
if (!projectArea?.groups?.area) {
|
||||
return
|
||||
}
|
||||
|
||||
const actualUrl = node.properties.href.replace(PROJECT_REGEX, "")
|
||||
|
||||
const base =
|
||||
projectUrls &&
|
||||
Object.hasOwn(projectUrls, projectArea.groups.area) &&
|
||||
projectUrls[projectArea.groups.area]?.url
|
||||
? projectUrls[projectArea.groups.area].url
|
||||
: baseUrl
|
||||
const path =
|
||||
projectUrls &&
|
||||
Object.hasOwn(projectUrls, projectArea.groups.area) &&
|
||||
projectUrls[projectArea.groups.area]?.path
|
||||
? projectUrls[projectArea.groups.area].path
|
||||
: projectArea.groups.area
|
||||
|
||||
node.properties.href = `${base}/${path}${actualUrl}`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export * from "./broken-link-checker.js"
|
||||
export * from "./cloudinary-img.js"
|
||||
export * from "./cross-project-links.js"
|
||||
export * from "./local-links.js"
|
||||
export * from "./page-number.js"
|
||||
export * from "./resolve-admonitions.js"
|
||||
export * from "./type-list-link-fixer.js"
|
||||
|
||||
export * from "./utils/fix-link.js"
|
||||
export * from "./utils/get-file-slug.js"
|
||||
export * from "./utils/get-front-matter.js"
|
||||
@@ -0,0 +1,40 @@
|
||||
import path from "path"
|
||||
import type { Transformer } from "unified"
|
||||
import type { LocalLinkOptions, UnistNode, UnistTree } from "./types/index.js"
|
||||
import { fixLinkUtil } from "./index.js"
|
||||
|
||||
export function localLinksRehypePlugin(options: LocalLinkOptions): Transformer {
|
||||
const { filePath, basePath } = options || {}
|
||||
return async (tree, file) => {
|
||||
if (!file.cwd) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!file.history.length) {
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
|
||||
file.history.push(filePath)
|
||||
}
|
||||
|
||||
const { visit } = await import("unist-util-visit")
|
||||
|
||||
const currentPageFilePath = file.history[0].replace(
|
||||
`/${path.basename(file.history[0])}`,
|
||||
""
|
||||
)
|
||||
const appsPath = basePath || path.join(file.cwd, "app")
|
||||
visit(tree as UnistTree, "element", (node: UnistNode) => {
|
||||
if (node.tagName !== "a" || !node.properties?.href?.match(/page\.mdx?/)) {
|
||||
return
|
||||
}
|
||||
|
||||
node.properties.href = fixLinkUtil({
|
||||
currentPageFilePath,
|
||||
linkedPath: node.properties.href,
|
||||
appsPath,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import path from "path"
|
||||
import type { Transformer } from "unified"
|
||||
import type { RawSidebarItemType } from "types"
|
||||
import type { UnistTree } from "./types/index.js"
|
||||
|
||||
export function pageNumberRehypePlugin({
|
||||
sidebar,
|
||||
}: {
|
||||
sidebar: RawSidebarItemType[]
|
||||
}): Transformer {
|
||||
return async (tree, file) => {
|
||||
const { valueToEstree } = await import("estree-util-value-to-estree")
|
||||
let number = ""
|
||||
|
||||
if (file.history.length) {
|
||||
// get page path
|
||||
const pageFilePath = file.history[0].replace(
|
||||
path.join(file.cwd, "app"),
|
||||
""
|
||||
)
|
||||
let pagePath = pageFilePath.replace(`/${path.basename(pageFilePath)}`, "")
|
||||
|
||||
if (!pagePath.length) {
|
||||
pagePath = "/"
|
||||
}
|
||||
|
||||
const pageSidebarItem = findSidebarItem(sidebar, pagePath)
|
||||
|
||||
if (pageSidebarItem?.number) {
|
||||
number = pageSidebarItem?.number
|
||||
}
|
||||
}
|
||||
|
||||
;(tree as UnistTree).children.unshift({
|
||||
type: "mdxjsEsm",
|
||||
value: `export const pageNumber = "${number}"`,
|
||||
data: {
|
||||
estree: {
|
||||
type: "Program",
|
||||
body: [
|
||||
{
|
||||
type: "ExportNamedDeclaration",
|
||||
declaration: {
|
||||
type: "VariableDeclaration",
|
||||
declarations: [
|
||||
{
|
||||
type: "VariableDeclarator",
|
||||
id: {
|
||||
type: "Identifier",
|
||||
name: "pageNumber",
|
||||
},
|
||||
init: valueToEstree(number),
|
||||
},
|
||||
],
|
||||
kind: "const",
|
||||
},
|
||||
specifiers: [],
|
||||
source: null,
|
||||
},
|
||||
],
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function findSidebarItem(
|
||||
sidebar: RawSidebarItemType[],
|
||||
path: string
|
||||
): RawSidebarItemType | undefined {
|
||||
let foundItem = undefined
|
||||
for (const item of sidebar) {
|
||||
if (path === item.path) {
|
||||
foundItem = item
|
||||
} else if (item.children) {
|
||||
foundItem = findSidebarItem(item.children, path)
|
||||
}
|
||||
|
||||
if (foundItem) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return foundItem
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Transformer } from "unified"
|
||||
import { UnistNode } from "./types/index.js"
|
||||
|
||||
const ALLOWED_NODE_NAMES = ["note", "tip", "info", "warning"]
|
||||
|
||||
export function resolveAdmonitionsPlugin(): Transformer {
|
||||
return async (tree) => {
|
||||
const { visit } = await import("unist-util-visit")
|
||||
visit(tree as UnistNode, (node: UnistNode) => {
|
||||
if (
|
||||
(node.type !== "containerDirective" &&
|
||||
node.type !== "leafDirective" &&
|
||||
node.type !== "textDirective") ||
|
||||
!node.name ||
|
||||
!ALLOWED_NODE_NAMES.includes(node.name)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
let title: string | undefined
|
||||
node.children?.some((childNode, childIndex) => {
|
||||
if (childNode.data?.directiveLabel && childNode.children?.length) {
|
||||
title = childNode.children.find((item) => item.type === "text")?.value
|
||||
|
||||
if (title) {
|
||||
node.children?.splice(childIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
return title !== undefined
|
||||
})
|
||||
|
||||
if (!node.data) {
|
||||
node.data = {}
|
||||
}
|
||||
|
||||
node.data.hName = "Note"
|
||||
node.data.hProperties = {
|
||||
title:
|
||||
title ||
|
||||
(node.name !== "note"
|
||||
? `${node.name.charAt(0).toUpperCase()}${node.name.substring(1)}`
|
||||
: "Note"),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import path from "path"
|
||||
import { Transformer } from "unified"
|
||||
import {
|
||||
TypeListLinkFixerOptions,
|
||||
UnistNode,
|
||||
UnistNodeWithData,
|
||||
UnistTree,
|
||||
} from "./types/index.js"
|
||||
import { FixLinkOptions, fixLinkUtil } from "./index.js"
|
||||
|
||||
const LINK_REGEX = /\[(.*?)\]\((?<link>.*?)\)/gm
|
||||
|
||||
function matchLinks(
|
||||
str: string,
|
||||
linkOptions: Omit<FixLinkOptions, "linkedPath">
|
||||
) {
|
||||
let linkMatches
|
||||
while ((linkMatches = LINK_REGEX.exec(str)) !== null) {
|
||||
if (!linkMatches.groups?.link) {
|
||||
return
|
||||
}
|
||||
|
||||
const newUrl = fixLinkUtil({
|
||||
...linkOptions,
|
||||
linkedPath: linkMatches.groups.link,
|
||||
})
|
||||
|
||||
str = str.replace(linkMatches.groups.link, newUrl)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
function traverseTypes(
|
||||
types: Record<string, unknown>[],
|
||||
linkOptions: Omit<FixLinkOptions, "linkedPath">
|
||||
) {
|
||||
return types.map((typeItem) => {
|
||||
typeItem.type = matchLinks(typeItem.type as string, linkOptions)
|
||||
typeItem.description = matchLinks(
|
||||
typeItem.description as string,
|
||||
linkOptions
|
||||
)
|
||||
if (typeItem.children) {
|
||||
typeItem.children = traverseTypes(
|
||||
typeItem.children as Record<string, unknown>[],
|
||||
linkOptions
|
||||
)
|
||||
}
|
||||
|
||||
return typeItem
|
||||
})
|
||||
}
|
||||
|
||||
export function typeListLinkFixerPlugin(
|
||||
options?: TypeListLinkFixerOptions
|
||||
): Transformer {
|
||||
const { filePath, basePath } = options || {}
|
||||
return async (tree, file) => {
|
||||
if (!file.cwd) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!file.history.length) {
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
|
||||
file.history.push(filePath)
|
||||
}
|
||||
|
||||
const { visit } = await import("unist-util-visit")
|
||||
|
||||
const currentPageFilePath = file.history[0].replace(
|
||||
`/${path.basename(file.history[0])}`,
|
||||
""
|
||||
)
|
||||
const appsPath = basePath || path.join(file.cwd, "app")
|
||||
visit(tree as UnistTree, "mdxJsxFlowElement", (node: UnistNode) => {
|
||||
if (node.name !== "TypeList") {
|
||||
return
|
||||
}
|
||||
|
||||
const typesAttributeIndex = node.attributes?.findIndex(
|
||||
(attribute) => attribute.name === "types"
|
||||
)
|
||||
if (typesAttributeIndex === undefined || typesAttributeIndex === -1) {
|
||||
return
|
||||
}
|
||||
const typesAttribute = node.attributes![typesAttributeIndex]
|
||||
|
||||
if (
|
||||
!typesAttribute ||
|
||||
!(typesAttribute.value as Record<string, unknown>)?.value
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const linkOptions = {
|
||||
currentPageFilePath,
|
||||
appsPath,
|
||||
}
|
||||
|
||||
let newItems: Record<string, unknown>[]
|
||||
|
||||
try {
|
||||
newItems = traverseTypes(
|
||||
JSON.parse(
|
||||
(typesAttribute.value as Record<string, unknown>).value as string
|
||||
) as Record<string, unknown>[],
|
||||
linkOptions
|
||||
)
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`[type-list-link-fixer-plugin]: An error occurred while parsing items for page ${file.history[0]}: ${e}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
;(
|
||||
node.attributes![typesAttributeIndex].value as Record<string, unknown>
|
||||
).value = JSON.stringify(newItems)
|
||||
|
||||
if (
|
||||
(node as UnistNodeWithData).attributes![typesAttributeIndex].value?.data
|
||||
?.estree?.body?.length
|
||||
) {
|
||||
const oldItems = (node as UnistNodeWithData).attributes[
|
||||
typesAttributeIndex
|
||||
].value.data!.estree!.body![0].expression!.elements!
|
||||
|
||||
;(node as UnistNodeWithData).attributes[
|
||||
typesAttributeIndex
|
||||
].value.data!.estree!.body![0].expression!.elements = newItems.map(
|
||||
(newItem, index) => {
|
||||
oldItems[index].properties = oldItems[index].properties.map(
|
||||
(property) => {
|
||||
if (Object.hasOwn(newItem, property.key.value)) {
|
||||
property.value.value = newItem[property.key.value]
|
||||
property.value.raw = JSON.stringify(
|
||||
newItem[property.key.value]
|
||||
)
|
||||
}
|
||||
|
||||
return property
|
||||
}
|
||||
)
|
||||
|
||||
return oldItems[index]
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Node } from "unist-builder"
|
||||
|
||||
export interface UnistNode extends Node {
|
||||
type: string
|
||||
name?: string
|
||||
tagName?: string
|
||||
value?: string
|
||||
properties?: {
|
||||
[key: string]: string
|
||||
}
|
||||
attributes?: {
|
||||
name: string
|
||||
value: unknown
|
||||
type?: string
|
||||
}[]
|
||||
children?: UnistNode[]
|
||||
}
|
||||
|
||||
export interface UnistNodeWithData extends UnistNode {
|
||||
attributes: {
|
||||
name: string
|
||||
value: {
|
||||
data?: {
|
||||
estree?: {
|
||||
body?: {
|
||||
type?: string
|
||||
expression?: {
|
||||
type?: string
|
||||
elements?: {
|
||||
properties: AttributeProperty[]
|
||||
}[]
|
||||
}
|
||||
}[]
|
||||
}
|
||||
}
|
||||
value?: string
|
||||
}
|
||||
type?: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface AttributeProperty {
|
||||
key: {
|
||||
value: string
|
||||
raw: string
|
||||
}
|
||||
value: {
|
||||
value: unknown
|
||||
raw: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface UnistTree extends Node {
|
||||
children: UnistNode[]
|
||||
}
|
||||
|
||||
export declare type CloudinaryConfig = {
|
||||
cloudName?: string
|
||||
flags?: string[]
|
||||
resize?: {
|
||||
action: string
|
||||
width?: number
|
||||
height?: number
|
||||
aspectRatio?: string
|
||||
}
|
||||
roundCorners?: number
|
||||
}
|
||||
|
||||
export declare type FrontMatter = {
|
||||
slug?: string
|
||||
sidebar_label?: string
|
||||
sidebar_group?: string
|
||||
sidebar_group_main?: boolean
|
||||
sidebar_position?: number
|
||||
sidebar_autogenerate_exclude?: boolean
|
||||
}
|
||||
|
||||
export declare type CrossProjectLinksOptions = {
|
||||
baseUrl: string
|
||||
projectUrls?: {
|
||||
[k: string]: {
|
||||
url: string
|
||||
path?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export declare type TypeListLinkFixerOptions = {
|
||||
filePath?: string
|
||||
basePath?: string
|
||||
}
|
||||
|
||||
export declare type LocalLinkOptions = {
|
||||
filePath?: string
|
||||
basePath?: string
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import path from "path"
|
||||
|
||||
export type FixLinkOptions = {
|
||||
currentPageFilePath: string
|
||||
linkedPath: string
|
||||
appsPath: string
|
||||
}
|
||||
|
||||
export function fixLinkUtil({
|
||||
currentPageFilePath,
|
||||
linkedPath,
|
||||
appsPath: basePath,
|
||||
}: FixLinkOptions) {
|
||||
// get absolute path of the URL
|
||||
const linkedFilePath = path
|
||||
.resolve(currentPageFilePath, linkedPath)
|
||||
.replace(basePath, "")
|
||||
// persist hash in new URL
|
||||
const hash = linkedFilePath.includes("#")
|
||||
? linkedFilePath.substring(linkedFilePath.indexOf("#"))
|
||||
: ""
|
||||
|
||||
return `${linkedFilePath.substring(
|
||||
0,
|
||||
linkedFilePath.indexOf(`/${path.basename(linkedFilePath)}`)
|
||||
)}${hash}`
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { getFrontMatterUtil } from "./get-front-matter.js"
|
||||
|
||||
export async function getFileSlugUtil(
|
||||
filePath: string
|
||||
): Promise<string | undefined> {
|
||||
const fileFrontmatter = await getFrontMatterUtil(filePath)
|
||||
|
||||
if (fileFrontmatter.slug) {
|
||||
// add to slugs array
|
||||
return fileFrontmatter.slug
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import remarkFrontmatter from "remark-frontmatter"
|
||||
import remarkParse from "remark-parse"
|
||||
import remarkStringify from "remark-stringify"
|
||||
import { unified } from "unified"
|
||||
import { read } from "to-vfile"
|
||||
import { matter } from "vfile-matter"
|
||||
import { FrontMatter } from "../types/index.js"
|
||||
|
||||
export async function getFrontMatterUtil(
|
||||
filePath: string
|
||||
): Promise<FrontMatter> {
|
||||
return (
|
||||
await unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkStringify)
|
||||
.use(remarkFrontmatter, ["yaml"])
|
||||
.use(() => {
|
||||
return (tree, file) => {
|
||||
matter(file)
|
||||
}
|
||||
})
|
||||
.process(await read(filePath))
|
||||
).data.matter as FrontMatter
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": ["tsconfig/base.json"],
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo-client",
|
||||
"noEmit": false,
|
||||
"lib": ["es2022"],
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -723,6 +723,16 @@ module.exports = {
|
||||
".animate-bg-surface": {
|
||||
"--animation-color": "var(--docs-bg-subtle-pressed)",
|
||||
},
|
||||
".code-block-highlight-dark": {
|
||||
"*::selection": {
|
||||
"background-color": "var(--docs-code-text-highlight)",
|
||||
},
|
||||
},
|
||||
".code-block-highlight-light": {
|
||||
"*::selection": {
|
||||
"background-color": "var(--docs-bg-highlight)",
|
||||
},
|
||||
},
|
||||
})
|
||||
addComponents({
|
||||
".btn-secondary-icon": {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"module": "./dist/index.js",
|
||||
|
||||
@@ -5,4 +5,5 @@ export declare type DocsConfig = {
|
||||
baseUrl: string
|
||||
sidebar: SidebarSectionItemsType
|
||||
filesBasePath?: string
|
||||
useNextLinks?: boolean
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
export type Ref<T> =
|
||||
| React.MutableRefObject<T | null>
|
||||
| ((instance: T | null) => void)
|
||||
|
||||
export type Key = {
|
||||
key?: string | number | null | undefined
|
||||
}
|
||||
Reference in new issue
Block a user