Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(api): add refresh button to examples #3301

Open
wants to merge 27 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c12385b
docs(api): add refresh button to examples
ST-DDT Dec 1, 2024
caa3928
chore: improve button behavior slightly
ST-DDT Dec 1, 2024
aa90d2d
chore: improve output format
ST-DDT Dec 1, 2024
dd8ff0a
chore: ignore examples without recordable results
ST-DDT Dec 1, 2024
c88e6bb
Merge branch 'next' into docs/api/examples-refresh
ST-DDT Dec 2, 2024
36104da
Merge branch 'next' into docs/api/examples-refresh
ST-DDT Dec 2, 2024
d83341e
temp
ST-DDT Dec 2, 2024
18cd35c
chore: use svg button
ST-DDT Dec 2, 2024
a7d7177
chore: use json5 format for test
ST-DDT Dec 2, 2024
9736051
Merge branch 'next' into docs/api/examples-refresh
ST-DDT Dec 3, 2024
00be2e1
Merge branch 'next' into docs/api/examples-refresh
ST-DDT Dec 13, 2024
075e716
chore: simplify result formatting
ST-DDT Dec 14, 2024
9139886
test: add formatting tests
ST-DDT Dec 14, 2024
a7b7b2b
test: add e2e refresh test
ST-DDT Dec 14, 2024
8c5e0a5
test: use static test values
ST-DDT Dec 14, 2024
bad1bda
Merge branch 'next' into docs/api/examples-refresh
ST-DDT Dec 14, 2024
a7f52a9
chore: fix regex
ST-DDT Dec 14, 2024
e6e1ff0
chore: simplify refresh placeholder
ST-DDT Dec 14, 2024
866b702
Update cypress/e2e/example-refresh.cy.ts
ST-DDT Dec 14, 2024
c4bb3dd
Merge branch 'next' into docs/api/examples-refresh
ST-DDT Dec 18, 2024
22039a9
fix: handle property after function call
ST-DDT Dec 19, 2024
c23f91f
Apply suggestions from code review
ST-DDT Dec 21, 2024
2247965
Apply suggestions from code review
ST-DDT Dec 21, 2024
c1e5de7
Apply suggestions from code review
ST-DDT Dec 21, 2024
64b397f
chore: format
ST-DDT Dec 21, 2024
f9f7d28
chore: add comment
ST-DDT Dec 21, 2024
31224ce
Merge branch 'next' into docs/api/examples-refresh
ST-DDT Dec 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ versions.json
/dist
/docs/.vitepress/cache
/docs/.vitepress/dist
/docs/api/*.ts
!/docs/api/api-types.ts
/docs/api/*.md
!/docs/api/index.md
/docs/api/api-search-index.json
/docs/public/api-diff-index.json

# Faker
Expand Down
32 changes: 32 additions & 0 deletions cypress/e2e/example-refresh.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
describe('example-refresh', () => {
it('should refresh the example', () => {
// given
cy.visit('/api/faker.html#constructor');
cy.get('.refresh').first().as('refresh');
cy.get('@refresh').next().find('code').as('codeBlock');
cy.get('@codeBlock').then(($el) => {
const originalCodeText = $el.text();

cy.get('@refresh')
.click()
.should('not.be.disabled') // stays disabled on error
.then(() => {
cy.get('@codeBlock').then(($el) => {
const newCodeText = $el.text();
expect(newCodeText).not.to.equal(originalCodeText);

cy.get('@refresh')
.click()
.should('not.be.disabled') // stays disabled on error
.then(() => {
cy.get('@codeBlock').then(($el) => {
const newCodeText2 = $el.text();
expect(newCodeText2).not.to.equal(originalCodeText);
expect(newCodeText2).not.to.equal(newCodeText);
});
});
});
});
});
});
});
14 changes: 14 additions & 0 deletions docs/.vitepress/components/api-docs/format.ts
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -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('<', '&lt;')
.replaceAll(
/(^ *|: )"([^'\n]*?)"(?=,?$|: )/gm,
(_, p1, p2) => `${p1}'${p2.replace(/\\"/g, '"')}'`
)
.replaceAll(/\n */g, ' ');
}
1 change: 1 addition & 0 deletions docs/.vitepress/components/api-docs/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
120 changes: 118 additions & 2 deletions docs/.vitepress/components/api-docs/method.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand All @@ -14,10 +17,113 @@ const {
throws,
signature,
examples,
refresh,
seeAlsos,
sourcePath,
} = method;

const code = ref<HTMLDivElement | null>(null);
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
const codeBlock = computed(() => code.value?.querySelector('div pre code'));
let codeLines: Element[] | undefined;
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved

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() {
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
if (refresh != null && codeBlock.value != null) {
codeLines ??= initRefresh();
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved

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[i];
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
const prettyResult = await formatResult(result);
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
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('\.');

Expand Down Expand Up @@ -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"
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
/>
<div ref="code" v-html="examples" />

<div v-if="seeAlsos.length > 0">
<h3>See Also</h3>
Expand Down Expand Up @@ -107,4 +219,8 @@ svg.source-link-icon {
display: inline;
margin-left: 0.3em;
}

h3.inline {
display: inline-block;
}
</style>
68 changes: 68 additions & 0 deletions docs/.vitepress/components/api-docs/refresh-button.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script setup lang="ts">
import { ref } from 'vue';

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));
}
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
</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>
1 change: 1 addition & 0 deletions docs/.vitepress/components/api-docs/refresh.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
`,
Expand Down
10 changes: 0 additions & 10 deletions docs/api/.gitignore
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved

This file was deleted.

1 change: 1 addition & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const config: ReturnType<typeof tseslint.config> = tseslint.config(
'.github/workflows/commentCodeGeneration.ts',
'.prettierrc.js',
'docs/.vitepress/components/shims.d.ts',
'docs/.vitepress/components/api-docs/format.ts',
'docs/.vitepress/shared/utils/slugify.ts',
'docs/.vitepress/theme/index.ts',
'eslint.config.js',
Expand Down
Loading
Loading