添加教程
This commit is contained in:
@@ -11,6 +11,9 @@
|
|||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
```js
|
||||||
|
console.log("122")
|
||||||
|
```
|
||||||
|
|
||||||
## 什么是 HTML
|
## 什么是 HTML
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
# 什么是 CSS
|
# 什么是 CSS
|
||||||
|
|
||||||
|
```css
|
||||||
|
.greeting {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -29,10 +29,10 @@ whenever(ctrl_r, () => {})
|
|||||||
:date-locale="dateZhCN"
|
:date-locale="dateZhCN"
|
||||||
inline-theme-disabled
|
inline-theme-disabled
|
||||||
>
|
>
|
||||||
<n-split :default-size="1 / 3" min="300px" max="700px">
|
<n-split :default-size="1 / 3" min="300px" max="800px">
|
||||||
<template #1><Tutorial /></template>
|
<template #1><Tutorial /></template>
|
||||||
<template #2>
|
<template #2>
|
||||||
<n-split direction="vertical">
|
<n-split direction="vertical" min="200px">
|
||||||
<template #1>
|
<template #1>
|
||||||
<Editors />
|
<Editors />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<n-tabs
|
<n-tabs
|
||||||
style="height: 100%"
|
style="height: 100%"
|
||||||
pane-class="pane"
|
pane-class="pane"
|
||||||
:default-value="currentTab"
|
:value="tab"
|
||||||
type="segment"
|
type="card"
|
||||||
@update:value="changeTab"
|
@update:value="changeTab"
|
||||||
>
|
>
|
||||||
<n-tab-pane name="html" tab="HTML">
|
<n-tab-pane name="html" tab="HTML">
|
||||||
@@ -24,11 +24,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<Editor language="css" :font-size="size" v-model:value="css" />
|
<Editor language="css" :font-size="size" v-model:value="css" />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="js" tab="JavaScript">
|
<n-tab-pane name="js" tab="JS">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<n-flex align="center">
|
<n-flex align="center">
|
||||||
<Icon icon="skill-icons:javascript" :width="20"></Icon>
|
<Icon icon="skill-icons:javascript" :width="20"></Icon>
|
||||||
<span>JavaScript</span>
|
<span>JS</span>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
<Editor language="js" :font-size="size" v-model:value="js" />
|
<Editor language="js" :font-size="size" v-model:value="js" />
|
||||||
@@ -71,14 +71,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import Editor from "./Editor.vue"
|
import Editor from "./Editor.vue"
|
||||||
import { useStorage } from "@vueuse/core"
|
import { html, css, js, reset, tab, size } from "../store"
|
||||||
import { html, css, js, reset, size, changeSize } from "../store"
|
|
||||||
|
|
||||||
const currentTab = useStorage("web-tab", "html")
|
function changeTab(name: string) {
|
||||||
|
tab.value = name
|
||||||
function changeTab(tab: "html" | "css" | "js" | "actions") {
|
}
|
||||||
if (tab === "actions") return
|
function changeSize(num: number) {
|
||||||
currentTab.value = tab
|
size.value = num
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -46,9 +46,10 @@ onMounted(preview)
|
|||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.title {
|
.title {
|
||||||
height: 40px;
|
height: 43px;
|
||||||
background-color: rgb(247, 247, 250);
|
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
|
border-bottom: 1px solid rgb(239, 239, 245);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.titleText {
|
.titleText {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|||||||
@@ -7,24 +7,35 @@
|
|||||||
<n-button text @click="prev" :disabled="step === '01'">
|
<n-button text @click="prev" :disabled="step === '01'">
|
||||||
<Icon :width="24" icon="pepicons-pencil:arrow-left"></Icon>
|
<Icon :width="24" icon="pepicons-pencil:arrow-left"></Icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button text @click="next" :disabled="end">
|
<n-button text @click="next" :disabled="last">
|
||||||
<Icon :width="24" icon="pepicons-pencil:arrow-right"></Icon>
|
<Icon :width="24" icon="pepicons-pencil:arrow-right"></Icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
<div class="markdown-body" v-html="content"></div>
|
<div class="markdown-body" v-html="content" ref="$content"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { step, prev, next } from "../store"
|
import { tab, html, js, css } from "../store"
|
||||||
import { onMounted, ref, shallowRef, watch } from "vue"
|
import { onMounted, ref, useTemplateRef, watch } from "vue"
|
||||||
import { marked } from "marked"
|
import { marked } from "marked"
|
||||||
import { markedHighlight } from "marked-highlight"
|
import { useStorage } from "@vueuse/core"
|
||||||
import hljs from "highlight.js"
|
|
||||||
|
|
||||||
const end = ref(false)
|
const step = useStorage("web-turtorial-step", "01")
|
||||||
const content = shallowRef("")
|
const last = ref(false)
|
||||||
|
const content = ref("")
|
||||||
|
const $content = useTemplateRef("$content")
|
||||||
|
|
||||||
|
function prev() {
|
||||||
|
let num = parseInt(step.value) - 1
|
||||||
|
step.value = num.toString().padStart(2, "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
let num = parseInt(step.value) + 1
|
||||||
|
step.value = num.toString().padStart(2, "0")
|
||||||
|
}
|
||||||
|
|
||||||
async function getContent() {
|
async function getContent() {
|
||||||
const res = await fetch(`/turtorial/${step.value}/README.md`)
|
const res = await fetch(`/turtorial/${step.value}/README.md`)
|
||||||
@@ -32,29 +43,49 @@ async function getContent() {
|
|||||||
if (!!data) {
|
if (!!data) {
|
||||||
const html = await marked.parse(data, { async: true })
|
const html = await marked.parse(data, { async: true })
|
||||||
content.value = html
|
content.value = html
|
||||||
end.value = false
|
last.value = false
|
||||||
} else {
|
} else {
|
||||||
end.value = true
|
content.value = ""
|
||||||
|
last.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
// 用 js 来写的,可以换成 vue 的方式
|
||||||
getContent()
|
function addCopyButton() {
|
||||||
marked.use({
|
const div = document.createElement("div")
|
||||||
gfm: true,
|
div.className = "my-action-btn"
|
||||||
async: true,
|
const pres = $content.value!.querySelectorAll("pre")
|
||||||
|
pres.forEach((pre) => {
|
||||||
|
let timer = 0
|
||||||
|
const copy = div.cloneNode(true) as HTMLDivElement
|
||||||
|
pre.appendChild(copy)
|
||||||
|
copy.onclick = () => {
|
||||||
|
// 功能
|
||||||
|
const outer = pre.childNodes[0] as HTMLPreElement
|
||||||
|
const match = outer.className.match(/-(.*)/)
|
||||||
|
let lang = "html"
|
||||||
|
if (match) lang = match[1].toLowerCase()
|
||||||
|
tab.value = lang
|
||||||
|
if (lang === "html") html.value = pre.textContent
|
||||||
|
if (lang === "css") css.value = pre.textContent
|
||||||
|
if (lang === "js") js.value = pre.textContent
|
||||||
|
// 样式
|
||||||
|
copy.classList.add("click")
|
||||||
|
clearTimeout(timer)
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
copy.classList.remove("click")
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
marked.use(
|
}
|
||||||
markedHighlight({
|
|
||||||
langPrefix: "hljs language-",
|
async function render() {
|
||||||
highlight(code, lang) {
|
await getContent()
|
||||||
const language = hljs.getLanguage(lang) ? lang : "plaintext"
|
addCopyButton()
|
||||||
return hljs.highlight(code, { language }).value
|
}
|
||||||
},
|
|
||||||
}),
|
onMounted(render)
|
||||||
)
|
watch(step, render)
|
||||||
})
|
|
||||||
watch(step, getContent)
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.container {
|
||||||
@@ -63,10 +94,11 @@ watch(step, getContent)
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
height: 40px;
|
height: 43px;
|
||||||
background-color: rgb(247, 247, 250);
|
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
border-bottom: 1px solid rgb(239, 239, 245);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.preview {
|
.preview {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -77,3 +109,22 @@ watch(step, getContent)
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<style>
|
||||||
|
.markdown-body pre {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.my-action-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
}
|
||||||
|
.my-action-btn::before {
|
||||||
|
content: "替换";
|
||||||
|
}
|
||||||
|
.my-action-btn.click::before {
|
||||||
|
content: "已替换";
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
18
src/main.ts
18
src/main.ts
@@ -1,10 +1,28 @@
|
|||||||
import { createApp } from "vue"
|
import { createApp } from "vue"
|
||||||
import { create } from "naive-ui"
|
import { create } from "naive-ui"
|
||||||
|
import { marked } from "marked"
|
||||||
import "normalize.css"
|
import "normalize.css"
|
||||||
import "github-markdown-css/github-markdown-light.css"
|
import "github-markdown-css/github-markdown-light.css"
|
||||||
import "highlight.js/styles/github.min.css"
|
import "highlight.js/styles/github.min.css"
|
||||||
import App from "./App.vue"
|
import App from "./App.vue"
|
||||||
|
|
||||||
|
import { markedHighlight } from "marked-highlight"
|
||||||
|
import hljs from "highlight.js"
|
||||||
|
|
||||||
|
marked.use({
|
||||||
|
gfm: true,
|
||||||
|
async: true,
|
||||||
|
})
|
||||||
|
marked.use(
|
||||||
|
markedHighlight({
|
||||||
|
langPrefix: "hljs language-",
|
||||||
|
highlight(code, lang) {
|
||||||
|
const language = hljs.getLanguage(lang) ? lang : "plaintext"
|
||||||
|
return hljs.highlight(code, { language }).value
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
const naive = create()
|
const naive = create()
|
||||||
app.use(naive)
|
app.use(naive)
|
||||||
|
|||||||
19
src/store.ts
19
src/store.ts
@@ -3,12 +3,16 @@ import { useStorage } from "@vueuse/core"
|
|||||||
const defaultHTML = `<div class="welcome">黄岩一职</div>`
|
const defaultHTML = `<div class="welcome">黄岩一职</div>`
|
||||||
const defaultCSS = `.welcome {
|
const defaultCSS = `.welcome {
|
||||||
color: red;
|
color: red;
|
||||||
|
font-size: 24px;
|
||||||
}`
|
}`
|
||||||
|
|
||||||
export const html = useStorage("web-html", defaultHTML)
|
export const html = useStorage("web-html", defaultHTML)
|
||||||
export const css = useStorage("web-css", defaultCSS)
|
export const css = useStorage("web-css", defaultCSS)
|
||||||
export const js = useStorage("web-js", "")
|
export const js = useStorage("web-js", "")
|
||||||
|
|
||||||
|
export const tab = useStorage("web-tab", "html")
|
||||||
|
export const size = useStorage("web-fontsize", 24)
|
||||||
|
|
||||||
export function reset(lang: "html" | "css" | "js") {
|
export function reset(lang: "html" | "css" | "js") {
|
||||||
if (lang === "html") {
|
if (lang === "html") {
|
||||||
html.value = defaultHTML
|
html.value = defaultHTML
|
||||||
@@ -18,18 +22,3 @@ export function reset(lang: "html" | "css" | "js") {
|
|||||||
js.value = ""
|
js.value = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const size = useStorage("web-fontsize", 24)
|
|
||||||
export function changeSize(num: number) {
|
|
||||||
size.value = num
|
|
||||||
}
|
|
||||||
|
|
||||||
export const step = useStorage("web-turtorial-step", "01")
|
|
||||||
export function prev() {
|
|
||||||
let num = parseInt(step.value) - 1
|
|
||||||
step.value = num.toString().padStart(2, "0")
|
|
||||||
}
|
|
||||||
export function next() {
|
|
||||||
let num = parseInt(step.value) + 1
|
|
||||||
step.value = num.toString().padStart(2, "0")
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user