Skip to content
CONSTRUCTING

Page properties Alpha 2.21.1 ​ ​

Tags
Integrations/VitePress-Plugin
Integrations/VitePress-Plugin/Page-Properties
Markdown/frontmatter
Progress
Created at
over 2 years ago
Updated at
over 2 years ago
Word count
1100 words
Reading time
7 minutes

Page properties was born from the heavily inspiration of Notion's page properties feature: render and edit frontmatter like a clean set of Notion-style properties.

Why ​ ​

Frontmatter often holds the most important metadata—status, created / updated date, owners, reading time—and yet it is usually hidden. Page properties surfaces that metadata right under the page title with a tidy layout and light editing experience during development.

Features ​ ​

Display frontmatter as properties with icons and grouping
Built-in property types: tags, datetime, progress, link, plain text
Dynamic properties for words count and reading time, calculated during dev/build
Per-locale schemas to match VitePress i18n setups
Optional inline editor in dev mode so you can tweak frontmatter quickly
Automatic insertion into markdown via the markdown section plugin

Installation ​ ​

Install @nolebase/vitepress-plugin-page-properties to your project dependencies by running the following command:

shell
shell
ni @nolebase/vitepress-plugin-page-properties -D
shell
shell
pnpm add @nolebase/vitepress-plugin-page-properties -D
shell
shell
npm install @nolebase/vitepress-plugin-page-properties -D
shell
shell
yarn add @nolebase/vitepress-plugin-page-properties -D

Getting started ​ ​

This plugin has two parts:

  • Vite plugins to calculate dynamic data and inject the <NolebasePageProperties /> section into every page.
  • Vue plugin to register UI components and styles with your VitePress theme.

Configure the Vite plugins ​ ​

In your VitePress site config (or vite.config.ts), register both PageProperties and PagePropertiesMarkdownSection. The example below matches how this docs site wires it in docs/vite.config.ts:

If you've never seen a colored diff before

This is a markup rule for displaying diff in the user interface (UI).

Red parts usually represents the lines you are going to remove, commonly appeared with a Minus sign -, or you could simply understand it as: this line will be removed.

Green parts usually represents the lines you are going to add, commonly appeared with a Plus sign +, or you could simply understand it as: this line will be added.

To learn more about diff, you can check out this answer about the history of diffutils and Git's documentation

A TypeScript User?

You need to configure at least the following options:

jsonc
jsonc
{
  "compilerOptions": {
    "module": "ESNext", 
    "moduleResolution": "Bundler", 
  },
  "include": [
    "**/.vitepress/**/*.ts",
    "**/.vitepress/**/*.mts",
    "**/.vitepress/**/*.vue"
  ],
  "exclude": [
    "node_modules"
  ]
}

And the options

  • module option specifies the JavaScript/TypeScript module format, and Nolebase Integrations uses the ESNext-compatible module format by default.
  • moduleResolution option specifies the module resolution policy, since all Nolebase Integrations plugins follow the latest ECMAScript specifications and export declarations, if you encounter a Cannot find module ... or its corresponding type declarations error you may need to set moduleResolution to Bundler.

If you want more configurations, you can refer to the following example:

jsonc
jsonc
{
  "compilerOptions": {
    "jsx": "preserve",
    "lib": [
      "DOM",
      "ESNext"
    ],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "resolveJsonModule": true,
    "strict": true,
    "strictNullChecks": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noEmit": true,
    "removeComments": false,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true
  },
  "include": [
    "**/.vitepress/**/*.ts",
    "**/.vitepress/**/*.mts",
    "**/.vitepress/**/*.vue"
  ],
  "exclude": [
    "node_modules"
  ]
}
ts
ts
import { 
join
} from 'node:path'
import {
defineConfig
} from 'vitepress'
import {
PageProperties
,
PagePropertiesMarkdownSection
,
} from '@nolebase/vitepress-plugin-page-properties/vite' export default
defineConfig
({
vite
: {
plugins
: [
PageProperties
(),
PagePropertiesMarkdownSection
({
excludes
: [
join
('pages', 'en', 'index.md'),
join
('pages', 'zh-CN', 'index.md'),
], }), ], }, })
  • PageProperties() calculates dynamic data (words count, reading time) from each markdown file.
  • PagePropertiesMarkdownSection() injects the <NolebasePageProperties /> block right after the first heading. Use excludes or exclude() when a page should opt out, or set pageProperties: false (or nolebase.pageProperties: false) in frontmatter for page-level opt-out.

Register the UI plugin in your theme ​ ​

Install the Vue plugin inside docs/.vitepress/theme/index.ts so the components and styles are available:

If you've never seen a colored diff before

This is a markup rule for displaying diff in the user interface (UI).

Red parts usually represents the lines you are going to remove, commonly appeared with a Minus sign -, or you could simply understand it as: this line will be removed.

Green parts usually represents the lines you are going to add, commonly appeared with a Plus sign +, or you could simply understand it as: this line will be added.

To learn more about diff, you can check out this answer about the history of diffutils and Git's documentation

A TypeScript User?

You need to configure at least the following options:

jsonc
jsonc
{
  "compilerOptions": {
    "module": "ESNext", 
    "moduleResolution": "Bundler", 
  },
  "include": [
    "**/.vitepress/**/*.ts",
    "**/.vitepress/**/*.mts",
    "**/.vitepress/**/*.vue"
  ],
  "exclude": [
    "node_modules"
  ]
}

And the options

  • module option specifies the JavaScript/TypeScript module format, and Nolebase Integrations uses the ESNext-compatible module format by default.
  • moduleResolution option specifies the module resolution policy, since all Nolebase Integrations plugins follow the latest ECMAScript specifications and export declarations, if you encounter a Cannot find module ... or its corresponding type declarations error you may need to set moduleResolution to Bundler.

If you want more configurations, you can refer to the following example:

jsonc
jsonc
{
  "compilerOptions": {
    "jsx": "preserve",
    "lib": [
      "DOM",
      "ESNext"
    ],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "resolveJsonModule": true,
    "strict": true,
    "strictNullChecks": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noEmit": true,
    "removeComments": false,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true
  },
  "include": [
    "**/.vitepress/**/*.ts",
    "**/.vitepress/**/*.mts",
    "**/.vitepress/**/*.vue"
  ],
  "exclude": [
    "node_modules"
  ]
}
ts
ts
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import type { Theme as ThemeConfig } from 'vitepress'
import {
  NolebasePagePropertiesPlugin,
} from '@nolebase/vitepress-plugin-page-properties/client'

import '@nolebase/vitepress-plugin-page-properties/client/style.css'

export const Theme: ThemeConfig = {
  extends: DefaultTheme,
  Layout: () => {
    // other layout options...
  },
  enhanceApp({ app }) {
    app.use(NolebasePagePropertiesPlugin({
      // Demo schema — adapt the keys to your own frontmatter
      properties: {
        'en': [
          { key: 'tags', type: 'tags', title: 'Tags' },
          { key: 'progress', type: 'progress', title: 'Progress' },
          { key: 'createdAt', type: 'datetime', title: 'Created at', formatAsFrom: true, dateFnsLocaleName: 'enUS' },
          { key: 'updatedAt', type: 'datetime', title: 'Updated at', formatAsFrom: true, dateFnsLocaleName: 'enUS' },
          { key: 'wordsCount', type: 'dynamic', title: 'Word count', options: { type: 'wordsCount' } },
          { key: 'readingTime', type: 'dynamic', title: 'Reading time', options: { type: 'readingTime', dateFnsLocaleName: 'enUS' } },
        ],
        'zh-CN': [
          { key: 'tags', type: 'tags', title: '标签' },
          { key: 'progress', type: 'progress', title: '进度' },
          { key: 'createdAt', type: 'datetime', title: '创建时间', formatAsFrom: true, dateFnsLocaleName: 'zhCN' },
          { key: 'updatedAt', type: 'datetime', title: '更新时间', formatAsFrom: true, dateFnsLocaleName: 'zhCN' },
          { key: 'wordsCount', type: 'dynamic', title: '字数', options: { type: 'wordsCount' } },
          { key: 'readingTime', type: 'dynamic', title: '阅读时间', options: { type: 'readingTime', dateFnsLocaleName: 'zhCN' } },
        ],
      },
    }))
  },
}

Describe your properties schema ​ ​

Define which frontmatter fields should appear, grouped by locale so that labels and date locales line up with your VitePress i18n. Property types supported:

  • tags: array of strings rendered as tags, omitEmpty?: boolean
  • plain: plain text, omitEmpty?: boolean
  • link: URL value, omitEmpty?: boolean
  • progress: number rendered as a progress bar, omitEmpty?: boolean
  • datetime: ISO/Date value with formatAsFrom?: boolean, dateFnsLocaleName?: LocaleName, format?: string, omitEmpty?: boolean
  • dynamic: calculated values. Options: { type: 'wordsCount' } or { type: 'readingTime', dateFnsLocaleName?: LocaleName }

The example below mirrors this site’s preset in packages/unconfig-vitepress/src/plugins/nolebase/index.ts:

ts
ts
import type { 
Property
} from '@nolebase/vitepress-plugin-page-properties/client'
const
pagePropertiesOptions
= {
properties
: {
'en': [ {
key
: 'tags',
type
: 'tags',
title
: 'Tags' },
{
key
: 'progress',
type
: 'progress',
title
: 'Progress' },
{
key
: 'createdAt',
type
: 'datetime',
title
: 'Created at',
formatAsFrom
: true,
dateFnsLocaleName
: 'enUS' },
{
key
: 'updatedAt',
type
: 'datetime',
title
: 'Updated at',
formatAsFrom
: true,
dateFnsLocaleName
: 'enUS' },
{
key
: 'wordsCount',
type
: 'dynamic',
title
: 'Word count',
options
: {
type
: 'wordsCount' } },
{
key
: 'readingTime',
type
: 'dynamic',
title
: 'Reading time',
options
: {
type
: 'readingTime',
dateFnsLocaleName
: 'enUS' } },
] satisfies
Property
<keyof any>[],
'zh-CN': [ {
key
: 'tags',
type
: 'tags',
title
: '标签' },
{
key
: 'progress',
type
: 'progress',
title
: '进度' },
{
key
: 'createdAt',
type
: 'datetime',
title
: '创建时间',
formatAsFrom
: true,
dateFnsLocaleName
: 'zhCN' },
{
key
: 'updatedAt',
type
: 'datetime',
title
: '更新时间',
formatAsFrom
: true,
dateFnsLocaleName
: 'zhCN' },
{
key
: 'wordsCount',
type
: 'dynamic',
title
: '字数',
options
: {
type
: 'wordsCount' } },
{
key
: 'readingTime',
type
: 'dynamic',
title
: '阅读时间',
options
: {
type
: 'readingTime',
dateFnsLocaleName
: 'zhCN' } },
], }, }
  • tags, progress, datetime, link, and plain pull values directly from frontmatter.
  • dynamic properties (wordsCount, readingTime) read the calculated data provided by PageProperties() during dev/build.

Use it in markdown ​ ​

Add the fields you declared in the schema to your page frontmatter. The markdown section plugin will place the property list just beneath the first # heading.

md
md
---
title: Page properties
tags:
  - Integrations/VitePress-Plugin
  - Markdown/frontmatter
progress: 85
createdAt: 2024-01-23
updatedAt: 2024-01-23
---

# Page properties

Your page content starts here...

During development the editor variant (<NolebasePagePropertiesEditor />) appears automatically so you can tweak values inline; in production only the read-only view renders.

Contributors

Changelog