@ansidev-oss/vitepress-theme-ansidev 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/{Swetrix-k7SUudua.js → Swetrix-GKEyGNrV.js} +29 -6
- package/dist/client/index.js +54 -54
- package/dist/client/styles.css +1 -1
- package/dist/client/types/index.d.ts +8 -0
- package/dist/node/composables/excerpt.d.ts +9 -0
- package/dist/node/composables/excerpt.test.d.ts +1 -0
- package/dist/node/composables/index.d.ts +1 -0
- package/dist/node/composables.d.mts +9 -0
- package/dist/node/composables.mjs +33 -1
- package/package.json +20 -24
- package/src/client/components/DonationWidget.vue +9 -3
- package/src/client/components/Footer.vue +11 -4
- package/src/client/components/Link.vue +11 -5
- package/src/client/components/LocaleSwitcher.vue +5 -5
- package/src/client/components/PostDate.test.ts +16 -7
- package/src/client/components/PostDate.vue +16 -7
- package/src/client/components/PostItem.vue +17 -7
- package/src/client/components/PostList.vue +7 -4
- package/src/client/components/ProjectCard.vue +42 -14
- package/src/client/components/ProjectList.vue +12 -5
- package/src/client/components/RouterLink.vue +3 -3
- package/src/client/components/SharingWidget.vue +3 -1
- package/src/client/components/StatusBadge.vue +10 -10
- package/src/client/components/TermBadge.vue +11 -7
- package/src/client/components/TermLink.vue +4 -4
- package/src/client/composables/date.ts +1 -1
- package/src/client/composables/lang.test.ts +6 -2
- package/src/client/composables/lang.ts +12 -5
- package/src/client/composables/locale.ts +4 -1
- package/src/client/composables/project.ts +3 -3
- package/src/client/composables/rank.ts +5 -2
- package/src/client/index.ts +1 -1
- package/src/client/layouts/Layout.vue +2 -2
- package/src/client/pages/ArchivesPage.vue +10 -5
- package/src/client/pages/CategoriesPage.vue +13 -5
- package/src/client/pages/HomePage.vue +3 -3
- package/src/client/pages/Page.vue +13 -11
- package/src/client/pages/PostsPage.vue +4 -4
- package/src/client/pages/ProjectsPage.vue +10 -7
- package/src/client/pages/TagsPage.vue +10 -4
- package/src/client/plugins/donation/components/Donation.vue +5 -3
- package/src/client/plugins/donation/components/DonationButton.vue +3 -1
- package/src/client/plugins/donation/index.ts +11 -8
- package/src/client/plugins/i18n/index.ts +2 -2
- package/src/client/plugins/sharing/components/Sharing.vue +39 -15
- package/src/client/plugins/sharing/components/SharingButton.vue +34 -14
- package/src/client/plugins/sharing/index.ts +3 -1
- package/src/client/styles/base.css +1 -1
- package/src/client/styles/blog.css +38 -14
- package/src/client/styles/index.css +2 -3
- package/src/client/types/index.ts +8 -0
- package/src/node/composables/excerpt.test.ts +88 -0
- package/src/node/composables/excerpt.ts +57 -0
- package/src/node/composables/index.ts +1 -0
- package/src/node/composables/markdown.ts +8 -2
- package/src/node/composables/route.ts +15 -7
- package/src/node/composables/slug.ts +12 -4
- package/src/node/config.ts +20 -14
- package/src/shared/composables/useSlug.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ansidev-oss/vitepress-theme-ansidev",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "The VitePress theme for ansidev's blog",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/node/index.js",
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
"import": "./dist/client/index.js",
|
|
28
28
|
"types": "./dist/client/index.d.ts"
|
|
29
29
|
},
|
|
30
|
-
"./styles.css":
|
|
30
|
+
"./styles.css": {
|
|
31
|
+
"import": "./dist/client/styles.css",
|
|
32
|
+
"types": "./styles.d.ts"
|
|
33
|
+
},
|
|
31
34
|
"./config": {
|
|
32
35
|
"import": "./dist/node/config.mjs",
|
|
33
36
|
"types": "./dist/node/config.d.ts"
|
|
@@ -39,49 +42,46 @@
|
|
|
39
42
|
},
|
|
40
43
|
"files": [
|
|
41
44
|
"dist",
|
|
45
|
+
"!dist/.pack",
|
|
42
46
|
"src",
|
|
43
47
|
"README.md",
|
|
44
48
|
"LICENSE"
|
|
45
49
|
],
|
|
46
50
|
"dependencies": {
|
|
47
51
|
"@tailwindcss/vite": "^4.2.0",
|
|
48
|
-
"@vueuse/core": "^
|
|
52
|
+
"@vueuse/core": "^15.0.0",
|
|
53
|
+
"markdown-it": "^15.0.3",
|
|
49
54
|
"vue-i18n": "^11.2.8"
|
|
50
55
|
},
|
|
51
56
|
"devDependencies": {
|
|
52
|
-
"@biomejs/biome": "2.6.3",
|
|
53
57
|
"@iconify/vue": "^5.0.0",
|
|
54
58
|
"@tailwindcss/postcss": "^4.2.0",
|
|
55
|
-
"@types/
|
|
59
|
+
"@types/markdown-it": "^14.2.0",
|
|
60
|
+
"@types/node": "^26.0.0",
|
|
56
61
|
"@vitejs/plugin-vue": "^6.0.4",
|
|
57
62
|
"@vue/test-utils": "^2.5.1",
|
|
58
63
|
"autoprefixer": "^10.4.24",
|
|
59
|
-
"
|
|
60
|
-
"glob": "^13.0.6",
|
|
64
|
+
"glob": "^14.0.0",
|
|
61
65
|
"gray-matter": "^4.0.3",
|
|
62
66
|
"happy-dom": "^21.0.4",
|
|
63
67
|
"medium-zoom": "^1.1.0",
|
|
64
68
|
"postcss": "^8.5.6",
|
|
65
|
-
"prettier": "^3.8.1",
|
|
66
69
|
"rimraf": "^6.1.3",
|
|
67
70
|
"swetrix": "^4.1.0",
|
|
68
71
|
"tailwindcss": "^4.2.0",
|
|
69
|
-
"ts-node": "^10.9.2",
|
|
70
|
-
"tsdown": "^0.23.0",
|
|
71
72
|
"typescript": "^6.0.0",
|
|
72
|
-
"vite": "
|
|
73
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
|
|
73
74
|
"vitepress": "^2.0.0-alpha.16",
|
|
74
|
-
"vitest": "^5.0.5",
|
|
75
75
|
"vue": "^3.5.28",
|
|
76
|
-
"vue-tsc": "^3.2.5"
|
|
76
|
+
"vue-tsc": "^3.2.5",
|
|
77
|
+
"vite-plus": "1.4.0"
|
|
77
78
|
},
|
|
78
79
|
"peerDependencies": {
|
|
79
80
|
"@iconify/vue": "^5.0.0",
|
|
80
|
-
"glob": "^13.0.6",
|
|
81
|
+
"glob": "^13.0.6 || ^14.0.0",
|
|
81
82
|
"gray-matter": "^4.0.3",
|
|
82
83
|
"medium-zoom": "^1.1.0",
|
|
83
84
|
"swetrix": "^4.1.0",
|
|
84
|
-
"ts-node": "^10.9.2",
|
|
85
85
|
"typescript": "^5.9.3",
|
|
86
86
|
"vitepress": "^2.0.0-alpha.16",
|
|
87
87
|
"vue": "^3.5.28"
|
|
@@ -92,15 +92,11 @@
|
|
|
92
92
|
},
|
|
93
93
|
"scripts": {
|
|
94
94
|
"typecheck": "tsc --noEmit",
|
|
95
|
-
"test": "
|
|
96
|
-
"lint:check": "
|
|
97
|
-
"lint": "
|
|
98
|
-
"dev:client": "vite build --watch",
|
|
99
|
-
"dev:node": "tsdown --watch",
|
|
100
|
-
"dev": "concurrently --kill-others \"pnpm dev:client\" \"pnpm dev:node\"",
|
|
95
|
+
"test": "vp test run",
|
|
96
|
+
"lint:check": "vp check",
|
|
97
|
+
"lint": "vp check --fix",
|
|
101
98
|
"clean": "rimraf dist",
|
|
102
|
-
"build
|
|
103
|
-
"build
|
|
104
|
-
"build": "pnpm clean && concurrently \"pnpm build:client\" \"pnpm build:node\""
|
|
99
|
+
"build": "vp run build:all",
|
|
100
|
+
"dev": "vp build --watch & vp pack --watch --on-success \"node ../../scripts/publish-node.mjs && node ../../scripts/touch-demo.mjs\" & wait"
|
|
105
101
|
}
|
|
106
102
|
}
|
|
@@ -8,15 +8,21 @@ const props = defineProps<{
|
|
|
8
8
|
direction?: 'horizontal' | 'vertical'
|
|
9
9
|
}>()
|
|
10
10
|
|
|
11
|
-
const directionCssClass =
|
|
11
|
+
const directionCssClass =
|
|
12
|
+
props.direction === 'vertical' ? 'space-y-4' : 'space-x-4'
|
|
12
13
|
|
|
13
14
|
const Donation = theme.value.donation
|
|
14
|
-
? defineAsyncComponent(
|
|
15
|
+
? defineAsyncComponent(
|
|
16
|
+
() => import('../plugins/donation/components/Donation.vue')
|
|
17
|
+
)
|
|
15
18
|
: () => null
|
|
16
19
|
</script>
|
|
17
20
|
|
|
18
21
|
<template>
|
|
19
|
-
<div
|
|
22
|
+
<div
|
|
23
|
+
class="flex flex-wrap py-4 items-center justify-center"
|
|
24
|
+
:class="directionCssClass"
|
|
25
|
+
>
|
|
20
26
|
<Donation :donation="theme.donation" />
|
|
21
27
|
</div>
|
|
22
28
|
</template>
|
|
@@ -9,16 +9,23 @@ const { hasSidebar, hasAside } = useLayout()
|
|
|
9
9
|
|
|
10
10
|
const copyright = computed(() => {
|
|
11
11
|
return typeof theme.value.themeFooter.copyright === 'string'
|
|
12
|
-
? theme.value.themeFooter.copyright.replace(
|
|
12
|
+
? theme.value.themeFooter.copyright.replace(
|
|
13
|
+
/#\{present\}/g,
|
|
14
|
+
new Date().getFullYear().toString()
|
|
15
|
+
)
|
|
13
16
|
: theme.value.themeFooter.copyright
|
|
14
17
|
})
|
|
15
18
|
|
|
16
19
|
const GoogleAnalytics = theme.value.googleAnalytics
|
|
17
|
-
? defineAsyncComponent(
|
|
20
|
+
? defineAsyncComponent(
|
|
21
|
+
() => import('../plugins/google-analytics/components/GoogleAnalytics.vue')
|
|
22
|
+
)
|
|
18
23
|
: () => null
|
|
19
24
|
|
|
20
25
|
const Swetrix = theme.value.swetrix
|
|
21
|
-
? defineAsyncComponent(
|
|
26
|
+
? defineAsyncComponent(
|
|
27
|
+
() => import('../plugins/swetrix/components/Swetrix.vue')
|
|
28
|
+
)
|
|
22
29
|
: () => null
|
|
23
30
|
</script>
|
|
24
31
|
|
|
@@ -58,7 +65,7 @@ const Swetrix = theme.value.swetrix
|
|
|
58
65
|
.VPFooter :deep(a) {
|
|
59
66
|
text-decoration-line: underline;
|
|
60
67
|
text-underline-offset: 2px;
|
|
61
|
-
transition:
|
|
68
|
+
transition: none;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
.VPFooter :deep(a:hover) {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
<script setup lang=
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import RouterLink from './RouterLink.vue'
|
|
3
3
|
|
|
4
4
|
const props = defineProps({
|
|
5
5
|
href: {
|
|
6
6
|
type: String,
|
|
7
7
|
required: true,
|
|
8
|
-
default: '#'
|
|
8
|
+
default: '#'
|
|
9
9
|
},
|
|
10
10
|
disableRouterLink: {
|
|
11
11
|
type: Boolean,
|
|
12
12
|
required: false,
|
|
13
|
-
default: false
|
|
14
|
-
}
|
|
13
|
+
default: false
|
|
14
|
+
}
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
const isInternalLink = props.href?.startsWith('/') && !props.disableRouterLink
|
|
@@ -25,7 +25,13 @@ const isAnchorLink = props.href?.startsWith('#')
|
|
|
25
25
|
<a v-else-if="disableRouterLink" target="_self" :href="href" v-bind="$attrs">
|
|
26
26
|
<slot />
|
|
27
27
|
</a>
|
|
28
|
-
<a
|
|
28
|
+
<a
|
|
29
|
+
v-else
|
|
30
|
+
target="_blank"
|
|
31
|
+
rel="noopener noreferrer"
|
|
32
|
+
:href="href"
|
|
33
|
+
v-bind="$attrs"
|
|
34
|
+
>
|
|
29
35
|
<slot />
|
|
30
36
|
</a>
|
|
31
37
|
</template>
|
|
@@ -6,8 +6,8 @@ defineProps({
|
|
|
6
6
|
size: {
|
|
7
7
|
type: Number,
|
|
8
8
|
required: false,
|
|
9
|
-
default: 20
|
|
10
|
-
}
|
|
9
|
+
default: 20
|
|
10
|
+
}
|
|
11
11
|
})
|
|
12
12
|
|
|
13
13
|
const { t, availableLocales, locale } = useI18n()
|
|
@@ -33,7 +33,7 @@ const toggleLocale = () => switchLocale(availableLocales, locale)
|
|
|
33
33
|
width: 1px;
|
|
34
34
|
height: 24px;
|
|
35
35
|
background-color: var(--vp-c-divider);
|
|
36
|
-
content:
|
|
36
|
+
content: '';
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
.VPLocaleSwitcher {
|
|
@@ -43,12 +43,12 @@ const toggleLocale = () => switchLocale(availableLocales, locale)
|
|
|
43
43
|
width: 36px;
|
|
44
44
|
height: 36px;
|
|
45
45
|
color: var(--vp-c-text-2);
|
|
46
|
-
transition:
|
|
46
|
+
transition: none;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
.VPLocaleSwitcher:hover {
|
|
50
50
|
color: var(--vp-c-text-1);
|
|
51
|
-
transition:
|
|
51
|
+
transition: none;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
.VPLocaleSwitcher > :deep(svg) {
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils'
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from '
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vite-plus/test'
|
|
3
3
|
import { createI18n } from 'vue-i18n'
|
|
4
4
|
import PostDate from './PostDate.vue'
|
|
5
5
|
|
|
6
|
-
const data = vi.hoisted(() => ({
|
|
6
|
+
const data = vi.hoisted(() => ({
|
|
7
|
+
frontmatter: { value: {} as Record<string, unknown> },
|
|
8
|
+
lang: { value: 'en-US' }
|
|
9
|
+
}))
|
|
7
10
|
vi.mock('vitepress', () => ({ useData: () => data }))
|
|
8
11
|
|
|
9
12
|
const i18n = createI18n({
|
|
10
13
|
legacy: false,
|
|
11
14
|
locale: 'en',
|
|
12
|
-
messages: { en: { posted_on: 'Posted on' } }
|
|
15
|
+
messages: { en: { posted_on: 'Posted on' } }
|
|
13
16
|
})
|
|
14
17
|
|
|
15
|
-
const render = (date?: Date | string) =>
|
|
16
|
-
|
|
18
|
+
const render = (date?: Date | string) =>
|
|
19
|
+
mount(PostDate, { props: { date }, global: { plugins: [i18n] } })
|
|
20
|
+
const datetimeOf = (date?: Date | string) =>
|
|
21
|
+
render(date).get('time').attributes('datetime')
|
|
17
22
|
|
|
18
23
|
describe('PostDate locale', () => {
|
|
19
24
|
afterEach(() => {
|
|
@@ -56,14 +61,18 @@ describe('PostDate datetime', () => {
|
|
|
56
61
|
})
|
|
57
62
|
|
|
58
63
|
it('matches the date prop when given a string', () => {
|
|
59
|
-
expect(datetimeOf('2023-04-05T06:07:08.000Z')).toBe(
|
|
64
|
+
expect(datetimeOf('2023-04-05T06:07:08.000Z')).toBe(
|
|
65
|
+
'2023-04-05T06:07:08.000Z'
|
|
66
|
+
)
|
|
60
67
|
expect(datetimeOf('2023-04-05')).toBe(new Date('2023-04-05').toISOString())
|
|
61
68
|
})
|
|
62
69
|
|
|
63
70
|
it('does not depend on render time', () => {
|
|
64
71
|
vi.useFakeTimers()
|
|
65
72
|
vi.setSystemTime(new Date('2030-01-01T00:00:00.000Z'))
|
|
66
|
-
expect(datetimeOf('2023-04-05T06:07:08.000Z')).toBe(
|
|
73
|
+
expect(datetimeOf('2023-04-05T06:07:08.000Z')).toBe(
|
|
74
|
+
'2023-04-05T06:07:08.000Z'
|
|
75
|
+
)
|
|
67
76
|
})
|
|
68
77
|
|
|
69
78
|
it('omits datetime and does not throw for a missing or invalid date', () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup lang=
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import { useI18n } from 'vue-i18n'
|
|
4
4
|
import { usePageLang } from '../composables/lang'
|
|
@@ -10,15 +10,20 @@ const props = defineProps({
|
|
|
10
10
|
date: [Date, String],
|
|
11
11
|
textSize: {
|
|
12
12
|
type: String,
|
|
13
|
-
default: null
|
|
14
|
-
}
|
|
13
|
+
default: null
|
|
14
|
+
}
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
const { t } = useI18n()
|
|
18
18
|
const pageLang = usePageLang()
|
|
19
19
|
|
|
20
20
|
const formatDate = (date: Date, locale: string) => {
|
|
21
|
-
const options: Intl.DateTimeFormatOptions = {
|
|
21
|
+
const options: Intl.DateTimeFormatOptions = {
|
|
22
|
+
year: 'numeric',
|
|
23
|
+
month: 'long',
|
|
24
|
+
day: 'numeric',
|
|
25
|
+
timeZone: 'UTC'
|
|
26
|
+
}
|
|
22
27
|
try {
|
|
23
28
|
return date.toLocaleDateString(locale, options)
|
|
24
29
|
} catch {
|
|
@@ -28,14 +33,15 @@ const formatDate = (date: Date, locale: string) => {
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
const dateObject = computed(() => {
|
|
31
|
-
const date =
|
|
36
|
+
const date =
|
|
37
|
+
props.date instanceof Date ? props.date : new Date(props.date as string)
|
|
32
38
|
|
|
33
39
|
const isValid = !Number.isNaN(+date)
|
|
34
40
|
|
|
35
41
|
return {
|
|
36
42
|
time: +date,
|
|
37
43
|
iso: isValid ? date.toISOString() : undefined,
|
|
38
|
-
string: formatDate(date, pageLang.value)
|
|
44
|
+
string: formatDate(date, pageLang.value)
|
|
39
45
|
}
|
|
40
46
|
})
|
|
41
47
|
</script>
|
|
@@ -45,7 +51,10 @@ const dateObject = computed(() => {
|
|
|
45
51
|
<dt class="sr-only">
|
|
46
52
|
{{ t('posted_on') }}
|
|
47
53
|
</dt>
|
|
48
|
-
<dd
|
|
54
|
+
<dd
|
|
55
|
+
class="font-medium leading-6"
|
|
56
|
+
:class="[textSize ? `text-${textSize}` : 'text-base']"
|
|
57
|
+
>
|
|
49
58
|
<time :datetime="dateObject.iso">{{ dateObject.string }}</time>
|
|
50
59
|
</dd>
|
|
51
60
|
</dl>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup lang=
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { computed, type PropType } from 'vue'
|
|
3
3
|
import { usePostDate } from '../composables'
|
|
4
4
|
import type { Post } from '../types'
|
|
@@ -7,8 +7,8 @@ import TermLink from './TermLink.vue'
|
|
|
7
7
|
const props = defineProps({
|
|
8
8
|
post: {
|
|
9
9
|
type: Object as PropType<Post>,
|
|
10
|
-
required: true
|
|
11
|
-
}
|
|
10
|
+
required: true
|
|
11
|
+
}
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
const postDate = computed(() => usePostDate(props.post.date))
|
|
@@ -16,18 +16,28 @@ const postDate = computed(() => usePostDate(props.post.date))
|
|
|
16
16
|
|
|
17
17
|
<template>
|
|
18
18
|
<div class="flex p-4 component-border">
|
|
19
|
-
<div class="text-center" style="min-width: 80px
|
|
19
|
+
<div class="text-center" style="min-width: 80px">
|
|
20
20
|
<div class="text-6xl font-bold">{{ postDate?.day }}</div>
|
|
21
21
|
<div>{{ postDate?.month }}, {{ postDate?.year }}</div>
|
|
22
22
|
</div>
|
|
23
23
|
<div class="pl-8">
|
|
24
24
|
<div class="flex flex-wrap mb-3">
|
|
25
|
-
<a
|
|
25
|
+
<a
|
|
26
|
+
class="text-2xl font-bold leading-8 tracking-tight"
|
|
27
|
+
:href="post?.url"
|
|
28
|
+
:title="post.title"
|
|
29
|
+
>{{ post.title }}</a
|
|
30
|
+
>
|
|
26
31
|
</div>
|
|
27
32
|
<div class="flex flex-wrap mb-3">
|
|
28
|
-
<TermLink
|
|
33
|
+
<TermLink
|
|
34
|
+
v-for="category in post.categories"
|
|
35
|
+
:key="category"
|
|
36
|
+
:text="category"
|
|
37
|
+
basePath="/categories/"
|
|
38
|
+
/>
|
|
29
39
|
</div>
|
|
30
|
-
<
|
|
40
|
+
<div v-html="post?.excerpt" />
|
|
31
41
|
</div>
|
|
32
42
|
</div>
|
|
33
43
|
</template>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup lang=
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import type { PropType } from 'vue'
|
|
3
3
|
import { useI18n } from 'vue-i18n'
|
|
4
4
|
import type { Post } from '../types'
|
|
@@ -8,15 +8,18 @@ defineProps({
|
|
|
8
8
|
posts: {
|
|
9
9
|
type: Object as PropType<Post[]>,
|
|
10
10
|
required: true,
|
|
11
|
-
default: []
|
|
12
|
-
}
|
|
11
|
+
default: []
|
|
12
|
+
}
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
const { t } = useI18n()
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
<template>
|
|
19
|
-
<div
|
|
19
|
+
<div
|
|
20
|
+
v-if="posts.length > 0"
|
|
21
|
+
class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4"
|
|
22
|
+
>
|
|
20
23
|
<PostItem v-for="post of posts" :key="post?.url" :post="post" />
|
|
21
24
|
</div>
|
|
22
25
|
<p v-if="posts.length === 0">{{ t('no_post') }}</p>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup lang=
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { computed, type PropType } from 'vue'
|
|
3
3
|
import { useI18n } from 'vue-i18n'
|
|
4
4
|
import { useProjectStatusStyle } from '../composables'
|
|
@@ -11,28 +11,46 @@ import TermLink from './TermLink.vue'
|
|
|
11
11
|
const props = defineProps({
|
|
12
12
|
project: {
|
|
13
13
|
type: Object as PropType<Project>,
|
|
14
|
-
required: true
|
|
15
|
-
}
|
|
14
|
+
required: true
|
|
15
|
+
}
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
const { t } = useI18n()
|
|
19
|
-
const projectStatusStyles = computed(() =>
|
|
19
|
+
const projectStatusStyles = computed(() =>
|
|
20
|
+
useProjectStatusStyle(props.project.developmentStatus)
|
|
21
|
+
)
|
|
20
22
|
</script>
|
|
21
23
|
|
|
22
24
|
<template>
|
|
23
25
|
<div class="flex flex-col p-4 component-border">
|
|
24
26
|
<div class="flex flex-row justify-between items-center mb-4">
|
|
25
|
-
<a
|
|
27
|
+
<a
|
|
28
|
+
:href="project.url"
|
|
29
|
+
class="text-2xl font-bold leading-8 tracking-tight"
|
|
30
|
+
>
|
|
26
31
|
{{ project.title }}
|
|
27
32
|
</a>
|
|
28
33
|
<div class="flex flex-row">
|
|
29
|
-
<a
|
|
30
|
-
|
|
34
|
+
<a
|
|
35
|
+
v-if="project.showcaseUrl"
|
|
36
|
+
class="mx-1 text-gray-500 hover:text-gray-600"
|
|
37
|
+
target="_blank"
|
|
38
|
+
rel="noopener noreferrer"
|
|
39
|
+
:href="project.showcaseUrl"
|
|
40
|
+
>
|
|
31
41
|
<span class="sr-only">external-link</span>
|
|
32
|
-
<Icon
|
|
42
|
+
<Icon
|
|
43
|
+
icon="bi:box-arrow-up-right"
|
|
44
|
+
class="text-dark dark:text-white ml-2"
|
|
45
|
+
/>
|
|
33
46
|
</a>
|
|
34
|
-
<a
|
|
35
|
-
|
|
47
|
+
<a
|
|
48
|
+
v-if="project.repositoryUrl"
|
|
49
|
+
class="mx-1 text-gray-500 hover:text-gray-600"
|
|
50
|
+
target="_blank"
|
|
51
|
+
rel="noopener noreferrer"
|
|
52
|
+
:href="project.repositoryUrl"
|
|
53
|
+
>
|
|
36
54
|
<span class="sr-only">github</span>
|
|
37
55
|
<Icon icon="bi:github" class="text-dark dark:text-white ml-2" />
|
|
38
56
|
</a>
|
|
@@ -43,18 +61,28 @@ const projectStatusStyles = computed(() => useProjectStatusStyle(props.project.d
|
|
|
43
61
|
<div class="flex flex-col justify-between">
|
|
44
62
|
<div v-if="project.license" class="mb-3">
|
|
45
63
|
{{ t('license') }}:
|
|
46
|
-
<Link
|
|
47
|
-
|
|
64
|
+
<Link
|
|
65
|
+
v-if="project.licenseUrl"
|
|
66
|
+
:href="project.licenseUrl"
|
|
67
|
+
class="link-alt"
|
|
68
|
+
>
|
|
69
|
+
{{ project.license }}
|
|
48
70
|
</Link>
|
|
49
71
|
<span v-else>{{ project.license }}</span>
|
|
50
72
|
</div>
|
|
51
73
|
<div class="mb-3">
|
|
52
|
-
{{ t('development_status.title') }}:
|
|
74
|
+
{{ t('development_status.title') }}:
|
|
75
|
+
<StatusBadge v-bind="projectStatusStyles">
|
|
53
76
|
{{ $t(`development_status.${project.developmentStatus}`) }}
|
|
54
77
|
</StatusBadge>
|
|
55
78
|
</div>
|
|
56
79
|
<div class="flex flex-wrap mb-3">
|
|
57
|
-
<TermLink
|
|
80
|
+
<TermLink
|
|
81
|
+
v-for="tech in project.techs"
|
|
82
|
+
:key="tech"
|
|
83
|
+
:text="tech"
|
|
84
|
+
basePath="/projects?tech="
|
|
85
|
+
/>
|
|
58
86
|
</div>
|
|
59
87
|
<PostDate :date="project.date" text-size="sm" />
|
|
60
88
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup lang=
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import type { PropType } from 'vue'
|
|
3
3
|
import { useI18n } from 'vue-i18n'
|
|
4
4
|
import type { Project } from '../types'
|
|
@@ -8,16 +8,23 @@ defineProps({
|
|
|
8
8
|
projects: {
|
|
9
9
|
type: Object as PropType<Project[]>,
|
|
10
10
|
required: true,
|
|
11
|
-
default: []
|
|
12
|
-
}
|
|
11
|
+
default: []
|
|
12
|
+
}
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
const { t } = useI18n()
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
<template>
|
|
19
|
-
<div
|
|
20
|
-
|
|
19
|
+
<div
|
|
20
|
+
v-if="projects.length > 0"
|
|
21
|
+
class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4"
|
|
22
|
+
>
|
|
23
|
+
<ProjectCard
|
|
24
|
+
v-for="project of projects"
|
|
25
|
+
:key="project?.url"
|
|
26
|
+
:project="project"
|
|
27
|
+
/>
|
|
21
28
|
</div>
|
|
22
29
|
<p v-if="projects.length === 0">{{ t('no_post') }}</p>
|
|
23
30
|
</template>
|
|
@@ -7,7 +7,9 @@ const route = useRoute()
|
|
|
7
7
|
const { theme, frontmatter, page } = useData()
|
|
8
8
|
|
|
9
9
|
const Sharing = theme.value.sharing
|
|
10
|
-
? defineAsyncComponent(
|
|
10
|
+
? defineAsyncComponent(
|
|
11
|
+
() => import('../plugins/sharing/components/Sharing.vue')
|
|
12
|
+
)
|
|
11
13
|
: () => null
|
|
12
14
|
|
|
13
15
|
const postURL = new URL(route.path, theme.value.siteURL).href
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
<script setup lang=
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
defineProps({
|
|
3
3
|
backgroundColor: {
|
|
4
4
|
type: String,
|
|
5
5
|
required: false,
|
|
6
|
-
default: 'bg-blue-100'
|
|
6
|
+
default: 'bg-blue-100'
|
|
7
7
|
},
|
|
8
8
|
darkBackgroundColor: {
|
|
9
9
|
type: String,
|
|
10
10
|
required: false,
|
|
11
|
-
default: 'dark:bg-blue-800'
|
|
11
|
+
default: 'dark:bg-blue-800'
|
|
12
12
|
},
|
|
13
13
|
textColor: {
|
|
14
14
|
type: String,
|
|
15
15
|
required: false,
|
|
16
|
-
default: 'text-blue-800'
|
|
16
|
+
default: 'text-blue-800'
|
|
17
17
|
},
|
|
18
18
|
darkTextColor: {
|
|
19
19
|
type: String,
|
|
20
20
|
required: false,
|
|
21
|
-
default: 'dark:text-blue-100'
|
|
21
|
+
default: 'dark:text-blue-100'
|
|
22
22
|
},
|
|
23
23
|
fontSize: {
|
|
24
24
|
type: String,
|
|
25
25
|
required: false,
|
|
26
|
-
default: 'text-base'
|
|
26
|
+
default: 'text-base'
|
|
27
27
|
},
|
|
28
28
|
fontWeight: {
|
|
29
29
|
type: String,
|
|
30
30
|
required: false,
|
|
31
|
-
default: 'font-normal'
|
|
31
|
+
default: 'font-normal'
|
|
32
32
|
},
|
|
33
33
|
rounded: {
|
|
34
34
|
type: Boolean,
|
|
35
35
|
required: false,
|
|
36
|
-
default: true
|
|
37
|
-
}
|
|
36
|
+
default: true
|
|
37
|
+
}
|
|
38
38
|
})
|
|
39
39
|
</script>
|
|
40
40
|
|
|
@@ -48,7 +48,7 @@ defineProps({
|
|
|
48
48
|
darkTextColor,
|
|
49
49
|
fontSize,
|
|
50
50
|
fontWeight,
|
|
51
|
-
rounded ? 'rounded' : ''
|
|
51
|
+
rounded ? 'rounded' : ''
|
|
52
52
|
]"
|
|
53
53
|
>
|
|
54
54
|
<slot />
|