feat(plugin-notes-data): provide notes-data

This commit is contained in:
pengzhanbo 2023-02-10 16:39:34 +08:00
parent 8120ad3e65
commit 86f51ff271
20 changed files with 302 additions and 31 deletions

View File

@ -1,6 +1,8 @@
import { definePlumeNotesConfig } from '@vuepress-plume/vuepress-theme-plume'
export default definePlumeNotesConfig({
dir: 'notes',
link: '/note',
notes: [
{
text: '',
@ -10,22 +12,22 @@ export default definePlumeNotesConfig({
'',
{
text: '指南',
children: ['快速开始', '编写文章'],
items: ['快速开始', '编写文章'],
},
{
text: '配置',
children: [
items: [
{
text: '主题配置',
link: '主题配置',
children: ['主题插件配置', 'notes配置'],
items: ['主题插件配置', 'notes配置'],
},
'页面配置',
],
},
{
text: '功能',
children: ['基础功能', 'markdown增强'],
items: ['基础功能', 'markdown增强'],
},
],
},
@ -39,7 +41,7 @@ export default definePlumeNotesConfig({
dir: 'netlify-functions',
text: 'plugin-netlify-functions',
link: 'netlify-functions',
children: ['', '介绍', '使用', '功能', 'API', 'functions开发指南'],
items: ['', '介绍', '使用', '功能', 'API', 'functions开发指南'],
},
],
},

View File

@ -5,13 +5,13 @@ import type { BlogPostData } from '../../shared/index.js'
declare const __VUE_HMR_RUNTIME__: Record<string, any>
export type ThemeDataRef<T extends BlogPostData = BlogPostData> = Ref<T>
export type BlogDataRef<T extends BlogPostData = BlogPostData> = Ref<T>
export const blogPostData: ThemeDataRef = ref(blogPostDataRaw)
export const blogPostData: BlogDataRef = ref(blogPostDataRaw)
export const useBlogPostData = <
T extends BlogPostData = BlogPostData
>(): ThemeDataRef<T> => blogPostData as ThemeDataRef<T>
>(): BlogDataRef<T> => blogPostData as BlogDataRef<T>
if (import.meta.webpackHot || import.meta.hot) {
__VUE_HMR_RUNTIME__.updateBlogData = (data: BlogPostData) => {

View File

@ -30,10 +30,14 @@
"ts:watch": "tsc -b tsconfig.build.json --watch"
},
"dependencies": {
"@vue/devtools-api": "^6.4.5",
"@vuepress/client": "2.0.0-beta.60",
"@vuepress/core": "2.0.0-beta.60",
"@vuepress/shared": "2.0.0-beta.60",
"@vuepress/utils": "2.0.0-beta.60"
"@vuepress/utils": "2.0.0-beta.60",
"chokidar": "^3.5.3",
"create-filter": "^1.0.0",
"vue": "^3.2.47"
},
"publishConfig": {
"access": "public"

View File

@ -1,7 +1,37 @@
import { setupDevtoolsPlugin } from '@vue/devtools-api'
import { defineClientConfig } from '@vuepress/client'
import { useNotesData } from './composables/index.js'
declare const __VUE_PROD_DEVTOOLS__: boolean
export default defineClientConfig({
setup() {
// do something
enhance({ app }) {
const notesData = useNotesData()
// setup devtools in dev mode
if (__VUEPRESS_DEV__ || __VUE_PROD_DEVTOOLS__) {
setupDevtoolsPlugin(
{
// fix recursive reference
app: app as any,
id: 'org.vuepress-plume.plugin-notes-data',
label: 'VuePress Notes Data Plugin',
packageName: '@vuepress/plugin-notes-data',
homepage: 'https://pengzhanbo.cn',
logo: 'https://v2.vuepress.vuejs.org/images/hero.png',
componentStateTypes: ['VuePress'],
},
(api) => {
api.on.inspectComponent((payload) => {
payload.instanceData.state.push({
type: 'VuePress',
key: 'notesData',
editable: false,
value: notesData.value,
})
})
}
)
}
},
})

View File

@ -0,0 +1 @@
export * from './notesDate.js'

View File

@ -0,0 +1,20 @@
import { notesData as notesDataRaw } from '@internal/notesData'
import { ref } from 'vue'
import type { Ref } from 'vue'
import type { NotesData } from '../../shared/index.js'
declare const __VUE_HMR_RUNTIME__: Record<string, any>
export type NotesDataRef<T extends NotesData = NotesData> = Ref<T>
export const notesData: NotesDataRef = ref(notesDataRaw)
export const useNotesData = <
T extends NotesData = NotesData
>(): NotesDataRef<T> => notesData as NotesDataRef<T>
if (import.meta.webpackHot || import.meta.hot) {
__VUE_HMR_RUNTIME__.updateNotesData = (data: NotesData) => {
notesData.value = data
}
}

View File

@ -1 +1,2 @@
export * from '../shared/index.js'
export { NotesData, NotesSidebarItem } from '../shared/index.js'
export * from './composables/index.js'

View File

@ -0,0 +1,7 @@
import type { NotesData } from '../shared/index.js'
declare module '@internal/notesData' {
const notesData: NotesData
export { notesData }
}

View File

@ -1,12 +1,18 @@
import type { App, Plugin } from '@vuepress/core'
import { path } from '@vuepress/utils'
import { getDirname, path } from '@vuepress/utils'
import type { NotesDataOptions } from '../shared/index.js'
import { prepareNotesData, watchNotesData } from './prepareNotesData.js'
export const notesDataPlugin = (options: NotesDataOptions): Plugin => {
return (app: App) => {
return {
name: '@vuepress-plume/vuepress-plugin-notes-data',
clientConfigFile: path.resolve(__dirname, '../client/clientConfig.js'),
clientConfigFile: path.resolve(
getDirname(import.meta.url),
'../client/clientConfig.js'
),
onPrepared: () => prepareNotesData(app, options),
onWatched: (app, watchers) => watchNotesData(app, watchers, options),
}
}
}

View File

@ -0,0 +1,159 @@
import path from 'node:path'
import type { App } from '@vuepress/core'
import * as chokidar from 'chokidar'
import { createFilter } from 'create-filter'
import type {
NotesData,
NotesDataOptions,
NotesItem,
NotesSidebar,
NotesSidebarItem,
} from '../shared/index.js'
import { ensureArray } from './utils.js'
const HMR_CODE = `
if (import.meta.webpackHot) {
import.meta.webpackHot.accept()
if (__VUE_HMR_RUNTIME__.updateNotesData) {
__VUE_HMR_RUNTIME__.updateNotesData(notesData)
}
}
if (import.meta.hot) {
import.meta.hot.accept(({ notesData }) => {
__VUE_HMR_RUNTIME__.updateNotesData(notesData)
})
}
`
interface NotePage {
relativePath: string
title: string
link: string
}
export const prepareNotesData = async (
app: App,
{ include, exclude, notes, dir, link }: NotesDataOptions
) => {
if (!notes || notes.length === 0) return
const filter = createFilter(ensureArray(include), ensureArray(exclude), {
resolve: false,
})
const DIR_PATTERN = new RegExp(`^${path.join(dir, '/')}`)
const notesPageList: NotePage[] = app.pages
.filter(
(page) =>
page.filePathRelative &&
page.filePathRelative.startsWith(dir) &&
filter(page.filePathRelative)
)
.map((page) => {
return {
relativePath: page.filePathRelative?.replace(DIR_PATTERN, '') || '',
title: page.title,
link: page.path,
}
})
const notesData: NotesData = {}
notes.forEach((note) => {
notesData[path.join('/', link, note.link)] = initSidebar(
note,
notesPageList.filter((page) =>
page.relativePath.startsWith(note.dir.trim().replace(/^\/|\/$/g, ''))
)
)
})
let content = `
export const notesData = ${JSON.stringify(notesData, null, 2)}
`
if (app.env.isDev) {
content += HMR_CODE
}
await app.writeTemp('internal/notesData.js', content)
}
export const watchNotesData = (
app: App,
watchers: any[],
options: NotesDataOptions
): void => {
if (!options.notes || options.notes.length === 0 || !options.dir) return
const dir = path.join('pages', options.dir, '**/*')
const watcher = chokidar.watch(dir, {
cwd: app.dir.temp(),
ignoreInitial: true,
})
watcher.on('add', () => prepareNotesData(app, options))
watcher.on('change', () => prepareNotesData(app, options))
watcher.on('unlink', () => prepareNotesData(app, options))
watchers.push(watcher)
}
function initSidebar(note: NotesItem, pages: NotePage[]): NotesSidebarItem[] {
console.log('pages:', pages)
if (!note.sidebar) return []
if (note.sidebar === 'auto') return []
return initSidebarByConfig(note, pages)
}
function initSidebarByConfig(
{ text, link, dir, sidebar }: NotesItem,
pages: NotePage[]
): NotesSidebarItem[] {
return (sidebar as NotesSidebar).map((item) => {
console.log('text: ', text, 's-item: ', item, 'dir: ', dir)
if (typeof item === 'string') {
const current = findNotePage(item, dir, pages)
return {
text: current?.title || text,
link: current?.link,
items: [],
}
} else {
// link = path.join(link || '', item.link || '')
const current = findNotePage(item.link || '', dir, pages)
return {
text: item.text || item.dir || current?.title,
link: current?.link,
items: initSidebarByConfig(
{
link: item.link || '',
text: item.text || '',
sidebar: item.items,
dir: path.join(dir, item.dir || ''),
},
pages
),
}
}
})
}
function findNotePage(
sidebar: string,
dir: string,
notePageList: NotePage[]
): NotePage | undefined {
if (sidebar === '' || sidebar === 'README.md' || sidebar === 'index.md') {
return notePageList.find((page) => {
const relative = page.relativePath
return (
relative === path.join(dir, 'README.md') ||
relative === path.join(dir, 'index.md')
)
})
} else {
return notePageList.find((page) => {
const relative = page.relativePath
return (
relative === path.join(dir, sidebar) ||
relative === path.join(dir, sidebar + '.md') ||
page.link === sidebar
)
})
}
}

View File

@ -0,0 +1,5 @@
export function ensureArray<T>(thing: T | T[] | null | undefined): T[] {
if (Array.isArray(thing)) return thing
if (thing === null || thing === undefined) return []
return [thing]
}

View File

@ -1,3 +1,25 @@
export interface NotesDataOptions {
a?: string
export type NotesDataOptions = {
dir: string
link: string
include?: string | string[]
exclude?: string | string[]
notes: NotesItem[]
}
export type NotesItem = {
dir: string
link: string
text: string
sidebar?: NotesSidebar | 'auto'
}
export type NotesSidebar = (NotesSidebarItem | string)[]
export type NotesSidebarItem = {
text?: string
link?: string
dir?: string
items?: NotesSidebar
}
export type NotesData = Record<string, NotesSidebarItem[]>

View File

@ -2,7 +2,11 @@
"extends": "../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
"outDir": "./lib",
"paths": {
"@internal/notesData": ["./src/client/notesData.d.ts"]
},
"types": ["@vuepress/client/types", "vite/client", "webpack-env"]
},
"include": ["./src"]
}

View File

@ -39,6 +39,7 @@
"@vuepress-plume/vuepress-plugin-blog-data": "workspace:*",
"@vuepress-plume/vuepress-plugin-caniuse": "workspace:*",
"@vuepress-plume/vuepress-plugin-copy-code": "workspace:*",
"@vuepress-plume/vuepress-plugin-notes-data": "workspace:*",
"@vuepress/client": "2.0.0-beta.60",
"@vuepress/core": "2.0.0-beta.60",
"@vuepress/plugin-active-header-links": "2.0.0-beta.60",

View File

@ -1,12 +1,10 @@
import type {
PlumeThemeNotesItem,
PlumeThemeNotesOptions,
} from '../shared/index.js'
NotesDataOptions,
NotesItem,
} from '@vuepress-plume/vuepress-plugin-notes-data'
export const definePlumeNotesConfig = (
notes: PlumeThemeNotesOptions
): PlumeThemeNotesOptions => notes
notes: NotesDataOptions
): NotesDataOptions => notes
export const definePlumeNotesItemConfig = (
item: PlumeThemeNotesItem
): PlumeThemeNotesItem => item
export const definePlumeNotesItemConfig = (item: NotesItem): NotesItem => item

View File

@ -3,6 +3,7 @@ import { baiduTongjiPlugin } from '@vuepress-plume/vuepress-plugin-baidu-tongji'
import { blogDataPlugin } from '@vuepress-plume/vuepress-plugin-blog-data'
import { caniusePlugin } from '@vuepress-plume/vuepress-plugin-caniuse'
import { copyCodePlugin } from '@vuepress-plume/vuepress-plugin-copy-code'
import { notesDataPlugin } from '@vuepress-plume/vuepress-plugin-notes-data'
import type { App, PluginConfig } from '@vuepress/core'
import { activeHeaderLinksPlugin } from '@vuepress/plugin-active-header-links'
import { docsearchPlugin } from '@vuepress/plugin-docsearch'
@ -39,6 +40,7 @@ export const setupPlugins = (
include: ['**/*.md'],
exclude: ['**/{README,index}.md', 'notes/**'],
}),
localeOptions.notes ? notesDataPlugin(localeOptions.notes) : [],
activeHeaderLinksPlugin({
headerLinkSelector: 'a.theme-plume-toc-link',
headerAnchorSelector: '.header-anchor',

View File

@ -39,10 +39,6 @@ export interface PlumeThemeOptions extends PlumeThemeLocaleOptions {
*/
exclude?: string[]
}
notes?: {
dir?: string
}
}
export type PlumeThemeLocaleOptions = PlumeThemeData

View File

@ -1,3 +1,4 @@
import type { NotesDataOptions } from '@vuepress-plume/vuepress-plugin-notes-data'
import type { LocaleData } from '@vuepress/core'
// import type { NavbarConfig, NavLink } from '../layout/index.js'
// import type { PlumeThemeNotesOptions } from './notes.js'
@ -5,7 +6,6 @@ import type { LocaleData } from '@vuepress/core'
// todo type
type NavbarConfig = any
type NavLink = any
type PlumeThemeNotesOptions = any
export interface PlumeThemeAvatar {
/**
@ -135,7 +135,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
*
* notes配置到navbar中
*/
notes?: false | PlumeThemeNotesOptions
notes?: false | NotesDataOptions
footer?: false | { content: string; copyright: string }

10
pnpm-lock.yaml generated
View File

@ -213,15 +213,23 @@ importers:
packages/plugin-notes-data:
specifiers:
'@vue/devtools-api': ^6.4.5
'@vuepress/client': 2.0.0-beta.60
'@vuepress/core': 2.0.0-beta.60
'@vuepress/shared': 2.0.0-beta.60
'@vuepress/utils': 2.0.0-beta.60
chokidar: ^3.5.3
create-filter: ^1.0.0
vue: ^3.2.47
dependencies:
'@vue/devtools-api': 6.4.5
'@vuepress/client': 2.0.0-beta.60
'@vuepress/core': 2.0.0-beta.60
'@vuepress/shared': 2.0.0-beta.60
'@vuepress/utils': 2.0.0-beta.60
chokidar: 3.5.3
create-filter: 1.0.0
vue: 3.2.47
packages/plugin-page-collection:
specifiers:
@ -271,6 +279,7 @@ importers:
'@vuepress-plume/vuepress-plugin-blog-data': workspace:*
'@vuepress-plume/vuepress-plugin-caniuse': workspace:*
'@vuepress-plume/vuepress-plugin-copy-code': workspace:*
'@vuepress-plume/vuepress-plugin-notes-data': workspace:*
'@vuepress/client': 2.0.0-beta.60
'@vuepress/core': 2.0.0-beta.60
'@vuepress/plugin-active-header-links': 2.0.0-beta.60
@ -305,6 +314,7 @@ importers:
'@vuepress-plume/vuepress-plugin-blog-data': link:../plugin-blog-data
'@vuepress-plume/vuepress-plugin-caniuse': link:../plugin-caniuse
'@vuepress-plume/vuepress-plugin-copy-code': link:../plugin-copy-code
'@vuepress-plume/vuepress-plugin-notes-data': link:../plugin-notes-data
'@vuepress/client': 2.0.0-beta.60
'@vuepress/core': 2.0.0-beta.60
'@vuepress/plugin-active-header-links': 2.0.0-beta.60

View File

@ -9,6 +9,9 @@
"@internal/blogData": [
"./packages/plugin-blog-data/src/client/blogPostData.d.ts"
],
"@internal/notesData": [
"./packages/plugin-notes-data/src/client/notesData.d.ts"
],
"@internal/*": ["./docs/.vuepress/.temp/internal/*"],
"@vuepress-plume/vuepress-*": ["./packages/*/src/node/index.ts"],
"@vuepress-plume/vuepress-theme-plume": [