diff options
| author | ST-DDT <[email protected]> | 2024-12-28 04:25:06 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-12-28 03:25:06 +0000 |
| commit | e6d27a353ec9c82e60b31c8d833768141beb4dab (patch) | |
| tree | e9b728c03218d458149cb711909034170f61f3fd /docs | |
| parent | 817f8a01d93378e00c03cf73154fcec34fd5feef (diff) | |
| download | faker-e6d27a353ec9c82e60b31c8d833768141beb4dab.tar.xz faker-e6d27a353ec9c82e60b31c8d833768141beb4dab.zip | |
docs(api): add refresh button to examples (#3301)
* docs(api): add refresh button to examples
* chore: improve button behavior slightly
* chore: improve output format
* chore: ignore examples without recordable results
* temp
* chore: use svg button
* chore: use json5 format for test
* chore: simplify result formatting
* test: add formatting tests
* test: add e2e refresh test
* test: use static test values
* chore: fix regex
* chore: simplify refresh placeholder
* Update cypress/e2e/example-refresh.cy.ts
* fix: handle property after function call
* Apply suggestions from code review
Co-authored-by: Shinigami <[email protected]>
* Apply suggestions from code review
Co-authored-by: Shinigami <[email protected]>
* Apply suggestions from code review
Co-authored-by: Shinigami <[email protected]>
* chore: format
* chore: add comment
---------
Co-authored-by: Shinigami <[email protected]>
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/.vitepress/components/api-docs/format.ts | 14 | ||||
| -rw-r--r-- | docs/.vitepress/components/api-docs/method.ts | 1 | ||||
| -rw-r--r-- | docs/.vitepress/components/api-docs/method.vue | 120 | ||||
| -rw-r--r-- | docs/.vitepress/components/api-docs/refresh-button.vue | 69 | ||||
| -rw-r--r-- | docs/.vitepress/components/api-docs/refresh.svg | 1 | ||||
| -rw-r--r-- | docs/.vitepress/config.ts | 1 | ||||
| -rw-r--r-- | docs/api/.gitignore | 10 |
7 files changed, 204 insertions, 12 deletions
diff --git a/docs/.vitepress/components/api-docs/format.ts b/docs/.vitepress/components/api-docs/format.ts new file mode 100644 index 00000000..34de1e0c --- /dev/null +++ b/docs/.vitepress/components/api-docs/format.ts @@ -0,0 +1,14 @@ +export function formatResult(result: unknown): string { + return result === undefined + ? 'undefined' + : typeof result === 'bigint' + ? `${result}n` + : JSON.stringify(result, undefined, 2) + .replaceAll('\\r', '') + .replaceAll('<', '<') + .replaceAll( + /(^ *|: )"([^'\n]*?)"(?=,?$|: )/gm, + (_, p1, p2) => `${p1}'${p2.replace(/\\"/g, '"')}'` + ) + .replaceAll(/\n */g, ' '); +} diff --git a/docs/.vitepress/components/api-docs/method.ts b/docs/.vitepress/components/api-docs/method.ts index 4da480b8..91f99d4e 100644 --- a/docs/.vitepress/components/api-docs/method.ts +++ b/docs/.vitepress/components/api-docs/method.ts @@ -8,6 +8,7 @@ export interface ApiDocsMethod { readonly throws: string | undefined; // HTML readonly signature: string; // HTML readonly examples: string; // HTML + readonly refresh: (() => Promise<unknown[]>) | undefined; readonly seeAlsos: string[]; readonly sourcePath: string; // URL-Suffix } diff --git a/docs/.vitepress/components/api-docs/method.vue b/docs/.vitepress/components/api-docs/method.vue index 83a4100c..37b44748 100644 --- a/docs/.vitepress/components/api-docs/method.vue +++ b/docs/.vitepress/components/api-docs/method.vue @@ -1,8 +1,11 @@ <script setup lang="ts"> +import { computed, ref, useTemplateRef } from 'vue'; import { sourceBaseUrl } from '../../../api/source-base-url'; import { slugify } from '../../shared/utils/slugify'; +import { formatResult } from './format'; import type { ApiDocsMethod } from './method'; import MethodParameters from './method-parameters.vue'; +import RefreshButton from './refresh-button.vue'; const { method } = defineProps<{ method: ApiDocsMethod }>(); const { @@ -14,10 +17,113 @@ const { throws, signature, examples, + refresh, seeAlsos, sourcePath, } = method; +const code = useTemplateRef('code'); +const codeBlock = computed(() => code.value?.querySelector('div pre code')); +const codeLines = ref<Element[]>(); + +function initRefresh(): Element[] { + if (codeBlock.value == null) { + return []; + } + const domLines = codeBlock.value.querySelectorAll('.line'); + let lineIndex = 0; + const result: Element[] = []; + while (lineIndex < domLines.length) { + // Skip empty and preparatory lines (no '^faker.' invocation) + if ( + domLines[lineIndex]?.children.length === 0 || + !/^\w*faker\w*\./i.test(domLines[lineIndex]?.textContent ?? '') + ) { + lineIndex++; + continue; + } + + // Skip to end of the invocation (if multiline) + while ( + domLines[lineIndex] != null && + !/^([^ ].*)?\)(\.\w+)?;? ?(\/\/|$)/.test( + domLines[lineIndex]?.textContent ?? '' + ) + ) { + lineIndex++; + } + + if (lineIndex >= domLines.length) { + break; + } + + const domLine = domLines[lineIndex]; + result.push(domLine); + lineIndex++; + + // Purge old results + if (domLine.lastElementChild?.textContent?.startsWith('//')) { + // Inline comments + domLine.lastElementChild.remove(); + } else { + // Multiline comments + while (domLines[lineIndex]?.children[0]?.textContent?.startsWith('//')) { + domLines[lineIndex].previousSibling?.remove(); // newline + domLines[lineIndex].remove(); // comment + lineIndex++; + } + } + + // Add space between invocation and comment (if missing) + const lastElementChild = domLine.lastElementChild; + if ( + lastElementChild != null && + !lastElementChild.textContent?.endsWith(' ') + ) { + lastElementChild.textContent += ' '; + } + } + + return result; +} + +async function onRefresh(): Promise<void> { + if (refresh != null && codeBlock.value != null) { + codeLines.value ??= initRefresh(); + + const results = await refresh(); + + // Remove old comments + codeBlock.value + .querySelectorAll('.comment-delete-marker') + .forEach((el) => el.remove()); + + // Insert new comments + for (let i = 0; i < results.length; i++) { + const result = results[i]; + const domLine = codeLines.value[i]; + const prettyResult = formatResult(result); + const resultLines = prettyResult.split('\\n'); + + if (resultLines.length === 1) { + domLine.insertAdjacentHTML('beforeend', newCommentSpan(resultLines[0])); + } else { + for (const line of resultLines.reverse()) { + domLine.insertAdjacentHTML('afterend', newCommentLine(line)); + } + } + } + } +} + +function newCommentLine(content: string): string { + return `<span class="line comment-delete-marker">\n${newCommentSpan(content)}</span>`; +} + +function newCommentSpan(content: string): string { + return `<span class="comment-delete-marker" style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// ${content}</span>`; +} + function seeAlsoToUrl(see: string): string { const [, module, methodName] = see.replace(/\(.*/, '').split('\.'); @@ -51,8 +157,14 @@ function seeAlsoToUrl(see: string): string { <div v-html="signature" /> - <h3>Examples</h3> - <div v-html="examples" /> + <h3 class="inline">Examples</h3> + <RefreshButton + class="refresh" + v-if="refresh != null" + style="margin-left: 0.5em" + :refresh="onRefresh" + /> + <div ref="code" v-html="examples" /> <div v-if="seeAlsos.length > 0"> <h3>See Also</h3> @@ -107,4 +219,8 @@ svg.source-link-icon { display: inline; margin-left: 0.3em; } + +h3.inline { + display: inline-block; +} </style> diff --git a/docs/.vitepress/components/api-docs/refresh-button.vue b/docs/.vitepress/components/api-docs/refresh-button.vue new file mode 100644 index 00000000..c2210eab --- /dev/null +++ b/docs/.vitepress/components/api-docs/refresh-button.vue @@ -0,0 +1,69 @@ +<script setup lang="ts"> +import { ref } from 'vue'; + +// This should probably use emit instead, but emit cannot be awaited +const { refresh } = defineProps<{ refresh: () => Promise<void> }>(); + +const spinning = ref(false); + +async function onRefresh() { + spinning.value = true; + await Promise.all([refresh(), delay(100)]); + spinning.value = false; +} + +// Extra delay to make the spinning effect more visible +// Some examples barely/don't change, so the spinning is the only visible effect +function delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +</script> + +<template> + <button + class="refresh" + title="Refresh Examples" + :disabled="spinning" + @click="onRefresh" + > + <div :class="{ spinning: spinning }" /> + </button> +</template> + +<style scoped> +button.refresh { + border: 1px solid var(--vp-code-copy-code-border-color); + border-radius: 4px; + width: 40px; + height: 40px; + font-size: 25px; + vertical-align: middle; +} + +button.refresh div { + background-image: url('refresh.svg'); + background-position: 50%; + background-size: 20px; + background-repeat: no-repeat; + width: 100%; + height: 100%; +} + +button.refresh:hover { + background-color: var(--vp-code-copy-code-bg); + opacity: 1; +} + +div.spinning { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +</style> diff --git a/docs/.vitepress/components/api-docs/refresh.svg b/docs/.vitepress/components/api-docs/refresh.svg new file mode 100644 index 00000000..8320a2b2 --- /dev/null +++ b/docs/.vitepress/components/api-docs/refresh.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M20 12a8 8 0 0 1-6.44 7.85 8 8 0 0 1-8.95-4.79 8 8 0 0 1 2.94-9.71 8 8 0 0 1 10.1 1" style="fill:none;stroke:rgba(128,128,128,1);stroke-width:2"/><path d="m19.65 4.25-4.04 4.03 5.5 1.47z" style="fill:rgba(128,128,128,1)"/></svg>
\ No newline at end of file diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 746803ad..4fcc469e 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -133,6 +133,7 @@ async function enableFaker() { e.g. 'faker.food.description()' or 'fakerZH_CN.person.firstName()' For other languages please refer to https://fakerjs.dev/guide/localization.html#available-locales For a full list of all methods please refer to https://fakerjs.dev/api/\`, logStyle); + enableFaker = () => imported; // Init only once return imported; } `, diff --git a/docs/api/.gitignore b/docs/api/.gitignore deleted file mode 100644 index 47b11a83..00000000 --- a/docs/api/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Markdown -*.md -!index.md - -# TypeScript -*.ts -!api-types.ts - -# JSON -*.json |
