Compare commits
7
Commits
1cefa4c661
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dacc3d5146 | ||
|
|
2ab2bba546 | ||
|
|
ef224f1326 | ||
|
|
886fcfde70 | ||
|
|
0b83b687d9 | ||
|
|
9043cf53fd | ||
|
|
6c963257cf |
@@ -29,3 +29,4 @@ pnpm-workspace.yaml
|
|||||||
/nuxt.run.xml
|
/nuxt.run.xml
|
||||||
/server_ nuxt.run.xml
|
/server_ nuxt.run.xml
|
||||||
/output.tar.gz
|
/output.tar.gz
|
||||||
|
.pnpm-store
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
FROM cnbcool/default-build-env:latest
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y wget unzip lsof nload htop net-tools dnsutils openssh-server zsh openssh-server command-not-found && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /root
|
||||||
|
RUN git clone https://git.lichx.top/li_chx/shell-config.git && \
|
||||||
|
cp -r shell-config/.* . && \
|
||||||
|
wget https://download.jetbrains.com/webstorm/WebStorm-2025.2.5.tar.gz && \
|
||||||
|
mkdir -p /ide_cnb && \
|
||||||
|
tar -zxvf WebStorm-2025.2.5.tar.gz -C /ide_cnb && \
|
||||||
|
rm WebStorm-2025.2.5.tar.gz && \
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash && \
|
||||||
|
source ~/.bashrc && \
|
||||||
|
nvm install 24 && \
|
||||||
|
npm install --global corepack@latest && \
|
||||||
|
corepack enable pnpm && \
|
||||||
|
apt update
|
||||||
|
|
||||||
|
# 单独处理 zsh 配置
|
||||||
|
RUN zsh -c "source ~/.zshrc" && \
|
||||||
|
echo "zsh" >> ~/.bashrc
|
||||||
|
|
||||||
|
RUN curl -fsSL https://code-server.dev/install.sh | sh \
|
||||||
|
&& code-server --install-extension nuxtr.nuxt-vscode-extentions \
|
||||||
|
&& code-server --install-extension bradlc.vscode-tailwindcss \
|
||||||
|
&& code-server --install-extension tencent-cloud.coding-copilot \
|
||||||
|
&& code-server --install-extension ms-vscode.vs-keybindings
|
||||||
|
|
||||||
|
ENV LANG C.UTF-8
|
||||||
|
ENV LANGUAGE C.UTF-8
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"vue.volar",
|
||||||
|
"vue.vscode-typescript-vue-plugin",
|
||||||
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"ms-vscode.vscode-typescript-next",
|
||||||
|
"nuxtr.nuxt-vscode-extentions"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"typescript.preferences.includePackageJsonAutoImports": "on",
|
||||||
|
"typescript.suggest.autoImports": true,
|
||||||
|
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||||
|
"vue.codeActions.enabled": true,
|
||||||
|
"vue.complete.casing.tags": "kebab",
|
||||||
|
"vue.complete.casing.props": "camel",
|
||||||
|
"vetur.validation.template": false,
|
||||||
|
"vetur.validation.script": false,
|
||||||
|
"vetur.validation.style": false,
|
||||||
|
"emmet.includeLanguages": {
|
||||||
|
"vue": "html"
|
||||||
|
},
|
||||||
|
"files.associations": {
|
||||||
|
"*.vue": "vue"
|
||||||
|
},
|
||||||
|
"editor.quickSuggestions": {
|
||||||
|
"strings": true
|
||||||
|
},
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,15 +9,18 @@ const props = withDefaults(defineProps<{
|
|||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(event: 'filterRuleChange', rule: (data: PostMetaData) => boolean): void;
|
(event: 'filterRuleChange', rule: (data: PostMetaData) => boolean): void;
|
||||||
}>();
|
}>();
|
||||||
const articleCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'article').length || 0);
|
|
||||||
const announcementCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'announcement').length || 0);
|
const typeGroup = computed(() => {
|
||||||
const ramblingCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'rambling').length || 0);
|
const articleCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'article').length || 0);
|
||||||
const countGroup = [
|
const announcementCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'announcement').length || 0);
|
||||||
|
const ramblingCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'rambling').length || 0);
|
||||||
|
return [
|
||||||
{ name: '文章', count: articleCount, type: 'article' },
|
{ name: '文章', count: articleCount, type: 'article' },
|
||||||
{ name: '絮语', count: ramblingCount, type: 'rambling' },
|
{ name: '絮语', count: ramblingCount, type: 'rambling' },
|
||||||
{ name: '公告', count: announcementCount, type: 'announcement' },
|
{ name: '公告', count: announcementCount, type: 'announcement' },
|
||||||
];
|
];
|
||||||
const categoryEnableStatus: Ref<boolean[]> = ref(Array(countGroup.length).fill(true));
|
});
|
||||||
|
const typeEnableStatus: Ref<boolean[]> = ref(Array(typeGroup.value.length).fill(true));
|
||||||
|
|
||||||
const categories = computed(() => {
|
const categories = computed(() => {
|
||||||
const categoryMap = new Map<string, number>();
|
const categoryMap = new Map<string, number>();
|
||||||
@@ -43,39 +46,104 @@ const tags = computed(() => {
|
|||||||
return tagArray;
|
return tagArray;
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateCategoryEnableStatus(index: number) {
|
const categoriesEnableStatus: Ref<boolean[]> = ref(new Array(categories.value.length).fill(true));
|
||||||
if (categoryEnableStatus.value.reduce((last, cur) => last && cur, true)) {
|
const tagsEnableStatus: Ref<boolean[]> = ref(new Array(tags.value.length).fill(true));
|
||||||
for (let i = 0; i < categoryEnableStatus.value.length; i++) {
|
|
||||||
|
function updateTypeEnableStatus(index: number) {
|
||||||
|
if (typeEnableStatus.value.reduce((last, cur) => last && cur, true)) {
|
||||||
|
for (let i = 0; i < typeEnableStatus.value.length; i++) {
|
||||||
if (i !== index) {
|
if (i !== index) {
|
||||||
categoryEnableStatus.value[i] = false;
|
typeEnableStatus.value[i] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!categoryEnableStatus.value.reduce((last, cur, localIndex) => last || (localIndex === index ? false : cur), false)) {
|
} else if (!typeEnableStatus.value.reduce((last, cur, localIndex) => last || (localIndex === index ? false : cur), false)) {
|
||||||
for (let i = 0; i < categoryEnableStatus.value.length; i++) {
|
for (let i = 0; i < typeEnableStatus.value.length; i++) {
|
||||||
categoryEnableStatus.value[i] = true;
|
typeEnableStatus.value[i] = true;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
categoryEnableStatus.value[index] = !categoryEnableStatus.value[index];
|
typeEnableStatus.value[index] = !typeEnableStatus.value[index];
|
||||||
|
updateRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCategoryEnableStatus(index: number) {
|
||||||
|
if (categoriesEnableStatus.value.reduce((last, cur) => last && cur, true)) {
|
||||||
|
for (let i = 0; i < categoriesEnableStatus.value.length; i++) {
|
||||||
|
if (i !== index) {
|
||||||
|
categoriesEnableStatus.value[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (categoriesEnableStatus.value[index] && !categoriesEnableStatus.value.reduce((last, cur, localIndex) => last || (localIndex === index ? false : cur), false)) {
|
||||||
|
for (let i = 0; i < categoriesEnableStatus.value.length; i++) {
|
||||||
|
categoriesEnableStatus.value[i] = true;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
categoriesEnableStatus.value[index] = !categoriesEnableStatus.value[index];
|
||||||
|
updateRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTagEnableStatus(index: number) {
|
||||||
|
if (tagsEnableStatus.value.reduce((last, cur) => last && cur, true)) {
|
||||||
|
for (let i = 0; i < tagsEnableStatus.value.length; i++) {
|
||||||
|
if (i !== index) {
|
||||||
|
tagsEnableStatus.value[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (tagsEnableStatus.value[index] && !tagsEnableStatus.value.reduce((last, cur, localIndex) => last || (localIndex === index ? false : cur), false)) {
|
||||||
|
for (let i = 0; i < tagsEnableStatus.value.length; i++) {
|
||||||
|
tagsEnableStatus.value[i] = true;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
tagsEnableStatus.value[index] = !tagsEnableStatus.value[index];
|
||||||
updateRule();
|
updateRule();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRule() {
|
function updateRule() {
|
||||||
|
const enabledCategories = categoriesEnableStatus.value.reduce((last, cur, localIndex) => {
|
||||||
|
if (cur) last.add(categories.value[localIndex]![0]);
|
||||||
|
return last;
|
||||||
|
}, new Set<string>());
|
||||||
|
const enabledTags = tagsEnableStatus.value.reduce((last, cur, localIndex) => {
|
||||||
|
if (cur) last.add(tags.value[localIndex]![0]);
|
||||||
|
return last;
|
||||||
|
}, new Set<string>());
|
||||||
|
// const enable
|
||||||
emits('filterRuleChange', (post) => {
|
emits('filterRuleChange', (post) => {
|
||||||
for (let i = 0; i < categoryEnableStatus.value.length; i++) {
|
// type check
|
||||||
if (categoryEnableStatus.value[i] && post.type === countGroup[i]!.type) {
|
let tempAns = false;
|
||||||
console.log('filter', post.title, 'true');
|
for (let i = 0; i < typeEnableStatus.value.length; i++) {
|
||||||
return true;
|
if (typeEnableStatus.value[i] && post.type === typeGroup.value[i]!.type) {
|
||||||
|
tempAns = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('filter', post.title, 'false');
|
// category check
|
||||||
|
if (tempAns)
|
||||||
|
tempAns = false;
|
||||||
|
else
|
||||||
return false;
|
return false;
|
||||||
|
if (post.category && enabledCategories.has(post.category))
|
||||||
|
tempAns = true;
|
||||||
|
else tempAns = !post.category && enabledCategories.size === categories.value.length;
|
||||||
|
// tag check
|
||||||
|
if (tempAns)
|
||||||
|
tempAns = false;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
if (tagsEnableStatus.value.length === enabledTags.size)
|
||||||
|
tempAns = true;
|
||||||
|
else for (const tag of post.tags || []) {
|
||||||
|
if (enabledTags.has(tag)) {
|
||||||
|
tempAns = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tempAns;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500 p-5">
|
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 p-5">
|
||||||
<div class="text-2xl ml-1 flex items-center">
|
<div class="text-2xl ml-1 flex items-center">
|
||||||
<Icon class="mr-2" name="material-symbols:category"/>
|
<Icon class="mr-2" name="material-symbols:category"/>
|
||||||
类型
|
类型
|
||||||
@@ -83,18 +151,18 @@ function updateRule() {
|
|||||||
<hr class="border-0 h-[1px] bg-old-neutral-600 mt-3 mb-1"/>
|
<hr class="border-0 h-[1px] bg-old-neutral-600 mt-3 mb-1"/>
|
||||||
<div class="flex mt-4">
|
<div class="flex mt-4">
|
||||||
<div
|
<div
|
||||||
v-for="(data, index) of countGroup"
|
v-for="(data, index) of typeGroup"
|
||||||
:key="data.name"
|
:key="data.name"
|
||||||
class="flex items-center flex-col flex-1 text-xl cursor-pointer hover:text-sky-400 dark:hover:text-[#cccaff] transition-colors duration-300"
|
class="flex items-center flex-col flex-1 text-xl cursor-pointer hover:text-sky-400 dark:hover:text-[#cccaff] transition-colors duration-300"
|
||||||
:class="{'text-old-neutral-400': !categoryEnableStatus[index]}"
|
:class="{'text-old-neutral-400': !typeEnableStatus[index]}"
|
||||||
@click="updateCategoryEnableStatus(index)"
|
@click="updateTypeEnableStatus(index)"
|
||||||
>
|
>
|
||||||
<div>{{ data.name }}</div>
|
<div>{{ data.name }}</div>
|
||||||
<div>{{ data.count }}</div>
|
<div>{{ data.count }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500 p-5 mt-4">
|
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 p-5 mt-4">
|
||||||
<div class="text-2xl ml-1 flex items-center">
|
<div class="text-2xl ml-1 flex items-center">
|
||||||
<Icon class="mr-2" name="material-symbols:book"/>
|
<Icon class="mr-2" name="material-symbols:book"/>
|
||||||
分类
|
分类
|
||||||
@@ -102,7 +170,10 @@ function updateRule() {
|
|||||||
<hr class="border-0 h-[1px] bg-old-neutral-600 mt-3 mb-1"/>
|
<hr class="border-0 h-[1px] bg-old-neutral-600 mt-3 mb-1"/>
|
||||||
<div
|
<div
|
||||||
v-for="([name,count],index) of categories" :key="index"
|
v-for="([name,count],index) of categories" :key="index"
|
||||||
class="flex justify-between pl-4 pr-4 hover:text-sky-400 dark:hover:text-[#cccaff] transition-colors duration-300">
|
class="flex justify-between pl-4 pr-4 hover:text-sky-400 dark:hover:text-[#cccaff] transition-colors duration-300"
|
||||||
|
:class="{'text-old-neutral-400': !categoriesEnableStatus[index]}"
|
||||||
|
@click="updateCategoryEnableStatus(index)"
|
||||||
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Icon
|
<Icon
|
||||||
name="material-symbols:book-outline"
|
name="material-symbols:book-outline"
|
||||||
@@ -114,7 +185,7 @@ function updateRule() {
|
|||||||
<div>{{ count }}</div>
|
<div>{{ count }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500 p-5 mt-4">
|
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 p-5 mt-4">
|
||||||
<div class="text-2xl ml-1 flex items-center">
|
<div class="text-2xl ml-1 flex items-center">
|
||||||
<Icon class="mr-2" name="material-symbols:bookmarks"/>
|
<Icon class="mr-2" name="material-symbols:bookmarks"/>
|
||||||
标签
|
标签
|
||||||
@@ -123,7 +194,10 @@ function updateRule() {
|
|||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<div
|
<div
|
||||||
v-for="([name,count],index) of tags" :key="index"
|
v-for="([name,count],index) of tags" :key="index"
|
||||||
class="flex items-center justify-between text-[15px] pl-2 pr-2 m-1 rounded-2xl shadow-[0_0_0_1px_#888] hover:text-sky-400 dark:hover:text-[#cccaff] hover:shadow-[0_0_0_1px_#00bcff] dark:hover:shadow-[0_0_0_1px_#cccaff] transition-colors transition-shadow duration-300">
|
class="flex items-center justify-between text-[15px] pl-2 pr-2 m-1 rounded-2xl shadow-[0_0_0_1px_#888] hover:text-sky-400 dark:hover:text-[#cccaff] hover:shadow-[0_0_0_1px_#00bcff] dark:hover:shadow-[0_0_0_1px_#cccaff] transition-shadow duration-300"
|
||||||
|
:class="{'text-old-neutral-400': !tagsEnableStatus[index]}"
|
||||||
|
@click="updateTagEnableStatus(index)"
|
||||||
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="clarity:hashtag-solid"
|
name="clarity:hashtag-solid"
|
||||||
size="17"
|
size="17"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500 p-5">
|
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 p-5">
|
||||||
Author: Lichx
|
Author: Lichx
|
||||||
<div>
|
<div>
|
||||||
Contact me:
|
Contact me:
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="pt-0 bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500">
|
<div class="pt-0 bg-old-neutral-200 dark:bg-old-neutral-800">
|
||||||
<MdPreview
|
<MdPreview
|
||||||
v-if="mounted"
|
v-if="mounted"
|
||||||
:key="editorId + '-' + colorMode"
|
:key="editorId + '-' + colorMode"
|
||||||
:editor-id="editorId"
|
:editor-id="editorId"
|
||||||
:theme="colorMode"
|
:theme="colorMode"
|
||||||
:model-value="eraseHeaderMarkdown"
|
:model-value="eraseHeaderMarkdown"
|
||||||
class="transition-all duration-500 max-w-full"
|
class="max-w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,6 +4,5 @@ const breakpoints = {
|
|||||||
'lg': '1024px',
|
'lg': '1024px',
|
||||||
'xl': '1280px',
|
'xl': '1280px',
|
||||||
'2xl': '1536px',
|
'2xl': '1536px',
|
||||||
'hidden-logo': '1736px',
|
|
||||||
};
|
};
|
||||||
export default breakpoints;
|
export default breakpoints;
|
||||||
|
|||||||
+1
-1
Submodule content updated: e4c08e4b0a...2865b12ed9
+16
-34
@@ -4,9 +4,7 @@ import useColorModeStore from '~/stores/colorModeStore';
|
|||||||
import { useWindowScroll } from '@vueuse/core';
|
import { useWindowScroll } from '@vueuse/core';
|
||||||
|
|
||||||
const { colorMode } = storeToRefs(useColorModeStore());
|
const { colorMode } = storeToRefs(useColorModeStore());
|
||||||
watch(colorMode, () => {
|
|
||||||
console.log('colorMode changed:', colorMode.value);
|
|
||||||
});
|
|
||||||
const isHome = computed(() => useRoute().path === '/');
|
const isHome = computed(() => useRoute().path === '/');
|
||||||
const items = ref<NavigationMenuItem[]>([
|
const items = ref<NavigationMenuItem[]>([
|
||||||
{
|
{
|
||||||
@@ -69,55 +67,38 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full min-h-[100vh] h-full">
|
<div class="bg-old-neutral-50 dark:bg-[#0b0d0d] w-full min-h-[100vh] h-full">
|
||||||
<UApp>
|
<UApp>
|
||||||
<div
|
<div
|
||||||
:class=" (collapsed ? 'h-[20vh]': 'h-[40vh]')"
|
:class=" (collapsed ? 'h-[20vh]': 'h-[40vh]')"
|
||||||
class="flex flex-col relative transition-all duration-500 ease-in-out" @mouseenter="() => {
|
class="flex flex-col relative transition-[height] duration-500 max-h-80">
|
||||||
|
<!-- header -->
|
||||||
|
<div
|
||||||
|
v-if="mounted"
|
||||||
|
@mouseenter="() => {
|
||||||
if(scrollY === 0) {
|
if(scrollY === 0) {
|
||||||
collapsed = false;
|
collapsed = false;
|
||||||
}
|
}
|
||||||
}"
|
}" @mouseleave="collapsed = true">
|
||||||
@mouseleave="collapsed = true">
|
|
||||||
<!-- header -->
|
|
||||||
<div v-if="mounted">
|
|
||||||
<Transition
|
|
||||||
enter-active-class="transition-opacity duration-500 ease-in-out"
|
|
||||||
enter-from-class="opacity-0"
|
|
||||||
enter-to-class="opacity-100"
|
|
||||||
leave-active-class="transition-opacity duration-500 ease-in-out"
|
|
||||||
leave-from-class="opacity-100"
|
|
||||||
leave-to-class="opacity-0"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-if="colorMode === 'light'"
|
v-if="colorMode === 'light'"
|
||||||
class="flex h-full w-full absolute bg-[url('/79d52228c770808810a310115567e6790380823a.png')] bg-cover bg-top ">
|
class="flex bg-top absolute w-full h-full bg-[url('/79d52228c770808810a310115567e6790380823a.webp')] ">
|
||||||
<slot name="header"/>
|
<slot name="header"/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="flex h-full w-full absolute bg-[url('/anime-8788959.jpg')] bg-cover bg-center">
|
class="flex bg-top absolute w-full h-full bg-[url('/anime-8788959.webp')]">
|
||||||
<slot name="header"/>
|
<slot name="header"/>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
|
||||||
<!-- header picture -->
|
<!-- header picture -->
|
||||||
<Transition
|
|
||||||
enter-active-class="transition-opacity duration-500 ease-in-out"
|
|
||||||
enter-from-class="opacity-0"
|
|
||||||
enter-to-class="opacity-100"
|
|
||||||
leave-active-class="transition-opacity duration-500 ease-in-out"
|
|
||||||
leave-from-class="opacity-100"
|
|
||||||
leave-to-class="opacity-0"
|
|
||||||
>
|
|
||||||
<div v-if="isScrollDown">
|
<div v-if="isScrollDown">
|
||||||
<div
|
<div
|
||||||
v-if="colorMode === 'light'"
|
v-if="colorMode === 'light'"
|
||||||
class="opacity-80 max-h-[48px] flex w-full h-full fixed bg-[url('/79d52228c770808810a310115567e6790380823a.png')] bg-cover bg-top"/>
|
class="opacity-80 max-h-[48px] flex w-full h-full fixed bg-[url('/79d52228c770808810a310115567e6790380823a.webp')] bg-cover bg-top"/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="opacity-20 max-h-[48px] flex w-full h-full fixed bg-[url('/anime-8788959.jpg')] bg-cover bg-center"/>
|
class="opacity-20 max-h-[48px] flex w-full h-full fixed bg-[url('/anime-8788959.webp')] bg-cover bg-center"/>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- navbar -->
|
<!-- navbar -->
|
||||||
<div
|
<div
|
||||||
@@ -127,7 +108,7 @@ onMounted(() => {
|
|||||||
<slot name="navbarLeft" :is-scroll-down="isScrollDown"/>
|
<slot name="navbarLeft" :is-scroll-down="isScrollDown"/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="transition-all duration-500 flex 2xl:w-[1240px] xl:w-[1020px] lg:w-[964px] md:w-[708px] sm:w-[580px] w-10/12">
|
class="transition-[width] duration-500 flex 2xl:w-[1240px] xl:w-[1020px] lg:w-[964px] md:w-[708px] sm:w-[580px] w-10/12">
|
||||||
<UNavigationMenu v-if="mounted" :items="items" :class="colorMode" class="w-full"/>
|
<UNavigationMenu v-if="mounted" :items="items" :class="colorMode" class="w-full"/>
|
||||||
<div v-else class="w-full h-12 animate-pulse"></div>
|
<div v-else class="w-full h-12 animate-pulse"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -147,10 +128,11 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center items-center duration-500 bg-white dark:bg-[#16191b] h-full">
|
<!-- content -->
|
||||||
|
<div class="flex justify-center items-center bg-white dark:bg-[#16191b] h-full">
|
||||||
<div
|
<div
|
||||||
:class="collapsed ? 'min-h-[80vh]' : 'min-h-[60vh]'"
|
:class="collapsed ? 'min-h-[80vh]' : 'min-h-[60vh]'"
|
||||||
class="transition-all duration-500 ease-in-out 2xl:w-[1240px] xl:w-[1020px] lg:w-[964px] md:w-[708px] sm:w-[580px] w-11/12">
|
class="transition-[width] duration-500 ease-in-out 2xl:w-[1240px] xl:w-[1020px] lg:w-[964px] md:w-[708px] sm:w-[580px] w-11/12">
|
||||||
<slot name="content"/>
|
<slot name="content"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+13
-1
@@ -33,11 +33,14 @@
|
|||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1",
|
||||||
"word-count": "^0.3.1"
|
"word-count": "^0.3.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.18.0",
|
"packageManager": "pnpm@10.22.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/clarity": "^1.2.4",
|
"@iconify-json/clarity": "^1.2.4",
|
||||||
|
"@iconify-json/codicon": "^1.2.32",
|
||||||
|
"@iconify-json/fluent": "^1.2.34",
|
||||||
"@iconify-json/lucide": "^1.2.68",
|
"@iconify-json/lucide": "^1.2.68",
|
||||||
"@iconify-json/material-symbols": "^1.2.40",
|
"@iconify-json/material-symbols": "^1.2.40",
|
||||||
|
"@iconify-json/octicon": "^1.2.19",
|
||||||
"@stylistic/eslint-plugin": "^5.4.0",
|
"@stylistic/eslint-plugin": "^5.4.0",
|
||||||
"@stylistic/eslint-plugin-jsx": "^4.4.1",
|
"@stylistic/eslint-plugin-jsx": "^4.4.1",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
@@ -47,5 +50,14 @@
|
|||||||
"overlayscrollbars": "^2.12.0",
|
"overlayscrollbars": "^2.12.0",
|
||||||
"typescript-eslint": "^8.44.1",
|
"typescript-eslint": "^8.44.1",
|
||||||
"vue-eslint-parser": "^10.2.0"
|
"vue-eslint-parser": "^10.2.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"@parcel/watcher",
|
||||||
|
"better-sqlite3",
|
||||||
|
"esbuild",
|
||||||
|
"unrs-resolver",
|
||||||
|
"vue-demi"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-26
@@ -1,7 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useColorModeStore from '~/stores/colorModeStore';
|
|
||||||
import breakpointsHelper from '~/utils/BreakpointsHelper';
|
import breakpointsHelper from '~/utils/BreakpointsHelper';
|
||||||
|
import ThemeChange from '~/pages/index/components/ThemeChange.vue';
|
||||||
|
|
||||||
|
const hitokoto = ref('加载中...');
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const src = await $fetch<{ hitokoto: string; from_who?: string; from?: string }>('https://v1.hitokoto.cn?c=k');
|
||||||
|
hitokoto.value = `${src.hitokoto} —— ${src.from_who ? src.from_who : '佚名'}${src.from ? `,${src.from}` : '未知来源'}`;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -25,31 +31,8 @@ import breakpointsHelper from '~/utils/BreakpointsHelper';
|
|||||||
<template #navbarRight>
|
<template #navbarRight>
|
||||||
<div class="flex items-center h-full">
|
<div class="flex items-center h-full">
|
||||||
<div class="flex-1"/>
|
<div class="flex-1"/>
|
||||||
<div class="flex-1 flex items-center justify-end duration500 ease-in-out">
|
<div class="flex-1 flex items-center justify-end ease-in-out">
|
||||||
<Transition
|
<ThemeChange />
|
||||||
mode="out-in"
|
|
||||||
enter-active-class="transition-opacity duration-300 ease-in-out"
|
|
||||||
enter-from-class="opacity-0"
|
|
||||||
enter-to-class="opacity-100"
|
|
||||||
leave-active-class="transition-opacity duration-300 ease-in-out"
|
|
||||||
leave-from-class="opacity-100"
|
|
||||||
leave-to-class="opacity-0"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
v-if="useColorModeStore().colorMode === 'dark'"
|
|
||||||
key="dark"
|
|
||||||
name="material-symbols:dark-mode"
|
|
||||||
class="text-2xl cursor-pointer mr-5"
|
|
||||||
@click="() => useColorModeStore().toggleColorMode()"
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
v-else
|
|
||||||
key="light"
|
|
||||||
name="material-symbols:clear-day-rounded"
|
|
||||||
class="text-2xl cursor-pointer mr-5"
|
|
||||||
@click="() => useColorModeStore().toggleColorMode()"
|
|
||||||
/>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -68,6 +51,9 @@ import breakpointsHelper from '~/utils/BreakpointsHelper';
|
|||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="w-full flex flex-col justify-center items-center p-10 text-old-neutral-500">
|
<div class="w-full flex flex-col justify-center items-center p-10 text-old-neutral-500">
|
||||||
|
<div>
|
||||||
|
{{ hitokoto }}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
© 2025 随机存取. 由Lichx制作
|
© 2025 随机存取. 由Lichx制作
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup lang="tsx">
|
||||||
|
const techStack = ['Vue', 'Nuxt', 'TypeScript', 'Python', 'Java', 'C#', 'Rust'];
|
||||||
|
const techStackPercent = [86, 80, 92, 75, 60, 90, 50];
|
||||||
|
const techStackIconNames = ['mdi:vuejs', 'lineicons:nuxt', 'mdi:language-typescript', 'mdi:language-python', 'mdi:language-java', 'mdi:language-csharp', 'mdi:language-rust'];
|
||||||
|
const techStackThemeColors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#FFB347', '#98D8C8', '#F7DC6F', '#BB8FCE'];
|
||||||
|
const mounted = ref(false);
|
||||||
|
onMounted(() => {
|
||||||
|
mounted.value = true;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="light:bg-old-neutral-200 dark:bg-old-neutral-800 p-5 mt-3">
|
||||||
|
<div class="text-2xl pb-2">关于作者</div>
|
||||||
|
lichx,目前就读于武汉理工大学,技术涉猎广泛但不精,仍在持续学习中 <br/>
|
||||||
|
</div>
|
||||||
|
<div class="light:bg-old-neutral-200 dark:bg-old-neutral-800 p-5 mt-3">
|
||||||
|
<div class="text-2xl pb-2">技术栈(相对熟练度)</div>
|
||||||
|
<TechStackCard
|
||||||
|
v-if="mounted" async-key="about page" :tech-stack="techStack"
|
||||||
|
:tech-stack-percent="techStackPercent" :tech-stack-icon-names="techStackIconNames"
|
||||||
|
:tech-stack-theme-colors="techStackThemeColors"/>
|
||||||
|
</div>
|
||||||
|
<div class="light:bg-old-neutral-200 dark:bg-old-neutral-800 p-5 mt-3">
|
||||||
|
<div class="text-2xl pb-2">在其他渠道关注 / 联系我</div>
|
||||||
|
<a href="https://github.com/li-chx" title="github,不常用">
|
||||||
|
<icon name="mdi:github" class="inline-block w-10 h-10 mr-4"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://git.lichx.top/li_chx" title="gitea,主要代码托管平台">
|
||||||
|
<icon name="pajamas:gitea" class="inline-block w-10 h-10 mr-4"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://leetcode.cn/u/gallant-paynekan/" title="leetcode,刷点水题骗骗自己">
|
||||||
|
<icon name="tabler:brand-leetcode" class="inline-block w-10 h-10 mr-4"/>
|
||||||
|
</a>
|
||||||
|
<a href="mailto:751176501@qq.com" title="能不能看见全看运气">
|
||||||
|
<icon name="material-symbols:mail-rounded" class="inline-block w-10 h-10"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<div class="light:bg-old-neutral-200 dark:bg-old-neutral-800 p-5">
|
||||||
|
<div class="text-2xl pb-2">关于本站</div>
|
||||||
|
本站,随机存取,起这个名字纯粹是因为作者想不出来了。最后基于我对该网站的需求,决定叫随机存取<br/>
|
||||||
|
这个网站最重要的还是记录我认为有趣的 学习 / 调试 过程,便于我随时取用,所以写的时候完全就是梦到哪写到哪<br/>
|
||||||
|
但是,如果本人闲暇之余写的文章能帮到你,当是莫大的荣幸<br/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import AuthorDescription from '~/pages/index/about/compoents/AuthorDescription.vue';
|
||||||
|
import SiteDescription from '~/pages/index/about/compoents/SiteDescription.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="mt-6 mb-6 w-full">
|
||||||
|
<SiteDescription />
|
||||||
|
<AuthorDescription />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -4,15 +4,23 @@ import type { PostMetaData } from '~/types/PostMetaData';
|
|||||||
import TimeLine from '~/pages/index/archive/components/TimeLine.vue';
|
import TimeLine from '~/pages/index/archive/components/TimeLine.vue';
|
||||||
import type { RadioGroupItem } from '@nuxt/ui';
|
import type { RadioGroupItem } from '@nuxt/ui';
|
||||||
|
|
||||||
const { data: srcPostsMetaData } = useAsyncData(async () => sortMetaData((await queryCollection('content').all()).map((x) => toMetaDataType(x)), 'published_at', true));
|
const srcPostsMetaData = ref<PostMetaData[]>([]);
|
||||||
const postsMetaData = ref<PostMetaData[]>([]);
|
const postsMetaData = ref<PostMetaData[]>([]);
|
||||||
|
|
||||||
const currentChoice = ref<'time' | 'category'>('time');
|
async function loadPostsMetaData() {
|
||||||
|
srcPostsMetaData.value = sortMetaData((await queryCollection('content').all()).map((x) => toMetaDataType(x)), 'published_at', true) || [];
|
||||||
|
srcPostsMetaData.value = srcPostsMetaData.value.filter((x) => !x.draft);
|
||||||
|
postsMetaData.value = srcPostsMetaData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadPostsMetaData();
|
||||||
|
|
||||||
watch(srcPostsMetaData, () => {
|
watch(srcPostsMetaData, () => {
|
||||||
postsMetaData.value = srcPostsMetaData.value?.filter((x) => !x.draft) || [];
|
postsMetaData.value = srcPostsMetaData.value || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const currentChoice = ref<'time' | 'category'>('time');
|
||||||
|
|
||||||
const choiceItems = ref<RadioGroupItem>([
|
const choiceItems = ref<RadioGroupItem>([
|
||||||
{ label: '时间', value: 'time' },
|
{ label: '时间', value: 'time' },
|
||||||
{ label: '类别', value: 'category' },
|
{ label: '类别', value: 'category' },
|
||||||
@@ -21,16 +29,16 @@ const choiceItems = ref<RadioGroupItem>([
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="table w-full mt-6">
|
<div class="table w-full mt-6 mb-6">
|
||||||
<div class="sticky top-16 float-left bg-old-neutral-200 dark:bg-old-neutral-800 max-h-[calc(100vh-4rem)]">
|
<div class="sticky top-16 float-left bg-old-neutral-200 dark:bg-old-neutral-800 max-h-[calc(100vh-4rem)]">
|
||||||
<div class="relative duration-500 transition-all xl:w-80 w-0 mr-2/3 overflow-hidden">
|
<div class="relative duration-500 transition-[width] xl:w-80 w-0 mr-2/3 overflow-hidden">
|
||||||
<div class="w-80 top-0 left-0 text-gray-800 dark:text-white p-5">
|
<div class="w-80 top-0 left-0 text-gray-800 dark:text-white p-5">
|
||||||
这里还没想好放什么
|
这里还没想好放什么
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="transition-all duration-500 float-right xl:w-[calc(100%-20rem-40px)] w-full bg-old-neutral-200 dark:bg-old-neutral-800 p-5">
|
class="transition-[width] duration-500 float-right xl:w-[calc(100%-20rem-40px)] w-full bg-old-neutral-200 dark:bg-old-neutral-800 p-5">
|
||||||
<URadioGroup
|
<URadioGroup
|
||||||
v-model="currentChoice" orientation="horizontal" variant="table" :items="choiceItems as any[]" size="sm"
|
v-model="currentChoice" orientation="horizontal" variant="table" :items="choiceItems as any[]" size="sm"
|
||||||
class="mb-5"/>
|
class="mb-5"/>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-6 pb-0 light:bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500">
|
<div class="p-6 pb-0 light:bg-old-neutral-200 dark:bg-old-neutral-800">
|
||||||
<UCollapsible v-model:open="open" :unmount-on-hide="false" class="flex flex-col gap-2 w-full">
|
<UCollapsible v-model:open="open" :unmount-on-hide="false" class="flex flex-col gap-2 w-full">
|
||||||
<div class="text-4xl flex justify-between items-center w-full">
|
<div class="text-4xl flex justify-between items-center w-full">
|
||||||
<div class="mb-0 pb-0">
|
<div class="mb-0 pb-0">
|
||||||
|
|||||||
@@ -5,22 +5,21 @@ import ArticleHeader from '~/pages/index/article/[articleID]/components/ArticleH
|
|||||||
|
|
||||||
const articleId = useRoute().params.articleID as string;
|
const articleId = useRoute().params.articleID as string;
|
||||||
const { data: article } = useAsyncData(async () => await queryCollection('content').where('id', '=', articleId).first());
|
const { data: article } = useAsyncData(async () => await queryCollection('content').where('id', '=', articleId).first());
|
||||||
|
|
||||||
const editorId = 'article-previewer';
|
const editorId = 'article-previewer';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="table w-full mt-6 mb-6 table-fixed">
|
<div class="table w-full mt-6 mb-6 table-fixed">
|
||||||
<div class="sticky top-16 float-left bg-old-neutral-200 dark:bg-old-neutral-800 max-h-[calc(100vh-4rem)] transition-colors duration-500">
|
<div class="sticky top-16 float-left bg-old-neutral-200 dark:bg-old-neutral-800 max-h-[calc(100vh-4rem)]">
|
||||||
<div class="relative duration-500 transition-all xl:w-80 w-0 mr-2/3 overflow-hidden">
|
<div class="relative duration-500 transition-[width] xl:w-80 w-0 mr-2/3 overflow-hidden">
|
||||||
<div class="w-80 top-0 left-0 text-gray-800 dark:text-white p-5 transition-colors duration-500">
|
<div class="w-80 top-0 left-0 text-gray-800 dark:text-white p-5">
|
||||||
<div class="text-3xl mb-2">目录</div>
|
<div class="text-3xl mb-2">目录</div>
|
||||||
<MdCatalog :editor-id="editorId" :scroll-element="'html'" class=""/>
|
<MdCatalog :editor-id="editorId" :scroll-element="'html'" class=""/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="transition-all duration-500 float-right xl:w-[calc(100%-20rem-40px)] w-full max-w-full">
|
<div class="transition-[width] duration-500 float-right xl:w-[calc(100%-20rem-40px)] w-full max-w-full">
|
||||||
<ArticleHeader v-if="article" class="w-full" :meta-data="toMetaDataType(article)"/>
|
<ArticleHeader v-if="article" class="w-full" :meta-data="toMetaDataType(article)"/>
|
||||||
<!-- <ArticleCard v-if="article" class=" w-full" :article="toArticleMetaDataType(article)"/>-->
|
<!-- <ArticleCard v-if="article" class=" w-full" :article="toArticleMetaDataType(article)"/>-->
|
||||||
<ReadonlyMdEditor :editor-id="editorId" :markdown="article?.rawbody" class="p-5 max-w-full"/>
|
<ReadonlyMdEditor :editor-id="editorId" :markdown="article?.rawbody" class="p-5 max-w-full"/>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-5 light:bg-old-neutral-200 dark:bg-old-neutral-800 min-h-64 transition-all duration-500">
|
<div class="p-5 light:bg-old-neutral-200 dark:bg-old-neutral-800 min-h-64">
|
||||||
<div class="text-4xl">
|
<div class="text-4xl">
|
||||||
{{ metaData?.title }}
|
{{ metaData?.title }}
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +105,7 @@ onMounted(() => {
|
|||||||
:tech-stack-icon-names="metaData?.tech_stack_icon_names"
|
:tech-stack-icon-names="metaData?.tech_stack_icon_names"
|
||||||
:tech-stack-theme-colors="metaData?.tech_stack_theme_colors"
|
:tech-stack-theme-colors="metaData?.tech_stack_theme_colors"
|
||||||
:tech-stack-percent="metaData?.tech_stack_percent"
|
:tech-stack-percent="metaData?.tech_stack_percent"
|
||||||
class="lg:w-64 w-0 transition-all duration-500"
|
class="lg:w-64 w-0 transition-[width] duration-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="min-w-64"/>
|
<div v-else class="min-w-64"/>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { DataAnomaly, defaultMetaData } from '~/types/PostMetaData';
|
import { DataAnomaly, defaultMetaData } from '~/types/PostMetaData';
|
||||||
import type { PostMetaData } from '~/types/PostMetaData';
|
import type { PostMetaData } from '~/types/PostMetaData';
|
||||||
import useColorModeStore from '~/stores/colorModeStore';
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
metaData?: PostMetaData;
|
metaData?: PostMetaData;
|
||||||
@@ -54,55 +53,25 @@ const safeEditorId = computed(() => {
|
|||||||
return `rambling_${encoded}`;
|
return `rambling_${encoded}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const showLightShadow = ref(false);
|
const showShadow = ref(true);
|
||||||
const showDarkShadow = ref(false);
|
|
||||||
|
|
||||||
function reverseCollapsed() {
|
function reverseCollapsed() {
|
||||||
if (collapsed.value) {
|
if (collapsed.value) {
|
||||||
collapsed.value = false;
|
collapsed.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const showLight = showLightShadow.value;
|
|
||||||
const showDark = showDarkShadow.value;
|
|
||||||
showLightShadow.value = false;
|
|
||||||
showDarkShadow.value = false;
|
|
||||||
collapsed.value = true;
|
collapsed.value = true;
|
||||||
|
showShadow.value = false;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showLightShadow.value = showLight;
|
showShadow.value = true;
|
||||||
showDarkShadow.value = showDark;
|
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorModeStore = useColorModeStore();
|
|
||||||
let colorModeCallBackKey = '';
|
|
||||||
onMounted(() => {
|
|
||||||
if (colorModeStore.colorMode === 'light') {
|
|
||||||
showLightShadow.value = true;
|
|
||||||
} else {
|
|
||||||
showDarkShadow.value = true;
|
|
||||||
}
|
|
||||||
colorModeCallBackKey = colorModeStore.registerCallBack(() => {
|
|
||||||
if (colorModeStore.colorMode === 'light') {
|
|
||||||
setTimeout(() => {
|
|
||||||
showLightShadow.value = true;
|
|
||||||
}, 500);
|
|
||||||
showDarkShadow.value = false;
|
|
||||||
} else {
|
|
||||||
showLightShadow.value = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
showDarkShadow.value = true;
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
colorModeStore.unregisterCallBack(colorModeCallBackKey);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="p-5 light:bg-old-neutral-200 dark:bg-old-neutral-800 min-h-64 transition-all duration-500"
|
class="p-5 light:bg-old-neutral-200 dark:bg-old-neutral-800 min-h-64"
|
||||||
@click="reverseCollapsed">
|
@click="reverseCollapsed">
|
||||||
<div class="text-4xl">
|
<div class="text-4xl">
|
||||||
{{ (typeChinese.get(metaData?.type) || 'unknown Type') + ':' }}{{ props.metaData.title }}
|
{{ (typeChinese.get(metaData?.type) || 'unknown Type') + ':' }}{{ props.metaData.title }}
|
||||||
@@ -146,18 +115,14 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="relative flex mt-2 justify-between overflow-hidden transition-all duration-300 ease-in-out min-h-[8.5rem]"
|
class="relative flex mt-2 justify-between overflow-hidden duration-300 ease-in-out min-h-[8.5rem]"
|
||||||
:class="{'max-h-[8.5rem]' : collapsed, 'max-h-[100vh]':!collapsed}">
|
:class="{'max-h-[8.5rem]' : collapsed, 'max-h-[100vh]':!collapsed}">
|
||||||
<ReadonlyMdEditor
|
<ReadonlyMdEditor
|
||||||
v-if="rawbody" :editor-id="safeEditorId" :markdown="rawbody!"
|
v-if="rawbody" :editor-id="safeEditorId" :markdown="rawbody!"
|
||||||
class="transition-all duration-500 w-full"/>
|
class="w-full"/>
|
||||||
<div
|
<div
|
||||||
class="absolute bottom-0 left-0 right-0 h-14 bg-gradient-to-t from-old-neutral-200 to-transparent pointer-events-none transition-opacity duration-300"
|
class="absolute bottom-0 left-0 right-0 h-14 bg-gradient-to-t from-old-neutral-200 dark:from-old-neutral-800 to-transparent pointer-events-none transition-opacity duration-300"
|
||||||
:class="collapsed&&showLightShadow?'opacity-100':'opacity-0'"
|
:class="collapsed && showShadow?'opacity-100':'opacity-0'"
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="absolute bottom-0 left-0 right-0 h-14 bg-gradient-to-t from-old-neutral-800 to-transparent pointer-events-none transition-opacity duration-300"
|
|
||||||
:class="collapsed&&showDarkShadow?'opacity-100':'opacity-0'"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import useColorModeStore from '~/stores/colorModeStore';
|
||||||
|
|
||||||
|
const colorModeStore = useColorModeStore();
|
||||||
|
|
||||||
|
function revealToggle(e?: MouseEvent) {
|
||||||
|
if (typeof document === 'undefined' || !e || !('startViewTransition' in document)) {
|
||||||
|
// SSR 或无事件时直接切换或不支持时直接切换
|
||||||
|
colorModeStore.toggleColorMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transition = document.startViewTransition(() => {
|
||||||
|
colorModeStore.toggleColorMode();
|
||||||
|
});
|
||||||
|
transition.ready.then(() => {
|
||||||
|
// 获取鼠标的坐标
|
||||||
|
const { clientX, clientY } = e!;
|
||||||
|
|
||||||
|
// 计算最大半径
|
||||||
|
const radius = Math.hypot(
|
||||||
|
Math.max(clientX, innerWidth - clientX),
|
||||||
|
Math.max(clientY, innerHeight - clientY),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 圆形动画扩散开始
|
||||||
|
document.documentElement.animate(
|
||||||
|
{
|
||||||
|
clipPath: [
|
||||||
|
`circle(0% at ${clientX}px ${clientY}px)`,
|
||||||
|
`circle(${radius}px at ${clientX}px ${clientY}px)`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// 设置时间,已经目标伪元素
|
||||||
|
{
|
||||||
|
duration: 500,
|
||||||
|
pseudoElement: '::view-transition-new(root)',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Transition
|
||||||
|
mode="out-in"
|
||||||
|
enter-active-class="transition-opacity duration-300 ease-in-out"
|
||||||
|
enter-from-class="opacity-0"
|
||||||
|
enter-to-class="opacity-100"
|
||||||
|
leave-active-class="transition-opacity duration-300 ease-in-out"
|
||||||
|
leave-from-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
v-if="colorModeStore.colorMode === 'dark'"
|
||||||
|
key="dark"
|
||||||
|
name="material-symbols:dark-mode"
|
||||||
|
class="text-2xl cursor-pointer mr-5"
|
||||||
|
@click="revealToggle($event)"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
v-else
|
||||||
|
key="light"
|
||||||
|
name="material-symbols:clear-day-rounded"
|
||||||
|
class="text-2xl cursor-pointer mr-5"
|
||||||
|
@click="revealToggle($event)"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
::view-transition-new(root),
|
||||||
|
::view-transition-old(root) {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
+22
-5
@@ -10,6 +10,7 @@ const postsMetaData = ref<PostMetaData[]>([]);
|
|||||||
|
|
||||||
async function loadPostsMetaData() {
|
async function loadPostsMetaData() {
|
||||||
srcPostsMetaData.value = sortMetaData((await queryCollection('content').all()).map((x) => toMetaDataType(x)), 'published_at', true) || [];
|
srcPostsMetaData.value = sortMetaData((await queryCollection('content').all()).map((x) => toMetaDataType(x)), 'published_at', true) || [];
|
||||||
|
srcPostsMetaData.value = srcPostsMetaData.value.filter((x) => !x.draft);
|
||||||
postsMetaData.value = srcPostsMetaData.value;
|
postsMetaData.value = srcPostsMetaData.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,9 +33,8 @@ function filterRuleChange(rule: (data: PostMetaData) => boolean) {
|
|||||||
<div>
|
<div>
|
||||||
<div class="table w-full mt-6 table-fixed">
|
<div class="table w-full mt-6 table-fixed">
|
||||||
<div class="sticky top-16 float-left max-h-[calc(100vh-4rem)]">
|
<div class="sticky top-16 float-left max-h-[calc(100vh-4rem)]">
|
||||||
<div class="relative duration-500 transition-all xl:w-80 w-0 overflow-hidden">
|
<div class="relative duration-500 transition-[width] xl:w-80 w-0 overflow-hidden">
|
||||||
<div class="w-80 top-0 left-0 text-gray-800 dark:text-white">
|
<div class="w-80 top-0 left-0 text-gray-800 dark:text-white">
|
||||||
<!-- <PersonalCard/>-->
|
|
||||||
<ArticleDescriptionCards
|
<ArticleDescriptionCards
|
||||||
v-if="postsMetaData"
|
v-if="postsMetaData"
|
||||||
class="mb-5" :posts-meta-data="srcPostsMetaData!"
|
class="mb-5" :posts-meta-data="srcPostsMetaData!"
|
||||||
@@ -42,19 +42,36 @@ function filterRuleChange(rule: (data: PostMetaData) => boolean) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="transition-all duration-500 float-right xl:w-[calc(100%-20rem-40px)] w-full">
|
<div class="transition-[width] duration-500 float-right xl:w-[calc(100%-20rem-40px)] w-full">
|
||||||
<!-- <ArticleCard class="mb-6 w-full transition-shadow duration-300 shadow-lg hover:shadow-old-neutral-600"/>-->
|
<!-- <ArticleCard class="mb-6 w-full transition-shadow duration-300 shadow-lg hover:shadow-old-neutral-600"/>-->
|
||||||
<div
|
<div
|
||||||
v-for="post in postsMetaData" :key="post.id"
|
v-for="post in postsMetaData" :key="post.id"
|
||||||
class="mb-6 w-full transition-shadow duration-300 shadow-lg hover:shadow-old-neutral-600 hover:cursor-pointer">
|
class="w-full transition-shadow duration-300 shadow-lg hover:shadow-old-neutral-600 hover:cursor-pointer">
|
||||||
<ArticleCard
|
<ArticleCard
|
||||||
v-if="!post.draft && post.type === 'article'"
|
v-if="!post.draft && post.type === 'article'"
|
||||||
class="w-full"
|
class="mb-6 w-full"
|
||||||
:meta-data="post"
|
:meta-data="post"
|
||||||
@click="toArticlePage(post)"/>
|
@click="toArticlePage(post)"/>
|
||||||
<SimpleCard
|
<SimpleCard
|
||||||
v-else-if="!post.draft && (post.type === 'rambling' || post.type === 'announcement')"
|
v-else-if="!post.draft && (post.type === 'rambling' || post.type === 'announcement')"
|
||||||
|
class="mb-6 w-full"
|
||||||
:meta-data="post"/>
|
:meta-data="post"/>
|
||||||
|
<div v-else-if="post.draft">
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{{post}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="postsMetaData.length === 0" class="w-full">
|
||||||
|
<div
|
||||||
|
class="w-full light:bg-old-neutral-200 dark:bg-old-neutral-800 transition-shadow duration-300 shadow-lg hover:shadow-old-neutral-600 hover:cursor-pointer">
|
||||||
|
<div class="pt-5 text-center text-2xl">
|
||||||
|
没有找到符合条件的文章
|
||||||
|
</div>
|
||||||
|
<div class="pt-3 pb-3 text-center text-sm text-old-neutral-500">
|
||||||
|
tips:类型、分类、标签间的关系为且,一类筛选下各个选项间的关系为或
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
@@ -1,37 +0,0 @@
|
|||||||
// tailwind config file
|
|
||||||
import plugin from 'tailwindcss/plugin';
|
|
||||||
import tailwindScrollbar from 'tailwind-scrollbar';
|
|
||||||
import breakpoints from '~/configs/breakpoints';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mode: 'jit',
|
|
||||||
darkMode: 'class',
|
|
||||||
theme: {
|
|
||||||
screens: breakpoints,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
plugin(function ({ addUtilities }) {
|
|
||||||
addUtilities({
|
|
||||||
'.scrollbar-hide': {
|
|
||||||
/* IE and Edge */
|
|
||||||
'-ms-overflow-style': 'none',
|
|
||||||
|
|
||||||
/* Firefox */
|
|
||||||
'scrollbar-width': 'none',
|
|
||||||
|
|
||||||
/* Safari and Chrome */
|
|
||||||
'&::-webkit-scrollbar': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
tailwindScrollbar,
|
|
||||||
],
|
|
||||||
content: [
|
|
||||||
'./app.vue',
|
|
||||||
'./components/**/*.{vue,js,ts}',
|
|
||||||
'./layouts/**/*.vue',
|
|
||||||
'./pages/**/*.vue',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
+14
-8
@@ -4,6 +4,9 @@ export function rgbToHsl(rgb: number[]) {
|
|||||||
throw new Error('Input must be an array of three numbers representing RGB values.');
|
throw new Error('Input must be an array of three numbers representing RGB values.');
|
||||||
}
|
}
|
||||||
let [r, g, b] = rgb;
|
let [r, g, b] = rgb;
|
||||||
|
r = r!;
|
||||||
|
b = b!;
|
||||||
|
g = g!;
|
||||||
r /= 255;
|
r /= 255;
|
||||||
g /= 255;
|
g /= 255;
|
||||||
b /= 255;
|
b /= 255;
|
||||||
@@ -36,6 +39,9 @@ export function hslToRgb(hsl: number[]) {
|
|||||||
throw new Error('Input must be an array of three numbers representing HSL values.');
|
throw new Error('Input must be an array of three numbers representing HSL values.');
|
||||||
}
|
}
|
||||||
let [h, s, l] = hsl;
|
let [h, s, l] = hsl;
|
||||||
|
h = h!;
|
||||||
|
s = s!;
|
||||||
|
l = l!;
|
||||||
h /= 360;
|
h /= 360;
|
||||||
s /= 100;
|
s /= 100;
|
||||||
l /= 100;
|
l /= 100;
|
||||||
@@ -65,16 +71,16 @@ export function toRGBArray(color: string): number[] {
|
|||||||
// 处理 rgb/rgba
|
// 处理 rgb/rgba
|
||||||
const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
|
const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
|
||||||
if (rgbMatch) {
|
if (rgbMatch) {
|
||||||
return [parseInt(rgbMatch[1]), parseInt(rgbMatch[2]), parseInt(rgbMatch[3])];
|
return [parseInt(rgbMatch[1]!), parseInt(rgbMatch[2]!), parseInt(rgbMatch[3]!)];
|
||||||
}
|
}
|
||||||
// 处理 #fff 或 #ffffff
|
// 处理 #fff 或 #ffffff
|
||||||
const hexMatch = color.match(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
|
const hexMatch = color.match(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
|
||||||
if (hexMatch) {
|
if (hexMatch) {
|
||||||
let hex = hexMatch[1];
|
let hex = hexMatch[1];
|
||||||
if (hex.length === 3) {
|
if (hex!.length === 3) {
|
||||||
hex = hex.split('').map((x) => x + x).join('');
|
hex = hex!.split('').map((x) => x + x).join('');
|
||||||
}
|
}
|
||||||
const num = parseInt(hex, 16);
|
const num = parseInt(hex!, 16);
|
||||||
return [
|
return [
|
||||||
(num >> 16) & 255,
|
(num >> 16) & 255,
|
||||||
(num >> 8) & 255,
|
(num >> 8) & 255,
|
||||||
@@ -97,14 +103,14 @@ export function toHexString(rgb: number[]): string {
|
|||||||
|
|
||||||
export function toLightColor(rgb: number[]): number[] {
|
export function toLightColor(rgb: number[]): number[] {
|
||||||
const hsl = rgbToHsl(rgb);
|
const hsl = rgbToHsl(rgb);
|
||||||
if (hsl[2] < 50)
|
if (hsl[2]! < 50)
|
||||||
hsl[2] = hsl[2] / 5 + 50; // 增加亮度
|
hsl[2] = hsl[2]! / 5 + 50; // 增加亮度
|
||||||
return hslToRgb(hsl);
|
return hslToRgb(hsl);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toDarkColor(rgb: number[]): number[] {
|
export function toDarkColor(rgb: number[]): number[] {
|
||||||
const hsl = rgbToHsl(rgb);
|
const hsl = rgbToHsl(rgb);
|
||||||
if (hsl[2] > 50)
|
if (hsl[2]! > 50)
|
||||||
hsl[2] = 50 - (hsl[2] - 50) / 5; // 减少亮度
|
hsl[2] = 50 - (hsl[2]! - 50) / 5; // 减少亮度
|
||||||
return hslToRgb(hsl);
|
return hslToRgb(hsl);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user