From f499e730623719a7da16835d16c4e408f24e8f51 Mon Sep 17 00:00:00 2001 From: Romain Lenzotti Date: Sat, 26 Oct 2024 15:06:57 +0200 Subject: [PATCH] feat(theme): add Api ref component --- docs/api.md | 7 +- packages/theme/DefaultTheme.ts | 3 +- .../api/{mapSymbols.ts => SymbolTypes.ts} | 20 ---- .../theme/composables/api/interfaces/Api.ts | 2 + .../theme/composables/api/mapSymbols.spec.ts | 48 --------- .../composables/api/useApiContent.spec.ts | 4 +- .../theme/composables/api/useApiContent.ts | 33 +++++- .../theme/composables/api/useApiModules.ts | 42 -------- .../theme/composables/api/useWarehouse.ts | 1 + .../config/__mocks__/useThemeConfig.ts | 2 +- .../composables/config/useThemeConfig.spec.ts | 2 + .../composables/config/useThemeConfig.ts | 1 - .../molecules/api-anchor/ApiAnchor.spec.ts | 2 +- .../theme/molecules/api-anchor/ApiAnchor.vue | 2 +- .../api-anchor/ApiAnchorQuery.spec.ts | 5 +- .../molecules/api-anchor/ApiAnchorQuery.vue | 10 +- .../molecules/api-list/ApiList.stories.ts | 28 +++++ packages/theme/molecules/api-list/ApiList.vue | 7 +- .../theme/molecules/banner/LightBanner.vue | 6 +- .../molecules/button-badge/ButtonBadge.vue | 100 +++++++++--------- .../button-boxes/ButtonBoxes.spec.ts | 10 +- .../button-boxes/ButtonBoxes.stories.ts | 8 +- .../molecules/button-boxes/ButtonBoxes.vue | 1 + .../molecules/card-package/CardPackage.vue | 2 +- .../molecules/filters/ClearableFilter.vue | 12 ++- packages/theme/molecules/filters/FilterBy.vue | 32 +++--- packages/theme/molecules/sort-by/SortBy.vue | 1 + packages/theme/organisms/api/Api.spec.ts | 35 ++++++ packages/theme/organisms/api/Api.stories.ts | 55 ++++++++++ packages/theme/organisms/api/Api.vue | 89 ++++++++++++++++ .../GithubContributors.spec.ts | 13 +-- .../GithubContributors.vue | 3 +- packages/theme/organisms/home/HomeBanner.vue | 2 +- .../organisms/warehouse/Warehouse.stories.ts | 50 ++++++++- .../theme/organisms/warehouse/Warehouse.vue | 1 + test/vitepress.client.ts | 2 + 36 files changed, 417 insertions(+), 224 deletions(-) rename packages/theme/composables/api/{mapSymbols.ts => SymbolTypes.ts} (51%) delete mode 100644 packages/theme/composables/api/mapSymbols.spec.ts delete mode 100644 packages/theme/composables/api/useApiModules.ts create mode 100644 packages/theme/molecules/api-list/ApiList.stories.ts create mode 100644 packages/theme/organisms/api/Api.spec.ts create mode 100644 packages/theme/organisms/api/Api.stories.ts create mode 100644 packages/theme/organisms/api/Api.vue diff --git a/docs/api.md b/docs/api.md index 210463c..bf86d77 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,4 +1,5 @@ --- +layout: page meta: - name: description content: Api Reference of Ts.ED. Use decorator to build your model and map data. @@ -6,8 +7,4 @@ meta: content: api reference model decorators ts.ed express typescript node.js javascript jsonschema json mapper serialization deserialization --- -# Api Reference - -
- -[//]: # () + diff --git a/packages/theme/DefaultTheme.ts b/packages/theme/DefaultTheme.ts index b7cb19e..3dfa001 100644 --- a/packages/theme/DefaultTheme.ts +++ b/packages/theme/DefaultTheme.ts @@ -17,6 +17,7 @@ import HomeFrameworks from "./organisms/home/HomeFrameworks.vue"; import HomeBody from "./organisms/home/HomeBody.vue"; import MessageCircleHeart from "./atoms/svg/MessageCircleHeart.vue"; import Warehouse from "./organisms/warehouse/Warehouse.vue"; +import Api from "./organisms/api/Api.vue"; export default { extends: DefaultTheme, @@ -29,6 +30,7 @@ export default { }); }, enhanceApp({app}) { + app.component("Api", Api); app.component("ApiList", ApiList); app.component("ApiAnchorQuery", ApiAnchorQuery); app.component("GithubContributors", GithubContributors); @@ -41,7 +43,6 @@ export default { app.component("Banner", Banner); app.component("Warehouse", Warehouse); app.directive("lazyload-observer", LazyLoadObserver); - // Layouts } } satisfies Theme; diff --git a/packages/theme/composables/api/mapSymbols.ts b/packages/theme/composables/api/SymbolTypes.ts similarity index 51% rename from packages/theme/composables/api/mapSymbols.ts rename to packages/theme/composables/api/SymbolTypes.ts index 4768bb0..226c70f 100644 --- a/packages/theme/composables/api/mapSymbols.ts +++ b/packages/theme/composables/api/SymbolTypes.ts @@ -1,5 +1,3 @@ -import type {ApiResponse} from "./interfaces/Api"; - export const SymbolTypes = [ { value: "decorator", @@ -42,21 +40,3 @@ export const SymbolTypes = [ code: "T" } ]; - -export function mapSymbols(items: ApiResponse) { - return Object.values(items.modules).reduce((symbols: any[], current) => { - return [ - ...symbols, - ...current.symbols.map((symbol) => { - return { - ...symbol, - // additional properties for the Api search tools - name: symbol.symbolName, - type: symbol.symbolType, - tags: symbol.status.join(","), - labels: symbol.status - }; - }) - ]; - }, []); -} diff --git a/packages/theme/composables/api/interfaces/Api.ts b/packages/theme/composables/api/interfaces/Api.ts index 5252f82..8706f9b 100644 --- a/packages/theme/composables/api/interfaces/Api.ts +++ b/packages/theme/composables/api/interfaces/Api.ts @@ -1,5 +1,6 @@ export type ApiSymbolType = "interface" | "decorator" | "class" | "type" | "enum" | "const" | "function" | "service"; export type ApiSymbolStatus = "stable" | "deprecated" | "experimental" | "private" | "public"; + export interface ApiSymbol { path?: string; symbolName: string; @@ -11,6 +12,7 @@ export interface ApiSymbol { } export interface ApiResponse { + symbolTypes: { label: string; value: string; }[]; modules: Record< string, { diff --git a/packages/theme/composables/api/mapSymbols.spec.ts b/packages/theme/composables/api/mapSymbols.spec.ts deleted file mode 100644 index 87a1d62..0000000 --- a/packages/theme/composables/api/mapSymbols.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {mapSymbols} from "./mapSymbols"; -import type {ApiResponse} from "./interfaces/Api"; - -describe("Map Symbols", () => { - it("should map symbols", () => { - const data = { - modules: { - "@tsed/core": { - symbols: [ - { - path: "/api/core/types/decorators/Configurable", - symbolName: "Configurable", - module: "@tsed/core", - symbolType: "decorator", - symbolLabel: "Decorator", - symbolCode: "@", - status: ["stable"] - } - ] - } - } - } satisfies ApiResponse; - - const result = mapSymbols(data); - - expect(result).toMatchInlineSnapshot(` - [ - { - "labels": [ - "stable", - ], - "module": "@tsed/core", - "name": "Configurable", - "path": "/api/core/types/decorators/Configurable", - "status": [ - "stable", - ], - "symbolCode": "@", - "symbolLabel": "Decorator", - "symbolName": "Configurable", - "symbolType": "decorator", - "tags": "stable", - "type": "decorator", - }, - ] - `); - }); -}); diff --git a/packages/theme/composables/api/useApiContent.spec.ts b/packages/theme/composables/api/useApiContent.spec.ts index a84d1c5..7a34201 100644 --- a/packages/theme/composables/api/useApiContent.spec.ts +++ b/packages/theme/composables/api/useApiContent.spec.ts @@ -29,6 +29,8 @@ describe("useApiContent", () => { } } }); - expect(useFetch).toHaveBeenCalledWith("https://tsed.io/api.json"); + expect(useFetch).toHaveBeenCalledWith("https://tsed.io/api.json",{ + afterFetch: expect.any(Function) + }); }); }); diff --git a/packages/theme/composables/api/useApiContent.ts b/packages/theme/composables/api/useApiContent.ts index 2d69d43..d922b3a 100644 --- a/packages/theme/composables/api/useApiContent.ts +++ b/packages/theme/composables/api/useApiContent.ts @@ -2,10 +2,39 @@ import {useFetch} from "@vueuse/core"; import type {ApiResponse} from "./interfaces/Api"; import {useThemeConfig} from "../config/useThemeConfig"; +export function mapSymbol(symbol: any) { + return { + ...symbol, + // additional properties for the Api search tools + name: symbol.symbolName, + type: symbol.symbolType, + tags: symbol.status.join(","), + labels: symbol.status + }; +} + export function useApiContent() { const theme = useThemeConfig(); - const apiUrl = theme.value.apiUrl; - return useFetch(apiUrl).get().json(); + return useFetch(apiUrl, { + afterFetch(ctx) { + ctx.data.modules = Object.fromEntries(Object.entries(ctx.data.modules) + .map(([key, item]: [string, any]) => { + + const symbols = new Map(); + + item.symbols.forEach((symbol: any) => { + symbol = mapSymbol(symbol); + symbols.set(symbol.symbolName, symbol); + }); + + item.symbols = [...symbols.values()]; + + return [key, item]; + })); + + return ctx; + } + }).get().json(); } diff --git a/packages/theme/composables/api/useApiModules.ts b/packages/theme/composables/api/useApiModules.ts deleted file mode 100644 index 64d6a03..0000000 --- a/packages/theme/composables/api/useApiModules.ts +++ /dev/null @@ -1,42 +0,0 @@ -import {useApiContent} from "./useApiContent"; -import type {ApiSymbol, ApiSymbolStatus} from "./interfaces/Api"; - -export interface UseApiModulesOptions { - currentType?: string; - currentStatus?: string; - keyword?: string; -} - -export function useApiModules({currentType, currentStatus, keyword}: UseApiModulesOptions) { - const {data} = useApiContent(); - - if (!data.value.modules) { - return {}; - } - - const modules = data.value.modules; - - return Object.keys(modules) - .sort((a, b) => (a < b ? -1 : 1)) - .reduce((acc: Record, key) => { - const symbols = modules[key].symbols.filter((symbol: ApiSymbol) => { - if (currentType && symbol.symbolType !== currentType) { - return false; - } - - if (currentStatus && symbol.status.indexOf(currentStatus as unknown as ApiSymbolStatus)) { - return false; - } - - if (keyword) { - return symbol.symbolName.toLowerCase().indexOf(keyword.toLocaleLowerCase()) > -1; - } - - return true; - }); - - acc[key] = {...modules[key], symbols}; - - return acc; - }, {}); -} diff --git a/packages/theme/composables/api/useWarehouse.ts b/packages/theme/composables/api/useWarehouse.ts index eab32f5..7a1f1c7 100644 --- a/packages/theme/composables/api/useWarehouse.ts +++ b/packages/theme/composables/api/useWarehouse.ts @@ -24,6 +24,7 @@ export function useWarehouse(docsRepo: string) { const fetchPackages = async () => { isActive.value = true; const endpoint = `${docsRepo.split("/rest")[0]}/rest/warehouse`; + try { const {data} = await axios.get(endpoint); diff --git a/packages/theme/composables/config/__mocks__/useThemeConfig.ts b/packages/theme/composables/config/__mocks__/useThemeConfig.ts index b70d843..0f5f6dd 100644 --- a/packages/theme/composables/config/__mocks__/useThemeConfig.ts +++ b/packages/theme/composables/config/__mocks__/useThemeConfig.ts @@ -1,5 +1,5 @@ export function useThemeConfig() { - const ref: {apiUrl: string; apiRedirectUrl: string; repo: string; githubProxyUrl: string; value: any} = { + const ref: { apiUrl: string; apiRedirectUrl: string; repo: string; githubProxyUrl: string; value: any } = { apiUrl: "https://tsed.io/api.json", apiRedirectUrl: "https://api-docs.tsed.io", repo: "tsedio/tsed", diff --git a/packages/theme/composables/config/useThemeConfig.spec.ts b/packages/theme/composables/config/useThemeConfig.spec.ts index 6b9ce53..df47fcd 100644 --- a/packages/theme/composables/config/useThemeConfig.spec.ts +++ b/packages/theme/composables/config/useThemeConfig.spec.ts @@ -5,6 +5,8 @@ describe("useThemeConfig", () => { const ref: any = { apiRedirectUrl: "https://api-docs.tsed.io", apiUrl: "https://tsed.io/api.json", + "githubProxyUrl": "https://api.tsed.io/rest/github/tsedio/tsed", + "repo": "tsedio/tsed", value: undefined }; ref.value = ref; diff --git a/packages/theme/composables/config/useThemeConfig.ts b/packages/theme/composables/config/useThemeConfig.ts index 9f6fed2..bfeb14a 100644 --- a/packages/theme/composables/config/useThemeConfig.ts +++ b/packages/theme/composables/config/useThemeConfig.ts @@ -4,6 +4,5 @@ import type {CustomThemeConfig} from "./interfaces/CustomThemeConfig"; export function useThemeConfig() { const {theme} = useData(); - return theme; } diff --git a/packages/theme/molecules/api-anchor/ApiAnchor.spec.ts b/packages/theme/molecules/api-anchor/ApiAnchor.spec.ts index 5d1b6d1..4216bfc 100644 --- a/packages/theme/molecules/api-anchor/ApiAnchor.spec.ts +++ b/packages/theme/molecules/api-anchor/ApiAnchor.spec.ts @@ -38,6 +38,6 @@ describe("", () => { expect(screen.getByText("Symbol name")).toBeTruthy(); expect(screen.getByText("Symbol name")).toHaveClass("line-through"); expect(screen.getByRole("link")).toHaveAttribute("href", "https://api-docs.tsed.io/path.html"); - expect(screen.getByRole("link")).toHaveClass("reset-link -bubble opacity-50"); + expect(screen.getByRole("link")).toHaveClass("reset-link -bubble"); }); }); diff --git a/packages/theme/molecules/api-anchor/ApiAnchor.vue b/packages/theme/molecules/api-anchor/ApiAnchor.vue index 7cf95ac..09f0107 100644 --- a/packages/theme/molecules/api-anchor/ApiAnchor.vue +++ b/packages/theme/molecules/api-anchor/ApiAnchor.vue @@ -29,7 +29,7 @@ const link = computed(() => { :is="link ? 'a' : 'span'" v-bind="link ? {href: link} : {}" data-name="ApiAnchor" - :class="`reset-link -${theme} ${deprecated ? 'opacity-50' : ''}`" + :class="`reset-link -${theme}`" :title="props.symbolName" > diff --git a/packages/theme/molecules/api-anchor/ApiAnchorQuery.spec.ts b/packages/theme/molecules/api-anchor/ApiAnchorQuery.spec.ts index 6086346..177176c 100644 --- a/packages/theme/molecules/api-anchor/ApiAnchorQuery.spec.ts +++ b/packages/theme/molecules/api-anchor/ApiAnchorQuery.spec.ts @@ -3,6 +3,7 @@ import {beforeEach} from "vitest"; import {useFetch} from "@vueuse/core"; import ApiAnchorQuery from "./ApiAnchorQuery.vue"; import {mount} from "@vue/test-utils"; +import {mapSymbol} from "../../composables/api/useApiContent"; vi.mock("@vueuse/core"); @@ -22,7 +23,7 @@ describe("", () => { modules: { "@tsed/core": { symbols: [ - { + mapSymbol({ path: "/api/core/types/decorators/Configurable", symbolName: "Configurable", module: "@tsed/core", @@ -30,7 +31,7 @@ describe("", () => { symbolLabel: "Decorator", symbolCode: "@", status: ["stable"] - } + }) ] } } diff --git a/packages/theme/molecules/api-anchor/ApiAnchorQuery.vue b/packages/theme/molecules/api-anchor/ApiAnchorQuery.vue index 8624b9c..866fb1a 100644 --- a/packages/theme/molecules/api-anchor/ApiAnchorQuery.vue +++ b/packages/theme/molecules/api-anchor/ApiAnchorQuery.vue @@ -3,7 +3,7 @@ import {computed, useSlots} from "vue"; import {useApiContent} from "../../composables/api/useApiContent"; import {useFilter} from "../../composables/filters/useFilter"; import type {ApiSymbol} from "../../composables/api/interfaces/Api"; -import {mapSymbols, SymbolTypes} from "../../composables/api/mapSymbols"; +import {SymbolTypes} from "../../composables/api/SymbolTypes"; import ApiAnchor from "./ApiAnchor.vue"; interface Props { @@ -44,7 +44,11 @@ const item = computed(() => { return {symbolName: name.value} as ApiSymbol; } - const value = filter(mapSymbols(data.value))[0]; + const symbols = Object.values(data.value.modules).flatMap(item => { + return item.symbols + }); + + const value = filter(symbols)[0]; if (value) { return value; @@ -66,5 +70,5 @@ const item = computed(() => { defineExpose({name, code}); diff --git a/packages/theme/molecules/api-list/ApiList.stories.ts b/packages/theme/molecules/api-list/ApiList.stories.ts new file mode 100644 index 0000000..f1f7a22 --- /dev/null +++ b/packages/theme/molecules/api-list/ApiList.stories.ts @@ -0,0 +1,28 @@ +import type {Meta, StoryObj} from "@storybook/vue3"; +import ApiList from "./ApiList.vue"; +/** + * Display Api references by given a query to filter the list. + */ +const meta = { + title: "ApiList", + component: ApiList, + parameters: { + layout: "centered" + }, + argTypes: { + + } +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + query: "status.includes('platform') && ['@tsed/common', '@tsed/platform-views', '@tsed/platform-params', '@tsed/platform-response-filter', '@tsed/platform-exceptions'].includes(module)" + }, + play() { + + } +}; diff --git a/packages/theme/molecules/api-list/ApiList.vue b/packages/theme/molecules/api-list/ApiList.vue index 9c11ba6..6b3164e 100644 --- a/packages/theme/molecules/api-list/ApiList.vue +++ b/packages/theme/molecules/api-list/ApiList.vue @@ -4,7 +4,6 @@ import ApiAnchor from "../api-anchor/ApiAnchor.vue"; import {useApiContent} from "../../composables/api/useApiContent"; import type {ApiSymbol} from "../../composables/api/interfaces/Api"; import {useFilter} from "../../composables/filters/useFilter"; -import {mapSymbols} from "../../composables/api/mapSymbols"; interface Props { items?: ApiSymbol[]; @@ -28,16 +27,18 @@ const symbols = computed(() => { return []; } - return filter(mapSymbols(data.value)); + return filter(Object.values(data.value.modules).flatMap(item => item.symbols)); }); + defineExpose({symbols}); +