refactor: use map for categories

This commit is contained in:
YunYouJun 2024-01-20 03:22:25 +08:00
parent abd2f2e495
commit 5f330c3ecf
19 changed files with 95 additions and 133 deletions

View File

@ -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>

View File

@ -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

View File

@ -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/) 等进行托管。

View File

@ -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',

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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 />

View File

@ -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">

View File

@ -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])
}
}

View File

@ -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)

View File

@ -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>>) {

View File

@ -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
})
}

View File

@ -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)
}

View File

@ -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,

View File

@ -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,