diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 8c62cd4eaa..3f8a287e3a 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -20,26 +20,21 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install + cache: true - name: ๐ŸŽจ Check for non-RTL/non-a11y CSS classes - run: pnpm vp run lint:css + run: vp run lint:css - name: ๐ŸŒ Compare translations - run: pnpm vp run i18n:check + run: vp run i18n:check - name: ๐ŸŒ Update lunaria data - run: pnpm vp run build:lunaria + run: vp run build:lunaria - name: ๐Ÿ”  Fix lint errors - run: pnpm vp run lint:fix + run: vp run lint:fix - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 # 635ffb0c9798bd160680f18fd73371e355b85f27 diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 0a4860dc5f..5adb687647 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -26,15 +26,13 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.event.pull_request.head.sha || github.sha }} - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* + cache: true - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install + - name: ๐ŸŸง Install pnpm globally + run: vp install -g pnpm - name: ๐Ÿงช Run Chromatic Visual and Accessibility Tests uses: chromaui/action@f191a0224b10e1a38b2091cefb7b7a2337009116 # v16.0.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b26e2a37b3..f4c414458b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,18 +28,16 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm + run-install: false - name: ๐Ÿ“ฆ Install dependencies (root only, no scripts) - run: pnpm install --filter . --ignore-scripts + run: vp install --filter . --ignore-scripts - name: ๐Ÿ”  Lint project - run: pnpm vp run lint + run: vp run lint types: name: ๐Ÿ’ช Type check @@ -48,18 +46,13 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install + cache: true - name: ๐Ÿ’ช Type check - run: pnpm vp run test:types + run: vp run test:types unit: name: ๐Ÿงช Unit tests @@ -68,18 +61,13 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install + cache: true - name: ๐Ÿงช Unit tests - run: pnpm vp test --project unit --coverage --reporter=default --reporter=junit --outputFile=test-report.junit.xml + run: vp test --project unit --coverage --reporter=default --reporter=junit --outputFile=test-report.junit.xml - name: โฌ†๏ธŽ Upload test results to Codecov if: ${{ !cancelled() }} @@ -94,21 +82,16 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install + cache: true - name: ๐ŸŒ Install browser - run: pnpm vp exec playwright install chromium-headless-shell + run: vp exec playwright install chromium-headless-shell - name: ๐Ÿงช Component tests - run: pnpm vp test --project nuxt --coverage --reporter=default --reporter=junit --outputFile=test-report.junit.xml + run: vp test --project nuxt --coverage --reporter=default --reporter=junit --outputFile=test-report.junit.xml - name: โฌ†๏ธŽ Upload coverage reports to Codecov uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 @@ -131,23 +114,18 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install + cache: true - name: ๐Ÿ—๏ธ Build project - run: pnpm vp run build:test + run: vp run build:test env: VALIDATE_HTML: true - name: ๐Ÿ–ฅ๏ธ Test project (browser) - run: pnpm vp run test:browser:prebuilt + run: vp run test:browser:prebuilt a11y: name: โ™ฟ Accessibility audit @@ -159,21 +137,16 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install + cache: true - name: ๐Ÿ—๏ธ Build project - run: pnpm vp run build:test + run: vp run build:test - name: โ™ฟ Accessibility audit (Lighthouse - ${{ matrix.mode }} mode) - run: pnpm vp run test:a11y:prebuilt + run: vp run test:a11y:prebuilt env: LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} LIGHTHOUSE_COLOR_MODE: ${{ matrix.mode }} @@ -185,18 +158,13 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install + cache: true - name: ๐Ÿงน Check for unused code - run: pnpm vp run knip + run: vp run knip i18n: name: ๐ŸŒ i18n validation @@ -205,20 +173,18 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm + run-install: false - name: ๐Ÿ“ฆ Install dependencies (root only, no scripts) - run: pnpm install --filter . --ignore-scripts + run: vp install --filter . --ignore-scripts - name: ๐ŸŒ Check for missing or dynamic i18n keys - run: pnpm vp run i18n:report + run: vp run i18n:report - name: ๐ŸŒ Check i18n schema is up to date run: | - pnpm vp run i18n:schema + vp run i18n:schema git diff --exit-code i18n/schema.json diff --git a/.github/workflows/lunaria.yml b/.github/workflows/lunaria.yml index b2bc818360..4e1cf26604 100644 --- a/.github/workflows/lunaria.yml +++ b/.github/workflows/lunaria.yml @@ -28,15 +28,10 @@ jobs: # Makes the action clone the entire git history fetch-depth: 0 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install + cache: true - name: Generate Lunaria Overview uses: lunariajs/action@4911ad0736d1e3b20af4cb70f5079aea2327ed8e # v1-prerelease diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index fc244db18e..bb8d7cc779 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -20,9 +20,10 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* + run-install: false - name: ๐Ÿ” Check for unreleased commits id: check diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 331540e6d9..5e318171f9 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -23,9 +23,10 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* + run-install: false - name: ๐Ÿ”ข Determine next version id: version @@ -58,13 +59,9 @@ jobs: git tag -a "$VERSION" -m "Release $VERSION" git push origin "$VERSION" - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - if: steps.check.outputs.skip == 'false' - name: ๐ŸŸง Install pnpm - - name: ๐Ÿ“ฆ Install dependencies if: steps.check.outputs.skip == 'false' - run: pnpm vp install --filter . --ignore-scripts + run: vp install --filter . --ignore-scripts - name: ๐Ÿ“ Generate release notes if: steps.check.outputs.skip == 'false' @@ -98,18 +95,14 @@ jobs: with: ref: release - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: voidzero-dev/setup-vp@a03302daa50cf2f017c8c7345730c2740df207d6 # v1 with: node-version: lts/* registry-url: https://registry.npmjs.org - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # 5e1c8eafbd745f64b1ef30a7d7ed7965034c486c - name: ๐ŸŸง Install pnpm - with: - cache: false + run-install: false - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install --filter npmx-connector... + run: vp install --filter npmx-connector... - name: ๐Ÿ”ข Set connector version env: @@ -122,7 +115,7 @@ jobs: echo "Publishing npmx-connector@${PKG_VERSION}" - name: ๐Ÿ—๏ธ Build connector - run: pnpm --filter npmx-connector build + run: vp run --filter npmx-connector build - name: ๐Ÿ“ค Publish to npm with provenance # Uses OIDC trusted publishing โ€” no NPM_TOKEN needed. diff --git a/.storybook/decorators.ts b/.storybook/decorators.ts new file mode 100644 index 0000000000..270cdec3a5 --- /dev/null +++ b/.storybook/decorators.ts @@ -0,0 +1,20 @@ +import AppFooter from '~/components/AppFooter.vue' +import AppHeader from '~/components/AppHeader.vue' + +/** + * Wraps a page story with the standard site chrome: AppHeader, main content + * area, and AppFooter. Use this for any full-page story so the layout stays + * consistent and changes only need to be made in one place. + */ +export const pageDecorator = () => ({ + components: { AppHeader, AppFooter }, + template: ` +
+ +
+ +
+ +
+ `, +}) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be2816ca6c..1628fdee01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,7 +119,7 @@ pnpm npmx-connector # Start the real connector (requires npm login) pnpm mock-connector # Start the mock connector (no npm login needed) # Code Quality -pnpm lint # Run linter (oxlint + oxfmt) +pnpm vp run lint # Run linter (oxlint + oxfmt) pnpm lint:fix # Auto-fix lint issues pnpm test:types # TypeScript type checking @@ -460,13 +460,13 @@ npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization. The following scripts help manage translation files. `en.json` is the reference locale. -| Command | Description | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `pnpm i18n:check [locale]` | Compares `en.json` with other locale files. Shows missing and extra keys. Optionally filter output by locale (e.g. `pnpm i18n:check ja-JP`). | -| `pnpm i18n:check:fix [locale]` | Same as check, but adds missing keys to other locales with English placeholders. | -| `pnpm i18n:report` | Audits translation keys against code usage in `.vue` and `.ts` files. Reports missing keys (used in code but not in locale), unused keys (in locale but not in code), and dynamic keys. | -| `pnpm i18n:report:fix` | Removes unused keys from `en.json` and all other locale files. | -| `pnpm i18n:schema` | Generates a JSON Schema from `en.json` at `i18n/schema.json`. Locale files reference this schema for IDE validation and autocompletion. | +| Command | Description | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `pnpm i18n:check:fix [locale]` | Compares `en.json` with other locale files and adds missing keys with English placeholders. Optionally filter output by locale (e.g. `pnpm i18n:check:fix ja-JP`). | +| `pnpm i18n:report:fix` | Removes unused keys from `en.json` and all other locale files. | +| `pnpm vp run i18n:check [locale]` | Same as check:fix, but only show missing and extra keys. | +| `pnpm vp run i18n:report` | Audits translation keys against code usage in `.vue` and `.ts` files. Reports missing keys (used in code but not in locale), unused keys (in locale but not in code), and dynamic keys. | +| `pnpm vp run i18n:schema` | Generates a JSON Schema from `en.json` at `i18n/schema.json`. Locale files reference this schema for IDE validation and autocompletion. | ### Adding a new locale @@ -502,7 +502,7 @@ Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essential We track the current progress of translations with [Lunaria](https://lunaria.dev/) on this site: https://i18n.npmx.dev/ If you see any outdated translations in your language, feel free to update the keys to match the English version. -Use `pnpm i18n:check` and `pnpm i18n:check:fix` to verify and fix your locale (see [i18n commands](#i18n-commands) above for details). +Use `pnpm i18n:check:fix` to fix your locale (see [i18n commands](#i18n-commands) above for details). #### Country variants (advanced) @@ -590,7 +590,7 @@ See how `es`, `es-ES`, and `es-419` are configured in [config/i18n.ts](./config/ - Use `common.*` for shared strings (loading, retry, close, etc.) - Use component-specific prefixes: `package.card.*`, `settings.*`, `nav.*` - Do not use dashes (`-`) in translation keys; always use underscore (`_`): e.g., `privacy_policy` instead of `privacy-policy` -- **Always use static string literals as translation keys.** Our i18n scripts (`pnpm i18n:report`) rely on static analysis to detect unused and missing keys. Dynamic keys cannot be analyzed and will be flagged as errors. +- **Always use static string literals as translation keys.** Our i18n scripts (`pnpm i18n:report:fix`) rely on static analysis to detect unused and missing keys. Dynamic keys cannot be analyzed and will be flagged as errors. **Bad:** diff --git a/app/components/Chart/PatternSlot.vue b/app/components/Chart/PatternSlot.vue deleted file mode 100644 index ed0ae0a1fe..0000000000 --- a/app/components/Chart/PatternSlot.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - diff --git a/app/components/Chart/SplitSparkline.vue b/app/components/Chart/SplitSparkline.vue index f3c5537e8e..583f36a909 100644 --- a/app/components/Chart/SplitSparkline.vue +++ b/app/components/Chart/SplitSparkline.vue @@ -1,5 +1,6 @@ + + diff --git a/app/components/Code/MobileTreeDrawer.vue b/app/components/Code/MobileTreeDrawer.vue index ecdf1f4f4e..dd524bf635 100644 --- a/app/components/Code/MobileTreeDrawer.vue +++ b/app/components/Code/MobileTreeDrawer.vue @@ -22,18 +22,17 @@ watch( const isLocked = useScrollLock(document) // Prevent body scroll when drawer is open watch(isOpen, open => (isLocked.value = open)) + +function toggle() { + isOpen.value = !isOpen.value +} + +defineExpose({ + toggle, +}) diff --git a/app/components/Code/Viewer.vue b/app/components/Code/Viewer.vue index 90bcf0c221..5edf96eb84 100644 --- a/app/components/Code/Viewer.vue +++ b/app/components/Code/Viewer.vue @@ -86,7 +86,7 @@ watch( diff --git a/app/components/LandingLogo.vue b/app/components/LandingLogo.vue new file mode 100644 index 0000000000..6fa385c2c6 --- /dev/null +++ b/app/components/LandingLogo.vue @@ -0,0 +1,76 @@ + + + diff --git a/app/composables/useSettings.ts b/app/composables/useSettings.ts index a79c746c20..533c03042b 100644 --- a/app/composables/useSettings.ts +++ b/app/composables/useSettings.ts @@ -38,6 +38,7 @@ export interface AppSettings { /** Automatically open the web auth page in the browser */ autoOpenURL: boolean } + codeContainerFull: boolean sidebar: { collapsed: string[] } @@ -63,6 +64,7 @@ const DEFAULT_SETTINGS: AppSettings = { connector: { autoOpenURL: false, }, + codeContainerFull: false, sidebar: { collapsed: [], }, @@ -236,3 +238,18 @@ export function useBackgroundTheme() { setBackgroundTheme, } } + +export function useCodeContainer() { + const { settings } = useSettings() + + const codeContainerFull = computed(() => settings.value.codeContainerFull) + + function toggleCodeContainer() { + settings.value.codeContainerFull = !settings.value.codeContainerFull + } + + return { + codeContainerFull, + toggleCodeContainer, + } +} diff --git a/app/pages/about.stories.ts b/app/pages/about.stories.ts index b895ba8e70..7d97312345 100644 --- a/app/pages/about.stories.ts +++ b/app/pages/about.stories.ts @@ -1,27 +1,13 @@ import About from './about.vue' import type { Meta, StoryObj } from '@storybook-vue/nuxt' -import AppHeader from '~/components/AppHeader.vue' -import AppFooter from '~/components/AppFooter.vue' +import { pageDecorator } from '../../.storybook/decorators' const meta = { component: About, parameters: { layout: 'fullscreen', }, - decorators: [ - () => ({ - components: { AppHeader, AppFooter }, - template: ` -
- -
- -
- -
- `, - }), - ], + decorators: [pageDecorator], } satisfies Meta export default meta diff --git a/app/pages/accessibility.stories.ts b/app/pages/accessibility.stories.ts index bfd0968592..9a6943ace5 100644 --- a/app/pages/accessibility.stories.ts +++ b/app/pages/accessibility.stories.ts @@ -1,27 +1,13 @@ import Accessibility from './accessibility.vue' import type { Meta, StoryObj } from '@storybook-vue/nuxt' -import AppHeader from '~/components/AppHeader.vue' -import AppFooter from '~/components/AppFooter.vue' +import { pageDecorator } from '../../.storybook/decorators' const meta = { component: Accessibility, parameters: { layout: 'fullscreen', }, - decorators: [ - () => ({ - components: { AppHeader, AppFooter }, - template: ` -
- -
- -
- -
- `, - }), - ], + decorators: [pageDecorator], } satisfies Meta export default meta diff --git a/app/pages/blog/first-post.md b/app/pages/blog/first-post.md deleted file mode 100644 index c63264d4ae..0000000000 --- a/app/pages/blog/first-post.md +++ /dev/null @@ -1,318 +0,0 @@ ---- -authors: - - name: Daniel Roe - blueskyHandle: danielroe.dev - - name: Salma Alam-Naylor - blueskyHandle: whitep4nth3r.com -title: 'Hello World' -tags: ['OpenSource', 'Nuxt'] -excerpt: 'My first post' -date: '2026-01-28T15:30:00Z' -slug: 'first-post' -description: 'My first post on the blog' -draft: true ---- - -# Markdown: Syntax - -- [Overview](#overview) - - [Philosophy](#philosophy) -- [Block Elements](#block-elements) - - [Paragraphs and Line Breaks](#paragraphs-and-line-breaks) - - [Headers](#headers) - - [Blockquotes](#blockquotes) - - [Lists](#lists) - - [Code Blocks](#code-blocks) -- [Span Elements](#span-elements) - - [Links](#links) - - [Emphasis](#emphasis) - - [Code](#code) - -**Note:** This document is itself written using Markdown; you -can [see the source for it by adding '.text' to the URL](/projects/markdown/syntax.text). - ---- - -## Overview - -### Philosophy - -Markdown is intended to be as easy-to-read and easy-to-write as is feasible. - -Readability, however, is emphasized above all else. A Markdown-formatted -document should be publishable as-is, as plain text, without looking -like it's been marked up with tags or formatting instructions. While -Markdown's syntax has been influenced by several existing text-to-HTML -filters -- including [Setext](http://docutils.sourceforge.net/mirror/setext.html), [atx](http://www.aaronsw.com/2002/atx/), [Textile](http://textism.com/tools/textile/), [reStructuredText](http://docutils.sourceforge.net/rst.html), -[Grutatext](http://www.triptico.com/software/grutatxt.html), and [EtText](http://ettext.taint.org/doc/) -- the single biggest source of -inspiration for Markdown's syntax is the format of plain text email. - -## Block Elements - -### Paragraphs and Line Breaks - -A paragraph is simply one or more consecutive lines of text, separated -by one or more blank lines. (A blank line is any line that looks like a -blank line -- a line containing nothing but spaces or tabs is considered -blank.) Normal paragraphs should not be indented with spaces or tabs. - -The implication of the "one or more consecutive lines of text" rule is -that Markdown supports "hard-wrapped" text paragraphs. This differs -significantly from most other text-to-HTML formatters (including Movable -Type's "Convert Line Breaks" option) which translate every line break -character in a paragraph into a `
` tag. - -When you _do_ want to insert a `
` break tag using Markdown, you -end a line with two or more spaces, then type return. - -### Headers - -Markdown supports two styles of headers, [Setext] [1] and [atx] [2]. - -Optionally, you may "close" atx-style headers. This is purely -cosmetic -- you can use this if you think it looks better. The -closing hashes don't even need to match the number of hashes -used to open the header. (The number of opening hashes -determines the header level.) - -### Blockquotes - -Markdown uses email-style `>` characters for blockquoting. If you're -familiar with quoting passages of text in an email message, then you -know how to create a blockquote in Markdown. It looks best if you hard -wrap the text and put a `>` before every line: - -> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, -> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. -> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. -> -> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse -> id sem consectetuer libero luctus adipiscing. - -Markdown allows you to be lazy and only put the `>` before the first -line of a hard-wrapped paragraph: - -> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, -> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. -> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. - -> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse -> id sem consectetuer libero luctus adipiscing. - -Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by -adding additional levels of `>`: - -> This is the first level of quoting. -> -> > This is nested blockquote. -> -> Back to the first level. - -Blockquotes can contain other Markdown elements, including headers, lists, -and code blocks: - -> ## This is a header. -> -> 1. This is the first list item. -> 2. This is the second list item. -> -> Here's some example code: -> -> return shell_exec("echo $input | $markdown_script"); - -Any decent text editor should make email-style quoting easy. For -example, with BBEdit, you can make a selection and choose Increase -Quote Level from the Text menu. - -### Lists - -Markdown supports ordered (numbered) and unordered (bulleted) lists. - -Unordered lists use asterisks, pluses, and hyphens -- interchangably --- as list markers: - -- Red -- Green -- Blue - -is equivalent to: - -- Red -- Green -- Blue - -and: - -- Red -- Green -- Blue - -Ordered lists use numbers followed by periods: - -1. Bird -2. McHale -3. Parish - -It's important to note that the actual numbers you use to mark the -list have no effect on the HTML output Markdown produces. The HTML -Markdown produces from the above list is: - -If you instead wrote the list in Markdown like this: - -1. Bird -1. McHale -1. Parish - -or even: - -3. Bird -1. McHale -1. Parish - -you'd get the exact same HTML output. The point is, if you want to, -you can use ordinal numbers in your ordered Markdown lists, so that -the numbers in your source match the numbers in your published HTML. -But if you want to be lazy, you don't have to. - -To make lists look nice, you can wrap items with hanging indents: - -- Lorem ipsum dolor sit amet, consectetuer adipiscing elit. - Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, - viverra nec, fringilla in, laoreet vitae, risus. -- Donec sit amet nisl. Aliquam semper ipsum sit amet velit. - Suspendisse id sem consectetuer libero luctus adipiscing. - -But if you want to be lazy, you don't have to: - -- Lorem ipsum dolor sit amet, consectetuer adipiscing elit. - Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, - viverra nec, fringilla in, laoreet vitae, risus. -- Donec sit amet nisl. Aliquam semper ipsum sit amet velit. - Suspendisse id sem consectetuer libero luctus adipiscing. - -List items may consist of multiple paragraphs. Each subsequent -paragraph in a list item must be indented by either 4 spaces -or one tab: - -1. This is a list item with two paragraphs. Lorem ipsum dolor - sit amet, consectetuer adipiscing elit. Aliquam hendrerit - mi posuere lectus. - - Vestibulum enim wisi, viverra nec, fringilla in, laoreet - vitae, risus. Donec sit amet nisl. Aliquam semper ipsum - sit amet velit. - -2. Suspendisse id sem consectetuer libero luctus adipiscing. - -It looks nice if you indent every line of the subsequent -paragraphs, but here again, Markdown will allow you to be -lazy: - -- This is a list item with two paragraphs. - - This is the second paragraph in the list item. You're - - only required to indent the first line. Lorem ipsum dolor - sit amet, consectetuer adipiscing elit. - -- Another item in the same list. - -To put a blockquote within a list item, the blockquote's `>` -delimiters need to be indented: - -- A list item with a blockquote: - - > This is a blockquote - > inside a list item. - -To put a code block within a list item, the code block needs -to be indented _twice_ -- 8 spaces or two tabs: - -- A list item with a code block: - - - -### Code Blocks - -Pre-formatted code blocks are used for writing about programming or -markup source code. Rather than forming normal paragraphs, the lines -of a code block are interpreted literally. Markdown wraps a code block -in both `
` and `` tags.
-
-To produce a code block in Markdown, simply indent every line of the
-block by at least 4 spaces or 1 tab.
-
-This is a normal paragraph:
-
-    This is a code block.
-
-Here is an example of AppleScript:
-
-    tell application "Foo"
-        beep
-    end tell
-
-A code block continues until it reaches a line that is not indented
-(or the end of the article).
-
-Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
-are automatically converted into HTML entities. This makes it very
-easy to include example HTML source code using Markdown -- just paste
-it and indent it, and Markdown will handle the hassle of encoding the
-ampersands and angle brackets. For example, this:
-
-    
-
-Regular Markdown syntax is not processed within code blocks. E.g.,
-asterisks are just literal asterisks within a code block. This means
-it's also easy to use Markdown to write about Markdown's own syntax.
-
-```
-tell application "Foo"
-    beep
-end tell
-```
-
-## Span Elements
-
-### Links
-
-Markdown supports two style of links: _inline_ and _reference_.
-
-In both styles, the link text is delimited by [square brackets].
-
-To create an inline link, use a set of regular parentheses immediately
-after the link text's closing square bracket. Inside the parentheses,
-put the URL where you want the link to point, along with an _optional_
-title for the link, surrounded in quotes. For example:
-
-This is [an example](http://example.com/) inline link.
-
-[This link](http://example.net/) has no title attribute.
-
-### Emphasis
-
-Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
-emphasis. Text wrapped with one `*` or `_` will be wrapped with an
-HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML
-`` tag. E.g., this input:
-
-_single asterisks_
-
-_single underscores_
-
-**double asterisks**
-
-**double underscores**
-
-### Code
-
-To indicate a span of code, wrap it with backtick quotes (`` ` ``).
-Unlike a pre-formatted code block, a code span indicates code within a
-normal paragraph. For example:
-
-Use the `printf()` function.
-
-![bagel image](https://plus.unsplash.com/premium_photo-1769376812336-847642b315a2?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D)
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 7cc3981569..48c5575b00 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -8,8 +8,6 @@ async function search() {
   startSearch()
 }
 
-const { env } = useAppConfig().buildInfo
-
 useSeoMeta({
   title: () => $t('seo.home.title'),
   ogTitle: () => $t('seo.home.title'),
@@ -32,25 +30,7 @@ defineOgImageComponent('Default', {
       
-

- - -

- -

- {{ $t('tagline') }} -

+ +// Maximum file size we'll try to load (500KB) - must match server +const MAX_FILE_SIZE = 500 * 1024 + definePageMeta({ name: 'code', path: '/package-code/:org?/:packageName/v/:version/:filePath(.*)?', @@ -12,6 +15,10 @@ definePageMeta({ const route = useRoute('code') +const mobileFileTreeRef = useTemplateRef('mobileFileTreeRef') + +const { codeContainerFull } = useCodeContainer() + // Parse package name, version, and file path from URL // Patterns: // /code/nuxt/v/4.2.0 โ†’ packageName: "nuxt", version: "4.2.0", filePath: null (show tree) @@ -95,25 +102,26 @@ const currentNode = computed(() => { return lastFound }) -const isViewingFile = computed(() => currentNode.value?.type === 'file') - -// Maximum file size we'll try to load (500KB) - must match server -const MAX_FILE_SIZE = 500 * 1024 +const isViewingFile = computed(() => currentNode.value?.type === 'file') // Estimate binary file based on mime type -const isBinaryFile = computed(() => { +const isBinaryFile = computed(() => { + if (!isViewingFile.value) return false + const contentType = fileContent.value?.contentType if (!contentType) return false return isBinaryContentType(contentType) }) -const isFileTooLarge = computed(() => { +const isFileTooLarge = computed(() => { + if (!isViewingFile.value) return false + const size = currentNode.value?.size return size !== undefined && size > MAX_FILE_SIZE }) // Fetch file content when a file is selected (and not too large) -const fileContentUrl = computed(() => { +const fileContentUrl = computed(() => { // Don't fetch if no file path, file tree not loaded, file is too large, or it's a directory if (!filePath.value || !fileTree.value || isFileTooLarge.value || !isViewingFile.value) { return null @@ -127,6 +135,19 @@ const { execute: fetchFileContent, } = useFetch(() => fileContentUrl.value!, { immediate: false }) +// Loading skeleton state +const isLoading = computed(() => { + if (!isViewingFile.value) { + return treeStatus.value !== 'success' && treeStatus.value !== 'error' + } + + return !fileStatus.value || fileStatus.value === 'pending' || fileStatus.value === 'idle' +}) + +function toggleMobileTreeDrawer(): void { + mobileFileTreeRef.value?.toggle() +} + watch( fileContentUrl, url => { @@ -185,24 +206,6 @@ watch(fileContent, () => { nextTick(scrollToLine) }) -// Build breadcrumb path segments -const breadcrumbs = computed(() => { - const parts = filePath.value?.split('/').filter(Boolean) ?? [] - const result: { name: string; path: string }[] = [] - - for (let i = 0; i < parts.length; i++) { - const part = parts[i] - if (part) { - result.push({ - name: part, - path: parts.slice(0, i + 1).join('/'), - }) - } - } - - return result -}) - // Navigation helper - build URL for a path function getCurrentCodeUrlWithPath(path?: string): string { return getCodeUrl({ @@ -236,36 +239,10 @@ function handleLineClick(lineNum: number, event: MouseEvent) { currentHash.value = newHash } -// Copy link to current line(s) -const { copied: permalinkCopied, copy: copyPermalink } = useClipboard({ copiedDuring: 2000 }) -function copyPermalinkUrl() { - const url = new URL(window.location.href) - copyPermalink(url.toString()) -} - -const { copied: fileContentCopied, copy: copyFileContent } = useClipboard({ - source: () => fileContent.value?.content || '', - copiedDuring: 2000, -}) - // Canonical URL for this code page const canonicalUrl = computed(() => `https://npmx.dev${getCodeUrl(route.params)}`) -// Toggle markdown view mode -const markdownViewModes = [ - { - key: 'preview', - label: $t('code.markdown_view_mode.preview'), - icon: 'i-lucide:eye', - }, - { - key: 'code', - label: $t('code.markdown_view_mode.code'), - icon: 'i-lucide:code', - }, -] as const - -const markdownViewMode = shallowRef<(typeof markdownViewModes)[number]['key']>('preview') +const markdownViewMode = shallowRef<'preview' | 'code'>('preview') const bytesFormatter = useBytesFormatter() @@ -310,6 +287,15 @@ defineOgImageComponent('Default', { description: () => pkg.value?.license ?? '', primaryColor: '#60a5fa', }) + +onPrehydrate(el => { + const settingsSaved = JSON.parse(localStorage.getItem('npmx-settings') || '{}') + const container = el.querySelector('#code-page-container') + + if (settingsSaved?.codeContainerFull === true && container) { + container.classList.add('container-full') + } +})