diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1534e453f..000000000 --- a/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -dist -.output -.nuxt -*.css -*.md diff --git a/.eslintrc.js b/.eslintrc.js index f0e658967..00a2c76e9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,6 +4,7 @@ module.exports = { browser: true, node: true, }, + ignorePatterns: ['dist/**/*', '.output/**/*', '.nuxt/**/*', '*.css', '*.md'], extends: ['@nuxtjs/eslint-config-typescript', '@antfu', 'plugin:prettier-vue/recommended', 'prettier'], settings: { 'import/ignore': ['vue'], diff --git a/.github/scripts/bump-edge.ts b/.github/scripts/bump-edge.ts index 39d3a3954..6efee14f0 100644 --- a/.github/scripts/bump-edge.ts +++ b/.github/scripts/bump-edge.ts @@ -2,12 +2,7 @@ import { promises as fsp } from 'fs' import { resolve } from 'path' import { execSync } from 'child_process' -const packages = { - '@docus/base': 'npm:@docus/base-edge@', - '@docus/docs-theme': 'npm:@docus/docs-theme-edge@', -} - -async function loadPackage(dir: string) { +const loadPackage = async (dir: string) => { const pkgPath = resolve(dir, 'package.json') const data = JSON.parse(await fsp.readFile(pkgPath, 'utf-8').catch(() => '{}')) @@ -21,17 +16,7 @@ async function loadPackage(dir: string) { } } -function replaceWithEdge(pkg: any, commit: string, version: string) { - ;[pkg.data.dependencies || {}, pkg.data.devDependencies || {}, pkg.data.peerDependencies || {}].forEach((deps) => { - Object.entries(deps).forEach(([key, value]: any) => { - if (value.includes(commit)) return - - if (packages[key]) pkg.data.dependencies[key] = packages[key] + version - }) - }) -} - -async function main() { +const main = async () => { const path = process.argv[2] const pkg = await loadPackage(path) @@ -44,13 +29,10 @@ async function main() { if (!pkg.data.version.includes(commit)) pkg.data.version = version - replaceWithEdge(pkg, commit, version) - pkg.save() } main().catch((err) => { - // eslint-disable-next-line no-console console.error(err) process.exit(1) }) diff --git a/.github/scripts/cleanup.sh b/.github/scripts/cleanup.sh index 7e8a136df..26023d95f 100755 --- a/.github/scripts/cleanup.sh +++ b/.github/scripts/cleanup.sh @@ -2,32 +2,13 @@ # Root rm -rf node_modules -rm -rf pnpm-lock.yaml - -# Base -rm -rf packages/base/.nuxt -rm -rf packages/base/.output -rm -rf packages/base/.turbo -rm -rf packages/base/node_modules - -# Docs Theme -rm -rf packages/docs-theme/.nuxt -rm -rf packages/docs-theme/.output -rm -rf packages/docs-theme/.turbo -rm -rf packages/docs-theme/node_modules - -# GitHub -rm -rf packages/github/playground/.nuxt -rm -rf packages/github/playground/.output -rm -rf packages/github/test/fixtures/basic/.nuxt -rm -rf packages/github/test/fixtures/basic/.output -rm -rf packages/github/dist -rm -rf packages/github/.turbo -rm -rf packages/github/node_modules - +rm -rf yarn.lock # Docs rm -rf docs/.nuxt rm -rf docs/.output -rm -rf docs/.turbo -rm -rf docs/node_modules + + +# Theme +rm -rf .nuxt +rm -rf .output diff --git a/.github/scripts/release-edge.sh b/.github/scripts/release-edge.sh index 47a5684ac..1a3a9a496 100755 --- a/.github/scripts/release-edge.sh +++ b/.github/scripts/release-edge.sh @@ -1,16 +1,18 @@ #!/bin/bash # Bump versions to edge -npx jiti ../../.github/scripts/bump-edge $PWD +npx jiti ./.github/scripts/bump-edge $PWD # Update token if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then - echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc - echo "registry=https://registry.npmjs.org/" >> ~/.npmrc - echo "always-auth=true" >> ~/.npmrc - npm whoami + echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc + echo "registry=https://registry.npmjs.org/" >> ~/.npmrc + echo "always-auth=true" >> ~/.npmrc + echo "npmAuthToken: ${NODE_AUTH_TOKEN}" >> ~/.yarnrc.yml + npm whoami fi # # Release package echo "Publishing $PWD" -npm publish -q --access public +npm publish --access public --tolerate-republish +echo "Published $PWD" diff --git a/.github/scripts/release.sh b/.github/scripts/release.sh index 93454817f..e93193238 100755 --- a/.github/scripts/release.sh +++ b/.github/scripts/release.sh @@ -1,14 +1,18 @@ #!/bin/bash +# Resolve yarn +YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install + # Update token if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc echo "registry=https://registry.npmjs.org/" >> ~/.npmrc echo "always-auth=true" >> ~/.npmrc + echo "npmAuthToken: ${NODE_AUTH_TOKEN}" >> ~/.yarnrc.yml npm whoami fi # Release package echo "Publishing $PWD" -# npm publish -q --access public +# npm publish --access public --tolerate-republish echo "Published!" diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index f25deac03..053f9bd2a 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -19,33 +19,21 @@ jobs: node: [16] steps: - - name: Checkout - uses: actions/checkout@master - with: - persist-credentials: false - fetch-depth: 2 - - - uses: pnpm/action-setup@v2.0.1 - with: - version: 6.32.2 + - uses: actions/checkout@v3 - - name: Setup Node.js environment - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: - node-version: 16 - cache: 'pnpm' + node-version: ${{ matrix.node }} + cache: 'yarn' - name: Install dependencies - run: pnpm install + run: yarn --immutable - name: Build - run: pnpm build - -# - name: Test -# run: pnpm test + run: yarn build - name: Release Edge version if: github.event_name == 'push' - run: pnpm run release:edge + run: ./.github/scripts/release-edge.sh env: - NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 384d94bc9..ed6018249 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,30 +20,18 @@ jobs: node: [16] steps: - - name: Checkout - uses: actions/checkout@master - with: - persist-credentials: false - fetch-depth: 2 - - - uses: pnpm/action-setup@v2.0.1 - with: - version: 6.32.2 + - uses: actions/checkout@v3 - - name: Setup Node.js environment - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: - node-version: 16 - cache: 'pnpm' + node-version: ${{ matrix.node }} + cache: 'yarn' - name: Install dependencies - run: pnpm install + run: yarn --immutable - name: Build - run: pnpm build - -# - name: Test -# run: pnpm test + run: yarn build - name: Check if the version has been bumped id: check @@ -51,6 +39,7 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} +# Temporarily disable CI main package releases # - name: Release Main version # if: github.event_name == 'push' && steps.check.outputs.changed == 'true' # run: pnpm release diff --git a/.gitignore b/.gitignore index 8a53ceebb..c003f11f6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,5 @@ dist .yarn/cache .yarn/*state* -# Turbo -.turbo - # Local History .history diff --git a/.npmrc b/.npmrc deleted file mode 100644 index bf2e7648b..000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -shamefully-hoist=true diff --git a/packages/docs-theme/assets/css/main.css b/app/css/main.css similarity index 83% rename from packages/docs-theme/assets/css/main.css rename to app/css/main.css index 397f1940e..64e938fac 100644 --- a/packages/docs-theme/assets/css/main.css +++ b/app/css/main.css @@ -4,11 +4,9 @@ @layer components { /* Images */ - .light-img { @apply block dark:hidden; } - .dark-img { @apply hidden dark:block; } @@ -74,32 +72,7 @@ .u-ring-black { @apply ring-black dark:ring-white; } } -/* Variables */ -:root { - --header-height: 4rem; - --footer-height: 108px; - --page-height: calc(100vh - calc(var(--header-height) + var(--footer-height) + 50px)); -} - -@screen sm { - :root { - --header-height: 4rem; - --footer-height: 4rem; - --page-height: calc(100vh - calc(var(--header-height) + var(--footer-height))); - } -} - -.dark:root { - color-scheme: dark; -} - -html.dark { - @apply bg-black; -} - -body { - @apply antialiased font-sans text-gray-700 dark:text-gray-200 bg-white dark:bg-black; -} +/* Components */ .docus-content { & > :first-child { @@ -111,8 +84,6 @@ body { .badge, .callout, .card { - - /* Color Schemes TODO: Improve the usage of these styles across components. @@ -221,3 +192,52 @@ body { } } } + +/* Base */ + +.dark:root { + color-scheme: dark; + /* DocSearch */ + --docsearch-primary-color: theme('colors.primary.500') !important; + --docsearch-text-color: rgb(245, 246, 247); + --docsearch-container-background: rgba(9, 10, 17, 0.8); + --docsearch-modal-background: rgb(21, 23, 42); + --docsearch-modal-shadow: inset 1px 1px 0 0 rgb(44, 46, 64), + 0 3px 8px 0 rgb(0, 3, 9); + --docsearch-searchbox-background: rgb(9, 10, 17); + --docsearch-searchbox-focus-background: #000; + --docsearch-hit-color: rgb(190, 195, 201); + --docsearch-hit-shadow: none; + --docsearch-hit-background: rgb(9, 10, 17); + --docsearch-key-gradient: linear-gradient( + -26.5deg, + rgb(86, 88, 114) 0%, + rgb(49, 53, 91) 100% + ); + --docsearch-key-shadow: inset 0 -2px 0 0 rgb(40, 45, 85), + inset 0 0 1px 1px rgb(81, 87, 125), 0 2px 2px 0 rgba(3, 4, 9, 0.3); + --docsearch-footer-background: rgb(30, 33, 54); + --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5), + 0 -4px 8px 0 rgba(0, 0, 0, 0.2); + --docsearch-logo-color: rgb(255, 255, 255); + --docsearch-muted-color: rgb(127, 132, 151); +} + +body { + @apply antialiased text-gray-700 dark:text-gray-200 bg-white dark:bg-black overflow-y-scroll; + font-family: var(--fonts-primary); +} + +:root { + /* DocSearch */ + --docsearch-primary-color: theme('colors.primary.500') !important; +} + +html.dark { + @apply bg-black; +} + +code, kbd, pre, samp { + font-family: var(--fonts-code); + font-size: 1em; +} diff --git a/app/integrations/docsearch.ts b/app/integrations/docsearch.ts new file mode 100644 index 000000000..a04dc2118 --- /dev/null +++ b/app/integrations/docsearch.ts @@ -0,0 +1,179 @@ +import { withoutTrailingSlash } from 'ufo' +import type { DocSearchOptions } from '@nuxtjs/algolia/dist/module.d' +import { computed, defineNuxtPlugin, ref, useRoute, useRouter, useRuntimeConfig } from '#imports' + +export default defineNuxtPlugin(async () => { + const config = useRuntimeConfig() + + const docSearchElement = ref() + + const hasDocSearch = computed(() => config?.algolia?.docSearch) + + // Setup Algolia DocSearch integration + if (hasDocSearch.value) { + const route = useRoute() + + const router = useRouter() + + /** + * Try to grab options from runtimeConfig. + * + * If not found, fallback on props. + */ + const options = computed(() => { + const { algolia } = useRuntimeConfig() + + if (algolia && algolia.docSearch) { + return algolia.docSearch + } + + return {} + }) + + /** + * Check if event is special click to avoid closing the DocSearch too soon. + */ + const isSpecialClick = (event: MouseEvent) => event.button === 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey + + /** + * Gets the relative path from an absolute URL provided by the DocSearch instance. + */ + const getRelativePath = (absoluteUrl: string) => { + const { pathname, hash } = new URL(absoluteUrl) + const url = window.location.origin + const relativeUrl = pathname.replace(url, '/') + hash + return withoutTrailingSlash(relativeUrl) + } + + /** + * Initialize the DocSearch instance. + * @param userOptions + */ + const initialize = async (userOptions: DocSearchOptions) => { + const el = document.createElement('div') + el.id = '#docsearch-container' + el.style.width = '0' + el.style.height = '0' + el.style.display = 'none' + document.body.appendChild(el) + docSearchElement.value = el + + // @ts-expect-errors - Import @docsearch at runtime + const docsearch = await Promise.all([import(/* webpackChunkName: "docsearch" */ '@docsearch/js'), import(/* webpackChunkName: "docsearch" */ '@docsearch/css')]).then( + ([docsearch]) => docsearch.default, + ) + + // TODO: Maybe bind this with @nuxt/i18n ? + // Resolve lang + const lang = userOptions?.lang || 'en' + + // Generate lang prefix + const langPrefix = `${userOptions.langAttribute || 'language'}:${lang}` + + // Get facet filters + const userFacetFilters = userOptions.facetFilters || [] + + // Create DocSearch instance + docsearch({ + /** + * Local implementation of this DocSearch box uses a local element with an `docsearch` id. + */ + container: el, + appId: userOptions.applicationId, + apiKey: userOptions.apiKey, + indexName: userOptions.indexName, + searchParameters: { + ...// Prefix facetFilters with langAttribute, otherwise use raw facetFilters + (!lang + ? { + facetFilters: userFacetFilters, + } + : { + facetFilters: [langPrefix].concat(userFacetFilters), + }), + ...userOptions.searchParameters, + }, + /** + * Transform items into relative URL format (compatibiltiy with Vue Router). + */ + transformItems: userOptions.transformItems + ? userOptions.transformItems + : (items) => { + return items.map((item) => { + return { + ...item, + url: getRelativePath(item.url), + } + }) + }, + navigator: userOptions.navigator + ? userOptions.navigator + : { + navigate: ({ itemUrl }) => { + const { pathname: hitPathname } = new URL(window.location.origin + itemUrl) + // Vue Router doesn't handle same-page navigation so we use + // the native browser location API for anchor navigation. + if (route.path === hitPathname) { + window.location.assign(window.location.origin + itemUrl) + } else { + router.push(itemUrl) + } + }, + }, + // @ts-expect-error - We don't actually use `hitComponent` but react on its calls. + hitComponent: userOptions.hitComponent + ? userOptions.hitComponent + : ({ hit, children }) => { + return { + type: 'a', + constructor: undefined, + __v: 1, + props: { + href: hit.url, + children, + onClick: (event: MouseEvent) => { + if (isSpecialClick(event)) { + return + } + // We rely on the native link scrolling when user is + // already on the right anchor because Vue Router doesn't + // support duplicated history entries. + if (route.fullPath === hit.url) { + return + } + const { pathname: hitPathname } = new URL(window.location.origin + hit.url) + // If the hits goes to another page, we prevent the native link behavior + // to leverage the Vue Router loading feature. + if (route.path !== hitPathname) { + event.preventDefault() + } + router.push(hit.url) + }, + }, + } + }, + // Spread user options, except the ones that are already used in the instance. + ...Object.entries(userOptions) + // Skip already used keys + .filter(([key]) => !['applicationId', 'apiKey', 'indexName', 'transformItems', 'navigator', 'hitComponent', 'facetFilters', 'langAttribute', 'lang'].includes(key)) + // Recompose options + .reduce((acc, [key, value]) => { + acc[key] = value + return acc + }, {}), + }) + } + + // Watch options and restart the instance if needed. + if (process.client) initialize(options.value) + } + + return { + provide: { + docSearch: { + element: docSearchElement, + hasDocSearch, + }, + }, + } +}) diff --git a/app/integrations/github.ts b/app/integrations/github.ts new file mode 100644 index 000000000..c15ba11a1 --- /dev/null +++ b/app/integrations/github.ts @@ -0,0 +1,38 @@ +import { addRouteMiddleware, defineNuxtPlugin, useRuntimeConfig, useState } from '#imports' + +export default defineNuxtPlugin(async () => { + const config = useRuntimeConfig() + + // Setup GitHub global integration + if (config?.github) { + addRouteMiddleware( + 'github', + async () => { + // Github integration state + const lastReleaseState = useState('docus-last-release') + const repositoryState = useState('docus-repository') + + // Check if data is already present + if (typeof lastReleaseState.value !== 'undefined' && typeof repositoryState.value !== 'undefined') return + + try { + await Promise.all([$fetch('/api/_github/last-release', { responseType: 'json' }), $fetch('/api/_github/repository', { responseType: 'json' })]).then( + ([lastRelease, repository]) => { + lastReleaseState.value = lastRelease + repositoryState.value = repository + }, + ) + } catch (e) { + lastReleaseState.value = false + repositoryState.value = false + + console.warn(`Cannot fetch GitHub global data.`) + + // eslint-disable-next-line no-console + console.info('If your repository is private, make sure to provide GITHUB_TOKEN environment in `.env`') + } + }, + { global: true }, + ) + } +}) diff --git a/app/module.ts b/app/module.ts new file mode 100644 index 000000000..208aa03db --- /dev/null +++ b/app/module.ts @@ -0,0 +1,52 @@ +import { fileURLToPath } from 'url' +import { addPlugin, defineNuxtModule } from '@nuxt/kit' +import { resolve } from 'pathe' + +const themeDir = fileURLToPath(new URL('./', import.meta.url)) +const resolveThemeDir = (path: string) => resolve(themeDir, path) + +export default defineNuxtModule({ + meta: { + name: 'docus', + version: '3.0.0', + compatibility: { + nuxt: '^3.0.0-rc.4', + bridge: false, + }, + configKey: 'docus', + }, + setup(_, nuxt) { + // Pre-render 404 page + /* + nuxt.hook('nitro:config', (nitroConfig) => { + nitroConfig.prerender = nitroConfig.prerender || {} + nitroConfig.prerender.routes = nitroConfig.prerender.routes || [] + nitroConfig.prerender.routes.push('/404') + nitroConfig.prerender.routes.push('/') + }) + */ + + // TODO: Remove this workaround + // Origin: https://github.com/nuxt/framework/pull/5709 + // Issue: https://github.com/nuxt/framework/issues/5827 + nuxt.hook('nitro:config', (nitroConfig) => { + nuxt.options.nitro.prerender = nuxt.options.nitro.prerender || {} + nuxt.options.nitro.prerender.routes = nuxt.options.nitro.prerender.routes || [] + nuxt.options.nitro.prerender.routes.push(...nitroConfig.prerender.routes) + }) + + // @ts-expect-error - GitHub module might not be installed + if (nuxt.options?.github) { + addPlugin({ + src: resolveThemeDir('integrations/github.ts'), + }) + } + + // @ts-expect-error - Algolia module might not be installed + if (nuxt.options?.algolia?.docSearch) { + addPlugin({ + src: resolveThemeDir('integrations/docsearch.ts'), + }) + } + }, +}) diff --git a/packages/docs-theme/app/router.options.ts b/app/router.options.ts similarity index 100% rename from packages/docs-theme/app/router.options.ts rename to app/router.options.ts diff --git a/packages/docs-theme/tailwind.config.js b/app/tailwind.config.js similarity index 88% rename from packages/docs-theme/tailwind.config.js rename to app/tailwind.config.js index e8c461608..5b09e7867 100644 --- a/packages/docs-theme/tailwind.config.js +++ b/app/tailwind.config.js @@ -1,3 +1,10 @@ +import { fileURLToPath } from 'url' +import { $dt } from '@nuxtjs/design-tokens' +import { resolve } from 'pathe' + +const themeDir = fileURLToPath(new URL('../', import.meta.url)) +const resolveThemeDir = (path) => resolve(themeDir, path) + const colors = { transparent: 'transparent', current: 'currentColor', @@ -176,21 +183,12 @@ const colors = { /** @type { import('tailwindcss/tailwind-config').TailwindConfig } */ export default { darkMode: 'class', - plugins: [ - require('@tailwindcss/typography'), - // Gives errors in console (color-adjust) - // require('@tailwindcss/forms'), - require('@tailwindcss/line-clamp'), - require('@tailwindcss/aspect-ratio'), - ], + plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms'), require('@tailwindcss/line-clamp'), require('@tailwindcss/aspect-ratio')], theme: { colors, extend: { colors: { - primary: colors.blue, - }, - fontFamily: { - sans: 'Inter, sans-serif', + primary: $dt('colors.primary'), }, spacing: { header: 'var(--header-height)', @@ -198,7 +196,7 @@ export default { page: 'var(--page-height)', }, maxWidth: { - '8xl': '90rem', + '8xl': 'var(--page-max-width)', }, minHeight: { page: 'var(--page-height)', diff --git a/packages/base/components/Debug.vue b/components/Debug.vue similarity index 100% rename from packages/base/components/Debug.vue rename to components/Debug.vue diff --git a/packages/base/components/NuxtLoadingBar.vue b/components/NuxtLoadingBar.vue similarity index 99% rename from packages/base/components/NuxtLoadingBar.vue rename to components/NuxtLoadingBar.vue index 64ee9f190..13f3513be 100644 --- a/packages/base/components/NuxtLoadingBar.vue +++ b/components/NuxtLoadingBar.vue @@ -19,7 +19,9 @@ const props = defineProps({ default: 3, }, }) + const { nuxtApp } = props + // Options & Data const data = reactive({ percent: 0, diff --git a/packages/docs-theme/components/app/Container.vue b/components/app/Container.vue similarity index 66% rename from packages/docs-theme/components/app/Container.vue rename to components/app/Container.vue index 31bfbc6da..10c2e3d40 100644 --- a/packages/docs-theme/components/app/Container.vue +++ b/components/app/Container.vue @@ -9,7 +9,7 @@ defineProps({ diff --git a/packages/docs-theme/components/app/ErrorPage.vue b/components/app/ErrorPage.vue similarity index 97% rename from packages/docs-theme/components/app/ErrorPage.vue rename to components/app/ErrorPage.vue index 46dd08811..8747f4bb0 100644 --- a/packages/docs-theme/components/app/ErrorPage.vue +++ b/components/app/ErrorPage.vue @@ -35,7 +35,7 @@ defineProps({

- Go back home + Go back home
diff --git a/packages/docs-theme/components/app/Footer.vue b/components/app/Footer.vue similarity index 56% rename from packages/docs-theme/components/app/Footer.vue rename to components/app/Footer.vue index c491c2538..f996672c0 100644 --- a/packages/docs-theme/components/app/Footer.vue +++ b/components/app/Footer.vue @@ -7,10 +7,10 @@ const icons = computed(() => theme.value?.footer?.icons || [])