Skip to content

Commit cbf5b48

Browse files
added markdown styles
1 parent ec2e377 commit cbf5b48

8 files changed

Lines changed: 121 additions & 153 deletions

File tree

frontend/kubecloud-v2/assets/scss/global.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
body,
22
html {
33
scroll-behavior: smooth;
4+
scroll-padding-top: 80px;
45
}
56

67
.text-link {
@@ -140,3 +141,13 @@ html {
140141
.v-container--fluid {
141142
max-width: 100% !important;
142143
}
144+
145+
blockquote > * {
146+
&:first-child {
147+
margin-top: 0 !important;
148+
}
149+
150+
&:last-child {
151+
margin-bottom: 0 !important;
152+
}
153+
}

frontend/kubecloud-v2/composables/docs.ts

Lines changed: 70 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import hljs from "highlight.js"
12
import yaml from "js-yaml"
2-
import { marked } from "marked"
3+
import { Marked } from "marked"
4+
import { markedHighlight } from "marked-highlight"
5+
import "highlight.js/styles/atom-one-dark.css"
36

47
export interface Doc {
58
title: string
@@ -9,10 +12,45 @@ export interface Doc {
912
content: string
1013
md: {
1114
html: string
12-
tableOfContent: []
15+
tableOfContent: { id: string, content: string }[]
1316
}
1417
}
1518

19+
const marked = new Marked(
20+
markedHighlight({
21+
emptyLangClass: "hljs",
22+
langPrefix: "hljs language-",
23+
highlight(code, lang) {
24+
const language = hljs.getLanguage(lang) ? lang : "plaintext"
25+
return hljs.highlight(code, { language }).value
26+
},
27+
}),
28+
)
29+
30+
const renderer = new marked.Renderer()
31+
32+
renderer.code = function ({ text }) {
33+
const parts = text.split("\n")
34+
const count = parts.length
35+
const size = 29 + count.toString().length * 15
36+
const lines = parts.map((_, i) => `<span class="text-accent">${i + 1}</span>`)
37+
38+
return `
39+
<pre
40+
class="my-6 border rounded d-flex"
41+
style="--v-border-color: 255, 255, 255; --v-border-opacity: 0.12"
42+
>
43+
<code class="py-4 d-inline-block border-e bg-surface text-center" style="width: ${size}px;">${lines.join("\n")}</code>
44+
<code class="hljs bg-surface" style="width: calc(100% - ${size}px);">${parts.join("\n")}</code>
45+
</pre>
46+
`
47+
}
48+
49+
renderer.blockquote = function ({ tokens }) {
50+
const content = this.parser.parse(tokens)
51+
return `<blockquote class="my-6 px-6 pa-4 border-s-lg border-primary rounded" style="--v-border-opacity: 1; background: rgba(var(--v-theme-primary), 0.12);">${content}</blockquote>`
52+
}
53+
1654
export const useDocs = createGlobalState(() => {
1755
// const router = useRouter()
1856

@@ -26,31 +64,45 @@ export const useDocs = createGlobalState(() => {
2664
.then(res => res.text())),
2765
)
2866

29-
const renderer = new marked.Renderer()
67+
let tableOfContent: Doc["md"]["tableOfContent"] = []
3068

69+
const linkIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42c-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0a5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.98 2.98 0 0 0 0-4.24a2.98 2.98 0 0 0-4.24 0l-3.53 3.53a2.98 2.98 0 0 0 0 4.24m2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0a5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.98 2.98 0 0 0 0 4.24a2.98 2.98 0 0 0 4.24 0l3.53-3.53a2.98 2.98 0 0 0 0-4.24a.973.973 0 0 1 0-1.42"/></svg>`
3170
renderer.heading = function ({ depth, tokens }) {
3271
const content = this.parser.parseInline(tokens)
72+
let id = ""
73+
let a = ""
74+
if (depth === 2) {
75+
id = content.toLowerCase().replaceAll(":", "").replaceAll(" ", "-")
76+
a = `<a href="#${id}" class="d-inline-block mr-2 text-primary opacity-50">${linkIcon}</a>`
77+
tableOfContent.push({ id, content })
78+
}
79+
3380
const lv = Math.min(depth + 3, 6)
34-
return `<h${lv} class="text-h${lv} mb-2">${content}</h${lv}>`
81+
return `
82+
<h${depth} id="${id}" class="text-h${lv} mb-4">
83+
${a}${content}
84+
</h${depth}>
85+
`
3586
}
3687

3788
renderer.paragraph = function ({ tokens }) {
3889
const content = this.parser.parseInline(tokens)
39-
return `<p class="text-body-1 text-accent mt-2 mb-4">${content}</p>`
90+
return `<p class="text-body-1 text-accent mt-3 mb-6">${content}</p>`
4091
}
4192

4293
renderer.listitem = function (x) {
43-
// console.log(x.tokens)
44-
45-
// return "item"
4694
const content = this.parser.parse(x.tokens)
47-
return `<li class="text-body-1 text-accent">${content}</li>`
95+
return `
96+
<li class="mb-2">
97+
<span class="text-body-1 text-accent">${content}</span>
98+
</li>
99+
`
48100
}
49101

50102
renderer.list = function ({ ordered, items }) {
51103
const tag = ordered ? "ol" : "ul"
52104
const body = items.map(item => this.listitem(item)).join("")
53-
return `<${tag} class="mt-2 mb-4 pl-4" style="list-style-type: square">${body}</${tag}>`
105+
return `<${tag} class="mt-4 mb-6 pl-4" style="list-style-type: square">${body}</${tag}>`
54106
}
55107

56108
renderer.link = function ({ href, tokens }) {
@@ -75,139 +127,27 @@ export const useDocs = createGlobalState(() => {
75127
return `<a class="text-link" href="${href}" onclick="xonClick(event, this);">${content}</a>`
76128
}
77129

130+
renderer.strong = function ({ tokens }) {
131+
const content = this.parser.parseInline(tokens)
132+
return `<strong class="text-white text-body-1 font-weight-bold">${content}</strong>`
133+
}
134+
78135
renderer.codespan = function ({ text }) {
79136
return `<code
80137
class="text-primary text-body-1 border border-primary py-1 px-2 rounded"
81-
style="background-color: rgba(var(--v-theme-primary), var(--v-border-opacity))"
138+
style="--v-border-opacity: 0.12; background-color: rgba(var(--v-theme-primary), var(--v-border-opacity))"
82139
>${text}</code>`
83140
}
84141

85-
/* Headings */
86-
// renderer.heading = ({ text, depth: level }) => {
87-
// return `<h${level} class="md-heading text-h${Math.min(level + 3, 6)}">${text}</h${level}>`
88-
// }
89-
90-
/* Paragraphs */
91-
// renderer.paragraph = (text) => {
92-
// return `<p class="md-paragraph">${text}</p>`
93-
// }
94-
95-
/* Links */
96-
// renderer.link = ({ href, title, text }) => {
97-
// const t = title ? ` title="${title}"` : ""
98-
// return `<a class="md-link" href="${href}"${t} target="_blank" rel="noopener noreferrer">${text}</a>`
99-
// }
100-
101-
/* Lists */
102-
// renderer.list = ({ ordered, items }) => {
103-
// const tag = ordered ? "ol" : "ul"
104-
// const body = items.map(item => `<li class="md-list-item">${item}</li>`).join("")
105-
// return `<${tag} class="md-list">${body}</${tag}>`
106-
// }
107-
108-
// renderer.listitem = (text) => {
109-
// return `<li class="md-list-item">${text}</li>`
110-
// }
111-
112-
/* Blockquotes */
113-
// renderer.blockquote = (quote) => {
114-
// return `<blockquote class="md-blockquote">${quote}</blockquote>`
115-
// }
116-
117-
/* Inline code */
118-
// renderer.codespan = (code) => {
119-
// return `<code class="md-inline-code">${code}</code>`
120-
// }
121-
122-
/* Code blocks */
123-
// renderer.code = ({ text, lang }) => {
124-
// const langClass = lang ? ` lang-${lang}` : ""
125-
// return `
126-
// <pre class="md-code-block${langClass}">
127-
// <code class="md-code">${text}</code>
128-
// </pre>
129-
// `
130-
// }
131-
132-
/* Tables */
133-
// renderer.table = ({ header, rows }) => {
134-
// const body = marked.parseInline(rows)
135-
// return `
136-
// <table class="md-table">
137-
// <thead class="md-table-head">${header}</thead>
138-
// <tbody class="md-table-body">${body}</tbody>
139-
// </table>
140-
// `
141-
// }
142-
143-
// renderer.tablerow = (content) => {
144-
// return `<tr class="md-table-row">${content}</tr>`
145-
// }
146-
147-
// renderer.tablecell = (content, flags) => {
148-
// const tag = flags.header ? "th" : "td"
149-
// return `<${tag} class="md-table-cell">${content}</${tag}>`
150-
// }
151-
152-
/* Images */
153-
// renderer.image = ({ href, title, text }) => {
154-
// const t = title ? ` title="${title}"` : ""
155-
// return `<img class="md-image" src="${href}" alt="${text}"${t} />`
156-
// }
157-
158-
/* Horizontal rule */
159-
// renderer.hr = () => {
160-
// return `<hr class="md-hr" />`
161-
// }
162-
163-
/* Strong / emphasis */
164-
// renderer.strong = (text) => {
165-
// return `<strong class="md-strong">${text}</strong>`
166-
// }
167-
168-
// renderer.em = (text) => {
169-
// return `<em class="md-em">${text}</em>`
170-
// }
171-
172-
// function e(name: string, attrs: Record<string, string>) {
173-
// const content = attrs.content ?? ""
174-
// delete attrs.content
175-
// return `<${name} ${Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ")}>${content}</${name}>`
176-
// }
177-
178-
// marked.use({
179-
// renderer: {
180-
// blockquote({ tokens }) {
181-
// const text = this.parser.parse(tokens)
182-
// return `
183-
// <blockquote
184-
// class="pa-4 border-s-lg border-primary bg-surface"
185-
// style="--v-border-opacity: 1"
186-
// >
187-
// ${text}
188-
// </blockquote>
189-
// `
190-
// },
191-
// code(code) {
192-
// console.log(code)
193-
194-
// if (!code.type) {
195-
// console.log({ code })
196-
// }
197-
// return `
198-
// <pre class="bg-red overflow-y-auto"><code class="language-${code.lang}">${code.text}</code></pre>
199-
// `
200-
// },
201-
// },
202-
// })
203142
return docs.map((doc, index) => {
143+
tableOfContent = []
204144
const content = contents[index] ?? ""
205145
return {
206146
...doc,
207147
content,
208148
md: {
209149
html: marked.parse(content, { renderer }),
210-
tableOfContent: [],
150+
tableOfContent,
211151
},
212152
}
213153
})

frontend/kubecloud-v2/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
"@vueuse/nuxt": "14.1.0",
2222
"async-await-mutex-lock": "^1.0.12",
2323
"axios": "^1.13.2",
24+
"highlight.js": "^11.11.1",
2425
"izitoast": "^1.4.0",
2526
"js-yaml": "^4.1.1",
2627
"marked": "^17.0.1",
28+
"marked-highlight": "^2.2.3",
2729
"nuxt": "^4.2.2",
2830
"nuxt-toast": "^1.4.0",
2931
"quill": "^2.0.3",

frontend/kubecloud-v2/pages/docs.vue

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
:prepend-icon="doc.icon"
1414
link
1515
exact
16+
rounded
1617
:title="doc.title"
1718
:to="ROUTES.Docs(doc.path)"
1819
class="text-accent"
@@ -25,14 +26,18 @@
2526
</v-list-item>
2627

2728
<v-list-item
28-
v-for="doc in docs"
29-
:key="doc.path"
29+
v-for="{ id, content } in activePage?.md.tableOfContent"
30+
:key="id"
3031
link
31-
exact
3232
density="compact"
33-
class="text-body-2"
33+
rounded
34+
:href="`#${id}`"
3435
>
35-
{{ doc.title }}
36+
<v-list-item-title
37+
class="text-body-2 pl-4 text-no-wrap"
38+
>
39+
{{ content }}
40+
</v-list-item-title>
3641
</v-list-item>
3742
</v-list>
3843
<!-- :to="ROUTES.Docs(doc.path)" -->

frontend/kubecloud-v2/pages/docs/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{{ page }}
44
</div> -->
55

6-
<div class="markdown" v-html="page?.md?.html" />
6+
<div v-html="page?.md?.html" />
77
</template>
88

99
<script setup lang="ts">

frontend/kubecloud-v2/public/docs/getting-started.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Mycelium Cloud uses peer-to-peer networking that enables:
2020
- **Secure Communication**: All traffic is encrypted through the Mycelium network
2121
- **No Public IPs Required**: Services accessible via Mycelium IPs
2222

23-
**Network Flow**: `User Machine → Mycelium Network → Cluster Node → Service`
23+
**Network Flow**: `User Machine Mycelium Network Cluster Node Service`
2424

2525
## Quick Start
2626

@@ -44,7 +44,7 @@ Mycelium Cloud uses peer-to-peer networking that enables:
4444

4545
#### Download Kubeconfig
4646

47-
1. Go to dashboard → Clusters → Click download icon (⬇️)
47+
1. Go to dashboard Clusters Click download icon (⬇️)
4848
2. Set kubeconfig: `export KUBECONFIG=/path/to/config`
4949
3. Test: `kubectl get nodes`
5050

@@ -66,4 +66,4 @@ Mycelium Cloud uses peer-to-peer networking that enables:
6666
sudo mycelium --peers tcp://188.40.132.242:9651 tcp://136.243.47.186:9651 tcp://185.69.166.7:9651 tcp://185.69.166.8:9651 tcp://65.21.231.58:9651 tcp://65.109.18.113:9651 tcp://209.159.146.190:9651 tcp://5.78.122.16:9651 tcp://5.223.43.251:9651 tcp://142.93.217.194:9651
6767
```
6868

69-
4. **SSH to nodes**: `ssh root@<mycelium-ip>`
69+
4. **SSH to nodes**: `ssh root@&lt;mycelium-ip&gt;`

0 commit comments

Comments
 (0)