Skip to content

Commit 9105c9b

Browse files
committed
Improve Breadcrumb, add CodeTabs and CodeWithCopy components
1 parent 26df5e2 commit 9105c9b

File tree

4 files changed

+275
-15
lines changed

4 files changed

+275
-15
lines changed

docs/.vuepress/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import sidebar from "./config-client/sidebar";
1313
import social from "./config-client/social";
1414

1515
import Chat from "./components/Chat.vue";
16+
import CodeTabs from "./components/CodeTabs.vue";
17+
import CodeWithCopy from "./components/CodeWithCopy.vue";
1618

1719
export default defineClientConfig({
1820
rootComponents: [
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<template>
2+
<div class="code-tabs">
3+
<div class="tab-buttons">
4+
<button v-for="(tab, index) in tabs" :key="index" :class="{ active: activeTab === index }" @click="activeTab = index">
5+
{{ tab.title }}
6+
</button>
7+
</div>
8+
9+
<div class="tab-content code-block-wrapper">
10+
<button class="copy-button" @click="copyCode" aria-label="Copy code">
11+
<svg v-if="!copied" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
12+
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/>
13+
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/>
14+
</svg>
15+
<svg v-else xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
16+
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/>
17+
</svg>
18+
</button>
19+
20+
<pre><code ref="codeRef" class="language-bash">{{ tabs[activeTab].content }}</code></pre>
21+
</div>
22+
</div>
23+
</template>
24+
25+
<script>
26+
export default {
27+
name: 'CodeTabs',
28+
props: {
29+
tabs: {
30+
type: Array,
31+
required: true
32+
}
33+
},
34+
data() {
35+
return {
36+
activeTab: 0,
37+
copied: false
38+
}
39+
},
40+
watch: {
41+
activeTab() {
42+
this.highlight()
43+
}
44+
},
45+
mounted() {
46+
this.highlight()
47+
},
48+
methods: {
49+
highlight() {
50+
this.$nextTick(() => {
51+
if (typeof window !== 'undefined' && window.hljs && this.$refs.codeRef) {
52+
window.hljs.highlightElement(this.$refs.codeRef)
53+
}
54+
})
55+
},
56+
copyCode() {
57+
const text = this.tabs[this.activeTab].content
58+
navigator.clipboard.writeText(text).then(() => {
59+
this.copied = true
60+
setTimeout(() => (this.copied = false), 2000)
61+
})
62+
}
63+
}
64+
}
65+
</script>
66+
67+
<style scoped>
68+
.code-tabs {
69+
border: 1px solid #ccc;
70+
border-radius: 8px;
71+
overflow: hidden;
72+
margin-bottom: 1.5rem;
73+
}
74+
75+
.tab-buttons {
76+
display: flex;
77+
background-color: #2d2d2d;
78+
border-bottom: 1px solid #444;
79+
}
80+
81+
.tab-buttons button {
82+
padding: 0.85em;
83+
flex: 1;
84+
background: none;
85+
border: none;
86+
cursor: pointer;
87+
font-weight: 500;
88+
color: #ccc;
89+
}
90+
91+
.tab-buttons button.active {
92+
color: #fff;
93+
border-bottom: 2px solid #1994f9;
94+
font-weight: bold;
95+
}
96+
97+
.tab-content {
98+
position: relative;
99+
background-color: #2d2d2d;
100+
font-family: monospace;
101+
padding: 1rem;
102+
}
103+
104+
.code-block-wrapper {
105+
background-color: #2d2d2d;
106+
padding: 12px;
107+
position: relative;
108+
overflow: hidden;
109+
}
110+
111+
pre {
112+
margin: 0;
113+
background-color: transparent;
114+
color: #2d2d2d;
115+
font-size: 14px;
116+
overflow-x: auto;
117+
white-space: pre-wrap;
118+
word-wrap: break-word;
119+
max-width: 100%;
120+
line-height: 1.5;
121+
box-shadow: none;
122+
}
123+
124+
code {
125+
color: #ccc;
126+
background: none;
127+
display: block;
128+
white-space: pre-wrap;
129+
word-wrap: break-word;
130+
}
131+
132+
.copy-button {
133+
position: absolute;
134+
top: 0.5rem;
135+
right: 0.5rem;
136+
background: none;
137+
border: none;
138+
cursor: pointer;
139+
padding: 0.2rem;
140+
z-index: 10;
141+
}
142+
143+
.copy-button svg {
144+
fill: #ccc;
145+
width: 20px;
146+
height: 20px;
147+
transition: fill 0.2s;
148+
}
149+
150+
.copy-button:hover svg {
151+
fill: #1994f9;
152+
}
153+
154+
.language-bash {
155+
font-size: 0.85em;
156+
padding: 0;
157+
}
158+
</style>
159+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<template>
2+
<div class="code-block-wrapper">
3+
<button class="copy-button" @click="copyCode" aria-label="Copy code">
4+
<svg v-if="!copied" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" class="copy-icon">
5+
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path>
6+
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
7+
</svg>
8+
<svg v-else xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" class="copy-icon">
9+
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
10+
</svg>
11+
</button>
12+
<slot />
13+
</div>
14+
</template>
15+
16+
<script setup>
17+
import { ref } from 'vue';
18+
19+
const copied = ref(false);
20+
21+
function copyCode() {
22+
const code = document.querySelector('.code-block-wrapper pre code')?.innerText;
23+
if (code) {
24+
navigator.clipboard.writeText(code);
25+
copied.value = true;
26+
27+
setTimeout(() => {
28+
copied.value = false;
29+
}, 2000);
30+
}
31+
}
32+
</script>
33+
34+
<style scoped>
35+
.copy-button svg {
36+
transition: fill 0.3s;
37+
}
38+
39+
.copy-icon {
40+
fill: #ccc;
41+
width: 20px;
42+
height: 20px;
43+
}
44+
45+
.code-block-wrapper {
46+
position: relative;
47+
}
48+
49+
.copy-button {
50+
position: absolute;
51+
top: 0.5rem;
52+
right: 0.5rem;
53+
background: none;
54+
border: none;
55+
cursor: pointer;
56+
padding: 0.2rem;
57+
z-index: 10;
58+
}
59+
60+
.copy-button:hover .copy-icon {
61+
fill: #1994f9;
62+
}
63+
</style>
Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,55 @@
11
<template>
22
<div class="breadcrumb-wrapper">
3-
<span class="breadcrumb-title">{{ siteTitle }}:</span>
4-
<router-link class="breadcrumb" v-for="crumb in breadCrumbs" :key="crumb.path" :to="crumb.path">
3+
<router-link
4+
v-for="(crumb, index) in breadCrumbs"
5+
:key="crumb.path"
6+
class="breadcrumb"
7+
:to="crumb.path"
8+
>
59
{{ crumb.title }}
610
</router-link>
711
</div>
812
</template>
913

1014
<script setup>
11-
import {computed, inject} from "vue";
12-
import {usePageData, useSiteData} from "@vuepress/client";
15+
import { computed } from "vue";
16+
import { usePageData, useSiteData } from "@vuepress/client";
17+
18+
const page = usePageData();
19+
const site = useSiteData();
20+
21+
const siteTitle = computed(() => site.value.title);
22+
23+
const titleMap = {
24+
'/els-for-languages/': 'ELS for Languages',
25+
'/introduction/': 'Introduction to Cloudlinux OS',
26+
'/cloudlinuxos/': 'CloudLinux OS',
27+
'/cln/': 'CLN - CloudLinux Licenses',
28+
'/ubuntu/': 'CloudLinux Subsystem For Ubuntu',
29+
'/user-docs/': 'End-user Documents',
30+
};
1331
14-
const page = usePageData()
15-
const site = useSiteData()
16-
const { locales: {siteTitle}} = inject("themeConfig")
1732
const breadCrumbs = computed(() => {
18-
const crumbs = [];
19-
if (page.value.path !== '/') {
20-
crumbs.push({path: page.value.path, title: page.value.title});
33+
const segments = page.value.path.split("/").filter(Boolean);
34+
const crumbs = [{ path: "/", title: "Documentation" }];
35+
let cumulativePath = "";
36+
37+
for (let i = 0; i < segments.length; i++) {
38+
cumulativePath += `/${segments[i]}`;
39+
const isLast = i === segments.length - 1;
40+
const fullPath = cumulativePath.endsWith(".html") ? cumulativePath : `${cumulativePath}/`;
41+
42+
let title;
43+
44+
if (isLast) {
45+
title = page.value.title;
46+
} else {
47+
title = titleMap[fullPath] || fullPath;
2148
}
49+
50+
crumbs.push({ path: fullPath, title });
51+
}
52+
2253
return crumbs;
2354
});
2455
</script>
@@ -28,17 +59,22 @@ const breadCrumbs = computed(() => {
2859
2960
.breadcrumb
3061
color $breadcrumbColor
62+
text-decoration none
3163
32-
&::after
64+
&:not(:last-child)::after
3365
content " > "
3466
font-family inherit
3567
font-size inherit
68+
color $breadcrumbColor
69+
70+
&:not(:last-child)
71+
cursor pointer
72+
73+
&:hover
74+
color #1994f9
3675
3776
&:last-child
3877
cursor default
78+
color $breadcrumbColor
3979
40-
.breadcrumb-title
41-
color $breadcrumbColor
42-
font-weight 600
43-
margin-right 2px
4480
</style>

0 commit comments

Comments
 (0)