valaxy-theme-yun 0.0.5

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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/components/YunAlgoliaSearch.vue +228 -0
  4. package/components/YunBackToTop.vue +59 -0
  5. package/components/YunBanner.vue +70 -0
  6. package/components/YunBase.vue +24 -0
  7. package/components/YunCard.vue +29 -0
  8. package/components/YunCategories.vue +45 -0
  9. package/components/YunCategory.vue +39 -0
  10. package/components/YunCloud.vue +76 -0
  11. package/components/YunConfig.vue +24 -0
  12. package/components/YunGirls.vue +104 -0
  13. package/components/YunGoDown.vue +46 -0
  14. package/components/YunLinks.vue +109 -0
  15. package/components/YunPageHeader.vue +16 -0
  16. package/components/YunPostCollapse.vue +187 -0
  17. package/components/YunPostMeta.vue +29 -0
  18. package/components/YunPostNav.vue +89 -0
  19. package/components/YunSay.vue +83 -0
  20. package/components/YunSidebar.vue +111 -0
  21. package/components/YunSidebarLinks.vue +30 -0
  22. package/components/YunSidebarNav.vue +95 -0
  23. package/components/YunSocialLinks.vue +31 -0
  24. package/components/YunSponsor.vue +75 -0
  25. package/components/YunWaline.vue +26 -0
  26. package/composables/index.ts +25 -0
  27. package/config/index.ts +192 -0
  28. package/dist/index.d.ts +93 -0
  29. package/dist/index.js +1 -0
  30. package/dist/index.mjs +1 -0
  31. package/index.ts +1 -0
  32. package/layouts/404.vue +25 -0
  33. package/layouts/archives.vue +21 -0
  34. package/layouts/base.vue +64 -0
  35. package/layouts/categories.vue +66 -0
  36. package/layouts/default.vue +7 -0
  37. package/layouts/home.vue +27 -0
  38. package/layouts/post.vue +22 -0
  39. package/layouts/tags.vue +83 -0
  40. package/locales/en.yml +0 -0
  41. package/locales/zh-CN.yml +0 -0
  42. package/package.json +25 -0
  43. package/styles/index.scss +2 -0
  44. package/styles/layout/index.scss +7 -0
  45. package/styles/layout/post.scss +86 -0
  46. package/styles/markdown.scss +31 -0
  47. package/styles/vars.scss +7 -0
  48. package/tsup.config.ts +13 -0
  49. package/utils/index.ts +5 -0
@@ -0,0 +1,104 @@
1
+
2
+ <script lang="ts" setup>
3
+ import { useRandomData } from 'valaxy-theme-yun/composables'
4
+ import { onImgError } from '../utils'
5
+
6
+ export interface GirlType {
7
+ name: string
8
+ url: string
9
+ avatar: string
10
+ from?: string
11
+ reason?: string
12
+ }
13
+
14
+ const props = defineProps<{
15
+ girls: GirlType[] | string
16
+ random?: boolean
17
+ }>()
18
+
19
+ const { data } = useRandomData(props.girls, props.random)
20
+ </script>
21
+
22
+ <template>
23
+ <div class="girls">
24
+ <ul class="girl-items">
25
+ <li v-for="girl, i in data" :key="girl.name" class="girl-item">
26
+ <a :href="girl.url || 'https://zh.moegirl.org/' + girl.name" :title="girl.reason" alt="portrait" target="_blank" rel="noopener">
27
+ <figure class="girl-info">
28
+ <img class="girl-avatar" loading="lazy" :src="girl.avatar" :alt="girl.name" :onError="onImgError">
29
+ <figcaption class="girl-name" :title="(i+1).toString()">{{ girl.name }}</figcaption>
30
+ <figcaption class="girl-from">{{ girl.from }}</figcaption>
31
+ </figure>
32
+ </a>
33
+ </li>
34
+ </ul>
35
+ </div>
36
+ </template>
37
+
38
+ <style lang="scss">
39
+ .girls {
40
+ text-align: center;
41
+
42
+ .girl-items {
43
+ display: flex;
44
+ justify-content: center;
45
+ flex-wrap: wrap;
46
+ padding-left: 0;
47
+ }
48
+ }
49
+
50
+ .girls-number {
51
+ color: white;
52
+ }
53
+
54
+ .girl-item {
55
+ display: inline-flex;
56
+ text-align: center;
57
+ justify-content: center;
58
+ width: 8rem;
59
+ margin: 1rem;
60
+
61
+ .girl {
62
+ &-info {
63
+ width: 100%;
64
+ padding: 0;
65
+ margin: 0;
66
+ }
67
+
68
+ &-avatar {
69
+ object-fit: cover;
70
+ object-position: center top;
71
+ width: 4rem;
72
+ height: 4rem;
73
+ border-radius: 50%;
74
+ padding: 0.2rem;
75
+ background-color: #fff;
76
+ box-shadow: 0 0 1rem rgba(0, 0, 0, 0.12);
77
+ transition: 0.5s;
78
+
79
+ &:hover {
80
+ box-shadow: 0 0 2rem rgba(0, 0, 0, 0.12);
81
+ }
82
+ }
83
+
84
+ &-name {
85
+ font-size: 0.9rem;
86
+ }
87
+
88
+ &-from {
89
+ font-size: 12px;
90
+ font-family: var(--yun-font-serif);
91
+ font-weight: bold;
92
+ color: var(--yun-c-text-light);
93
+
94
+ &::before {
95
+ content: '「';
96
+ }
97
+
98
+ &::after {
99
+ content: '」';
100
+ }
101
+ }
102
+ }
103
+ }
104
+ </style>
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <a class="go-down" aria-label="go-down" href="javascript:window.scrollTo(0, banner.clientHeight);">
3
+ <div i-ri-arrow-down-s-line inline-flex />
4
+ </a>
5
+ </template>
6
+
7
+ <style lang="scss">
8
+ .go-down {
9
+ cursor: pointer;
10
+
11
+ display: inline-flex;
12
+ justify-content: center;
13
+ align-items: center;
14
+
15
+ position: absolute;
16
+ bottom: 1rem;
17
+ z-index: var(--yun-z-go-down);
18
+ animation: float 2s ease-in-out infinite;
19
+
20
+ font-size: 2.5rem;
21
+ color: var(--yun-c-primary);
22
+
23
+ transition: color var(--yun-transition-duration);
24
+
25
+ &:hover {
26
+ color: rgba(var(--yun-c-primary-rgb), 0.6);
27
+ }
28
+ }
29
+
30
+ @keyframes float {
31
+ 0% {
32
+ opacity: 1;
33
+ transform: translateY(0);
34
+ }
35
+
36
+ 50% {
37
+ opacity: 0.8;
38
+ transform: translateY(-0.8rem);
39
+ }
40
+
41
+ 100% {
42
+ opacity: 1;
43
+ transform: translateY(0);
44
+ }
45
+ }
46
+ </style>
@@ -0,0 +1,109 @@
1
+ <script lang="ts" setup>
2
+ import { useRandomData } from '../composables'
3
+ import { onImgError } from '../utils'
4
+
5
+ export interface LinkType {
6
+ avatar: string
7
+ name: string
8
+ url: string
9
+ color: string
10
+ blog: string
11
+ desc: string
12
+ }
13
+
14
+ const props = defineProps<{
15
+ links: LinkType[]
16
+ random: boolean
17
+ }>()
18
+
19
+ const { data } = useRandomData(props.links, props.random)
20
+ </script>
21
+
22
+ <template>
23
+ <div class="links">
24
+ <ul class="link-items">
25
+ <li v-for="link, i in data" :key="i" class="link-item" :style="`--primary-color: ${link.color}`">
26
+ <a class="link-url" p="x-4 y-2" :href="link.url" :title="link.name" alt="portrait" rel="friend">
27
+ <div class="link-left">
28
+ <img class="link-avatar" w="16" h="16" loading="lazy" :src="link.avatar" :alt="link.name" :onError="onImgError">
29
+ </div>
30
+ <div class="link-info" m="l-2">
31
+ <div class="link-blog" font="serif black">{{ link.blog }}</div>
32
+ <div class="link-desc">{{ link.desc }}</div>
33
+ </div>
34
+ </a>
35
+ </li>
36
+ </ul>
37
+ </div>
38
+ </template>
39
+
40
+ <stye lang="scss">
41
+
42
+ .link-item {
43
+ display: inline-flex;
44
+ }
45
+
46
+ .links {
47
+ .link-items {
48
+ display: flex;
49
+ text-align: center;
50
+ justify-content: center;
51
+ flex-wrap: wrap;
52
+
53
+ padding-left: 0;
54
+ }
55
+
56
+ .link-url {
57
+ --smc-primary: var(--primary-color);
58
+
59
+ display: inline-flex;
60
+ text-align: center;
61
+ justify-self: center;
62
+ line-height: 1.5;
63
+ margin: 0.5rem;
64
+ transition: 0.2s;
65
+ color: var(--primary-color, black);
66
+ border: 1px solid var(--primary-color, gray);
67
+
68
+ &:hover {
69
+ color: white;
70
+ background-color: var(--primary-color, gray);
71
+ box-shadow: 0 2px 20px var(--primary-color, gray);
72
+ }
73
+
74
+ .link {
75
+ &-left {
76
+ line-height: 0;
77
+ }
78
+
79
+ &-avatar {
80
+ margin: 0;
81
+ display: inline-flex;
82
+ max-width: 100%;
83
+ border-radius: 50%;
84
+ background-color: #fff;
85
+ border: 1px solid var(--primary-color, gray);
86
+ transition: 0.5s;
87
+
88
+ &:hover {
89
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
90
+ }
91
+ }
92
+
93
+ &-desc {
94
+ font-size: 0.8rem;
95
+ width: 10rem;
96
+ white-space: nowrap;
97
+ text-overflow: ellipsis;
98
+ overflow: hidden;
99
+ }
100
+ }
101
+ }
102
+
103
+ .link-info {
104
+ display: inline-flex;
105
+ flex-direction: column;
106
+ justify-content: center;
107
+ }
108
+ }
109
+ </stye>
@@ -0,0 +1,16 @@
1
+ <script lang="ts" setup>
2
+ defineProps<{
3
+ color?: string
4
+ icon?: string
5
+ title?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <header class="post-header">
11
+ <h1 class="post-title" p="2" text="2xl center" font="serif black" :style="`color:${color}`">
12
+ <div v-if="icon" class="icon" m="r-1" inline-flex :class="icon" />
13
+ <span>{{ title }}</span>
14
+ </h1>
15
+ </header>
16
+ </template>
@@ -0,0 +1,187 @@
1
+ <script lang="ts" setup>
2
+ import { computed, ref, watch } from 'vue'
3
+ import type { Post } from 'valaxy'
4
+ import { formatDate, sortByDate } from 'valaxy'
5
+ import { useI18n } from 'vue-i18n'
6
+
7
+ const { t } = useI18n()
8
+
9
+ const props = defineProps<{
10
+ posts: Post[]
11
+ }>()
12
+
13
+ const years = ref<number[]>([])
14
+ const postList = ref<Record<string, Post[]>>({})
15
+
16
+ watch(() => props.posts, () => {
17
+ postList.value = {}
18
+ years.value = []
19
+ props.posts.forEach((post) => {
20
+ if (post.date) {
21
+ const year = parseInt(formatDate(post.date, 'YYYY'))
22
+ if (postList.value[year]) { postList.value[year].push(post) }
23
+ else {
24
+ years.value.push(year)
25
+ postList.value[year] = [post]
26
+ }
27
+ }
28
+ })
29
+ }, { immediate: true })
30
+
31
+ const isDesc = ref(true)
32
+ const sortedYears = computed(() => {
33
+ const y = years.value
34
+ const arr = y.sort((a, b) => b - a)
35
+ return isDesc.value ? arr : arr.reverse()
36
+ })
37
+
38
+ </script>
39
+
40
+ <template>
41
+ <div class="post-collapse px-10 lt-sm:px-5">
42
+ <div w="full" text="center" class="yun-text-light" p="2">
43
+ {{ t('counter.archives', posts.length) }}
44
+ </div>
45
+
46
+ <div class="post-collapse-action" text="center">
47
+ <button class="yun-icon-btn shadow hover:shadow-md" @click="isDesc = !isDesc">
48
+ <div v-if="isDesc" i-ri-sort-desc />
49
+ <div v-else i-ri-sort-asc />
50
+ </button>
51
+ </div>
52
+
53
+ <div v-for="year in sortedYears" :key="year" m="b-6">
54
+ <div class="collection-title">
55
+ <h2 :id="`#archive-year-${year}`" class="archive-year" text="4xl" p="y-2">
56
+ {{ year }}
57
+ </h2>
58
+ </div>
59
+
60
+ <article v-for="post,j in sortByDate(postList[year], isDesc)" :key="j" class="post-item">
61
+ <header class="post-header">
62
+ <div class="post-meta">
63
+ <time v-if="post.date" class="post-time" font="mono" opacity="80">{{ formatDate(post.date, 'MM-DD') }}</time>
64
+ </div>
65
+ <h2 class="post-title" font="serif black">
66
+ <a :href="post.path" class="post-title-link">
67
+ {{ post.title }}
68
+ </a>
69
+ </h2>
70
+ </header>
71
+ </article>
72
+ </div>
73
+ </div>
74
+ </template>
75
+
76
+ <style lang="scss">
77
+ .post-collapse {
78
+ position: relative;
79
+
80
+ &-title {
81
+ font-size: 2rem;
82
+ text-align: center;
83
+ }
84
+
85
+ .collection-title {
86
+ position: relative;
87
+ margin: 0;
88
+ border-bottom: 2px solid rgba(var(--yun-c-primary-rgb), 0.6);
89
+
90
+ &::before {
91
+ content: '';
92
+ position: absolute;
93
+ top: 50%;
94
+ width: 2px;
95
+ height: 50%;
96
+ background: rgba(var(--yun-c-primary-rgb), 0.3);
97
+ }
98
+
99
+ .archive-year {
100
+ color: var(--yun-c-primary);
101
+ margin: 0 1.5rem;
102
+
103
+ &::before {
104
+ content: '';
105
+ position: absolute;
106
+ left: 0;
107
+ top: 35%;
108
+ margin-left: -11px;
109
+ margin-top: -4px;
110
+ width: 1.5rem;
111
+ height: 1.5rem;
112
+ background: var(--yun-c-primary);
113
+ border-radius: 50%;
114
+ }
115
+ }
116
+ }
117
+
118
+ .post-item {
119
+ position: relative;
120
+
121
+ &::before {
122
+ content: '';
123
+ position: absolute;
124
+ width: 2px;
125
+ height: 100%;
126
+ background: rgba(var(--yun-c-primary-rgb), 0.3);
127
+ }
128
+ }
129
+
130
+ .post-header {
131
+ display: flex;
132
+ align-items: center;
133
+
134
+ position: relative;
135
+ border-bottom: 1px solid rgba(var(--yun-c-primary-rgb), 0.3);
136
+ display: flex;
137
+
138
+ &::before {
139
+ content: '';
140
+ position: absolute;
141
+ left: 0;
142
+ width: 10px;
143
+ height: 10px;
144
+ margin-left: -4px;
145
+ border-radius: 50%;
146
+ border: 1px solid var(--yun-c-primary);
147
+ background-color: var(--yun-c-bg-light);
148
+ z-index: 1;
149
+ transition: background var(--yun-transition-duration);
150
+ }
151
+
152
+ &:hover {
153
+ &::before {
154
+ background: var(--yun-c-primary);
155
+ }
156
+ }
157
+
158
+ .post-title {
159
+ margin-left: 0.1rem;
160
+ padding: 0;
161
+ font-size: 1rem;
162
+ display: inline-flex;
163
+ align-items: center;
164
+
165
+ .post-title-link {
166
+ .icon {
167
+ width: 1.1rem;
168
+ height: 1.1rem;
169
+ margin-right: 0.3rem;
170
+ }
171
+ }
172
+ }
173
+
174
+ .post-meta {
175
+ font-size: 1rem;
176
+ margin: 1rem 0 1rem 1.2rem;
177
+ white-space: nowrap;
178
+ }
179
+ }
180
+ }
181
+
182
+ .last-word {
183
+ font-size: 1rem;
184
+ margin-top: 1rem;
185
+ margin-bottom: 0;
186
+ }
187
+ </style>
@@ -0,0 +1,29 @@
1
+ <script lang="ts" setup>
2
+ import type { Post } from 'valaxy'
3
+ import { useI18n } from 'vue-i18n'
4
+ import { formatDate } from 'valaxy'
5
+
6
+ const { t } = useI18n()
7
+
8
+ defineProps<{
9
+ frontmatter: Post
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <div v-if="frontmatter" class="post-meta justify-center" flex="~" text="sm">
15
+ <div v-if="frontmatter.date" class="post-time flex items-center">
16
+ <div class="inline-block" i-ri-calendar-line />
17
+ <time m="l-1" :title="t('post.posted')">{{ formatDate(frontmatter.date) }}</time>
18
+
19
+ <template v-if="frontmatter.updated && frontmatter.updated !== frontmatter.date">
20
+ <span m="x-2">-</span>
21
+ <div i-ri-calendar-2-line />
22
+ <time m="l-1" :title="t('post.edited')">{{ formatDate(frontmatter.updated) }}</time>
23
+ </template>
24
+ </div>
25
+ </div>
26
+ </template>
27
+
28
+ <style lang="scss">
29
+ </style>
@@ -0,0 +1,89 @@
1
+ <script lang="ts" setup>
2
+ import { usePrevNext } from '~/composables'
3
+ const [prev, next] = usePrevNext()
4
+ </script>
5
+
6
+ <template>
7
+ <div class="post-nav">
8
+ <div class="post-nav-item">
9
+ <router-link v-if="prev" class="post-nav-prev" :to="prev.path" :title="prev.title">
10
+ <div class="icon" i-ri-arrow-left-s-line />
11
+ <span class="title truncate" text="sm">{{ prev.title }}</span>
12
+ </router-link>
13
+ </div>
14
+ <div class="post-nav-item">
15
+ <router-link v-if="next" :to="next.path" :title="next.title" class="post-nav-next">
16
+ <span class="title truncate" text="sm">{{ next.title }}</span>
17
+ <div class="icon" i-ri-arrow-right-s-line />
18
+ </router-link>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <style lang="scss">
24
+ @use '~/styles/mixins' as *;
25
+
26
+ .post-nav {
27
+ display: flex;
28
+ justify-content: space-between;
29
+ align-items: center;
30
+
31
+ &-item {
32
+ display: inline-flex;
33
+ justify-content: center;
34
+ align-items: center;
35
+
36
+ color: var(--yun-c-primary);
37
+
38
+ outline: none;
39
+ font-size: 1.5rem;
40
+
41
+ font-weight: bold;
42
+ text-transform: uppercase;
43
+ height: 3rem;
44
+ transition: 0.4s;
45
+
46
+ &:hover {
47
+ background-color: rgba(var(--yun-c-primary-rgb), 0.1);
48
+ box-shadow: 0 0 15px rgba(black, 0.1);
49
+ }
50
+ }
51
+
52
+ &-prev {
53
+ padding: 0 0.6rem 0 0.1rem;
54
+ }
55
+
56
+ &-next {
57
+ padding: 0 0.1rem 0 0.6rem;
58
+ }
59
+
60
+ &-prev, &-next {
61
+ display: inline-flex;
62
+ align-items: center;
63
+
64
+ height: 3rem;
65
+
66
+ font-size: 1rem;
67
+
68
+ .title {
69
+ overflow: hidden;
70
+ max-width: 10rem;
71
+ }
72
+
73
+ .icon {
74
+ width: 1.2rem;
75
+ height: 1.2rem;
76
+ }
77
+ }
78
+ }
79
+
80
+ @include desktop {
81
+ .post-nav {
82
+ &-prev, &-next {
83
+ .title {
84
+ max-width: 18rem;
85
+ }
86
+ }
87
+ }
88
+ }
89
+ </style>
@@ -0,0 +1,83 @@
1
+ <script lang="ts" setup>
2
+ import { onMounted, ref } from 'vue'
3
+ import { useThemeConfig } from 'valaxy'
4
+
5
+ const themeConfig = useThemeConfig()
6
+
7
+ const sayContent = ref('')
8
+ const sayAuthor = ref('')
9
+ const sayFrom = ref('')
10
+
11
+ /**
12
+ * 获取在线 API 语录
13
+ */
14
+ function fetchApiToSay() {
15
+ const api = themeConfig.value.say.hitokoto.enable ? themeConfig.value.say.hitokoto.api : themeConfig.value.say.api
16
+ if (!api) return
17
+
18
+ fetch(api)
19
+ .then((res) => {
20
+ if (res.ok) {
21
+ res.json().then((data) => {
22
+ if (themeConfig.value.say.hitokoto.enable) {
23
+ sayContent.value = data.hitokoto
24
+ sayAuthor.value = data.from_who
25
+ sayFrom.value = data.from
26
+ }
27
+ else {
28
+ const sentence = data[Math.floor(Math.random() * data.length)]
29
+ if (sentence.content) {
30
+ sayContent.value = sentence.content
31
+ sayAuthor.value = sentence.author
32
+ sayFrom.value = sentence.from
33
+ }
34
+ else {
35
+ sayContent.value = sentence
36
+ }
37
+ }
38
+ })
39
+ }
40
+ else {
41
+ throw new Error(
42
+ `${themeConfig.value.say.api}, HTTP error, status = ${res.status}`,
43
+ )
44
+ }
45
+ })
46
+ .catch((err) => {
47
+ console.error(err.message)
48
+ })
49
+ }
50
+
51
+ onMounted(() => {
52
+ fetchApiToSay()
53
+ })
54
+ </script>
55
+
56
+ <template>
57
+ <div class="say">
58
+ <span v-if="sayContent" class="say-content" :class="['animate-fade-in', 'animate-iteration-1']">{{ sayContent }}</span>
59
+ <span v-if="sayAuthor" class="say-author"> {{ sayAuthor }}</span>
60
+ <span v-if="sayFrom" class="say-from">{{ sayFrom }}</span>
61
+ </div>
62
+ </template>
63
+
64
+ <style lang="scss">
65
+ .say {
66
+ color: var(--yun-c-text);
67
+ display: block;
68
+ text-align: center;
69
+ font-family: var(--yun-font-serif);
70
+ font-weight: bold;
71
+ padding: 0.5rem;
72
+ border-top: var(--yun-border-width) solid var(--yun-c-text-light);
73
+ border-bottom: var(--yun-border-width) solid var(--yun-c-text-light);
74
+
75
+ .say-content {
76
+ display: block;
77
+ }
78
+
79
+ .say-from {
80
+ display: block;
81
+ }
82
+ }
83
+ </style>