mirror of https://github.com/YunYouJun/valaxy
refactor: use map for categories
This commit is contained in:
parent
abd2f2e495
commit
5f330c3ecf
|
@ -6,8 +6,8 @@ defineProps<{
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<p class="leading-0!">
|
||||
<a class="inline text-xl leading-0" :href="link" target="_blank" rel="noopener noreferrer">
|
||||
<p class="leading-0! text-4xl">
|
||||
<a class="inline leading-0" :href="link" target="_blank" rel="noopener noreferrer">
|
||||
<div :class="icon" />
|
||||
</a>
|
||||
</p>
|
||||
|
|
|
@ -14,6 +14,7 @@ We need a plugin system that allows users to use/load only certain features quic
|
|||
:::
|
||||
|
||||
## 命名规范 {lang="zh-CN"}
|
||||
|
||||
## Naming conventions {lang="en"}
|
||||
|
||||
::: zh-CN
|
||||
|
@ -45,6 +46,7 @@ Plugin name: `valaxy-addon-<name>`.
|
|||
:::
|
||||
|
||||
## 说明 {lang="zh-CN"}
|
||||
|
||||
## Explanation {lang="en"}
|
||||
|
||||
::: zh-CN
|
|
@ -45,7 +45,7 @@ npm run build
|
|||
|
||||
#### GitHub Pages
|
||||
|
||||
<BrandIcon icon="i-simple-icons-github" link="https://pages.github.com/" />
|
||||
<BrandIcon icon="i-logos:github-icon" link="https://pages.github.com/" />
|
||||
|
||||
::: zh-CN
|
||||
在使用 `pnpm create valaxy` 创建模版项目时,已内置文件[`.github/workflows/gh-pages.yml`](https://github.com/YunYouJun/valaxy/blob/main/packages/create-valaxy/template-blog/.github/workflows/gh-pages.yml) 以实现 GitHub Actions 的自动部署工作流。
|
||||
|
@ -73,7 +73,7 @@ When you use `pnpm create valaxy` to create a template project, it contains the
|
|||
|
||||
#### Netlify
|
||||
|
||||
<BrandIcon icon="i-simple-icons-netlify" link="https://www.netlify.com/" />
|
||||
<BrandIcon icon="i-logos:netlify-icon" link="https://www.netlify.com/" />
|
||||
|
||||
::: zh-CN
|
||||
已内置 `netlify.toml`。
|
||||
|
@ -87,9 +87,10 @@ When you use `pnpm create valaxy` to create a template project, it contains the
|
|||
|
||||
#### Vercel
|
||||
|
||||
<BrandIcon icon="i-simple-icons-vercel" link="https://vercel.com/" />
|
||||
<BrandIcon icon="i-logos:vercel-icon" link="https://vercel.com/" />
|
||||
|
||||
::: zh-CN
|
||||
|
||||
- 在 Vercel 的 Dashboard 上,点击 `Add New...`,随后点击 `Project` 新建一个项目。
|
||||
- 在左侧选择要部署的仓库,点击 `Import`,随后将 `Framework Preset` 设置为 `Other` 并更改 `Build and Output Settings`。
|
||||
- 将 `Output Directory` 设置为 `dist` 后,点击 `Deploy`。
|
||||
|
@ -97,6 +98,7 @@ When you use `pnpm create valaxy` to create a template project, it contains the
|
|||
:::
|
||||
|
||||
::: en
|
||||
|
||||
- On Vercel Dashboard, click `Add New...`, then click `Project` to create a project.
|
||||
- Select the repository you want to deploy and click `Import` and then set `Framework Preset` to `Other` and modify `Build and Output Settings`.
|
||||
- Turn on the switch on the right of the textbox and type `dist`, click `Deploy`.
|
||||
|
@ -105,7 +107,7 @@ When you use `pnpm create valaxy` to create a template project, it contains the
|
|||
|
||||
#### Cloudflare Pages
|
||||
|
||||
<BrandIcon icon="i-simple-icons-cloudflare" link="https://pages.cloudflare.com/" />
|
||||
<BrandIcon icon="i-logos:cloudflare-icon" link="https://pages.cloudflare.com/" />
|
||||
|
||||
::: zh-CN
|
||||
|
||||
|
@ -121,6 +123,7 @@ When you use `pnpm create valaxy` to create a template project, it contains the
|
|||
:::
|
||||
|
||||
::: en
|
||||
|
||||
- Login to your Cloudflare account and navigate to "Pages" page.
|
||||
- Click `Create a project` and `Connect to Git`, then select your GitHub or GitLab repository and click `Begin setup`.
|
||||
- Select your Production branch.
|
||||
|
@ -136,7 +139,7 @@ When you use `pnpm create valaxy` to create a template project, it contains the
|
|||
|
||||
#### Others {lang="en"}
|
||||
|
||||
<BrandIcon icon="i-simple-icons-render" link="https://render.com/" />
|
||||
<BrandIcon class="text-xl!" icon="i-simple-icons-render" link="https://render.com/" />
|
||||
|
||||
::: zh-CN
|
||||
你还可以使用 [Render](https://render.com/) 等进行托管。
|
||||
|
|
|
@ -122,7 +122,7 @@ export default defineValaxyConfig<PressTheme.Config>({
|
|||
items: [
|
||||
{
|
||||
text: 'nav.why-need-addons',
|
||||
link: '/addons',
|
||||
link: '/addons/why',
|
||||
},
|
||||
{
|
||||
text: 'nav.use-an-addon',
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { useThemeConfig } from 'valaxy'
|
||||
import type { Categories } from 'valaxy'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { PressTheme } from '../types'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
categories: Categories
|
||||
/**
|
||||
* 当前层级
|
||||
*/
|
||||
level?: number
|
||||
displayCategory?: (category: string) => void
|
||||
collapsable?: boolean
|
||||
}>(), {
|
||||
level: 0,
|
||||
collapsable: true,
|
||||
})
|
||||
|
||||
const collapsable = ref(props.collapsable)
|
||||
|
||||
const themeConfig = useThemeConfig<PressTheme.Config>()
|
||||
const sidebar = computed(() => themeConfig.value.sidebar)
|
||||
|
||||
function getCategoryByName(name: string) {
|
||||
return props.categories.find(c => c.name === name)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul v-for="item in sidebar" :key="item" class="category-list">
|
||||
<PressCategory
|
||||
v-if="getCategoryByName(item)"
|
||||
:category="getCategoryByName(item)"
|
||||
:level="level + 1"
|
||||
:display-category="displayCategory"
|
||||
:collapsable="collapsable"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.category-list {
|
||||
&:first-child {
|
||||
.category-list-item {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-list-item {
|
||||
a {
|
||||
color: var(--va-c-text-light);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: var(--va-c-primary);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--va-c-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-list-item {
|
||||
.folder-action {
|
||||
&:hover {
|
||||
color: var(--va-c-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-list+.category-list {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
|
@ -52,7 +52,7 @@ function getTitle(post: Post | any) {
|
|||
</li>
|
||||
|
||||
<ul v-if="!collapsable">
|
||||
<li v-for="categoryItem, i in category.children" :key="i" class="post-list-item">
|
||||
<li v-for="categoryItem, i in category.children.values()" :key="i" class="post-list-item">
|
||||
<template v-if="!isCategoryList(categoryItem)">
|
||||
<RouterLink v-if="categoryItem.title" :to="categoryItem.path || ''" class="inline-flex items-center" active-class="active">
|
||||
<span m="l-1" text="sm">{{ getTitle(categoryItem) }}</span>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts" setup>
|
||||
import type { CategoryList } from 'valaxy'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
categories: CategoryList
|
||||
item: string
|
||||
}>()
|
||||
|
||||
const category = computed(() => {
|
||||
const c = props.categories.children.get(props.item)
|
||||
return c
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PressCategory
|
||||
v-if="category"
|
||||
:category="category"
|
||||
:collapsable="false"
|
||||
/>
|
||||
</template>
|
|
@ -11,8 +11,8 @@ const pages = usePageList()
|
|||
const themeConfig = useThemeConfig()
|
||||
|
||||
const sidebar = computed(() => themeConfig.value.sidebar)
|
||||
const cs = useCategories('', pages.value)
|
||||
const categories = computed(() => {
|
||||
const cs = useCategories('', pages.value)
|
||||
const cList = cs.value
|
||||
removeItemFromCategory(cList, 'Uncategorized')
|
||||
|
||||
|
@ -23,13 +23,10 @@ const categories = computed(() => {
|
|||
removeItemFromCategory(cList, item.name)
|
||||
})
|
||||
}
|
||||
|
||||
return cList
|
||||
})
|
||||
|
||||
function getCategoryByName(name: string) {
|
||||
return categories.value.children.find(c => c.name === name)
|
||||
}
|
||||
|
||||
const { hasSidebar } = useSidebar()
|
||||
</script>
|
||||
|
||||
|
@ -42,10 +39,9 @@ const { hasSidebar } = useSidebar()
|
|||
<div text="left" m="2">
|
||||
<ul v-for="item in sidebar" :key="item" class="category-list">
|
||||
<template v-if="typeof item === 'string'">
|
||||
<PressCategory
|
||||
v-if="getCategoryByName(item)"
|
||||
:category="getCategoryByName(item)"
|
||||
:collapsable="false"
|
||||
<PressCategoryByName
|
||||
:categories="categories"
|
||||
:item="item"
|
||||
/>
|
||||
</template>
|
||||
<PressSidebarItem
|
||||
|
|
|
@ -24,7 +24,7 @@ const categoryList = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<ul v-for="category in categories" :key="category.name" class="category-list" m="l-4">
|
||||
<ul v-for="category in categories.values()" :key="category.name" class="category-list" m="l-4">
|
||||
<YunCategory
|
||||
:parent-key="category.name"
|
||||
:category="category"
|
||||
|
|
|
@ -74,7 +74,7 @@ onMounted(() => {
|
|||
|
||||
<template v-if="!collapse">
|
||||
<ul>
|
||||
<li v-for="categoryItem, i in category.children" :key="i" class="post-list-item" m="l-4">
|
||||
<li v-for="categoryItem, i in category.children.values()" :key="i" class="post-list-item" m="l-4">
|
||||
<template v-if="isCategoryList(categoryItem)">
|
||||
<YunCategory
|
||||
:parent-key="parentKey ? `${parentKey}/${categoryItem.name}` : categoryItem.name"
|
||||
|
|
|
@ -55,7 +55,7 @@ useSchemaOrg([
|
|||
</template>
|
||||
<template #main-content>
|
||||
<div text="center" class="yun-text-light" p="2">
|
||||
{{ t('counter.categories', categories.children.length) }}
|
||||
{{ t('counter.categories', Array.from(categories.children).length) }}
|
||||
</div>
|
||||
<YunCategories :categories="categories.children" />
|
||||
<RouterView />
|
||||
|
|
|
@ -38,7 +38,7 @@ const licenseHtml = computed(() => {
|
|||
{{ t('post.copyright.link') + t('symbol.colon') }}
|
||||
</strong>
|
||||
<a :href="url" target="_blank" :title="t('post.copyright.link')">
|
||||
{{ url }}
|
||||
{{ decodeURI(url) }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="post-copyright-license">
|
||||
|
|
|
@ -16,10 +16,10 @@ export interface CategoryList {
|
|||
* total posts
|
||||
*/
|
||||
total: number
|
||||
children: (Post | CategoryList)[]
|
||||
children: Map<string, Post | CategoryList>
|
||||
}
|
||||
export type Category = CategoryList
|
||||
export type Categories = (Post | CategoryList)[]
|
||||
export type Categories = Map<string, Post | CategoryList>
|
||||
|
||||
// todo write unit test
|
||||
export function isCategoryList(category: any): category is CategoryList {
|
||||
|
@ -53,12 +53,12 @@ export function useCategories(category?: MaybeRef<string>, posts: Post[] = []) {
|
|||
const categoryList: CategoryList = {
|
||||
name: 'All',
|
||||
total: posts.length,
|
||||
children: [
|
||||
{ name: 'Uncategorized', total: 0, children: [] },
|
||||
],
|
||||
children: new Map([
|
||||
['Uncategorized', { name: 'Uncategorized', total: 0, children: new Map() }],
|
||||
]),
|
||||
}
|
||||
|
||||
const uncategorized = categoryList.children.find(item => item.name === 'Uncategorized')!
|
||||
const uncategorized = categoryList.children.get('Uncategorized')!
|
||||
|
||||
posts.forEach((post: Post) => {
|
||||
if (post.categories) {
|
||||
|
@ -69,20 +69,21 @@ export function useCategories(category?: MaybeRef<string>, posts: Post[] = []) {
|
|||
let parentCategory: CategoryList = curCategoryList
|
||||
|
||||
post.categories.forEach((categoryName, i) => {
|
||||
// console.log(parentCategory, curCategoryList.children, 'post', categoryName)
|
||||
curCategoryList.total += 1
|
||||
curCategoryList = (curCategoryList.children.find(item => item.name === categoryName)) as CategoryList
|
||||
curCategoryList = curCategoryList.children.get(categoryName) as CategoryList
|
||||
|
||||
if (!curCategoryList) {
|
||||
curCategoryList = {
|
||||
name: categoryName,
|
||||
total: 0,
|
||||
children: [],
|
||||
children: new Map(),
|
||||
}
|
||||
parentCategory.children.push(curCategoryList)
|
||||
parentCategory.children.set(categoryName, curCategoryList)
|
||||
}
|
||||
|
||||
if (i === len - 1) {
|
||||
curCategoryList.children.push(post)
|
||||
curCategoryList.children.set(post.path!, post)
|
||||
curCategoryList.total += 1
|
||||
}
|
||||
|
||||
|
@ -92,23 +93,25 @@ export function useCategories(category?: MaybeRef<string>, posts: Post[] = []) {
|
|||
else {
|
||||
// for string
|
||||
const categoryName = post.categories
|
||||
const curCategory = categoryList.children.find(item => item.name === categoryName)
|
||||
const curCategory = categoryList.children.get(categoryName)
|
||||
if (curCategory) {
|
||||
curCategory.total += 1
|
||||
curCategory.children.push(post)
|
||||
curCategory.children.set(post.path!, post)
|
||||
}
|
||||
else {
|
||||
categoryList.children.push({
|
||||
categoryList.children.set(categoryName, {
|
||||
name: categoryName,
|
||||
total: 1,
|
||||
children: [post],
|
||||
children: new Map([
|
||||
[post.path!, post],
|
||||
]),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
uncategorized.total += 1
|
||||
uncategorized.children.push(post)
|
||||
uncategorized.children.set(post.path!, post)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -116,7 +119,7 @@ export function useCategories(category?: MaybeRef<string>, posts: Post[] = []) {
|
|||
|
||||
// clear uncategorized
|
||||
if (uncategorized!.total === 0)
|
||||
categoryList.children.shift()
|
||||
categoryList.children.delete('Uncategorized')
|
||||
|
||||
if (!categories) {
|
||||
return categoryList
|
||||
|
@ -125,7 +128,7 @@ export function useCategories(category?: MaybeRef<string>, posts: Post[] = []) {
|
|||
let curCategoryList = categoryList
|
||||
const categoryArr = categories.split('/')
|
||||
for (const categoryName of categoryArr) {
|
||||
const tempCList = curCategoryList.children.find(item => item.name === categoryName)
|
||||
const tempCList = curCategoryList.children.get(categoryName)
|
||||
if (tempCList && tempCList.children) {
|
||||
curCategoryList = tempCList as CategoryList
|
||||
}
|
||||
|
@ -147,10 +150,7 @@ export function useCategories(category?: MaybeRef<string>, posts: Post[] = []) {
|
|||
export function removeItemFromCategory(categoryList: CategoryList, categoryName: string) {
|
||||
if (isCategoryList(categoryList)) {
|
||||
const categoryArr = categoryName.split('/')
|
||||
// todo loop find
|
||||
const categoryListItemIndex = categoryList.children.findIndex(item => item.name === categoryArr[0])
|
||||
|
||||
categoryList.children.splice(categoryListItemIndex, 1)
|
||||
categoryList.children.delete(categoryArr[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,9 +18,12 @@ export function usePostTitle(post: ComputedRef<Post>) {
|
|||
* get all page in 'pages' folder
|
||||
*/
|
||||
export function usePageList() {
|
||||
const router = useRouter()
|
||||
return computed<Post[]>(() => {
|
||||
const excludePages = ['/:..all', '/:all(.*)*', '/', '/:path(.*)']
|
||||
const router = useRouter()
|
||||
if (!router)
|
||||
return []
|
||||
|
||||
const routes = router.getRoutes()
|
||||
.filter(i => i.name)
|
||||
.filter(i => i.meta)
|
||||
|
|
|
@ -39,8 +39,7 @@ import valaxyMessages from '/@valaxyjs/locales'
|
|||
function shouldHotReload(payload: PageDataPayload): boolean {
|
||||
const payloadPath = payload.path.replace(/(\bindex)?\.md$/, '')
|
||||
const locationPath = location.pathname.replace(/(\bindex)?\.html$/, '')
|
||||
// console.log(payloadPath, locationPath)
|
||||
return ensureSuffix('/', payloadPath) === ensureSuffix('/', locationPath)
|
||||
return ensureSuffix('/', encodeURI(payloadPath)) === ensureSuffix('/', encodeURI(locationPath))
|
||||
}
|
||||
|
||||
export async function install({ app, router }: ViteSSGContext, config: ComputedRef<ValaxyConfig<DefaultTheme.Config>>) {
|
||||
|
|
|
@ -30,19 +30,22 @@ export const useSiteStore = defineStore('site', () => {
|
|||
if (payload.path.endsWith('.md'))
|
||||
path = payload.path.slice(0, -3)
|
||||
|
||||
const routeName = path.split('/').slice(1).join('-')
|
||||
|
||||
const routeName = path
|
||||
if (!router.hasRoute(routeName))
|
||||
return
|
||||
|
||||
// can not use generatedRoutes, otherwise will trigger ValaxyMain refresh
|
||||
const route = router.getRoutes().find(r => r.name === routeName)!
|
||||
router.removeRoute(routeName)
|
||||
if (route.meta)
|
||||
route.meta.frontmatter = payload.pageData.frontmatter
|
||||
if (route.meta) {
|
||||
route.meta.frontmatter = {
|
||||
...route.meta.frontmatter,
|
||||
...payload.pageData.frontmatter,
|
||||
}
|
||||
}
|
||||
router.addRoute(route)
|
||||
|
||||
// trigger computed reload
|
||||
// trigger `computed` reload, not server
|
||||
reload.value += 1
|
||||
})
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ export async function initServer(options: ResolvedValaxyOptions, viteConfig: Inl
|
|||
server = await createServer(options, viteConfigs, {
|
||||
async onConfigReload(newConfig, config, force = false) {
|
||||
if (force) {
|
||||
consola.info('[valaxy]', `${yellow('force')} reload the server`)
|
||||
vLogger.info(`${yellow('force')} reload the server`)
|
||||
initServer(options, viteConfig)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,10 +30,15 @@ export function loadConfig<T extends UserInputConfig = UserInputConfig>(options:
|
|||
}): ResolvedConfig<T> {
|
||||
const { name, cwd } = options
|
||||
const filePath = resolve(cwd, `${name}.config.ts`)
|
||||
const data = jiti(fileURLToPath(import.meta.url), {
|
||||
interopDefault: true,
|
||||
requireCache: false,
|
||||
})(filePath)
|
||||
let data = {} as T
|
||||
|
||||
try {
|
||||
data = jiti(fileURLToPath(import.meta.url), {
|
||||
interopDefault: true,
|
||||
requireCache: false,
|
||||
})(filePath)
|
||||
}
|
||||
catch (e) { }
|
||||
|
||||
return {
|
||||
config: data,
|
||||
|
|
|
@ -4,6 +4,7 @@ import matter from 'gray-matter'
|
|||
import { isDate } from '@antfu/utils'
|
||||
import { convert } from 'html-to-text'
|
||||
import type { ExcerptType, Page } from 'valaxy/types'
|
||||
import type { RouteMeta } from 'vue-router'
|
||||
import type { ResolvedValaxyOptions } from '../options'
|
||||
import { EXCERPT_SEPARATOR } from '../constants'
|
||||
|
||||
|
@ -49,6 +50,11 @@ export function createRouterPlugin(options: ResolvedValaxyOptions) {
|
|||
*/
|
||||
async extendRoute(route) {
|
||||
const defaultFrontmatter = JSON.parse(JSON.stringify(valaxyConfig.siteConfig.frontmatter)) || {}
|
||||
if (route.meta && route.meta.frontmatter) {
|
||||
// reset frontmatter, extendRoute will be trigger when save md file
|
||||
const { frontmatter: _, otherMeta } = route.meta
|
||||
route.meta = otherMeta as RouteMeta
|
||||
}
|
||||
// merge deeply
|
||||
route.addToMeta({
|
||||
frontmatter: defaultFrontmatter,
|
||||
|
|
Loading…
Reference in New Issue