Page properties Alpha 2.21.1
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
Installation
Install @nolebase/vitepress-plugin-page-properties to your project dependencies by running the following command:
ni @nolebase/vitepress-plugin-page-properties -Dpnpm add @nolebase/vitepress-plugin-page-properties -Dnpm install @nolebase/vitepress-plugin-page-properties -Dyarn add @nolebase/vitepress-plugin-page-properties -DGetting 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:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler",
},
"include": [
"**/.vitepress/**/*.ts",
"**/.vitepress/**/*.mts",
"**/.vitepress/**/*.vue"
],
"exclude": [
"node_modules"
]
}And the options
moduleoption specifies the JavaScript/TypeScript module format, and Nolebase Integrations uses theESNext-compatible module format by default.moduleResolutionoption specifies the module resolution policy, since all Nolebase Integrations plugins follow the latest ECMAScript specifications and export declarations, if you encounter aCannot find module ... or its corresponding type declarationserror you may need to setmoduleResolutiontoBundler.
If you want more configurations, you can refer to the following example:
{
"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"
]
}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. Useexcludesorexclude()when a page should opt out, or setpageProperties: false(ornolebase.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:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler",
},
"include": [
"**/.vitepress/**/*.ts",
"**/.vitepress/**/*.mts",
"**/.vitepress/**/*.vue"
],
"exclude": [
"node_modules"
]
}And the options
moduleoption specifies the JavaScript/TypeScript module format, and Nolebase Integrations uses theESNext-compatible module format by default.moduleResolutionoption specifies the module resolution policy, since all Nolebase Integrations plugins follow the latest ECMAScript specifications and export declarations, if you encounter aCannot find module ... or its corresponding type declarationserror you may need to setmoduleResolutiontoBundler.
If you want more configurations, you can refer to the following example:
{
"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"
]
}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?: booleanplain: plain text,omitEmpty?: booleanlink: URL value,omitEmpty?: booleanprogress: number rendered as a progress bar,omitEmpty?: booleandatetime: ISO/Date value withformatAsFrom?: boolean,dateFnsLocaleName?: LocaleName,format?: string,omitEmpty?: booleandynamic: 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:
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, andplainpull values directly from frontmatter.dynamicproperties (wordsCount,readingTime) read the calculated data provided byPageProperties()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.
---
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.


Neko
Rizumu