feat(theme): plume-theme next devloping !

add plugin-auto-frontmatter | add plugin-blog-data | devloping new theme
This commit is contained in:
pengzhanbo 2022-10-19 02:30:20 +08:00
parent 44a05f1ffe
commit 5de60d4d6e
198 changed files with 1213 additions and 6558 deletions

2
.gitignore vendored
View File

@ -12,3 +12,5 @@ dist/
*.log
*.tsbuildinfo
.mind
packages/theme-back

View File

@ -24,6 +24,7 @@
"composables",
"Docsearch",
"esbuild",
"frontmatter",
"gsap",
"iarna",
"leancloud",

View File

@ -1,5 +1,5 @@
import * as path from 'path'
import { themePlume } from '@vuepress-plume/vuepress-theme-plume'
import themePlume from '@vuepress-plume/vuepress-theme-plume'
import { viteBundler } from '@vuepress/bundler-vite'
import { webpackBundler } from '@vuepress/bundler-webpack'
import { defineUserConfig } from '@vuepress/cli'

View File

@ -1,10 +1,10 @@
---
title: BFC 块级格式化上下文
createTime: 2018/05/17 12:28:33
createTime: 2022-03-26T11:46:50.024Z
permalink: /article/o5g7ggvf
author: pengzhanbo
top: false
tags:
tags:
- html
type: null
---
@ -35,4 +35,4 @@ BFC, Block Formating Context。是 W3C CSS2.1规范中的一个概念。 是页
1. 同一个BFC的外边距会发生折叠合并 通过将其放在不同的BFC中规避折叠。
2. BFC可以包含浮动元素即清除浮动。
3. BFC可以阻止元素被浮动元素覆盖。
3. BFC可以阻止元素被浮动元素覆盖。

View File

@ -1,9 +1,9 @@
---
title: CSS At-Rules
createTime: 2018/10/06 08:16:38
createTime: 2022-03-26T11:46:50.024Z
permalink: /article/btkqop1a
author: pengzhanbo
tags:
tags:
- css
top: false
type: null
@ -160,4 +160,4 @@ type: null
## @media
媒体查询,详见 [CSS @media 媒体查询](/post/fe5ruia1/)
媒体查询,详见 [CSS @media 媒体查询](/post/fe5ruia1/)

View File

@ -1,9 +1,9 @@
---
title: CSS 媒体查询
createTime: 2018/08/18 08:43:02
createTime: 2022-03-26T11:46:50.024Z
permalink: /article/fe5ruia1
author: pengzhanbo
tags:
tags:
- css
top: false
type: null

View File

@ -1,9 +1,9 @@
---
title: CSS选择器
createTime: 2018/09/20 03:29:20
createTime: 2022-03-26T11:46:50.024Z
permalink: /article/8vev8ixl
author: pengzhanbo
tags:
tags:
- css
top: false
type: null
@ -644,4 +644,4 @@ type: null
}
</style>
...
```
```

View File

@ -1,9 +1,9 @@
---
title: <!DOCTYPE> 文档类型声明
createTime: 2018/03/14 01:06:52
createTime: 2022-03-26T11:46:50.025Z
permalink: /article/s8udp6vp
author: pengzhanbo
tags:
tags:
- html
top: false
type: null
@ -105,4 +105,4 @@ http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
``` html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
```
```

View File

@ -1,9 +1,9 @@
---
title: HTML5新特性
createTime: 2018/02/17 12:49:58
createTime: 2022-03-26T11:46:50.025Z
permalink: /article/8rv45yuy
author: pengzhanbo
tags:
tags:
- html
top: false
type: null
@ -272,4 +272,4 @@ history.replaceState({}, 'bar', 'bar.html')
})
```
[History API - Web API 接口参考 | MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/History_API)
[History API - Web API 接口参考 | MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/History_API)

View File

@ -1,10 +1,10 @@
---
title: WebComponent——template
lang: zh-CN
tags:
tags:
- html
- javascript
createTime: 2018/8/2 11:15:27
createTime: 2022-03-26T11:46:50.025Z
permalink: /article/5fmy4kla
author: pengzhanbo
top: false

View File

@ -1,9 +1,9 @@
---
title: WebComponent——custom elements
tags:
tags:
- html
- javascript
createTime: 2018/08/01 11:15:27
createTime: 2022-03-26T11:46:50.025Z
permalink: /article/m63fd7lf
author: pengzhanbo
top: false

View File

@ -1,9 +1,9 @@
---
title: meta 标签说明
createTime: 2018/03/15 01:21:48
createTime: 2022-03-26T11:46:50.025Z
permalink: /article/bp1nxjs6
author: pengzhanbo
tags:
tags:
- html
top: false
type: null
@ -216,4 +216,4 @@ content="app-id=APP_ID,affiliate-data=AFFILIATE_ID,app-argument=SOME_TEXT">
<meta name="theme-color" content="#E64545">
<!-- 添加到主屏 -->
<meta name="mobile-web-app-capable" content="yes">
```
```

View File

@ -1,9 +1,9 @@
---
title: 继承与原型链
createTime: 2018/07/06 09:40:54
createTime: 2022-03-26T13:40:54.727Z
permalink: /article/extends-prototype
author: pengzhanbo
tags:
tags:
- javascript
top: false
type: null

View File

@ -1,10 +1,10 @@
---
title: 正则表达式
lang: zh-CN
createTime: 2018/11/26 11:15:27
createTime: 2022-03-26T11:46:50.025Z
permalink: /article/e8qbp0dh
author: pengzhanbo
tags:
tags:
- javascript
top: false
type: null

View File

@ -1,8 +1,9 @@
---
title: Event Loop 浏览器端的事件循环
createTime: 2021/06/03 01:53:17
createTime: 2022-03-26T11:46:50.000Z
permalink: /article/browser-event-loop
author: pengzhanbo
top: false
type: null
---

View File

@ -1,6 +1,6 @@
---
title: 详解 Promise
createTime: 2020/11/22 12:58:28
createTime: 2022-03-26T11:46:50.026Z
permalink: /article/q40nq4hv
author: pengzhanbo
sticky: true

View File

@ -1,9 +1,9 @@
---
title: 1px解决方案
createTime: 2019/05/15 10:41:32
createTime: 2022-03-26T11:46:50.026Z
permalink: /article/tz7ncicn
author: pengzhanbo
tags:
tags:
- html
- css
- develop

View File

@ -1,6 +1,6 @@
---
title: lerna使用
createTime: 2021/11/26 06:28:37
createTime: 2022-03-26T11:46:50.027Z
permalink: /article/i1wc1uld
author: pengzhanbo
top: false
@ -118,4 +118,4 @@ lerna run build # 相当于在 package1、package2 中执行 npm run build
```
### lerna clean
删除所有包的node_modules
删除所有包的node_modules

View File

@ -1,9 +1,9 @@
---
title: 移动端适配方案
createTime: 2020/08/14 01:54:29
createTime: 2022-03-26T11:46:50.027Z
permalink: /article/vhpmovsm
author: pengzhanbo
tags:
tags:
- develop
top: false
type: null
@ -108,4 +108,4 @@ css像素是一个抽象单位主要用在浏览器上用来精确的度
1. 容器适配
2. 文本适配
3. 大于1px的边框、圆角、阴影
4. 内边距和外边距
4. 内边距和外边距

View File

@ -1,10 +1,10 @@
---
title: Jenkins 使用
lang: zh-CN
createTime: 2018/09/16 11:15:27
createTime: 2022-03-26T11:46:50.027Z
permalink: /article/bmtl5ah4
author: pengzhanbo
tags:
tags:
- 工具
top: false
type: null

View File

@ -1,6 +1,6 @@
---
title: caniuse
createTime: 2021/02/07 06:41:12
createTime: 2022-03-26T11:46:50.027Z
permalink: /article/h4z91gyz
author: pengzhanbo
top: false
@ -10,4 +10,4 @@ type: null
### 工具
将caniuse 的feature 结果以图片或者iframe的形式嵌入到站点。
[https://caniuse.bitsofco.de/](https://caniuse.bitsofco.de/)
[https://caniuse.bitsofco.de/](https://caniuse.bitsofco.de/)

View File

@ -1,10 +1,10 @@
---
title: VSCode 常用插件推荐
lang: zh-CN
createTime: 2018/12/29 11:15:27
createTime: 2022-03-26T11:46:50.027Z
permalink: /article/ofp08jd8
author: pengzhanbo
tags:
tags:
- VSCode
top: false
type: null

View File

@ -1,9 +1,9 @@
---
title: Vue组件间通信
lang: zh-CN
tags:
tags:
- vue
createTime: 2018/07/20 11:15:27
createTime: 2022-03-26T11:46:50.028Z
permalink: /article/iezlvhvg
author: pengzhanbo
top: false

View File

@ -1,6 +1,6 @@
---
title: 面试2
createTime: 2022/04/04 01:48:00
createTime: 2022-04-03T17:48:00.400Z
author: pengzhanbo
permalink: /article/exavsmm1
---

View File

@ -1,8 +1,8 @@
---
title: 面试题以及个人答案 JS篇
tags:
tags:
- 面试
createTime: 2018/08/23 11:15:27
createTime: 2022-03-26T11:46:50.028Z
permalink: /article/4ml7z17g
author: pengzhanbo
top: false

View File

@ -1,8 +1,8 @@
---
title: 面试题以及个人答案 CSS篇
tags:
tags:
- 面试
createTime: 2018/08/22 11:15:27
createTime: 2022-03-26T11:46:50.028Z
permalink: /article/565o1wn0
author: pengzhanbo
top: false

View File

@ -2,4 +2,7 @@
home: true
banner: /images/big-banner.jpg
motto: 世间的美好总是不期而遇,恬静而自然。
author: pengzhanbo
createTime: '2022/03/26 07:46:50'
---

View File

@ -1,6 +1,7 @@
---
title: README
createTime: 2022/04/04 11:13:30
createTime: '2022/04/04 11:13:30'
author: pengzhanbo
permalink: /note/
---

View File

@ -1,6 +1,6 @@
---
title: plugin-caniuse
createTime: 2022/05/13 01:02:51
createTime: '2022/05/13 01:02:51'
author: pengzhanbo
permalink: /note/vuepress-plugin/caniuse/
---

View File

@ -1,6 +1,6 @@
---
title: API
createTime: 2022/05/13 05:49:14
createTime: 2022-05-14T10:43:53.200Z
author: pengzhanbo
permalink: /note/vuepress-plugin/netlify-functions/api/
---

View File

@ -1,6 +1,6 @@
---
title: 指南
createTime: 2022/05/13 01:28:38
createTime: '2022/05/13 01:28:38'
author: pengzhanbo
permalink: /note/vuepress-plugin/netlify-functions/
---

View File

@ -1,6 +1,6 @@
---
title: functions开发指南
createTime: 2022/05/13 05:45:24
createTime: 2022-05-14T10:43:53.201Z
author: pengzhanbo
permalink: /note/vuepress-plugin/netlify-functions/develop-functions/
---

View File

@ -1,6 +1,6 @@
---
title: 介绍
createTime: 2022/05/13 05:47:06
createTime: 2022-05-14T10:43:53.202Z
author: pengzhanbo
permalink: /note/vuepress-plugin/netlify-functions/intro/
---

View File

@ -1,6 +1,6 @@
---
title: 使用
createTime: 2022/05/13 05:45:01
createTime: 2022-05-14T10:43:53.203Z
author: pengzhanbo
permalink: /note/vuepress-plugin/netlify-functions/usage/
---

View File

@ -1,6 +1,6 @@
---
title: 功能
createTime: 2022/05/13 05:45:11
createTime: 2022-05-14T10:43:53.204Z
author: pengzhanbo
permalink: /note/vuepress-plugin/netlify-functions/feature/
---

View File

@ -1,6 +1,6 @@
---
title: vuepress-theme-plume
createTime: 2022/04/08 08:52:12
createTime: '2022/04/08 08:52:12'
author: pengzhanbo
permalink: /note/vuepress-theme-plume/
article: true

View File

@ -1,6 +1,6 @@
---
title: markdown增强
createTime: 2022/04/09 06:43:32
createTime: 2022-05-14T10:43:53.216Z
author: pengzhanbo
permalink: /note/vuepress-theme-plume/markdown-enhance/
---

View File

@ -1,6 +1,6 @@
---
title: notes配置
createTime: 2022/04/09 02:48:41
createTime: 2022-05-14T10:43:53.218Z
author: pengzhanbo
permalink: /note/vuepress-theme-plume/notes-config/
---

View File

@ -1,6 +1,6 @@
---
title: 主题插件配置
createTime: 2022/04/09 02:48:30
createTime: 2022-05-14T10:43:53.219Z
author: pengzhanbo
permalink: /note/vuepress-theme-plume/plugins-config/
---

View File

@ -1,6 +1,6 @@
---
title: 主题配置
createTime: 2022/04/09 12:18:12
createTime: 2022-05-14T10:43:53.219Z
author: pengzhanbo
permalink: /note/vuepress-theme-plume/theme-config/
---

View File

@ -1,6 +1,6 @@
---
title: 基础功能
createTime: 2022/04/09 06:43:20
createTime: 2022-05-14T10:43:53.220Z
author: pengzhanbo
permalink: /note/vuepress-theme-plume/basis-power/
---

View File

@ -1,6 +1,6 @@
---
title: 快速开始
createTime: 2022/04/08 09:43:20
createTime: 2022-05-14T10:43:53.221Z
author: pengzhanbo
permalink: /note/vuepress-theme-plume/quick-start/
---

View File

@ -1,6 +1,6 @@
---
title: 编写文章
createTime: 2022/04/09 12:13:56
createTime: 2022-05-14T10:43:53.221Z
author: pengzhanbo
permalink: /note/vuepress-theme-plume/write-article/
---

View File

@ -1,6 +1,6 @@
---
title: 页面配置
createTime: 2022/04/09 01:24:17
createTime: 2022-05-14T10:43:53.222Z
author: pengzhanbo
permalink: /note/vuepress-theme-plume/page-config/
---

View File

@ -0,0 +1,115 @@
# `@vuepress-plume/vuepress-plugin-auto-frontmatter`
自动生成 `*.md` 文件的 `frontmatter` 配置。
## Install
```
yarn add @vuepress-plume/vuepress-plugin-auto-frontmatter
```
## Usage
``` js
// .vuepress/config.js
import { autoFrontmatterPlugin } from '@vuepress-plume/vuepress-plugin-auto-frontmatter'
export default {
//...
plugins: [
autoFrontmatterPlugin({
formatter: {
createTime(formatTime, matter, file) {
if (formatTime) return formatTime
return file.createTime
}
}
})
]
// ...
}
```
## `autoFrontmatterPlugin([options])`
### options
`{ glob?: string | string[]; formatter: Formatter }`
- `glob`
glob 匹配字符串或数组,匹配需要自动生成 `frontmatter` 的 md文件。
默认预设为 `['**/*.md', '!.vuepress/', '!node_modules/']`
自定义匹配将被合并到预设配置中
example: `['blog/**']`
- `formatter`
配置`frontmatter`每个字段的生成规则。
```ts
interface MarkdownFile {
filepath: string
relativePath: string
content: string
createTime: Date
}
interface FormatterFn<T = any, K = object> {
(value: T, data: K, file: MarkdownFile): T
}
type FormatterObject<K = object, T = any> = Record<
string,
FormatterFn<T, K>
>
type FormatterArray = {
glob: string
formatter: FormatterObject
}[]
type Formatter = FormatterObject | FormatterArray
/**
* formatterObj 对象中的 key 即为 frontmatter 配置中的key
* 其方法返回的值将作为 frontmatter[key] 的值
* *.md
* ---
* createTime: 2022-03-26T11:46:50.000Z
* ---
*/
const formatterObj: Formatter = {
createTime(formatTime, matter, file) {
if (formatTime) return formatTime
return file.createTime
}
}
const formatterArr: Formatter = [
{
// 更精细化的匹配某个 md文件支持glob 匹配字符串
glob: '**/{README,index}.md',
// formatter 仅对 glob命中的文件有效
formatter: {
home(value, matter, file) {
return value
}
},
{
// 通配如果文件没有被其他精细glob命中
// 则使用 通配 formatter
// 如果是数组,必须有且用一个 glob为 * 的 项
glob: '*',
formatter: {
title(title) {
return title || '默认标题'
}
}
}
}
]
```
## Why ?
- **为什么需要这个插件?**
有时候在开发一些主题时,期望使用户更专注于内容的编写,尽可能减少配置性的工作,可以将一些重复性的必要的配置
直接通过本插件自动生成。
以及,我确实想在写新文章的时候,更少的做配置工作,于是便有了这个插件

View File

@ -0,0 +1,48 @@
{
"name": "@vuepress-plume/vuepress-plugin-auto-frontmatter",
"version": "1.0.0-beta.45",
"description": "The Plugin for VuePres 2",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
},
"license": "MIT",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"type": "module",
"exports": {
".": "./lib/node/index.js",
"./package.json": "./package.json"
},
"main": "lib/node/index.js",
"types": "./lib/node/index.d.ts",
"files": [
"lib"
],
"scripts": {
"build": "pnpm run clean && pnpm run copy && pnpm run ts",
"clean": "rimraf lib *.tsbuildinfo",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
"ts": "tsc -b tsconfig.build.json"
},
"dependencies": {
"@vuepress/core": "2.0.0-beta.51",
"@vuepress/shared": "2.0.0-beta.51",
"@vuepress/utils": "2.0.0-beta.51",
"chokidar": "^3.5.3",
"glob-to-regexp": "^0.4.1",
"gray-matter": "^4.0.3"
},
"publishConfig": {
"access": "public"
},
"keyword": [
"VuePress",
"vuepress plugin",
"autoFrontmatter",
"vuepress-plugin-plugin-auto-frontmatter"
]
}

View File

@ -0,0 +1,16 @@
declare module 'glob-to-regexp' {
interface GlobToRegexp {
(
glob: string,
options?: {
globstar?: boolean
extended?: boolean
flags?: string
}
): RegExp
}
const globToRegexp: GlobToRegexp
export default globToRegexp
}

View File

@ -0,0 +1,8 @@
import type { AutoFrontmatterOptions } from '../shared/index.js'
import { autoFrontmatterPlugin } from './plugin.js'
export * from './plugin.js'
export { AutoFrontmatterOptions }
export default autoFrontmatterPlugin

View File

@ -0,0 +1,83 @@
import type { Plugin } from '@vuepress/core'
import { fs } from '@vuepress/utils'
import chokidar from 'chokidar'
import globToRegexp from 'glob-to-regexp'
import grayMatter from 'gray-matter'
import type {
AutoFrontmatterOptions,
FormatterArray,
FormatterObject,
MarkdownFile,
} from '../shared/index.js'
import { readMarkdown, readMarkdownList } from './readFiles.js'
export const autoFrontmatterPlugin = ({
glob = '',
formatter = {},
}: AutoFrontmatterOptions = {}): Plugin => {
glob = glob ? (Array.isArray(glob) ? glob : [glob]) : []
glob = ['**/*.{md,MD}', '!.vuepress/', '!node_modules/', ...glob]
const matterFormatter: FormatterArray = Array.isArray(formatter)
? formatter
: [{ glob: '*', formatter }]
const globFormatter: FormatterObject =
matterFormatter.find(({ glob }) => glob === '*')?.formatter || {}
const otherFormatters = matterFormatter
.filter(({ glob }) => glob !== '*')
.map(({ glob, formatter }) => {
return {
glob,
regexp: globToRegexp(glob, {
globstar: true,
extended: true,
}),
formatter,
}
})
function formatMarkdown(file: MarkdownFile): void {
const { filepath, relativePath } = file
const formatter =
otherFormatters.find(({ regexp }) => regexp.test(relativePath))
?.formatter || globFormatter
const { data, content } = grayMatter(file.content)
Object.keys(formatter).forEach((key) => {
const value = formatter[key](data[key], data, file)
data[key] = value ?? data[key]
})
const newContent = grayMatter.stringify({ content }, data)
fs.writeFileSync(filepath, newContent)
}
return {
name: '@vuepress-plume/vuepress-plugin-auto-frontmatter',
onInitialized: async (app) => {
const markdownList = await readMarkdownList(
app.dir.source(),
glob as string[]
)
markdownList.forEach((file) => formatMarkdown(file))
},
onWatched: async (app, watchers) => {
const watcher = chokidar.watch('**/*.md', {
cwd: app.dir.source(),
ignoreInitial: true,
ignored: /(node_modules|\.vuepress)\//,
})
watcher.on('add', (relativePath) => {
if ((glob as string[]).some((_) => !globToRegexp(_).test(relativePath)))
return
formatMarkdown(readMarkdown(app.dir.source(), relativePath))
})
watchers.push(watcher)
},
}
}

View File

@ -0,0 +1,33 @@
import { fs, globby, path } from '@vuepress/utils'
import type { MarkdownFile } from '../shared/index.js'
type MarkdownFileList = MarkdownFile[]
export const readMarkdownList = async (
sourceDir: string,
glob: string[]
): Promise<MarkdownFileList> => {
const files: string[] = await globby(glob, {
cwd: sourceDir,
gitignore: true,
})
return files.map((file) => readMarkdown(sourceDir, file))
}
export const readMarkdown = (
sourceDir: string,
relativePath: string
): MarkdownFile => {
const filepath = path.join(sourceDir, relativePath)
return {
filepath,
relativePath,
content: fs.readFileSync(filepath, 'utf-8'),
createTime: getFileCreateTime(fs.statSync(filepath)),
}
}
export const getFileCreateTime = (stat: fs.Stats): Date => {
return stat.birthtime.getFullYear() !== 1970 ? stat.birthtime : stat.atime
}

View File

@ -0,0 +1,36 @@
export interface MarkdownFile {
filepath: string
relativePath: string
content: string
createTime: Date
}
export interface FormatterFn<T = any, K = object> {
(value: T, data: K, file: MarkdownFile): T
}
export type FormatterObject<K = object, T = any> = Record<
string,
FormatterFn<T, K>
>
export type FormatterArray = {
glob: string
formatter: FormatterObject
}[]
export interface AutoFrontmatterOptions {
/**
* glob string
*/
glob?: string | string[]
/**
* {
* key(value, data, file) {
* return value
* }
* }
*/
formatter?: FormatterObject | FormatterArray
}

View File

@ -0,0 +1,9 @@
{
"extends": "../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
},
"include": ["./src"],
"files": []
}

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (C) 2021 - PRESENT by pengzhanbo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,51 @@
{
"name": "@vuepress-plume/vuepress-plugin-blog-data",
"version": "1.0.0-beta.45",
"description": "The Plugin for VuePres 2",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
},
"license": "MIT",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"type": "module",
"exports": {
".": "./lib/node/index.js",
"./client": "./lib/client/index.js",
"./package.json": "./package.json"
},
"main": "lib/node/index.js",
"types": "./lib/node/index.d.ts",
"files": [
"lib"
],
"scripts": {
"build": "pnpm run clean && pnpm run copy && pnpm run ts",
"clean": "rimraf lib *.tsbuildinfo",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
"ts": "tsc -b tsconfig.build.json"
},
"dependencies": {
"@vue/devtools-api": "^6.4.5",
"@vuepress/client": "2.0.0-beta.51",
"@vuepress/core": "2.0.0-beta.51",
"@vuepress/shared": "2.0.0-beta.51",
"@vuepress/utils": "2.0.0-beta.51",
"chokidar": "^3.5.3",
"glob-to-regexp": "^0.4.1",
"vue": "^3.2.41"
},
"publishConfig": {
"access": "public"
},
"keyword": [
"VuePress",
"vuepress plugin",
"blogData",
"vuepress-plugin-plugin-blog-data"
]
}

View File

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

View File

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

View File

@ -0,0 +1,20 @@
import { blogPostData as blogPostDataRaw } from '@internal/blogData'
import { ref } from 'vue'
import type { Ref } from 'vue'
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 const blogPostData: ThemeDataRef = ref(blogPostDataRaw)
export const useBlogPostData = <
T extends BlogPostData = BlogPostData
>(): ThemeDataRef<T> => blogPostData as ThemeDataRef<T>
if (import.meta.webpackHot || import.meta.hot) {
__VUE_HMR_RUNTIME__.updateBlogData = (data: BlogPostData) => {
blogPostData.value = data
}
}

View File

@ -0,0 +1,36 @@
import { setupDevtoolsPlugin } from '@vue/devtools-api'
import { defineClientConfig } from '@vuepress/client'
import { useBlogPostData } from './composables/index.js'
export default defineClientConfig({
enhance({ app }) {
// provide theme data & theme locale data
const blogPostData = useBlogPostData()
// setup devtools in dev mode
if (__VUEPRESS_DEV__ || __VUE_PROD_DEVTOOLS__) {
setupDevtoolsPlugin(
{
// fix recursive reference
app: app as any,
id: 'org.vuepress-plume.plugin-blog-data',
label: 'VuePress Blog Data Plugin',
packageName: '@vuepress/plugin-blog-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: 'blogPostData',
editable: false,
value: blogPostData.value,
})
})
}
)
}
},
})

View File

@ -0,0 +1,4 @@
import type { BlogPostData, BlogPostDataItem } from '../shared/index.js'
export * from './composables/index.js'
export { BlogPostData, BlogPostDataItem }

View File

@ -0,0 +1,16 @@
declare module 'glob-to-regexp' {
interface GlobToRegexp {
(
glob: string,
options?: {
globstar?: boolean
extended?: boolean
flags?: string
}
): RegExp
}
const globToRegexp: GlobToRegexp
export default globToRegexp
}

View File

@ -0,0 +1,6 @@
import { blogDataPlugin } from './plugin.js'
export * from '../shared/index.js'
export * from './plugin.js'
export default blogDataPlugin

View File

@ -0,0 +1,63 @@
import type { Plugin } from '@vuepress/core'
import { getDirname, path } from '@vuepress/utils'
import chokidar from 'chokidar'
import globToRegexp from 'glob-to-regexp'
import { preparedBlogData } from './prepareBlogData.js'
import type { BlogDataPluginOptions } from './index.js'
const __dirname = getDirname(import.meta.url)
export interface PluginOption
extends Omit<BlogDataPluginOptions, 'include' | 'exclude'> {
include: {
(filepath: string): boolean
}[]
exclude: {
(filepath: string): boolean
}[]
}
const globOptions = {
globstar: true,
extended: true,
}
export const blogDataPlugin = ({
include,
exclude,
...pluginOptions
}: BlogDataPluginOptions = {}): Plugin => {
const options: PluginOption = {
include: toArray(include)
.map((str) => globToRegexp(str, globOptions))
.map((regexp) => (filepath: string) => regexp.test(filepath)),
exclude: toArray(exclude)
.map((str) => globToRegexp(str, globOptions))
.map((regexp) => (filepath: string) => !regexp.test(filepath)),
...pluginOptions,
}
return {
name: '@vuepress-plume/vuepress-plugin-blog-data',
clientConfigFile: path.resolve(__dirname, '../client/config.js'),
onPrepared: async (app) => await preparedBlogData(app, options),
onWatched(app, watchers) {
const watcher = chokidar.watch('pages/**/*', {
cwd: app.dir.temp(),
ignoreInitial: true,
})
watcher.on('add', async () => await preparedBlogData(app, options))
watcher.on('change', async () => await preparedBlogData(app, options))
watcher.on('unlink', async () => await preparedBlogData(app, options))
watchers.push(watcher)
},
}
}
function toArray(likeArr: string | string[] | undefined): string[] {
if (Array.isArray(likeArr)) return likeArr
return likeArr ? [likeArr] : []
}

View File

@ -0,0 +1,79 @@
import type { App } from '@vuepress/core'
import type { BlogPostData, BlogPostDataItem } from '../shared/index.js'
import type { PluginOption } from './plugin.js'
const HMR_CODE = `
if (import.meta.webpackHot) {
import.meta.webpackHot.accept()
if (__VUE_HMR_RUNTIME__.updateBlogData) {
__VUE_HMR_RUNTIME__.updateBlogData(blogPostData)
}
}
if (import.meta.hot) {
import.meta.hot.accept(({ blogPostData }) => {
__VUE_HMR_RUNTIME__.updateBlogData(blogPostData)
})
}
`
const getTimestamp = (time: Date): number => {
return new Date(time).getTime()
}
export const preparedBlogData = async (
app: App,
options: PluginOption
): Promise<void> => {
let pages = app.pages.filter((page) => {
return (
page.filePathRelative &&
options.exclude.every((filter) => filter(page.filePathRelative!)) &&
options.include.some((filter) => filter(page.filePathRelative!))
)
})
if (options.sortBy) {
pages = pages.sort((prev, next) => {
if (options.sortBy === 'createTime') {
return getTimestamp(prev.frontmatter.createTime as Date) <
getTimestamp(next.frontmatter.createTime as Date)
? 1
: -1
} else {
return typeof options.sortBy === 'function' &&
options.sortBy(prev, next)
? 1
: -1
}
})
}
const blogData: BlogPostData = pages.map((page) => {
let extended: Partial<BlogPostDataItem> = {}
if (typeof options.extendBlogData === 'function') {
extended = options.extendBlogData(page)
}
const data = {
path: page.path,
title: page.title,
...extended,
}
if (options.excerpt) data.excerpt = page.excerpt
return data as BlogPostDataItem
})
let content = `\
export const blogPostData = JSON.parse(${JSON.stringify(
JSON.stringify(blogData)
)})
`
// inject HMR code
if (app.env.isDev) {
content += HMR_CODE
}
await app.writeTemp('internal/blogData.js', content)
}

View File

@ -0,0 +1,16 @@
export interface BlogDataPluginOptions {
include?: string | string[]
exclude?: string | string[]
sortBy?: 'createTime' | false | (<T>(prev: T, next: T) => boolean)
excerpt?: boolean
extendBlogData?: <T>(page: T) => Partial<BlogPostDataItem>
}
export type BlogPostData<T extends object = object> = BlogPostDataItem<T>[]
export type BlogPostDataItem<T extends object = object> = {
path: string
title: string
excerpt: string
[x: string]: any
} & T

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"baseUrl": ".",
"paths": {
"@internal/blogData": ["./src/client/blogPostData.d.ts"]
},
"types": ["@vuepress/client/types", "vite/client", "webpack-env"]
},
"include": ["./src"]
}

View File

@ -1,229 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.0.0-beta.26](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.25...v1.0.0-beta.26) (2022-04-25)
### Features
* support sitemap and seo ([a57af59](https://github.com/pengzhanbo/vuepress-theme-plume/commit/a57af599e649d1f7ce357f704a222c7babc77b06))
# [1.0.0-beta.25](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.24...v1.0.0-beta.25) (2022-04-25)
### Bug Fixes
* fix define config ([00bca40](https://github.com/pengzhanbo/vuepress-theme-plume/commit/00bca40d895499017308359bfce682cec055f42b))
# [1.0.0-beta.24](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.23...v1.0.0-beta.24) (2022-04-25)
### Features
* 主题配置函数 ([844feea](https://github.com/pengzhanbo/vuepress-theme-plume/commit/844feeae7406f5aee8edef35bce4a08e808f692e))
# [1.0.0-beta.23](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.22...v1.0.0-beta.23) (2022-04-23)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.22](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.21...v1.0.0-beta.22) (2022-04-18)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.21](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.20...v1.0.0-beta.21) (2022-04-18)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.20](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.19...v1.0.0-beta.20) (2022-04-18)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.19](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.18...v1.0.0-beta.19) (2022-04-18)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.18](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.17...v1.0.0-beta.18) (2022-04-14)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.17](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.16...v1.0.0-beta.17) (2022-04-12)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.16](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.15...v1.0.0-beta.16) (2022-04-12)
### Bug Fixes
* sidebar ([a2794d7](https://github.com/pengzhanbo/vuepress-theme-plume/commit/a2794d72ea7a276d8fd876475f5e9c77c33f5e90))
# [1.0.0-beta.15](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.14...v1.0.0-beta.15) (2022-04-12)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.14](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.13...v1.0.0-beta.14) (2022-04-12)
### Bug Fixes
* 修复sidebar问题 ([420ec9f](https://github.com/pengzhanbo/vuepress-theme-plume/commit/420ec9fb663793fe2d4fd7e9e61f12ca0d05217e))
# [1.0.0-beta.13](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.12...v1.0.0-beta.13) (2022-04-12)
### Bug Fixes
* 修复note生成sidebar时目录嵌套时未正确识别路径 ([7b3a6e2](https://github.com/pengzhanbo/vuepress-theme-plume/commit/7b3a6e2252582a19bdbf42c1ddf85dfab199d57b))
# [1.0.0-beta.12](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.11...v1.0.0-beta.12) (2022-04-08)
### Bug Fixes
* 修复page页toc问题 ([2d44799](https://github.com/pengzhanbo/vuepress-theme-plume/commit/2d4479909f4c84a8d71c8a97c93f21bde3b8208c))
# [1.0.0-beta.11](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.10...v1.0.0-beta.11) (2022-04-08)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.10](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.9...v1.0.0-beta.10) (2022-04-08)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.9](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.8...v1.0.0-beta.9) (2022-04-06)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.8](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.7...v1.0.0-beta.8) (2022-04-06)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.7](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.6...v1.0.0-beta.7) (2022-04-05)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.6](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.5...v1.0.0-beta.6) (2022-04-05)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.5](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2022-04-05)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.4](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.3...v1.0.0-beta.4) (2022-04-05)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.3](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.2...v1.0.0-beta.3) (2022-04-05)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# [1.0.0-beta.2](https://github.com/pengzhanbo/vuepress-theme-plume/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2022-04-05)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume
# 1.0.0-beta.1 (2022-04-05)
**Note:** Version bump only for package @vuepress-plume/vuepress-theme-plume

View File

@ -1,15 +1,6 @@
{
"name": "@vuepress-plume/vuepress-theme-plume",
"version": "1.0.0-beta.45",
"description": "A Blog Theme for VuePress 2.0",
"keywords": [
"VuePress",
"Theme",
"plume",
"vuepress-theme",
"vuepress-theme-plume",
"theme-plume"
],
"version": "0.0.0",
"homepage": "https://pengzhanbo.cn/note/vuepress-theme-plume",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
@ -19,7 +10,7 @@
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
},
"license": "MIT",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"type": "module",
"exports": {
".": "./lib/node/index.js",
@ -43,7 +34,9 @@
},
"dependencies": {
"@types/lodash.merge": "^4.6.7",
"@vuepress-plume/vuepress-plugin-auto-frontmatter": "workspace:*",
"@vuepress-plume/vuepress-plugin-baidu-tongji": "workspace:*",
"@vuepress-plume/vuepress-plugin-blog-data": "workspace:*",
"@vuepress-plume/vuepress-plugin-caniuse": "workspace:*",
"@vuepress-plume/vuepress-plugin-copy-code": "workspace:*",
"@vuepress/client": "2.0.0-beta.51",
@ -62,15 +55,8 @@
"@vuepress/plugin-toc": "2.0.0-beta.51",
"@vuepress/shared": "2.0.0-beta.51",
"@vuepress/utils": "2.0.0-beta.51",
"@vueuse/core": "^9.3.0",
"chokidar": "^3.5.3",
"date-fns": "^2.29.3",
"gray-matter": "^4.0.3",
"json2yaml": "^1.1.0",
"lodash.merge": "^4.6.2",
"nanoid": "^4.0.0",
"sass": "^1.55.0",
"sass-loader": "^13.1.0",
"ts-debounce": "^4.0.0",
"vue": "^3.2.41",
"vue-router": "4.1.5",
@ -78,16 +64,5 @@
"vuepress-plugin-md-enhance": "2.0.0-beta.110",
"vuepress-plugin-seo2": "2.0.0-beta.110",
"vuepress-plugin-sitemap2": "2.0.0-beta.110"
},
"peerDependencies": {
"sass-loader": "^13.0.2"
},
"peerDependenciesMeta": {
"sass-loader": {
"optional": true
}
},
"publishConfig": {
"access": "public"
}
}

View File

@ -1,171 +0,0 @@
<script lang="ts" setup>
import BlogInfo from '@theme-plume/BlogInfo.vue'
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
import type { PageHeader } from '@vuepress/client'
import { computed } from 'vue'
import { useArchive } from '../composables/index.js'
import Toc from './Toc.js'
const archiveList = useArchive()
const headers = computed(() => {
return archiveList.value.map(({ year }) => {
return {
level: 2,
slug: year,
title: year,
link: '',
children: [],
} as PageHeader
})
})
</script>
<template>
<div class="archive-wrapper">
<div class="archive-container">
<DropdownTransition>
<div class="archive-content">
<div class="archive-box">
<div
v-for="archive in archiveList"
:key="archive.year"
class="archive-items"
>
<h2>
<a
class="header-anchor"
:href="'#' + archive.year"
aria-hidden="true"
>#</a
>
{{ archive.year }}
</h2>
<ul class="archive-list">
<li v-for="child in archive.children" :key="child.link">
<span>{{ child.date }}</span>
<RouterLink :to="child.link">{{ child.text }}</RouterLink>
</li>
</ul>
</div>
</div>
<div class="archive-toc">
<Toc :headers="headers" />
</div>
</div>
</DropdownTransition>
<BlogInfo></BlogInfo>
</div>
</div>
</template>
<style lang="scss">
@import '../styles/_mixins';
.archive-wrapper {
@include wrapper;
.archive-container {
@include container_wrapper;
display: flex;
align-items: flex-start;
padding: 1.25rem 0;
}
.archive-content {
display: flex;
align-items: flex-start;
flex: 1;
.archive-box {
flex: 1;
}
}
.archive-items {
h2 {
position: relative;
margin: 0 0 0 4rem;
border-left: solid 4px var(--c-border);
border-bottom: none;
padding: 1.25rem;
&::before {
content: '';
display: inline-block;
width: 0.75rem;
height: 0.75rem;
background-color: var(--c-bg-container);
border-radius: 6px;
border: solid 2px var(--c-border-dark);
position: absolute;
top: 50%;
left: -2px;
transform: translate(-50%, -50%);
}
}
}
.archive-list {
list-style: none;
padding-left: 0;
margin: 0;
li {
position: relative;
margin-left: 4rem;
border-left: solid 4px var(--c-border);
padding: 0.75rem 1.25rem;
&::before {
content: '';
display: inline-block;
width: 0.625rem;
height: 0.625rem;
background-color: var(--c-bg-container);
border-radius: 5px;
border: solid 2px var(--c-border-dark);
position: absolute;
top: 50%;
left: -2px;
transform: translate(-50%, -50%);
transition: border-color var(--t-color);
}
> span {
position: absolute;
left: -1.25rem;
top: 50%;
transform: translate(-100%, -50%);
color: var(--c-text-light);
font-size: 14px;
transition: color var(--t-color);
}
> a {
display: inline-block;
width: 100%;
padding: 0.5rem 1.25rem;
background-color: var(--c-bg-container);
border-radius: var(--p-around);
// box-shadow: var(--shadow-sm);
color: var(--c-text);
transition: color var(--t-color), box-shadow var(--t-color);
}
&:hover {
> span,
> a {
color: var(--c-text-accent);
}
// > a {
// box-shadow: var(--shadow);
// }
&::before {
border-color: var(--c-text-accent);
}
}
}
}
}
</style>

View File

@ -1,47 +0,0 @@
<script lang="ts" setup>
import Sidebar from '@theme-plume/Sidebar.vue'
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
import type { SidebarOptions } from '../../shared/index.js'
import { useAsideNavbar, useNavbarConfig } from '../composables/index.js'
const navbarConfig = useNavbarConfig()
const { asideNavbarShow, triggerAsideNavbar } = useAsideNavbar()
</script>
<template>
<Transition name="fade">
<div
v-show="asideNavbarShow"
class="aside-navbar-wrapper"
@click.self="triggerAsideNavbar(false)"
>
<Sidebar :aside="(navbarConfig as SidebarOptions)" />
</div>
</Transition>
</template>
<style lang="scss">
@import '../styles/variables';
.aside-navbar-wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.75);
z-index: 30;
.plume-theme-sidebar-wrapper {
display: block;
position: relative;
top: 0;
right: 0;
width: 70%;
height: 100%;
padding: 1.25rem 0 1.25rem 2.25rem;
}
}
@media (min-width: $MQMobile) {
.aside-navbar-wrapper {
display: none;
}
}
</style>

View File

@ -1,105 +0,0 @@
<script lang="ts">
/* eslint-disable import/first, import/no-duplicates, import/order */
import { defineComponent } from 'vue'
export default defineComponent({
inheritAttrs: false,
})
</script>
<script lang="ts" setup>
import { computed, toRefs } from 'vue'
import type { PropType } from 'vue'
import { useRoute } from 'vue-router'
import { useSiteData } from '@vuepress/client'
import type { NavLink } from '../../shared/index.js'
import { isLinkHttp, isLinkMailto, isLinkTel } from '@vuepress/shared'
const props = defineProps({
item: {
type: Object as PropType<NavLink>,
require: true,
default: () => ({ text: '' }),
},
})
const route = useRoute()
const site = useSiteData()
const { item } = toRefs(props)
const hasHttpProtocol = computed(() => isLinkHttp(item.value.link))
const hasNonHttpProtocol = computed(
() => isLinkMailto(item.value.link) || isLinkTel(item.value.link)
)
const linkTarget = computed(() => {
if (hasNonHttpProtocol.value) return undefined
if (item.value.target) return item.value.target
if (hasHttpProtocol.value) return '_blank'
return undefined
})
const isBlankTarget = computed(() => linkTarget.value === '_blank')
const isRouterLink = computed(
() =>
!hasHttpProtocol.value && !hasNonHttpProtocol.value && !isBlankTarget.value
)
const linkRel = computed(() => {
if (hasNonHttpProtocol.value) return undefined
if (item.value.rel) return item.value.rel
if (isBlankTarget.value) return 'noopener noreferrer'
return undefined
})
const linkAriaLabel = computed(() => item.value.ariaLabel || item.value.text)
const shouldBeActiveInSubpath = computed(() => {
const localeKeys = Object.keys(site.value.locales)
if (localeKeys.length) {
return !localeKeys.some((key) => key === item.value.link)
}
return item.value.link !== '/'
})
const isActiveInSubpath = computed(() => {
if (!shouldBeActiveInSubpath.value) return false
return route.path.startsWith(item.value.link)
})
const isActive = computed(() => {
if (isRouterLink.value) return false
if (item.value.activeMatch) {
return new RegExp(item.value.activeMatch).test(route.path)
}
return isActiveInSubpath.value
})
</script>
<template>
<RouterLink
v-if="isRouterLink"
:class="{ 'router-link-active': isActive }"
:to="item.link"
:aria-label="linkAriaLabel"
v-bind="$attrs"
>
<slot name="before" />
{{ item.text }}
<slot name="after" />
</RouterLink>
<a
v-else
class="external-link"
:href="item.link"
:rel="linkRel"
:target="linkTarget"
:aria-label="linkAriaLabel"
v-bind="$attrs"
>
<slot name="before" />
{{ item.text }}
<ExternalLinkIcon v-if="isBlankTarget" />
<slot name="after" />
</a>
</template>

View File

@ -1,65 +0,0 @@
<script lang="ts" setup>
import { debounce } from 'ts-debounce'
import { onMounted, ref } from 'vue'
import { getScrollTop, scrollTo } from '../utils/index.js'
import { BackTopIcon } from './icons/index.js'
const opacity = ref<number>(0)
const MAX_TOP = 300
const canShow = debounce((): void => {
opacity.value = getScrollTop(document) >= MAX_TOP ? 1 : 0
})
const scrollToTop = (): void => {
scrollTo(document, 0)
}
onMounted(() => {
if (__VUEPRESS_SSR__) return
canShow()
window.addEventListener('scroll', () => canShow(), false)
})
</script>
<template>
<div class="btn-back-top" :style="{ opacity: opacity }" @click="scrollToTop">
<BackTopIcon />
</div>
</template>
<style lang="scss">
@import '../styles/variables';
.btn-back-top {
position: fixed;
right: 3rem;
bottom: 2.1rem;
width: 3rem;
height: 3rem;
text-align: center;
padding: 0.75rem 0;
border-radius: 50%;
background-color: var(--c-bg-container);
box-shadow: var(--shadow-md);
cursor: pointer;
transition: opacity var(--t-color);
z-index: 99;
.back-top-icon {
width: 1.75rem;
height: 1.75rem;
color: var(--c-brand);
transition: color var(--t-color);
}
&:hover {
.back-top-icon {
color: var(--c-brand-light);
}
}
}
@media (max-width: $MQMobile) {
.btn-back-top {
right: 1.25rem;
bottom: 2rem;
}
}
</style>

View File

@ -1,32 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { useThemeLocaleData } from '../composables/index.js'
import BloggerInfo from './BloggerInfo.vue'
const themeLocale = useThemeLocaleData()
const showInfo = computed(() => {
const avatar = themeLocale.value.avatar || {}
return avatar.name || avatar.url
})
</script>
<template>
<aside v-if="showInfo" class="blog-info-wrapper">
<BloggerInfo />
</aside>
</template>
<style lang="scss">
@import '../styles/variables';
.blog-info-wrapper {
width: 18.75rem;
margin-left: 1.25rem;
position: sticky;
top: calc(var(--navbar-height) + 1.25rem);
border-left: solid 1px var(--c-border);
}
@media (max-width: $MQMobile) {
.blog-info-wrapper {
display: none;
}
}
</style>

View File

@ -1,195 +0,0 @@
<script lang="ts" setup>
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
import { isLinkHttp, isLinkMailto } from '@vuepress/shared'
import type { FunctionalComponent, Ref } from 'vue'
import { computed, ref } from 'vue'
import { usePostStat, useThemeLocaleData } from '../composables/index.js'
import {
EmailIcon,
FacebookIcon,
FolderIcon,
GithubIcon,
LinkedinIcon,
PostIcon,
QQIcon,
TagIcon,
TwitterIcon,
WeiBoIcon,
ZhiHuIcon,
} from './icons/index.js'
interface SocialItem {
url: string
icon: FunctionalComponent
}
type SocialData = SocialItem[]
type SocialRef = Ref<SocialData>
const themeLocale = useThemeLocaleData()
const avatar = computed(() => themeLocale.value.avatar || {})
const useSocialList = (): SocialRef => {
const list: SocialRef = ref([])
const social = themeLocale.value.social || {}
if (social.QQ) {
const url = isLinkHttp(social.QQ)
? social.QQ
: `https://wpa.qq.com/msgrd?v=3&uin=${social.QQ}&site=qq&menu=yes`
list.value.push({ url, icon: QQIcon })
}
if (social.email) {
const url = isLinkMailto(social.email)
? social.email
: `mailto:${social.email}`
list.value.push({ url, icon: EmailIcon })
}
if (social.github) {
const url = isLinkHttp(social.github)
? social.github
: `https://github.com/${social.github}`
list.value.push({ url, icon: GithubIcon })
}
if (social.linkedin) {
list.value.push({ url: social.linkedin, icon: LinkedinIcon })
}
if (social.weiBo) {
list.value.push({ url: social.weiBo, icon: WeiBoIcon })
}
if (social.zhiHu) {
list.value.push({ url: social.zhiHu, icon: ZhiHuIcon })
}
if (social.facebook) {
list.value.push({ url: social.facebook, icon: FacebookIcon })
}
if (social.twitter) {
list.value.push({ url: social.twitter, icon: TwitterIcon })
}
return list
}
const socialList = useSocialList()
const postStat = usePostStat()
</script>
<template>
<DropdownTransition>
<section class="blogger-info">
<div class="blogger-profile">
<p v-if="avatar.url" class="avatar-img">
<img :src="avatar.url" :alt="avatar.name" />
</p>
<div>
<h3>{{ avatar.name }}</h3>
<p>{{ avatar.description }}</p>
</div>
</div>
<p class="blogger-social">
<a
v-for="item in socialList"
:key="item.url"
target="_blank"
:href="item.url"
>
<Component :is="item.icon" />
</a>
</p>
<div class="post-stat">
<div class="post-stat-item">
<PostIcon />
<span>{{ postStat.postTotal }}</span>
</div>
<div class="post-stat-item">
<FolderIcon />
<span>{{ postStat.categoryTotal }}</span>
</div>
<div class="post-stat-item">
<TagIcon />
<span>{{ postStat.tagTotal }}</span>
</div>
</div>
</section>
</DropdownTransition>
</template>
<style lang="scss">
.blogger-info {
padding: 1.25rem;
// border-radius: var(--p-around);
// background-color: var(--c-bg-container);
// box-shadow: var(--shadow);
.blogger-profile {
display: flex;
align-items: center;
p {
font-size: 14px;
}
}
.avatar-img {
width: 30%;
padding-right: 0.8rem;
img {
width: 100%;
}
}
p,
h3 {
text-align: left;
margin: 0;
}
h3 {
padding-bottom: 0.5rem;
font-size: 18px;
}
.blogger-social {
vertical-align: middle;
text-align: center;
a {
display: inline-block;
margin: 0.5rem 0.15rem 0;
vertical-align: middle;
}
.icon {
width: 28px;
height: 28px;
}
.email-icon,
.github-icon,
.weiBo-icon {
width: 1.5rem;
height: 1.5rem;
}
}
.post-stat {
// display: flex;
display: none;
justify-content: space-around;
align-items: center;
border-top: 1px solid var(--c-border);
margin-top: 1.75rem;
padding-top: 1rem;
.post-stat-item {
text-align: center;
color: var(--c-text-quote);
.icon {
width: 2rem;
height: 2rem;
color: var(--c-text-lightest);
}
span {
display: inline-block;
width: 100%;
font-size: 1.25rem;
font-weight: 500;
}
}
}
}
</style>

View File

@ -1,43 +0,0 @@
<script lang="ts" setup>
import BlogInfo from '@theme-plume/BlogInfo.vue'
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
import { useCategoryList } from '../composables/index.js'
import CategoryGroup from './CategoryGroup.vue'
const categoryList = useCategoryList()
</script>
<template>
<div class="category-wrapper">
<div class="category-container">
<DropdownTransition>
<div class="category-content">
<CategoryGroup
v-for="(category, index) in categoryList"
:key="category.type + '_' + index"
:category="category"
:index="index"
/>
</div>
</DropdownTransition>
<BlogInfo></BlogInfo>
</div>
</div>
</template>
<style lang="scss">
@import '../styles/_mixins';
.category-wrapper {
@include wrapper;
.category-container {
@include container_wrapper;
display: flex;
align-items: flex-start;
padding: 1.25rem 0;
}
.category-content {
flex: 1;
}
}
</style>

View File

@ -1,98 +0,0 @@
<script lang="ts" setup>
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
import type { FunctionalComponent, PropType } from 'vue'
import { h } from 'vue'
import type { CategoryItem } from '../composables/index.js'
const props = defineProps({
category: {
type: Object as PropType<CategoryItem>,
required: true,
},
head: {
type: Number,
default: 2,
},
index: {
type: Number,
required: true,
},
})
const Heading: FunctionalComponent = () => {
const head = props.head > 4 ? 4 : props.head
return h(
`h${head}`,
{ id: props.category.label.trim().replace(/\s+/g, '-') },
[props.category.label]
)
}
</script>
<template>
<DropdownTransition :delay="index * 0.04">
<section class="category-group-wrapper">
<Heading />
<ul class="category-list">
<li
v-for="post in category.postList"
:key="post.path"
class="category-item"
>
<span>[{{ post.createTime }}]</span>
<RouterLink :to="post.path">{{ post.title }}</RouterLink>
</li>
</ul>
<CategoryGroup
v-for="(cate, i) in category.children"
:key="cate.type + '__' + i"
:category="cate"
:head="head + 1"
:index="i"
/>
</section>
</DropdownTransition>
</template>
<style lang="scss">
.category-group-wrapper {
padding: 1.25rem 1.5rem;
background-color: var(--c-bg-container);
border-radius: var(--p-around);
margin-bottom: 1.25rem;
// box-shadow: var(--shadow);
transition: box-shadow var(--t-color);
.category-group-wrapper {
box-shadow: none;
border-radius: 0;
padding: 0;
margin-left: 1.25rem;
border-bottom: solid 1px var(--c-border);
&:last-child {
border-bottom: none;
}
.category-group-wrapper {
margin-left: 0;
}
}
.category-list {
list-style: none;
padding-left: 1.25rem;
}
.category-item {
span {
color: var(--c-text-lighter);
margin-right: 1.25rem;
}
a {
color: var(--c-text);
&:hover {
color: var(--c-text-accent);
}
}
}
}
</style>

View File

@ -1,76 +0,0 @@
<script setup lang="ts">
import { useDarkMode, useThemeLocaleData } from '../composables/index.js'
const themeLocale = useThemeLocaleData()
const isDarkMode = useDarkMode()
const toggleDarkMode = (): void => {
isDarkMode.value = !isDarkMode.value
}
</script>
<template>
<button
class="toggle-dark-button"
:title="themeLocale.toggleDarkMode"
@click="toggleDarkMode"
>
<svg
v-show="!isDarkMode"
class="icon"
focusable="false"
viewBox="0 0 32 32"
>
<path
d="M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6z"
fill="currentColor"
/>
<path
d="M5.394 6.813l1.414-1.415l3.506 3.506L8.9 10.318z"
fill="currentColor"
/>
<path d="M2 15.005h5v2H2z" fill="currentColor" />
<path
d="M5.394 25.197L8.9 21.691l1.414 1.415l-3.506 3.505z"
fill="currentColor"
/>
<path d="M15 25.005h2v5h-2z" fill="currentColor" />
<path
d="M21.687 23.106l1.414-1.415l3.506 3.506l-1.414 1.414z"
fill="currentColor"
/>
<path d="M25 15.005h5v2h-5z" fill="currentColor" />
<path
d="M21.687 8.904l3.506-3.506l1.414 1.415l-3.506 3.505z"
fill="currentColor"
/>
<path d="M15 2.005h2v5h-2z" fill="currentColor" />
</svg>
<svg v-show="isDarkMode" class="icon" focusable="false" viewBox="0 0 32 32">
<path
d="M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3z"
fill="currentColor"
/>
</svg>
</button>
</template>
<style lang="scss">
.toggle-dark-button {
display: flex;
margin-left: 1rem;
border: 0;
background: none;
color: var(--c-text);
opacity: 0.8;
cursor: pointer;
&:hover {
opacity: 1;
}
.icon {
width: 1.25rem;
height: 1.25rem;
}
}
</style>

View File

@ -1,59 +0,0 @@
<script lang="ts">
import { defineComponent, Transition, TransitionGroup } from 'vue'
import type { PropType } from 'vue'
export default defineComponent({
name: 'DropdownTransition',
components: {
Transition,
TransitionGroup,
},
props: {
type: { type: String as PropType<'single' | 'group'>, default: 'single' },
delay: { type: Number, default: 0 },
duration: { type: Number, default: 0.25 },
},
setup(props) {
const setStyle = (item: Element): void => {
;(
item as HTMLElement
).style.transition = `transform ${props.duration}s ease-in-out ${props.delay}s, opacity ${props.duration}s ease-in-out ${props.delay}s`
;(item as HTMLElement).style.transform = 'translateY(-20px)'
;(item as HTMLElement).style.opacity = '0'
}
const unsetStyle = (item: Element): void => {
;(item as HTMLElement).style.transform = 'translateY(0)'
;(item as HTMLElement).style.opacity = '1'
}
return {
setStyle,
unsetStyle,
}
},
})
</script>
<template>
<Transition
v-if="type === 'single'"
name="drop"
appear
@appear="setStyle"
@after-appear="unsetStyle"
@enter="setStyle"
@after-enter="unsetStyle"
@before-leave="setStyle"
>
<slot />
</Transition>
<TransitionGroup
v-if="type === 'group'"
name="drop"
appear
@appear="setStyle"
@after-appear="unsetStyle"
@enter="setStyle"
@after-enter="unsetStyle"
@before-leave="setStyle"
>
<slot />
</TransitionGroup>
</template>

View File

@ -1,28 +1,3 @@
<script lang="ts" setup>
import BlogInfo from '@theme-plume/BlogInfo.vue'
import HomeBigBanner from '@theme-plume/HomeBigBanner.vue'
import PostList from '@theme-plume/PostList.vue'
</script>
<template>
<div class="plume-theme-home">
<HomeBigBanner></HomeBigBanner>
<div class="plume-theme-container">
<PostList></PostList>
<BlogInfo></BlogInfo>
</div>
</div>
<div>home</div>
</template>
<style lang="scss">
@import '../styles/_mixins';
.plume-theme-home {
@include wrapper;
.plume-theme-container {
@include container_wrapper;
display: flex;
justify-content: flex-start;
align-items: flex-start;
padding: 1.25rem 0;
}
}
</style>

View File

@ -1,166 +0,0 @@
<script lang="ts" setup>
import { usePageFrontmatter, withBase } from '@vuepress/client'
import { isLinkHttp } from '@vuepress/shared'
import { computed, onMounted, ref } from 'vue'
import type { PlumeThemeHomeFrontmatter } from '../../shared/index.js'
import { useThemeLocaleData } from '../composables/index.js'
import { scrollTo } from '../utils/index.js'
import { ArrowBottomIcon } from './icons/index.js'
const frontmatter = usePageFrontmatter<PlumeThemeHomeFrontmatter>()
const MOBILE_WIDTH = 716
const bannerImg = ref(frontmatter.value.banner || '')
const hasBanner = computed(() => !!bannerImg.value)
const bannerStyle = computed(() => {
if (!hasBanner.value) return ''
const url = isLinkHttp(bannerImg.value)
? bannerImg.value
: withBase(bannerImg.value)
return {
'background-image': `url(${url})`,
}
})
function handleResize(): void {
if (__VUEPRESS_SSR__) return
const width = document.documentElement.offsetWidth
if (!hasBanner.value) return
if (width < MOBILE_WIDTH) {
bannerImg.value =
frontmatter.value.mobileBanner || frontmatter.value.banner || ''
} else {
bannerImg.value = frontmatter.value.banner || ''
}
}
onMounted(() => {
handleResize()
window.addEventListener('resize', handleResize, false)
window.addEventListener('orientationchange', handleResize, false)
})
let screenHeight = 0
const arrowHandle = (): void => {
if (!screenHeight) {
screenHeight =
document.documentElement.clientHeight || document.body.clientHeight
screenHeight -=
document.querySelector<HTMLElement>('.navbar-wrapper')?.offsetHeight || 0
}
scrollTo(document, screenHeight)
}
const themeLocale = useThemeLocaleData()
const avatar = themeLocale.value.avatar || {}
</script>
<template>
<div v-if="hasBanner" class="home-big-banner-wrapper" :style="bannerStyle">
<ArrowBottomIcon @click="arrowHandle" />
<div class="home-blogger-info">
<div class="blogger-img">
<img :src="avatar.url" :alt="avatar.name" />
</div>
<h3>{{ avatar.name }}</h3>
<p v-if="frontmatter.motto" class="blogger-motto">
{{ frontmatter.motto }}
</p>
</div>
</div>
</template>
<style lang="scss">
@import '../styles/_variables';
.home-big-banner-wrapper {
position: relative;
display: flex;
width: 100%;
height: calc(100vh - var(--navbar-height));
background-color: transparent;
background-position: 0 0;
background-size: cover;
background-repeat: no-repeat;
background-attachment: fixed;
.arrow-bottom-icon {
position: absolute;
bottom: 1.25rem;
left: 50%;
width: 3rem;
height: 3rem;
color: var(--c-home-arrow-bottom);
animation: home-banner-arrow 1.5s ease 0.3s infinite;
cursor: pointer;
}
.home-blogger-info {
margin: auto;
text-align: center;
.blogger-img {
width: 15rem;
height: 15rem;
border-radius: 50%;
overflow: hidden;
padding: 1.25rem;
background-color: rgba(0, 0, 0, 0.25);
margin: auto;
img {
width: 100%;
border-radius: 50%;
}
}
h3 {
display: inline-block;
font-size: 4rem;
max-width: var(--content-width);
color: rgba(255, 255, 255, 0.85);
padding: 0 1.25rem;
margin: 1rem 0;
}
.blogger-motto {
max-width: var(--content-width);
font-size: 2rem;
color: rgba(255, 255, 255, 0.75);
padding: 0 1.25rem;
border-radius: var(--p-around);
}
}
}
@media (max-width: $MQMobile) {
.home-big-banner-wrapper .home-blogger-info {
.blogger-img {
width: 50vw;
height: 50vw;
}
h3 {
font-size: 3rem;
}
.blogger-motto {
font-size: 1.5rem;
}
}
}
@keyframes home-banner-arrow {
0% {
opacity: 0;
transform: translateX(-50%) translateY(-10px);
}
10% {
opacity: 0.45;
}
95% {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
100% {
opacity: 0.25;
transform: translateX(-50%) translateY(-7px);
}
}
</style>

View File

@ -1,160 +0,0 @@
<script lang="ts" setup>
import DarkModeButton from '@theme-plume/DarkModeButton.vue'
import NavbarBrand from '@theme-plume/NavbarBrand.vue'
import NavbarItems from '@theme-plume/NavbarItems.vue'
import ToggleSidebarButton from '@theme-plume/ToggleSidebarButton.vue'
import { computed, onMounted, ref } from 'vue'
import { useAsideNavbar, useThemeLocaleData } from '../composables/index.js'
import { getCssValue } from '../utils/index.js'
const themeLocale = useThemeLocaleData()
const { triggerAsideNavbar } = useAsideNavbar()
const navbar = ref<HTMLElement | null>(null)
const navbarBrand = ref<HTMLElement | null>(null)
const linksWrapperMaxWith = ref(0)
const linksWrapperStyle = computed(() => {
if (!linksWrapperMaxWith.value) {
return {}
}
return {
maxWidth: linksWrapperMaxWith.value + 'px',
}
})
const enableDarkMode = computed(() => themeLocale.value.darkMode)
onMounted(() => {
const MOBILE_DESKTOP_BREAKPOINT = 719
const navbarHorizontalPadding =
getCssValue(navbar.value, 'paddingLeft') +
getCssValue(navbar.value, 'paddingRight')
const handleLinkWrapWidth = (): void => {
if (window.innerWidth <= MOBILE_DESKTOP_BREAKPOINT) {
linksWrapperMaxWith.value = 0
} else {
linksWrapperMaxWith.value =
navbar.value!.offsetWidth -
navbarHorizontalPadding -
(navbarBrand.value?.offsetWidth || 0)
}
}
handleLinkWrapWidth()
window.addEventListener('resize', handleLinkWrapWidth, false)
window.addEventListener('orientationchange', handleLinkWrapWidth, false)
})
</script>
<template>
<header ref="navbar" class="navbar-wrapper">
<ToggleSidebarButton @toggle="triggerAsideNavbar(true)" />
<span ref="navbarBrand" class="navbar-brand-wrapper">
<NavbarBrand />
</span>
<div class="navbar-items-wrapper" :style="linksWrapperStyle">
<slot name="before" />
<div class="navbar-item-search">
<NavbarSearch />
</div>
<NavbarItems class="can-hide" is-header />
<slot name="after" />
<DarkModeButton v-if="enableDarkMode" />
</div>
</header>
</template>
<style lang="scss">
@import '../styles/variables';
.navbar-wrapper {
--navbar-line-height: calc(
var(--navbar-height) - 2 * var(--navbar-padding-v)
);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: var(--navbar-height);
padding: 0 0 0 var(--navbar-padding-h);
background-color: var(--c-bg-navbar);
backdrop-filter: saturate(50%) blur(8px);
// box-shadow: var(--shadow);
line-height: var(--navbar-line-height);
transition: background-color 0.3s ease;
.navbar-brand-wrapper {
display: inline-block;
height: 100%;
}
.logo {
height: var(--navbar-line-height);
margin-right: var(--navbar-padding-v);
vertical-align: top;
}
.site-name {
font-size: 1.3rem;
font-weight: 600;
color: var(--c-text);
position: relative;
transition: color 0.3s ease;
}
.navbar-items-wrapper {
display: flex;
align-items: center;
flex-grow: 1;
white-space: nowrap;
font-size: 0.9rem;
height: 100%;
padding-right: var(--navbar-padding-h);
.navbar-item-search {
flex-grow: 1;
padding-left: 1.5rem;
}
.search-box {
flex: 0 0 auto;
vertical-align: top;
}
.navbar-items .navbar-item {
& > .router-link-active {
color: var(--c-text-accent);
}
}
}
}
.DocSearch {
transition: background-color var(--t-color);
}
@media (max-width: $MQMobile) {
.navbar-wrapper {
padding-left: 4rem;
.can-hide {
display: none;
}
.site-name {
width: calc(100vw - 9.4rem);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.navbar-items-wrapper {
justify-content: flex-end;
.navbar-item-search {
flex-grow: 0;
}
}
}
}
</style>

View File

@ -1,84 +0,0 @@
<script lang="ts" setup>
import {
ClientOnly,
useRouteLocale,
useSiteLocaleData,
withBase,
} from '@vuepress/client'
import { computed, h } from 'vue'
import type { FunctionalComponent } from 'vue'
import type { NavLink } from '../../shared/index.js'
import {
useDarkMode,
useSidebarIndex,
useThemeLocaleData,
} from '../composables/index.js'
const routeLocale = useRouteLocale()
const siteLocale = useSiteLocaleData()
const themeLocale = useThemeLocaleData()
const isDarkMode = useDarkMode()
const { hasSidebar } = useSidebarIndex()
const navbarBrandLink = computed(
() => (themeLocale.value.home as NavLink)?.link || routeLocale.value
)
const navbarBrandTitle = computed(() => siteLocale.value.title)
const navbarBrandLogo = computed(() => {
if (isDarkMode.value && themeLocale.value.logoDark !== undefined) {
return themeLocale.value.logoDark
}
return themeLocale.value.logo
})
const NavbarBrandLogo: FunctionalComponent = () => {
if (!navbarBrandLogo.value) return null
const img = h('img', {
class: 'logo',
src: withBase(navbarBrandLogo.value),
alt: navbarBrandTitle.value,
})
if (themeLocale.value.logoDark === undefined) {
return img
}
return h(ClientOnly, img)
}
</script>
<template>
<RouterLink
:to="navbarBrandLink"
:class="{
'navbar-brand': true,
'has-sidebar': hasSidebar,
}"
>
<NavbarBrandLogo />
<span
v-if="navbarBrandTitle"
class="site-name"
:class="{ 'can-hide': navbarBrandLogo }"
>
{{ navbarBrandTitle }}
</span>
</RouterLink>
</template>
<style lang="scss">
@import '../styles/variables';
.navbar-brand {
display: flex;
height: 100%;
align-items: center;
&.has-sidebar {
width: calc(18rem - var(--navbar-padding-h));
border-bottom: solid 1px var(--c-border);
}
}
@media (max-width: $MQMobile) {
.navbar-brand.has-sidebar {
width: auto;
border-bottom: none;
}
}
</style>

View File

@ -1,334 +0,0 @@
<script lang="ts" setup>
import AutoLink from '@theme-plume/AutoLink.vue'
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
import type { PropType } from 'vue'
import { computed, ref, toRefs, watch } from 'vue'
import { useRoute } from 'vue-router'
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
import type {
NavbarItem,
NavGroup,
ResolveNavbarItem,
} from '../../shared/index.js'
const props = defineProps({
item: {
type: Object as PropType<Exclude<ResolveNavbarItem, NavbarItem>>,
required: true,
},
isHeader: {
type: Boolean,
required: true,
},
})
const { item } = toRefs(props)
const dropdownAriaLabel = computed(
() => item.value.ariaLabel || item.value.text
)
const open = ref(false)
const route = useRoute()
watch(
() => route.path,
() => {
open.value = false
}
)
const handleDropdown = (e: MouseEvent): void => {
const isTriggerByTab = e.detail === 0
if (isTriggerByTab || props.isHeader) {
open.value = !open.value
} else {
open.value = false
}
}
const isLastItemOfArray = (item: unknown, arr: unknown[]): boolean =>
arr[arr.length - 1] === item
const onSubTitleFocusout = (child: any): void => {
if (
isLastItemOfArray(child, item.value.children) &&
child.children &&
child.children.length === 0
) {
open.value = false
}
}
const onGrandChildFocusout = (grandchild: unknown, child: any): void => {
if (
isLastItemOfArray(grandchild, child.children) &&
isLastItemOfArray(child, item.value.children)
) {
open.value = false
}
}
</script>
<template>
<div
class="navbar-dropdown-wrapper"
:class="{ open }"
@mouseleave="open = false"
>
<button
v-if="isHeader"
class="navbar-dropdown-title"
type="button"
:aria-label="dropdownAriaLabel"
@click="handleDropdown"
@mouseenter="open = true"
>
<span class="title">{{ item.text }}</span>
<span class="arrow down"></span>
</button>
<button
v-else
class="navbar-dropdown-title-mobile"
type="button"
:aria-label="dropdownAriaLabel"
@click="open = !open"
>
<span class="title">{{ item.text }}</span>
<span class="arrow" :class="open ? 'down' : 'right'"></span>
</button>
<DropdownTransition>
<ul v-show="open" class="navbar-dropdown">
<li
v-for="child in item.children"
:key="child.text"
class="navbar-dropdown-item"
>
<template v-if="(child as NavGroup<NavbarItem>).children">
<h4 class="navbar-dropdown-subtitle">
<AutoLink
v-if="(child as NavbarItem).link"
:item="(child as NavbarItem)"
@focusout="onSubTitleFocusout(child)"
/>
<span v-else>{{ child.text }}</span>
</h4>
<ul class="navbar-dropdown-subitem-wrapper">
<li
v-for="grandchild in (child as unknown as NavGroup<NavbarItem>).children"
:key="grandchild.link"
class="navbar-dropdown-subitem"
>
<AutoLink
:item="grandchild"
@focusout="onGrandChildFocusout(grandchild, child)"
/>
</li>
</ul>
</template>
<template v-else>
<AutoLink
:item="(child as NavbarItem)"
@focusout="
isLastItemOfArray(child, item.children) && (open = false)
"
/>
</template>
</li>
</ul>
</DropdownTransition>
</div>
</template>
<style lang="scss">
@import '../styles/_variables';
@import '../styles/_mixins';
.navbar-dropdown-wrapper {
cursor: pointer;
.navbar-dropdown-title {
display: block;
font-size: 1rem;
font-family: inherit;
cursor: inherit;
padding: inherit;
line-height: 1.4rem;
background: transparent;
border: none;
font-weight: 500;
color: var(--c-text);
&:hover {
border-color: transparent;
}
.arrow {
vertical-align: middle;
margin-top: -1px;
margin-left: 0.4rem;
}
}
.navbar-dropdown-title-mobile {
@extend .navbar-dropdown-title;
display: none;
font-weight: 600;
font-size: inherit;
&:hover {
color: var(--c-text-accent);
}
}
.navbar-dropdown {
list-style: none;
.navbar-dropdown-item {
color: inherit;
line-height: 1.7rem;
cursor: default;
.navbar-dropdown-subtitle {
margin: 0.45rem 0 0;
border-top: 1px solid var(--c-border);
padding: 1rem 0 0.45rem 0;
font-size: 1rem;
color: var(--c-text-light);
& > span {
padding: 0 1.5rem;
}
& > a {
font-weight: inherit;
&.router-link-active {
&::after {
display: none;
}
}
}
}
.navbar-dropdown-subitem-wrapper {
padding: 0;
list-style: none;
.navbar-dropdown-subitem {
font-size: 1em;
}
}
a {
display: block;
line-height: 1.7rem;
position: relative;
border-bottom: none;
font-weight: 400;
margin-bottom: 0;
padding: 0 1.5rem 0 1.25rem;
&:hover {
color: var(--c-text-accent);
}
&.router-link-active {
color: var(--c-text-accent);
&::after {
content: '';
width: 0;
height: 0;
border-left: 5px solid var(--c-text-accent);
border-top: 3px solid transparent;
border-bottom: 3px solid transparent;
position: absolute;
top: calc(50% - 2px);
left: 9px;
}
}
}
&:first-child .navbar-dropdown-subtitle {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
}
}
}
@media (max-width: $MQMobile) {
.navbar-dropdown-wrapper {
&.open .navbar-dropdown-title {
margin-bottom: 0.5rem;
}
.navbar-dropdown-title {
display: none;
}
.navbar-dropdown-title-mobile {
display: block;
}
.navbar-dropdown {
@include dropdown_wrapper;
.navbar-dropdown-item {
.navbar-dropdown-subtitle {
padding-top: 0;
margin-top: 0;
border-top: 0;
padding-bottom: 0;
}
.navbar-dropdown-subtitle,
& > a {
font-size: 1rem;
line-height: 2rem;
}
.navbar-dropdown-subitem {
font-size: 1rem;
padding-left: 1rem;
}
}
}
}
}
@media (min-width: ($MQMobile + 1)) {
.navbar-dropdown-wrapper {
height: 1.8rem;
&.open .navbar-dropdown {
opacity: 1;
transform: none;
}
.navbar-dropdown {
opacity: 0;
transform: translateY(-0.5rem);
transition: opacity 0.3s ease, transform 0.3s ease;
height: auto !important;
max-height: calc(100vh - 2.7rem);
overflow-y: auto;
position: absolute;
top: 100%;
right: 0;
box-sizing: border-box;
background-color: var(--c-bg-container);
padding: 1.5rem 0.75rem;
border: 1px solid var(--c-border);
border-bottom-color: var(--c-border-dark);
text-align: left;
border-radius: 0.25rem;
white-space: nowrap;
margin: 0;
box-shadow: var(--shadow);
}
}
}
</style>

View File

@ -1,79 +0,0 @@
<script lang="ts" setup>
import AutoLink from '@theme-plume/AutoLink.vue'
import NavbarDropdown from '@theme-plume/NavbarDropdown.vue'
import { computed } from 'vue'
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
import type {
NavGroup,
NavLink,
ResolveNavbarItem,
} from '../../shared/index.js'
import {
useNavbarConfig,
useNavbarRepo,
useNavbarSelectLanguage,
} from '../composables/index.js'
defineProps({
isHeader: {
type: Boolean,
required: false,
default: false,
},
})
const navbarConfig = useNavbarConfig()
const navbarSelectLanguage = useNavbarSelectLanguage()
const navbarRepo = useNavbarRepo()
const navbarLinks = computed(() => [
...navbarConfig.value,
...navbarSelectLanguage.value,
...navbarRepo.value,
])
</script>
<template>
<nav v-if="navbarLinks.length" class="navbar-items">
<div v-for="item in navbarLinks" :key="item.text" class="navbar-item">
<NavbarDropdown
v-if="(item as NavGroup<ResolveNavbarItem>).children"
:item="(item as NavGroup<ResolveNavbarItem>)"
:is-header="isHeader"
/>
<AutoLink v-else :item="(item as NavLink)" />
</div>
</nav>
</template>
<style lang="scss">
.navbar-wrapper {
.navbar-items {
--navbar-line-height: calc(
var(--navbar-height) - 2 * var(--navbar-padding-v)
);
display: inline-block;
a {
display: inline-block;
line-height: 1.4rem;
color: inherit;
&:hover,
&.router-lint-active {
color: var(--c-text-accent);
}
}
.navbar-item {
position: relative;
display: inline-block;
margin-left: 1.5rem;
line-height: var(--navbar-line-height);
&:first-child {
margin-left: 0;
}
}
}
}
</style>

View File

@ -1,110 +0,0 @@
<script lang="ts" setup>
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
import PostMeta from '@theme-plume/PostMeta.vue'
import Sidebar from '@theme-plume/Sidebar.vue'
import { usePageData } from '@vuepress/client'
import { computed } from 'vue'
import type { PlumeThemePageData } from '../../shared/index.js'
import { useDarkMode } from '../composables/index.js'
import Toc from './Toc.js'
const page = usePageData<PlumeThemePageData>()
const isDarkMode = useDarkMode()
const isNote = computed(() => {
return page.value.isNote || false
})
const enabledSidebar = computed(() => {
return isNote.value
})
</script>
<template>
<DropdownTransition>
<main class="page-wrapper">
<slot name="top"></slot>
<div class="page-container" :class="{ 'has-sidebar': enabledSidebar }">
<main class="plume-theme-content">
<Sidebar v-if="enabledSidebar" />
<div class="page-content" :class="{ 'note-content': isNote }">
<h1>{{ page.title }}</h1>
<PostMeta :post="page" type="post" border />
<Content class="post-content" />
<div class="comment-container">
<Comment :darkmode="isDarkMode" />
</div>
</div>
<div v-if="page.headers?.length > 0" class="plume-theme-page-toc">
<Toc />
</div>
</main>
</div>
<slot name="bottom"></slot>
</main>
</DropdownTransition>
</template>
<style lang="scss">
@import '../styles/_mixins';
@import '../styles/variables';
.page-wrapper {
@include wrapper;
.page-container {
display: flex;
// padding-top: 1.25rem;
padding-bottom: 1.25rem;
.plume-theme-content {
@include container_wrapper;
@include content;
display: flex;
flex: 1;
}
.page-content {
flex: 1;
width: 100%;
max-width: var(--content-width);
padding: 0 2rem 1rem;
margin: 0 auto;
&.note-content {
max-width: var(--content-note-width);
}
}
.post-content {
padding-top: 2rem;
}
img {
max-width: 100%;
}
&.has-sidebar {
padding-top: 0;
padding-bottom: 0;
.plume-theme-content {
max-width: 100%;
}
}
}
}
.comment-container {
margin-top: 8rem;
}
@media (max-width: $MQMobile) {
.page-wrapper .page-container .page-content {
padding: 0 0.75rem 1rem;
h1 {
font-size: 1.5rem;
}
}
.plume-theme-page-toc {
display: none;
}
}
</style>

View File

@ -1,49 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { useThemeLocaleData } from '../composables/index.js'
const themeLocale = useThemeLocaleData()
const footer = computed(() => {
return themeLocale.value.footer
})
</script>
<template>
<footer v-if="footer" class="theme-plume-footer">
<!-- eslint-disable vue/no-v-html -->
<div
v-if="footer.content"
class="theme-plume-footer-content"
v-html="footer.content"
></div>
<div
v-if="footer.copyright"
class="theme-plume-footer-copyright"
v-html="footer.copyright"
></div>
</footer>
</template>
<style lang="scss">
.theme-plume-footer {
position: absolute;
left: 0;
bottom: 0;
display: flex;
justify-content: flex-start;
align-items: flex-start;
width: 100%;
padding: 1.25rem;
background-color: var(--c-bg-container);
// box-shadow: var(--shadow-footer);
font-size: 0.875rem;
text-align: center;
.theme-plume-footer-content {
flex: 1;
}
.theme-plume-footer-copyright {
margin: auto;
padding: 0 1.25rem;
}
}
</style>

View File

@ -1,203 +0,0 @@
<script lang="ts" setup>
import { useOffsetPagination } from '@vueuse/core'
import { computed, ref, toRefs } from 'vue'
import { ArrowDoubleLeftIcon, ArrowDoubleRightIcon } from './icons/index.js'
const emit = defineEmits(['togglePage'])
const props = defineProps({
page: {
type: Number,
required: true,
},
total: {
type: Number,
required: true,
},
})
const { page, total } = toRefs(props)
function handlePage({ currentPage }: { currentPage: number }): void {
emit('togglePage', currentPage)
}
const { currentPage, pageCount, isFirstPage, isLastPage, prev, next } =
useOffsetPagination({
total: total.value,
page: page.value,
pageSize: 10,
onPageChange: handlePage,
onPageCountChange: handlePage,
})
const pageList = computed(() => {
const list: (number | '')[] = []
const count = pageCount.value
const current = currentPage.value
if (count <= 3 || current <= 2) {
new Array(Math.min(3, count)).fill(0).forEach((_, i) => list.push(i + 1))
if (count > 3) {
list.push('')
list.push(count)
}
} else if (current > count - 2) {
list.push(1)
list.push('')
new Array(3).fill(count - 2).forEach((_, i) => list.push(_ + i))
} else {
list.push(1)
current > 3 && list.push('')
;[current - 1, current, current + 1].forEach((page: number) =>
list.push(page)
)
current < count - 2 && list.push('')
list.push(count)
}
return list
})
const inputPage = ref(1)
function handleJump(): void {
if (
inputPage.value &&
inputPage.value >= 1 &&
inputPage.value <= pageCount.value &&
currentPage.value !== Number(inputPage.value)
) {
currentPage.value = Number(inputPage.value)
emit('togglePage', inputPage.value)
}
}
</script>
<template>
<div v-if="pageCount > 1" class="pagination-wrapper">
<div class="pagination-container">
<button
type="button"
class="btn-prev"
:disabled="isFirstPage"
@click="prev"
>
<ArrowDoubleLeftIcon />
</button>
<template v-for="count in pageList" :key="count">
<button
v-if="count"
type="button"
:disabled="count === currentPage"
@click="currentPage = count"
>
{{ count }}
</button>
<button v-else type="button" disabled>..</button>
</template>
<button
type="button"
class="btn-next"
:disabled="isLastPage"
@click="next"
>
<ArrowDoubleRightIcon />
</button>
</div>
<div class="pagination-form can-hide">
<span>跳转到</span>
<input v-model="inputPage" type="number" :min="1" :max="pageCount" />
<span>/{{ pageCount }}</span>
<button type="button" class="btn-jump" @click="handleJump">确认</button>
</div>
</div>
</template>
<style lang="scss">
@import '../styles/variables';
.pagination-wrapper {
display: flex;
justify-content: flex-start;
align-items: center;
text-align: center;
font-size: 0.875rem;
.pagination-container {
flex: 1;
button {
// border-right: solid 1px var(--c-border);
&:last-of-type {
border-right: none;
}
}
}
button {
background-color: var(--c-bg-container);
cursor: pointer;
font-size: inherit;
padding: 0 0.8rem;
height: 2.125rem;
line-height: 2.125rem;
border: solid 1px transparent;
color: var(--c-text);
// box-shadow: var(--shadow-sm);
&:disabled {
color: var(--c-text-accent);
cursor: unset;
}
&.btn-prev,
&.btn-next,
&.btn-jump {
color: var(--c-text-accent);
&:disabled {
color: var(--c-text);
}
}
&.btn-prev,
&.btn-next {
.icon {
width: 0.875rem;
height: 0.875rem;
vertical-align: text-top;
}
}
}
.pagination-form {
input {
font-size: inherit;
padding: 0.5rem;
width: 3.25rem;
height: 2.125rem;
line-height: 2.125rem;
border: solid 1px transparent;
color: var(--c-text);
// box-shadow: var(--shadow-sm);
outline: 0;
background-color: var(--c-bg-light);
margin-right: 0.5rem;
&:focus {
border-color: var(--c-brand);
}
}
span {
margin-right: 0.5rem;
}
}
}
@media (max-width: $MQMobile) {
.pagination-wrapper {
.can-hide {
display: none;
}
// .pagination-container {
// display: flex;
// justify-content: space-between;
// padding: 0 2rem;
// }
}
}
</style>

View File

@ -1,59 +0,0 @@
<script lang="ts" setup>
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
import type { PropType } from 'vue'
import { useRouter } from 'vue-router'
import type { PostItem } from '../../shared/index.js'
import AutoLink from './AutoLink.vue'
import { TopIcon } from './icons/index.js'
import PostMeta from './PostMeta.vue'
defineProps({
post: {
type: Object as PropType<PostItem>,
required: true,
},
index: {
type: Number,
default: 0,
},
})
const router = useRouter()
const handlePost = (path: string): void => {
router.push({ path })
}
</script>
<template>
<DropdownTransition :delay="index * 0.04">
<section :key="post.path" class="post-list-item">
<div :class="{ sticky: post.sticky }">
<TopIcon v-if="post.sticky" />
<!-- <div
v-if="post.banner"
class="post-banner"
@click="handlePost(post.path)"
>
<div
:style="{
'background-image': `url(${post.banner})`,
}"
></div>
</div> -->
<h3>
<AutoLink :item="{ text: post.title, link: post.path }" />
</h3>
<PostMeta :post="post" :show-author="false" />
<!--eslint-disable vue/no-v-html-->
<div
v-if="post.excerpt"
class="post-excerpt"
v-html="post.excerpt"
></div>
<div v-if="post.excerpt" class="post-more">
<AutoLink :item="{ text: '阅读全文', link: post.path }" />
</div>
</div>
</section>
</DropdownTransition>
</template>

View File

@ -1,195 +0,0 @@
<script lang="ts" setup>
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
import PostItem from '@theme-plume/PostItem.vue'
import { usePageFrontmatter } from '@vuepress/client'
import type { PropType } from 'vue'
import { nextTick, onMounted, toRefs, watch } from 'vue'
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
import type { PlumeThemeHomeFrontmatter } from '../../shared/index.js'
import type { PostListData } from '../composables/index.js'
import { usePostList } from '../composables/index.js'
import { scrollTo } from '../utils/index.js'
import Pagination from './Pagination.vue'
const props = defineProps({
postList: {
type: Array as PropType<PostListData | undefined>,
required: false,
default: () => undefined,
},
})
const router = useRouter()
const frontmatter = usePageFrontmatter<PlumeThemeHomeFrontmatter>()
const propsRef = toRefs(props)
const { postList, total, page, setPostListPage, resetPostIndex } = usePostList()
watch(
[propsRef.postList],
([newPostList]) => {
newPostList && resetPostIndex(newPostList as unknown as PostListData)
},
{ immediate: true }
)
const route = useRoute()
let offsetHeight = 0
onBeforeRouteUpdate((to, from) => {
if (__VUEPRESS_SSR__) return
if (to.fullPath === from.fullPath) return
setPostListPage((to.query.p as unknown as number) || 1)
const { home, banner, mobileBanner } = frontmatter.value
let top = 0
if (home && (banner || mobileBanner)) {
offsetHeight =
offsetHeight ||
document.querySelector<HTMLElement>('.navbar-wrapper')?.offsetHeight ||
0
top = document.documentElement.clientHeight - offsetHeight
}
setTimeout(() => scrollTo(document, top), 0)
})
onMounted(async () => {
if (__VUEPRESS_SSR__) return
await nextTick()
setPostListPage((route.query.p as unknown as number) || 1)
})
const togglePage = (currentPage: number): void => {
router.push({
path: route.path,
query: {
...route.query,
p: currentPage,
},
})
}
</script>
<template>
<div class="post-list-wrapper">
<DropdownTransition>
<div>
<PostItem
v-for="(post, index) in postList"
:key="post.path"
:post="post"
:index="index"
></PostItem>
</div>
</DropdownTransition>
<Pagination :page="page" :total="total" @toggle-page="togglePage" />
</div>
</template>
<style lang="scss">
@import '../styles/_variables';
.post-list-wrapper {
flex: 1;
padding-top: 2rem;
.post-list-item {
> div {
position: relative;
padding: 1.25rem 1.5rem;
background-color: var(--c-bg-container);
border-radius: var(--p-around);
margin-bottom: 2.25rem;
// box-shadow: var(--shadow);
transition: box-shadow var(--t-color);
overflow: hidden;
&.sticky {
background-color: var(--c-bg-sticky);
}
// &:hover {
// box-shadow: var(--shadow-lg);
// }
}
.top-icon {
position: absolute;
top: 0;
left: 0;
width: 2.65rem;
height: 2.65rem;
color: var(--c-brand);
}
}
.post-banner {
position: relative;
height: 18.75rem;
margin: -1.25rem -1.5rem 1.25rem -1.5rem;
overflow: hidden;
cursor: pointer;
> div {
width: 100%;
height: 100%;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
transform: scale(100%);
transition: transform var(--t-transform);
&:hover {
transform: scale(120%);
}
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 1.5rem;
width: 0;
height: 0;
border: solid 1.25rem;
border-color: transparent transparent var(--c-bg-container) transparent;
z-index: 1;
}
}
h3 {
width: 100%;
margin-top: 0;
overflow: hidden;
text-overflow: ellipsis;
a {
color: var(--c-text);
}
}
.post-excerpt {
padding-top: 1.25rem;
:first-child {
margin-top: 0;
}
:last-child {
margin-bottom: 1rem;
}
}
.post-more {
text-align: right;
a {
display: inline-block;
padding: 0.5rem 0.75rem;
border-radius: var(--p-around);
background-color: var(--c-bg);
color: var(--c-brand);
}
}
}
@media (max-width: $MQMobile) {
.post-list-wrapper {
.post-list-item {
border-radius: 0;
}
}
}
</style>

View File

@ -1,126 +0,0 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import type { PostItem } from '../../shared/index.js'
import { useThemeLocaleData } from '../composables/index.js'
import { getColor, normalizePath } from '../utils/index.js'
import { ClockIcon, FolderIcon, TagIcon, UserIcon } from './icons/index.js'
const props = defineProps({
post: {
type: Object as PropType<PostItem>,
required: true,
},
border: {
type: Boolean,
required: false,
default: false,
},
showAuthor: {
type: Boolean,
default: true,
},
})
const route = useRoute()
const router = useRouter()
const themeLocale = useThemeLocaleData()
const tags = computed(() => {
return (props.post.tags || []).filter((_, i) => i < 4)
})
const category = computed(() => {
return (props.post.category || []).filter((cate) =>
Boolean(cate.name?.trim())
)
})
const handleTag = (tag: string): void => {
const tagConfig = themeLocale.value.tag
if (!tagConfig) return
const link = tagConfig.link.replace(/^\/|\/$/g, '')
router.replace({
path: `/${link}/`,
query: { tag: normalizePath(tag) },
})
}
</script>
<template>
<div class="post-meta" :class="{ border: post.excerpt || border }">
<div v-if="post.author && showAuthor" class="post-meta-author">
<UserIcon />
<span>{{ post.author }}</span>
</div>
<div v-if="category.length > 0" class="post-meta-category">
<FolderIcon />
<template v-for="(cate, i) in category" :key="cate.type">
<span>{{ cate.name }}</span>
<span v-if="i < category.length - 1"> / </span>
</template>
</div>
<div v-if="tags.length > 0">
<TagIcon />
<template v-for="tag in tags" :key="tag">
<span
class="post-meta-tag"
:style="{ 'background-color': getColor() }"
@click="handleTag(tag)"
>
{{ tag }}
</span>
</template>
</div>
<div class="post-meta-create-time">
<ClockIcon />
<span>{{ post.createTime }}</span>
</div>
</div>
</template>
<style lang="scss">
.post-meta {
color: var(--c-text-light);
overflow: hidden;
font-size: 14px;
&.border {
border-bottom: solid 1px var(--c-border);
}
& > div {
float: left;
display: flex;
justify-content: flex-start;
align-items: center;
margin-right: 1.25rem;
height: 2rem;
line-height: 1.5rem;
padding-bottom: 0.5rem;
}
.icon {
width: 0.875rem;
height: 0.875rem;
margin-right: 0.2rem;
color: var(--c-text-lighter);
}
.post-meta-tag {
display: inline-block;
height: 1.25rem;
line-height: 1.25rem;
padding: 0 0.4rem;
color: #fff;
border-radius: 0.75rem;
margin: 0 0.15rem;
cursor: pointer;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
}
</style>

View File

@ -1,94 +0,0 @@
<script lang="ts" setup>
import SidebarItems from '@theme-plume/SidebarItems.vue'
import type { PropType } from 'vue'
import { onMounted, ref, watchEffect } from 'vue'
import { useRoute } from 'vue-router'
import type { SidebarOptions } from '../../shared/index.js'
import { useAsideNavbar, useSidebarIndex } from '../composables/index.js'
defineProps({
aside: {
type: Array as PropType<SidebarOptions>,
required: false,
default: () => [],
},
})
const route = useRoute()
const { sidebarList } = useSidebarIndex()
const { triggerAsideNavbar } = useAsideNavbar()
watchEffect(() => {
triggerAsideNavbar(false)
})
const el = ref<HTMLElement | null>(null)
onMounted(() => {
const activeEl = el.value?.querySelector<HTMLElement>('.router-link-active')
activeEl && activeEl.scrollIntoView(false)
})
</script>
<template>
<aside
:ref="(e) => (el = e as HTMLElement)"
class="plume-theme-sidebar-wrapper"
>
<SidebarItems
v-if="aside.length"
class="aside-navbar"
:sidebar-list="aside"
/>
<SidebarItems :sidebar-list="sidebarList" />
</aside>
</template>
<style lang="scss">
@import '../styles/variables';
.plume-theme-sidebar-wrapper {
position: sticky;
top: calc(var(--navbar-height) + 1.25rem);
width: 18rem;
flex-shrink: 0;
height: calc(100vh - var(--navbar-height) - 1.25rem);
border-right: solid 1px var(--c-border);
font-size: 1.125rem;
padding-left: 1.25rem;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--c-brand) var(--c-border);
background-color: var(--c-bg-container);
transition: transform var(--t-color), background-color var(--t-color);
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar-track {
background-color: var(--c-border);
}
&::-webkit-scrollbar-thumb {
border-radius: 3.5px;
background-color: rgba(34, 34, 34, 0.75);
}
> .aside-navbar {
position: relative;
padding-bottom: 0.75rem;
margin-bottom: 1.25rem;
&::after {
content: '';
position: absolute;
left: -1.25rem;
bottom: -0.25rem;
right: 0;
border-bottom: solid 4px var(--c-border);
}
}
}
@media (max-width: $MQMobile) {
.plume-theme-sidebar-wrapper {
display: none;
}
}
</style>

View File

@ -1,157 +0,0 @@
<script lang="ts" setup>
import AutoLink from '@theme-plume/AutoLink.vue'
import { computed, ref, watchEffect } from 'vue'
import type { PropType } from 'vue'
import type { SidebarItem, SidebarOptions } from '../../shared/index.js'
import { useThemeLocaleData } from '../composables/index.js'
import { hasOwn } from '../utils/index.js'
import { ArrowRightIcon } from './icons/index.js'
type SidebarListComputed = SidebarItem & { open: boolean }
const props = defineProps({
sidebarList: {
type: Array as PropType<SidebarOptions>,
required: true,
},
deep: {
type: Number,
required: false,
default: 1,
},
})
const themeLocale = useThemeLocaleData()
const collapsible = computed(() => {
if (
!themeLocale.value.notes ||
!hasOwn(themeLocale.value.notes, 'collapsible')
) {
return true
} else {
return themeLocale.value.notes.collapsible
}
})
const sidebarList = ref<SidebarListComputed[]>([])
watchEffect(() => {
sidebarList.value = props.sidebarList.map((sidebar) => {
return { ...sidebar, open: !!collapsible.value }
})
})
const sidebarClick = (sidebar: SidebarListComputed): void => {
if (props.deep === 1) {
sidebar.open = !sidebar.open
}
}
</script>
<template>
<ul class="sidebar-items">
<li
v-for="sidebar in sidebarList"
:key="sidebar.text"
:class="{ line: deep === 1 }"
>
<p
:class="{
'sidebar-items-title': deep === 1,
'sidebar-items-subtitle':
deep > 1 && sidebar.children && sidebar.children.length,
}"
@click.self="sidebarClick(sidebar)"
>
<ArrowRightIcon
v-if="deep === 1 && sidebar.children && sidebar.children.length"
:class="{ open: sidebar.open }"
@click.self="sidebarClick(sidebar)"
/>
<AutoLink
v-if="sidebar.link"
:item="{ text: sidebar.text, link: sidebar.link }"
/>
<span v-else @click.self="sidebarClick(sidebar)">
{{ sidebar.text }}
</span>
</p>
<SidebarItems
v-if="sidebar.children && sidebar.children.length"
v-show="sidebar.open"
:sidebar-list="sidebar.children"
:deep="deep + 1"
/>
</li>
</ul>
</template>
<style lang="scss">
.plume-theme-sidebar-wrapper {
.sidebar-items {
list-style: none;
margin: 0;
padding: 0;
li {
a {
color: var(--c-sidebar-text);
margin: 0.25rem 0;
font-weight: 500;
flex: 1;
&:hover {
color: var(--c-text-accent);
}
&.router-link-active {
color: var(--c-text-accent);
}
}
span {
font-weight: 600;
margin: 0.25rem 0;
flex: 1;
}
p {
margin: 0.25rem 0;
}
p.sidebar-items-title {
position: relative;
margin: 0;
padding: 0.25rem 0;
line-height: 1.55;
cursor: pointer;
.arrow-right-icon {
position: absolute;
left: -1.5rem;
top: 8px;
width: 1.25rem;
height: 1.25rem;
transform: rotate(0);
transition: transform var(--t-color);
color: var(--c-text-quote);
&.open {
transform: rotate(90deg);
}
}
}
p.sidebar-items-subtitle {
color: var(--c-text);
font-weight: bolder;
}
&.line {
// border-bottom: solid 1px var(--c-border);
&:last-child {
border-bottom: none;
}
}
}
.sidebar-items {
font-size: 16px;
}
}
}
</style>

View File

@ -1,100 +0,0 @@
<script lang="ts" setup>
import BlogInfo from '@theme-plume/BlogInfo.vue'
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
import PostList from '@theme-plume/PostList.vue'
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import type { PostListRef } from '../composables/index.js'
import { usePostAllIndex, useTagList } from '../composables/index.js'
import { normalizePath } from '../utils/index.js'
const tagList = useTagList()
const route = useRoute()
const router = useRouter()
const postList: PostListRef = ref([])
const postAllList = usePostAllIndex()
const currentTag = computed(() => {
return route.query.tag || ''
})
watch(
[currentTag, route],
([nowTag]) => {
if (nowTag) {
postList.value = postAllList.value.filter((post) => {
return post.tags.some((tag) => normalizePath(tag) === nowTag)
})
} else {
postList.value = []
}
},
{ immediate: true }
)
const handleTag = (tag: string): void => {
router.replace({
path: route.path,
query: { tag: normalizePath(tag) },
})
}
</script>
<template>
<main class="tag-wrapper">
<div class="tag-container">
<div class="tag-content">
<DropdownTransition>
<section class="tag-list">
<span
v-for="{ tag, color } in tagList"
:key="tag"
class="tag"
:style="{ 'background-color': color }"
@click="handleTag(tag)"
>{{ tag }}</span
>
</section>
</DropdownTransition>
<PostList :post-list="postList"></PostList>
</div>
<BlogInfo></BlogInfo>
</div>
</main>
</template>
<style lang="scss">
@import '../styles/_mixins';
.tag-wrapper {
@include wrapper;
.tag-container {
@include container_wrapper;
display: flex;
align-items: flex-start;
padding: 1.25rem 0;
}
.tag-content {
flex: 1;
}
.tag-list {
width: 100%;
padding: 0 1.25rem 0.75rem;
margin: 0 -0.25rem;
.tag {
height: 1.75rem;
font-size: 16px;
line-height: 1.75rem;
display: inline-block;
padding: 0 0.75rem;
border-radius: 0.85rem;
margin: 0 0.25rem 0.5rem;
background-color: var(--c-bg-lighter);
cursor: pointer;
color: #fff;
// box-shadow: var(--shadow-sm);
}
}
}
</style>

View File

@ -1,148 +0,0 @@
import { usePageData } from '@vuepress/client'
import type { PageHeader } from '@vuepress/client'
import type { PropType, VNode } from 'vue'
import { computed, defineComponent, h, toRefs } from 'vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { useRoute } from 'vue-router'
import { scrollTo } from '../utils/index.js'
export type TocPropsHeaders = PageHeader[]
export interface TocPropsOptions {
containerTag: string
containerClass: string
listClass: string
itemClass: string
linkClass: string
linkActiveClass: string
linkChildrenActiveClass: string
}
export interface TocProps {
headers: TocPropsHeaders
options: TocPropsOptions
}
const renderLink = (
header: PageHeader,
options: TocPropsOptions,
route: RouteLocationNormalizedLoaded
): VNode => {
const hash = `#${header.slug}`
const linkClass = [options.linkClass]
if (options.linkActiveClass && route.hash === hash) {
linkClass.push(options.linkActiveClass)
}
if (
options.linkChildrenActiveClass &&
header.children.some((item) => `#${item.slug}` === route.hash)
) {
linkClass.push(options.linkChildrenActiveClass)
}
const setActiveRouteHash = (): void => {
const headerAnchors: HTMLAnchorElement[] = Array.from(
document.querySelectorAll('.header-anchor')
)
const anchor = headerAnchors.find(
(anchor) => decodeURI(anchor.hash) === hash
)
if (!anchor) return
const el = document.documentElement
const top = anchor.getBoundingClientRect().top - 80 + el.scrollTop
scrollTo(document, top)
}
return h(
'a',
{
href: hash,
class: linkClass,
ariaLabel: header.title,
onClick: (e: MouseEvent) => {
e.preventDefault()
setActiveRouteHash()
},
},
header.title
)
}
const renderHeaders = (
headers: PageHeader[],
options: TocPropsOptions,
route: RouteLocationNormalizedLoaded
): VNode[] => {
if (headers.length === 0) {
return []
}
return [
h(
'ul',
{ class: options.listClass },
headers.map((header) =>
h('li', { class: options.itemClass }, [
renderLink(header, options, route),
renderHeaders(header.children, options, route),
])
)
),
]
}
const Toc = defineComponent({
name: 'Toc',
props: {
headers: {
type: Array as PropType<TocPropsHeaders>,
required: false,
default: null,
},
options: {
type: Object as PropType<TocPropsOptions>,
required: false,
default: () => ({}),
},
},
setup(props) {
const { headers: propsHeaders, options: propsOptions } = toRefs(props)
const defaultOptions: TocPropsOptions = {
containerTag: 'nav',
containerClass: 'theme-plume-toc',
listClass: 'theme-plume-toc-list',
itemClass: 'theme-plume-toc-item',
linkClass: 'theme-plume-toc-link',
linkActiveClass: 'active',
linkChildrenActiveClass: 'active',
}
const route = useRoute()
const page = usePageData()
const headers = computed<TocPropsHeaders>(() => {
const headerToUse = propsHeaders.value || page.value.headers
return headerToUse[0]?.level === 1 ? headerToUse[0].children : headerToUse
})
const options = computed<TocPropsOptions>(() => ({
...defaultOptions,
...propsOptions.value,
}))
return () => {
const renderedHeaders = renderHeaders(headers.value, options.value, route)
if (options.value.containerTag) {
return h(
options.value.containerTag,
{ class: options.value.containerClass },
renderedHeaders
)
}
return renderedHeaders
}
},
})
export default Toc

View File

@ -1,65 +0,0 @@
<script setup lang="ts">
import { useThemeLocaleData } from '../composables/index.js'
defineEmits(['toggle'])
const themeLocale = useThemeLocaleData()
</script>
<template>
<div
class="toggle-sidebar-button"
:title="themeLocale.toggleSidebar || ''"
aria-expanded="false"
role="button"
tabindex="0"
@click="$emit('toggle')"
>
<div class="icon" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</div>
</div>
</template>
<style lang="scss">
@import '../styles/_mixins';
.toggle-sidebar-button {
position: absolute;
top: 0.6rem;
left: 1rem;
display: none;
padding: 0.6rem;
cursor: pointer;
}
.toggle-sidebar-button .icon {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 1.25rem;
height: 1.25rem;
cursor: inherit;
span {
display: inline-block;
width: 100%;
height: 2px;
border-radius: 2px;
background-color: var(--c-text);
transition: transform var(--t-transform);
&:nth-child(2) {
margin: 6px 0;
}
}
}
@media screen and (max-width: $MQMobile) {
.toggle-sidebar-button {
display: block;
}
}
</style>

View File

@ -1,57 +0,0 @@
<script lang="ts" setup>
defineProps({
type: {
type: String,
required: false,
default: 'tip',
},
text: {
type: String,
required: false,
default: '',
},
vertical: {
type: String,
required: false,
default: undefined,
},
})
</script>
<template>
<span class="badge" :class="type" :style="{ verticalAlign: vertical }">
<slot>{{ text }}</slot>
</span>
</template>
<style lang="scss">
.badge {
display: inline-block;
font-size: 14px;
height: 18px;
line-height: 18px;
border-radius: 3px;
padding: 0 6px;
color: var(--c-bg);
vertical-align: top;
transition: color var(--t-color), background-color var(--t-color);
&.tip {
background-color: var(--c-badge-tip);
}
&.warning {
background-color: var(--c-badge-warning);
}
&.danger {
background-color: var(--c-badge-danger);
}
.table-of-contents & {
vertical-align: middle;
}
& + & {
margin-left: 5px;
}
}
</style>

View File

@ -1,39 +0,0 @@
import { defineComponent, h } from 'vue'
import type { VNode } from 'vue'
export const IconBase = defineComponent({
name: 'IconBase',
props: {
name: {
type: String,
required: false,
default: '',
},
color: {
type: String,
required: false,
default: 'currentColor',
},
viewBox: {
type: String,
required: false,
default: '0 0 20 20',
},
},
setup:
(props, { slots }) =>
(): VNode =>
h(
'svg',
{
xmlns: 'http://www.w3.org/2000/svg',
class: ['icon', `${props.name}-icon`],
viewBox: props.viewBox,
ariaLabelledby: props.name,
},
[
h('title', { id: props.name, lang: 'en' }, `${props.name}`),
h('g', { fill: props.color }, slots.default?.()),
]
),
})

View File

@ -1,109 +0,0 @@
import { h } from 'vue'
import type { FunctionalComponent } from 'vue'
import { IconBase } from './IconBase.js'
export const UserIcon: FunctionalComponent = () =>
h(IconBase, { name: 'user' }, () =>
h('path', {
'fill-rule': 'evenodd',
'clip-rule': 'evenodd',
'd': 'M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z',
})
)
UserIcon.displayName = 'UserIcon'
export const FolderIcon: FunctionalComponent = () =>
h(IconBase, { name: 'folder' }, () =>
h('path', {
d: 'M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z',
})
)
FolderIcon.displayName = 'FolderIcon'
export const ClockIcon: FunctionalComponent = () =>
h(IconBase, { name: 'clock' }, () =>
h('path', {
'fill-rule': 'evenodd',
'clip-rule': 'evenodd',
'd': 'M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z',
})
)
ClockIcon.displayName = 'ClockIcon'
export const TagIcon: FunctionalComponent = () =>
h(IconBase, { name: 'tag' }, () =>
h('path', {
'fill-rule': 'evenodd',
'clip-rule': 'evenodd',
'd': 'M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z',
})
)
TagIcon.displayName = 'TagIcon'
export const TopIcon: FunctionalComponent = () =>
h(IconBase, { name: 'top', viewBox: '0 0 1024 1024' }, () => [
h('path', {
d: 'M80.96 449.194667l37.696-37.717334 19.626667 19.605334-37.717334 37.717333zM197.205333 541.44l116.16-116.138667 13.568 13.568-116.16 116.16zM220.565333 565.162667l116.16-116.16 13.568 13.589333-116.16 116.138667zM173.845333 517.738667l116.16-116.16 13.568 13.589333-116.16 116.138667zM245.354667 587.477333l116.202666-116.096 13.568 13.589334-116.202666 116.096z',
// fill: '#FA8D14',
}),
h('path', {
d: 'M339.2 0L0 345.6V1024L1024 0H339.2z m-115.2 283.733333l46.933333 46.933334-14.933333 12.8-4.266667-4.266667-140.8 140.8 4.266667 4.266667-14.933333 14.933333-46.933334-46.933333 170.666667-168.533334z m2.133333 375.466667l-12.8-12.8 29.866667-29.866667L149.333333 520.533333l64-64-12.8-12.8L108.8 533.333333l-10.666667-10.666666 89.6-89.6-10.666666-10.666667 14.933333-14.933333 10.666667 10.666666 91.733333-91.733333 10.666667 10.666667-91.733334 91.733333 12.8 12.8 68.266667-68.266667 96 96 27.733333-27.733333 12.8 12.8-204.8 204.8z m232.533334-236.8l-17.066667 17.066667c-6.4-6.4-14.933333-10.666667-21.333333-14.933334 8.533333-4.266667 14.933333-10.666667 21.333333-17.066666 6.4-6.4 6.4-12.8 0-19.2l-136.533333-136.533334-34.133334 34.133334-14.933333-17.066667L332.8 192l14.933333 14.933333-25.6 27.733334 138.666667 138.666666c14.933333 14.933333 14.933333 32-2.133333 49.066667z m-81.066667-200.533333l38.4-38.4-21.333333-34.133334-46.933334 46.933334-14.933333-14.933334 123.733333-123.733333 12.8 17.066667-59.733333 59.733333 21.333333 34.133333 57.6-57.6 98.133334 98.133334-14.933334 14.933333-83.2-83.2-78.933333 78.933333 85.333333 85.333334-14.933333 14.933333-102.4-98.133333z m138.666667 162.133333c-6.4-2.133333-14.933333-4.266667-25.6-4.266667 19.2-34.133333 25.6-61.866667 23.466666-85.333333-2.133333-21.333333-17.066667-44.8-42.666666-70.4L448 200.533333l14.933333-14.933333 23.466667 23.466667c17.066667 17.066667 29.866667 34.133333 38.4 49.066666 38.4-8.533333 74.666667-14.933333 106.666667-19.2l2.133333 25.6c-34.133333 2.133333-68.266667 8.533333-100.266667 14.933334 2.133333 4.266667 2.133333 8.533333 2.133334 12.8 6.4 23.466667 0 55.466667-19.2 91.733333z',
// fill: '#FA8D14',
}),
h('path', {
d: 'M183.765333 346.965333l37.696-37.717333 19.626667 19.584-37.696 37.738667zM132.288 398.037333l37.76-37.674666 19.584 19.626666-37.738667 37.674667z',
// fill: '#FA8D14',
}),
])
TopIcon.displayName = 'TopIcon'
export const ArrowRightIcon: FunctionalComponent = () =>
h(IconBase, { name: 'arrow-right', viewBox: '0 0 1024 1024' }, () =>
h('path', {
d: 'M709.546667 560.256c-2.389333 2.474667-11.52 13.056-19.968 21.76-49.706667 54.741333-179.370667 144.341333-247.253334 171.690667-10.325333 4.394667-36.394667 13.696-50.304 14.293333-13.354667 0-26.026667-3.072-38.186666-9.301333a79.957333 79.957333 0 0 1-33.92-38.570667c-4.266667-11.221333-10.922667-44.8-10.922667-45.397333-6.656-36.736-10.325333-96.426667-10.325333-162.389334 0-62.848 3.669333-120.106667 9.088-157.397333 0.64-0.597333 7.253333-42.325333 14.549333-56.618667A76.16 76.16 0 0 1 389.632 256h2.389333c18.176 0.64 56.362667 16.853333 56.362667 17.450667 64.213333 27.392 190.890667 112.597333 241.834667 169.216 0 0 14.336 14.549333 20.565333 23.637333 9.728 13.056 14.549333 29.226667 14.549333 45.397333 0 18.048-5.461333 34.858667-15.786666 48.554667z',
})
)
ArrowRightIcon.displayName = 'ArrowRightIcon'
export const ArrowBottomIcon: FunctionalComponent = () =>
h(IconBase, { name: 'arrow-bottom', viewBox: '0 0 1024 1024' }, () =>
h('path', {
d: 'M150.001 502.111a22.487 22.487 0 0 1 13.185 4.245l348.86 250.152 348.858-250.152a22.577 22.577 0 0 1 26.28 36.665L525.14 802.656a22.577 22.577 0 0 1-26.28 0L136.816 543.02a22.577 22.577 0 0 1 13.185-40.91z m737.183-257.196L525.14 504.55a22.577 22.577 0 0 1-26.28 0L136.816 244.915a22.577 22.577 0 1 1 26.28-36.665l348.859 250.152L860.814 208.25a22.577 22.577 0 1 1 26.28 36.665z',
})
)
ArrowBottomIcon.displayName = 'ArrowBottomIcon'
export const BackTopIcon: FunctionalComponent = () =>
h(IconBase, { name: 'back-top', viewBox: '0 0 1024 1024' }, () =>
h('path', {
d: 'M725.902 498.916c18.205-251.45-93.298-410.738-205.369-475.592l-6.257-3.982-6.258 3.414c-111.502 64.853-224.711 224.142-204.8 475.59-55.751 53.476-80.214 116.623-80.214 204.8v15.36l179.2-35.27c11.378 40.39 58.596 69.973 113.21 69.973 54.613 0 101.262-29.582 112.64-68.836l180.337 36.41v-15.36c-0.569-89.885-25.031-153.6-82.489-206.507zM571.733 392.533c-33.564 31.29-87.04 28.445-118.329-5.12s-28.444-87.04 5.12-117.76c33.565-31.289 87.04-28.444 118.33 5.12s28.444 86.471-5.12 117.76z m-56.32 368.64c-35.84 0-64.284 29.014-64.284 64.285 0 35.84 54.044 182.613 64.284 182.613s64.285-146.773 64.285-182.613c0-35.271-29.014-64.285-64.285-64.285z',
})
)
BackTopIcon.displayName = 'BackTopIcon'
export const PostIcon: FunctionalComponent = () =>
h(IconBase, { name: 'post', viewBox: '0 0 1024 1024' }, () =>
h('path', {
d: 'M805.376 81.0496 188.7232 81.0496c-52.6336 0-94.8736 42.3936-94.8736 94.6176l0 664.576c0 52.2752 42.496 94.6176 94.8736 94.6176L805.376 934.8608c52.6336 0 94.8736-42.3936 94.8736-94.6176L900.2496 175.7184C900.2496 123.392 857.8048 81.0496 805.376 81.0496zM288.768 204.8c39.3216 0 71.168 31.5904 71.168 71.168 0 39.3216-31.5904 71.168-71.168 71.168-39.3216 0-71.168-31.5904-71.168-71.168C217.6 236.6464 249.1904 204.8 288.768 204.8zM506.368 741.0176 217.6 741.0176l0-47.4112L506.368 693.6064 506.368 741.0176zM671.3344 617.2672 217.6 617.2672 217.6 569.856l453.7344 0L671.3344 617.2672zM671.3344 493.568 217.6 493.568 217.6 446.1056l453.7344 0L671.3344 493.568z',
})
)
PostIcon.displayName = 'PostIcon'
export const ArrowDoubleRightIcon: FunctionalComponent = () =>
h(IconBase, { name: 'arrow-double-right', viewBox: '0 0 1024 1024' }, () =>
h('path', {
d: 'M160.117 212.026v-82.233a8 8 0 0 1 13.33-5.966l407.697 364.298c0.9 0.804 1.753 1.658 2.556 2.558 11.764 13.186 10.62 33.419-2.556 45.192L173.448 900.173a8 8 0 0 1-13.33-5.966v-82.233a16 16 0 0 1 5.338-11.93L487.814 512 165.456 223.957a16 16 0 0 1-5.339-11.931z m272.057 0v-82.233a8 8 0 0 1 13.33-5.966l407.697 364.298c0.9 0.804 1.753 1.658 2.556 2.558 11.764 13.186 10.62 33.419-2.556 45.192L445.505 900.173a8 8 0 0 1-13.33-5.966v-82.233a16 16 0 0 1 5.339-11.93L759.87 512 437.514 223.957a16 16 0 0 1-5.34-11.931z',
})
)
ArrowDoubleRightIcon.displayName = 'ArrowDoubleRightIcon'
export const ArrowDoubleLeftIcon: FunctionalComponent = () =>
h(IconBase, { name: 'arrow-double-left', viewBox: '0 0 1024 1024' }, () => [
h('path', {
d: 'M495.976 476.195c19.777 17.656 21.494 48 3.837 67.774a48.003 48.003 0 0 1-3.837 3.836L536.082 512l-40.106-35.805zM864 212.083v-82.217a8 8 0 0 0-13.328-5.967L442.69 488.13c-0.9 0.804-1.754 1.657-2.558 2.557-11.772 13.184-10.626 33.412 2.558 45.183l407.983 364.231A8 8 0 0 0 864 894.134v-82.217a16 16 0 0 0-5.344-11.936L536.082 512l322.574-287.981A16 16 0 0 0 864 212.083zM495.976 476.195c19.777 17.656 21.494 48 3.837 67.774a48.003 48.003 0 0 1-3.837 3.836L536.082 512l-40.106-35.805zM864 212.083v-82.217a8 8 0 0 0-13.328-5.967L442.69 488.13c-0.9 0.804-1.754 1.657-2.558 2.557-11.772 13.184-10.626 33.412 2.558 45.183l407.983 364.231A8 8 0 0 0 864 894.134v-82.217a16 16 0 0 0-5.344-11.936L536.082 512l322.574-287.981A16 16 0 0 0 864 212.083z',
}),
h('path', {
d: 'M223.976 476.195c19.777 17.656 21.494 48 3.837 67.774a48.003 48.003 0 0 1-3.837 3.836L264.082 512l-40.106-35.805zM592 212.083v-82.217a8 8 0 0 0-13.328-5.967L170.69 488.13c-0.9 0.804-1.754 1.657-2.558 2.557-11.772 13.184-10.626 33.412 2.558 45.183l407.983 364.231A8 8 0 0 0 592 894.134v-82.217a16 16 0 0 0-5.344-11.936L264.082 512l322.574-287.981A16 16 0 0 0 592 212.083zM223.976 476.195c19.777 17.656 21.494 48 3.837 67.774a48.003 48.003 0 0 1-3.837 3.836L264.082 512l-40.106-35.805zM592 212.083v-82.217a8 8 0 0 0-13.328-5.967L170.69 488.13c-0.9 0.804-1.754 1.657-2.558 2.557-11.772 13.184-10.626 33.412 2.558 45.183l407.983 364.231A8 8 0 0 0 592 894.134v-82.217a16 16 0 0 0-5.344-11.936L264.082 512l322.574-287.981A16 16 0 0 0 592 212.083z',
}),
])
ArrowDoubleLeftIcon.displayName = 'ArrowDoubleLeftIcon'

View File

@ -1,3 +0,0 @@
export * from './IconBase.js'
export * from './icon.js'
export * from './socialIcon.js'

View File

@ -1,126 +0,0 @@
import { h } from 'vue'
import type { FunctionalComponent } from 'vue'
import { IconBase } from './IconBase.js'
export const GithubIcon: FunctionalComponent = () =>
h(IconBase, { name: 'github', viewBox: '0 0 1024 1024' }, () =>
h('path', {
d: 'M512 0C229.283787 0 0.142041 234.942803 0.142041 524.867683c0 231.829001 146.647305 428.553077 350.068189 497.952484 25.592898 4.819996 34.976961-11.38884 34.976961-25.294314 0-12.45521-0.469203-45.470049-0.725133-89.276559-142.381822 31.735193-172.453477-70.380469-172.453477-70.380469-23.246882-60.569859-56.816233-76.693384-56.816234-76.693385-46.493765-32.58829 3.540351-31.948468 3.540351-31.948467 51.356415 3.71097 78.356923 54.086324 78.356923 54.086324 45.683323 80.19108 119.817417 57.072162 148.993321 43.593236 4.649376-33.91059 17.915029-57.029508 32.50298-70.167195-113.675122-13.222997-233.151301-58.223843-233.1513-259.341366 0-57.285437 19.919806-104.163095 52.678715-140.846248-5.246544-13.265652-22.820334-66.626844 4.990615-138.884127 0 0 42.996069-14.076094 140.760939 53.787741 40.863327-11.644769 84.627183-17.445825 128.177764-17.6591 43.465272 0.213274 87.271782 6.014331 128.135109 17.6591 97.679561-67.906489 140.59032-53.787741 140.59032-53.787741 27.938914 72.257282 10.407779 125.618474 5.118579 138.884127 32.844219 36.683154 52.593405 83.560812 52.593405 140.846248 0 201.586726-119.646798 245.990404-233.663158 258.957473 18.341577 16.208835 34.721032 48.199958 34.721032 97.210357 0 70.167195-0.639822 126.7275-0.639823 143.960051 0 14.033439 9.213443 30.370239 35.190235 25.209005 203.250265-69.527373 349.769606-266.123484 349.769605-497.867175C1023.857959 234.942803 794.673558 0 512 0',
fill: '#3E75C3',
})
)
GithubIcon.displayName = 'GithubIcon'
export const EmailIcon: FunctionalComponent = () =>
h(IconBase, { name: 'email', viewBox: '0 0 1024 1024' }, () => [
[
h('path', {
d: 'M848.76288 333.62432H164.99712C99.32288 333.62432 46.08 386.87232 46.08 452.54144v297.28768c0 65.67424 53.24288 118.92224 118.91712 118.92224h683.77088c65.66912 0 118.91712-53.24288 118.91712-118.92224V452.54144c-0.00512-65.66912-53.248-118.91712-118.92224-118.91712z',
fill: '#96383D',
}),
h('path', {
d: 'M639.8208 51.2h-474.8288a44.58496 44.58496 0 0 0-44.59008 44.59008v609.44896a44.57984 44.57984 0 0 0 44.59008 44.59008h683.776a44.58496 44.58496 0 0 0 44.59008-44.59008V304.73728L639.8208 51.2z',
fill: '#EBE2CE',
}),
h('path', {
d: 'M551.4752 229.57568H209.59232v44.59008h341.88288v-44.59008zM209.59232 794.42432h594.58048v-44.5952H209.59232v44.5952z m0-89.18528h594.58048v-44.5952H209.59232v44.5952z m0-178.37568h594.58048v-44.5952H209.59232v44.5952z m0 89.18528h594.58048v-44.59008H209.59232v44.59008z m0-222.96576v44.59008h594.58048v-44.59008H209.59232z',
fill: '#C9C1B1',
}),
h('path', {
d: 'M941.83936 393.31328L75.60704 955.02848c12.89216 10.93632 29.29664 17.77152 47.44192 17.77152H893.5936c40.91904 0 74.09152-33.4592 74.09152-74.74688V449.60768c-0.00512-22.58432-10.14784-42.58816-25.84576-56.2944z',
fill: '#D54D54',
}),
h('path', {
d: 'M71.99232 396.5696C56.25344 410.18368 46.08 430.08512 46.08 452.54144v445.93152C46.08 939.53024 79.34976 972.8 120.40192 972.8h772.95104c18.20672 0 34.65216-6.79424 47.56992-17.664L71.99232 396.5696z',
fill: '#EA5455',
}),
h('path', {
d: 'M655.52384 66.90816v236.8l237.82912 74.89024V304.73728z',
fill: '',
}),
h('path', {
d: 'M640.66048 52.0448v207.2576a44.58496 44.58496 0 0 0 44.5952 44.5952h207.2576l-251.8528-251.8528z',
fill: '#FFFBF2',
}),
],
])
EmailIcon.displayName = 'EmailIcon'
export const ZhiHuIcon: FunctionalComponent = () =>
h(IconBase, { name: 'zhiHu', viewBox: '0 0 1024 1024' }, () => [
h('path', {
d: 'M512 73.28A438.72 438.72 0 1 0 950.72 512 438.72 438.72 0 0 0 512 73.28z m-98.56 458.88l-16.8 66.88 23.68-20.8s53.92 61.28 64 76.48 1.44 68.96 1.44 68.96l-92.48-113.12s-29.12 101.12-68.48 124.16a97.6 97.6 0 0 1-80 6.56 342.08 342.08 0 0 0 85.44-89.76 382.88 382.88 0 0 0 39.52-119.36h-115.04s8.8-40.48 24.16-41.6 90.88 0 90.88 0l-1.76-124.8-43.2 2.24a96 96 0 0 1-32 48c-24.16 17.44-38.4 10.88-38.4 10.88s42.72-118.24 55.84-141.28 50.4-25.12 50.4-25.12l-23.04 66.72h147.84c17.6 0 18.56 40.64 18.56 40.64h-90.56v122.56s61.28-2.24 81.12 0 19.68 41.6 19.68 41.6z m329.44 160h-91.52l-65.12 46.24-13.6-46.24h-36.96v-368h208z',
fill: '#49C0FB',
}),
h('path', {
d: 'M602.88 691.68l54.88-41.44h43.04V364.64h-121.12v285.6h11.2l12 41.44z',
fill: '#49C0FB',
}),
])
ZhiHuIcon.displayName = 'ZhiHuIcon'
export const WeiBoIcon: FunctionalComponent = () =>
h(IconBase, { name: 'weiBo', viewBox: '0 0 1024 1024' }, () => [
h('path', {
d: 'M448.698182 482.210909c-96.814545 4.654545-175.010909 56.785455-175.010909 121.949091s78.196364 114.501818 175.010909 109.847273S623.709091 647.912727 623.709091 582.749091c-0.930909-64.232727-79.127273-105.192727-175.010909-100.538182z m65.163636 164.770909c-29.789091 39.098182-88.436364 57.716364-145.221818 26.065455-26.996364-14.894545-26.065455-43.752727-26.065455-43.752728s-11.170909-92.16 85.643637-103.330909c97.745455-12.101818 115.432727 81.92 85.643636 121.018182z',
fill: '#EA5D5C',
}),
h('path', {
d: 'M448.698182 584.610909c-6.516364 4.654545-7.447273 13.032727-3.723637 18.618182 2.792727 5.585455 11.170909 6.516364 16.756364 1.861818 5.585455-4.654545 8.378182-13.032727 4.654546-18.618182-2.792727-5.585455-10.24-6.516364-17.687273-1.861818zM403.083636 597.643636c-18.618182 1.861818-30.72 17.687273-30.72 33.512728 0 14.894545 14.894545 26.065455 32.581819 24.203636 17.687273-1.861818 32.581818-15.825455 32.581818-31.650909s-13.963636-27.927273-34.443637-26.065455z',
fill: '#EA5D5C',
}),
h('path', {
d: 'M512 0C229.003636 0 0 229.003636 0 512s229.003636 512 512 512 512-229.003636 512-512S794.996364 0 512 0z m197.352727 626.501818C669.323636 712.145455 538.065455 754.036364 441.250909 746.589091c-92.16-7.447273-211.316364-38.167273-223.418182-151.738182 0 0-6.516364-51.2 42.821818-117.294545 0 0 70.749091-99.607273 152.669091-128.465455 82.850909-27.927273 92.16 19.549091 92.16 48.407273-4.654545 24.203636-12.101818 38.167273 18.618182 28.858182 0 0 80.989091-38.167273 114.501818-4.654546 26.996364 26.996364 4.654545 65.163636 4.654546 65.163637s-11.170909 12.101818 12.101818 16.756363c21.410909 3.723636 94.021818 37.236364 53.992727 122.88z m-80.058182-236.450909c-8.378182 0-15.825455-7.447273-15.825454-15.825454 0-9.309091 7.447273-15.825455 15.825454-15.825455 0 0 99.607273-18.618182 87.505455 89.367273v1.861818c-0.930909 7.447273-7.447273 13.963636-15.825455 13.963636-9.309091 0-15.825455-7.447273-15.825454-15.825454 0-1.861818 15.825455-73.541818-55.854546-57.716364zM797.789091 493.381818c-2.792727 18.618182-12.101818 11.170909-22.341818 11.170909-13.032727 0-23.272727-16.756364-23.272728-29.789091 0-11.170909 4.654545-22.341818 4.654546-22.341818 0.930909-4.654545 12.101818-34.443636-7.447273-78.196363-35.374545-60.509091-106.123636-60.509091-114.501818-57.716364-8.378182 3.723636-21.410909 5.585455-21.410909 5.585454-13.032727 0-23.272727-10.24-23.272727-23.272727 0-11.170909 7.447273-19.549091 16.756363-22.341818 0 0 0 0.930909 0.930909 0.930909s1.861818 0.930909 1.861819 0.930909c10.24-1.861818 45.614545-4.654545 79.127272 3.723637 62.370909 14.894545 146.152727 83.781818 108.916364 211.316363z',
fill: '#EA5D5C',
}),
])
WeiBoIcon.displayName = 'WeiBoIcon'
// <svg t="1648887372594" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3602" width="200" height="200"><path d="" fill="#68A5E1" p-id="3603"></path></svg>
export const QQIcon: FunctionalComponent = () =>
h(IconBase, { name: 'qq', viewBox: '0 0 1024 1024' }, () =>
h('path', {
d: 'M512.268258 64.433103c-247.183323 0-447.569968 200.380501-447.569968 447.563825 0 247.189467 200.385621 447.570992 447.569968 447.570992s447.569968-200.380501 447.569968-447.569968c0-247.184347-200.386645-447.564849-447.569968-447.564849z m252.85872 584.692787c-18.997168 16.287968-43.668709-53.628042-47.2134-42.875198-8.642616 26.161294-12.695154 43.646184-38.148944 72.127602-1.35972 1.521494 29.43056 12.647032 38.148944 36.396051 8.346713 22.756875 24.596797 58.811973-81.725503 70.125906-62.389428 6.635801-107.471099-33.244533-111.964932-32.85648-8.325212 0.734126-4.618747 0-13.568528 0-7.321804 0-7.807126 0.534468-14.69685 0-1.899307-0.140272-22.632985 32.85648-115.364231 32.85648-71.878798 0-90.48177-45.243445-76.032701-70.125906 14.464428-24.877342 38.579999-32.122354 35.176604-36.06636-16.73643-19.39546-28.287904-40.1404-35.176604-58.882621-1.705793-4.666869-3.135137-9.209848-4.262434-13.574672-2.611931-10.008479-22.627866 58.76385-44.111028 42.875198-21.483162-15.883533-19.567472-56.309597-5.659014-95.003248 14.033372-39.006959 49.37687-76.562049 49.771065-84.854496 1.412962-30.849665-3.044011-35.975235 0-44.078263 6.780169-18.149391 15.034732-11.190043 15.034733-20.609788 0-118.64476 88.172909-214.829571 196.933079-214.829571 108.755051 0 196.928984 96.184811 196.928984 214.829571 0 4.554242 11.815637 0 17.474651 20.609788 1.165181 4.256291 1.968931 20.684531 0.58771 44.078263-0.658358 11.238165 29.954789 24.914202 45.777913 84.854496 15.845649 59.945414 0 88.215912-7.909514 95.003248z',
fill: '#68A5E1',
})
)
QQIcon.displayName = 'QQIcon'
export const TwitterIcon: FunctionalComponent = () =>
h(IconBase, { name: 'twitter', viewBox: '0 0 1024 1024' }, () => [
h('path', {
d: 'M512.274401 959.556658c247.17718 0 447.556658-200.366167 447.556658-447.556658 0-247.16387-200.379477-447.556658-447.556658-447.556658-247.188443 0-447.569968 200.392788-447.569968 447.556658 0 247.190491 200.382549 447.556658 447.569968 447.556658',
fill: '#78CBEF',
}),
h('path', {
d: 'M736.810405 394.754891c-16.48353 7.310541-34.227463 12.256931-52.82122 14.478763 19.004336-11.383557 33.588558-29.396772 40.435279-50.868671-17.780793 10.536804-37.440415 18.183179-58.42392 22.294079-16.741549-17.872942-40.666677-29.038412-67.134113-29.038412-50.766282 0-91.948998 41.192954-91.948998 91.972548 0 7.220439 0.784296 14.222791 2.366199 20.943574-76.439183-3.841618-144.191723-40.421969-189.587726-96.109044-7.915657 13.630985-12.452493 29.422369-12.452493 46.282688 0 31.877646 16.241893 60.042683 40.924696 76.552835-15.072616-0.460748-29.26981-4.637177-41.682371-11.485946v1.131393c0 44.585086 31.698466 81.757243 73.804725 90.185867-7.723167 2.160398-15.841554 3.239573-24.246628 3.239574a91.24866 91.24866 0 0 1-17.294447-1.63105c11.691747 36.527109 45.654023 63.139936 85.90705 63.845394-31.477307 24.682804-71.144672 39.382725-114.227718 39.382725-7.42624 0-14.762379-0.410578-21.946982-1.270642 40.706609 26.070168 89.057546 41.308653 140.992081 41.308653 169.209337 0 261.697922-140.132017 261.697922-261.695874 0-3.997248-0.078839-7.979138-0.244709-11.899595a186.466924 186.466924 0 0 0 45.883373-47.618859',
fill: '#FFFFFF',
}),
])
TwitterIcon.displayName = 'TwitterIcon'
export const FacebookIcon: FunctionalComponent = () =>
h(IconBase, { name: 'facebook', viewBox: '0 0 1024 1024' }, () => [
h('path', {
d: 'M512.262115 959.556658c247.175132 0 447.569968-200.366167 447.569968-447.556658 0-247.16387-200.394836-447.556658-447.569968-447.556658-247.17718 0-447.556658 200.392788-447.556658 447.556658-0.001024 247.190491 200.378454 447.556658 447.556658 447.556658',
fill: '#537BBC',
}),
h('path', {
d: 'M404.292383 436.216104h46.269378v-44.969044c0-19.828563 0.499656-50.408946 14.904699-69.347753 15.172957-20.05689 36.000832-33.690947 71.826579-33.690946 58.371702 0 82.952117 8.326235 82.952118 8.326235l-11.564785 68.550147s-19.285904-5.576079-37.275569-5.57608c-17.99888 0-34.111763 6.449454-34.111764 24.438095v52.269346h73.791416l-5.152191 66.958004h-68.639225v232.604221h-86.731278V503.174108h-46.269378v-66.958004z',
fill: '#FFFFFF',
}),
])
FacebookIcon.displayName = 'FacebookIcon'
export const LinkedinIcon: FunctionalComponent = () =>
h(IconBase, { name: 'linkedin', viewBox: '0 0 1024 1024' }, () => [
h('path', {
d: 'M512.267234 959.569968c247.223255 0 447.572016-200.400979 447.572016-447.582255 0-247.171037-200.347737-447.558705-447.572016-447.558705-247.194586 0-447.568944 200.387669-447.568944 447.558705 0 247.1823 200.373334 447.582255 447.568944 447.582255',
fill: '#1284C7',
}),
h('path', {
d: 'M387.013295 699.188763h-87.249365V419.999808h87.249365v279.188955z m-45.860848-314.114707h-0.628666c-31.57048 0-52.042043-21.341866-52.042043-48.378582 0-27.573232 21.086918-48.478922 53.286064-48.478922 32.175596 0 51.975491 20.852449 52.607228 48.402131 0.001024 27.046955-20.430608 48.455373-53.222583 48.455373z m394.899259 314.114707H637.125954V554.711376c0-37.815157-15.457597-63.618091-49.496664-63.61809-26.03126 0-40.488521 17.410146-47.233878 34.204937-2.518758 6.013279-2.133777 14.405043-2.133777 22.820356v151.06916h-98.001184s1.273713-255.921161 0-279.188955h98.001184v43.815125c5.794167-19.157918 37.097413-46.5018 87.093733-46.5018 61.986018 0 110.696338 40.168045 110.696338 126.630041v155.246613z',
fill: '#FFFFFF',
}),
])
LinkedinIcon.displayName = 'LinkedinIcon'

Some files were not shown because too many files have changed in this diff Show More