Commit 54b969c2 authored by Rock王子文's avatar Rock王子文

feat: init

parents
.DS_Store
.vite-ssg-dist
.vite-ssg-temp
*.local
dist
dist-ssr
node_modules
.idea/
*.log
.temp
.cache
{
"name": "ggg3",
"version": "0.0.1",
"description": "Vuepress plugin for demo-block & code-edit",
"main": "dist/ggg3.es.js",
"module": "dist/ggg3.es.js",
"scripts": {
"build": "vite build ."
},
"dependencies": {
"@codemirror/lang-javascript": "^6.1.4",
"@codemirror/theme-one-dark": "^6.1.1",
"@rollup/plugin-commonjs": "24.1.0-0",
"@vue/shared": "^3.2.37",
"@vueuse/core": "^9.1.0",
"ansi-styles": "^6.2.1",
"chalk": "^4.1.2",
"codemirror": "^6.0.1",
"consola": "^2.15.3",
"escape-html": "^1.0.3",
"fast-glob": "^3.2.12",
"markdown-it": "^13.0.1",
"markdown-it-container": "^3.0.0",
"prismjs": "^1.29.0",
"vue-codemirror": "^6.1.1"
},
"peerDependencies": {
"@vuepress/client": "2.0.0-beta.61",
"vue": "^3.2.47",
"vuepress": "2.0.0-beta.61"
},
"devDependencies": {
"@types/markdown-it": "^12.2.3",
"@vuepress/client": "2.0.0-beta.61",
"vite": "^2.9.15",
"vue": "^3.2.47",
"vuepress": "2.0.0-beta.61"
}
}
This diff is collapsed.
<script>
import { Codemirror } from 'vue-codemirror'
import { javascript } from '@codemirror/lang-javascript'
import { oneDark } from '@codemirror/theme-one-dark'
export default {
name: 'CodeEdit',
components: {
Codemirror,
},
data() {
return {
source: '',
codeSource: '',
extensions: [javascript(), oneDark],
}
},
mounted() {
const source = sessionStorage.getItem('gcodeSource')
this.source = source
this.codeSource = decodeURIComponent(this.source)
this.handleRun()
},
methods: {
onCtrlSClick(event) {
event.preventDefault()
this.handleRun()
},
handleRun() {
fetch(`/updateTemp?codesource=${encodeURIComponent(this.codeSource)}`)
},
handleReset() {
const res = window.confirm('确认要重置吗?')
if (res) {
const code = sessionStorage.getItem('gcodeSource')
this.source = code
this.codeSource = decodeURIComponent(this.source)
}
},
handleRefresh() {
this.$refs.gframe.contentWindow.location.reload()
},
},
}
</script>
<template>
<ClientOnly>
<div class="kf-preview-block">
<div class="operate-container">
<span class="btn" @click="handleRun"> 运行(ctrl + s) </span>
<span class="btn" @click="handleReset"> 重置代码 </span>
<span class="btn" @click="handleRefresh"> 刷新效果 </span>
</div>
<div class="preview-panel">
<div class="preview-source" @keydown.ctrl.s="onCtrlSClick">
<Codemirror
v-model="codeSource" placeholder="Code goes here..." :autofocus="true" language="javascript"
:extensions="extensions" :indent-with-tab="true" :tab-size="2"
/>
</div>
<div class="preview-code">
<iframe ref="gframe" src="/gpreview" />
</div>
</div>
</div>
</ClientOnly>
</template>
<style>
/* 重写样式 ==============start============== */
.navbar {
display: none;
}
.theme-default-content {
max-width: 100% !important;
}
iframe {
width: 100%;
height: 100%;
}
/* 重新样式 ===============end============= */
.kf-preview-block .btn {
color: #1f93ff;
cursor: pointer;
margin-left: 16px;
}
.kf-preview-block {
background: #fff;
display: flex;
flex-direction: column;
border: solid 1px #ebebeb;
border-radius: 3px;
transition: 0.3s;
height: 85vh;
overflow: hidden;
}
.kf-preview-block .operate-container {
text-align: right;
padding-right: 40px;
border-bottom: solid 1px #ebebeb;
}
.kf-preview-block .preview-header {
display: flex;
align-items: center;
height: 60px;
}
.kf-preview-block .preview-panel {
display: flex;
flex: 1;
overflow: hidden;
}
.kf-preview-block .preview-source {
display: block;
width: 50%;
background-color: #f3f4f5;
overflow: auto;
}
.kf-preview-block .preview-code {
display: block;
width: 50%;
padding: 24px;
}
.kf-preview-block .CodeMirror.cm-s-monokai {
height: 100%;
}
</style>
<template>
<ClientOnly>
<div class="kf-demo-block" :class="[{ hover: hovering }]" @mouseenter="hovering = true"
@mouseleave="hovering = false">
<!-- danger here DO NOT USE INLINE SCRIPT TAG -->
<p text="sm" v-html="decodedDescription" />
<div class="example">
<component :is="formatPathDemos[path]"></component>
</div>
<transition name="code-slide">
<div v-show="sourceVisible" class="example-source-wrapper">
<div class="example-source language-vue" v-html="decodedCode" />
</div>
</transition>
<div ref="control" :class="['kf-demo-block-control', { 'is-fixed': sourceVisible }]"
@click="toggleSourceVisible(!sourceVisible)">
<transition name="text-slide">
<i v-show="hovering || sourceVisible">{{ iconClass }}</i>
</transition>
<transition name="text-slide">
<span v-show="hovering || sourceVisible" class="btn">{{ controlText }}</span>
</transition>
<div class="control-button-container">
<span v-show="hovering || sourceVisible" size="small" type="text" class="control-button copy-button btn"
@click.stop="handleCopy">
{{ copyMessage }}
</span>
<transition name="text-slide">
<span v-show="hovering || sourceVisible" class="control-button run-online-button btn"
@click.stop="handleCodeView">
在线运行
</span>
</transition>
</div>
</div>
</div>
</ClientOnly>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useClipboard, useToggle } from '@vueuse/core'
const props = defineProps<{
demos: object
source: string
path: string
rawSource: string
description?: string
}>()
const { copy } = useClipboard({
source: decodeURIComponent(props.rawSource),
read: false,
})
let hovering = ref(false);
let copyMessage = ref('复制代码');
const [sourceVisible, toggleSourceVisible] = useToggle()
const formatPathDemos = computed(() => {
return props.demos
})
const iconClass = computed(() => {
return sourceVisible ? '▲' : '▼';
})
const controlText = computed(() => {
return sourceVisible ? '隐藏代码' : '显示代码';
})
const decodedDescription = computed(() =>
decodeURIComponent(props.description!)
)
const decodedCode = computed(() =>
decodeURIComponent(props.source!)
)
async function handleCopy() {
await copy();
copyMessage.value = '复制成功🎉';
setTimeout(() => {
copyMessage.value = '复制代码';
}, 2000);
}
function handleCodeView() {
sessionStorage.setItem('gcodeSource', props.rawSource);
window.open('/gedit');
}
</script>
<style lang="scss">
.kf-demo-block {
position: relative;
border: solid 1px #ebebeb;
padding: 20px;
border-radius: 3px;
transition: 0.3s;
.code-slide-enter,
.code-slide-enter-active,
.code-slide-leave,
.code-slide-leave-active {
transition:
0.3s max-height ease-in-out,
0.3s padding-top ease-in-out,
0.3s padding-bottom ease-in-out;
}
.example {
margin-bottom: 20px;
}
.btn {
color: #1f93ff;
cursor: pointer;
margin-left: 16px;
}
&.hover {
box-shadow: 0 0 8px 0 rgb(232 237 250 / 60%), 0 2px 4px 0 rgb(232 237 250 / 50%);
}
code {
font-family: Menlo, Monaco, Consolas, Courier, monospace;
}
.demo-button {
float: right;
}
.source {
padding: 24px;
}
.meta {
border-top: solid 1px #eaeefb;
overflow: hidden;
height: 0;
transition: height 0.3s;
}
.description {
padding: 20px;
box-sizing: border-box;
border: solid 1px #ebebeb;
border-radius: 3px;
font-size: 14px;
line-height: 22px;
color: #666;
word-break: break-word;
margin: 10px;
p {
margin: 0;
line-height: 26px;
}
}
.highlight {
div[class*="language-"] {
border-radius: 0;
}
}
#highlight {
& > .language-vue > .language-vue {
padding-top: 0;
margin-top: 0;
}
}
.kf-demo-block-control {
position: relative;
z-index: 9;
border-top: solid 1px #eaeefb;
height: 44px;
box-sizing: border-box;
// border-bottom-left-radius: 4px;
// border-bottom-right-radius: 4px;
border: 1px solid #d3dce6;
background-color: #eaeefb;
text-align: center;
margin-top: -1px;
color: #d3dce6;
cursor: pointer;
&.is-fixed {
position: sticky;
top: 0;
bottom: 20px;
}
>i {
position: absolute;
transform: translateX(-30px);
font-size: 14px;
line-height: 44px;
transition: 0.3s;
display: inline-block;
color: #1f93ff;
}
>span {
position: absolute;
transform: translateX(-30px);
font-size: 14px;
line-height: 44px;
transition: 0.3s;
display: inline-block;
}
&:hover {
// background-color: #f9fafc;
}
& .text-slide-enter,
& .text-slide-leave-active {
opacity: 0;
transform: translateX(10px);
}
.control-button-container {
line-height: 40px;
position: absolute;
top: 0;
right: 0;
padding-left: 5px;
padding-right: 25px;
}
.control-button {
font-size: 14px;
margin: 0 10px;
}
}
}
</style>
<script setup>
import CodeEdit from '@temp/CodeEdit.vue'
</script>
<ClientOnly>
<CodeEdit />
</ClientOnly>
<script setup>
import tempCmp from '@temp/tempCode.vue';
</script>
<ClientOnly>
<tempCmp />
</ClientOnly>
// import { createPage } from '@vuepress/core'
import type MarkdownIt from 'markdown-it'
import { mdPlugin } from './plugins/plugins'
import { MarkdownTransform } from './plugins/markdown-transform'
import { HotUpdate } from './plugins/hot-update'
import fs from 'fs'
import path from 'path'
export default function preview2edit() {
return {
name: 'test1',
multiple: false,
alias: {
'@docs': path.resolve('', 'docs'),
},
extendsMarkdown: async (md: MarkdownIt, app) => {
mdPlugin(md, app)
},
onInitialized: async (app) => {
if (!app.env.isDev) {
return;
}
await Promise.all([
app.writeTemp('CodeEdit.vue', fs.readFileSync(path.resolve(__dirname, './CodeEdit.vue'))),
app.writeTemp('Demo.vue', fs.readFileSync(path.resolve(__dirname, './Demo.vue'))),
app.writeTemp('tempCode.vue', ''),
])
// const editPage = await createPage(app, {
// path: '/gedit.html',
// filePath: path.resolve(__dirname, './gedit.md')
// })
// app.pages.push(editPage)
// const previewPage = await createPage(app, {
// path: '/gpreview.html',
// filePath: path.resolve(__dirname, './gpreview.md')
// })
// app.pages.push(previewPage)
},
extendsBundlerOptions: async (bundlerOptions, app) => {
// 生产模式没有node服务
if (app.env.isDev) {
bundlerOptions.viteOptions.plugins.push(MarkdownTransform(app))
bundlerOptions.viteOptions.plugins.push(HotUpdate(app))
}
}
}
}
\ No newline at end of file
import type { Plugin, ViteDevServer, Connect } from 'vite'
export function HotUpdate(app): Plugin {
return {
name: 'hotUpdate-galaxy2',
configureServer(server: ViteDevServer) {
server.middlewares.use('/updateTemp', async function (req: Connect.IncomingMessage, res, next) {
if (req.method === 'GET' && (req.originalUrl as string).includes('/updateTemp')) {
const codesource = (req.originalUrl as string)?.split('/updateTemp?codesource=')?.[1];
codesource && await updateTempVue(decodeURIComponent(codesource), app);
return next();
}
return next();
})
}
}
}
const galaxyTemplate = `<script setup>
                import {ref} from 'vue';
import test from '@@/test.vue'
                var a = ref('66');
            </script>
            <template>
                <div>{{a}}</div>
                <input type="text" v-model="a">
<p>=====================</p>
<test/>
            </template>
<style>
.navbar {
display: none;
}
.theme-default-content {
max-width: 100% !important;
}
iframe {
width: 100%;
height: 100%;
}
</style>
`
const defaultStyle = `
<style>
.navbar {
display: none;
}
.theme-default-content {
max-width: 100% !important;
}
iframe {
width: 100%;
height: 100%;
}
</style>`
function updateTempVue(content, app) {
return app.writeTemp('tempCode.vue', [content, defaultStyle].join('\n'));
}
import path from 'path'
import glob from 'fast-glob'
import type { Plugin } from 'vite'
type Append = Record<'headers' | 'footers' | 'scriptSetups', string[]>
export function MarkdownTransform(app): Plugin {
return {
name: 'galaxy-md-transform',
enforce: 'pre',
async transform(code, id) {
if (!id.includes('pages/component') || !id.endsWith('.vue')) return
const componentId = path.basename(id, '.html.vue')
const append: Append = {
headers: [],
footers: [],
scriptSetups: [],
}
// code = transformVpScriptSetup(code, append)
const pattern = `examples/${componentId}/*.vue`
const compPaths = await glob(pattern, {
cwd: app.dir.source(),
});
append.scriptSetups = compPaths.map((item) => {
const key = path.basename(item, '.vue');
return `import ${key} from '@docs/${item}';
demos['${key}'] = ${key};
`
});
compPaths.length && append.scriptSetups.unshift(`const demos = {}; import Demo from '@temp/Demo.vue'`)
let newCode = combineMarkdown(
code,
[combineScriptSetup(append.scriptSetups), ...append.headers],
append.footers
)
return newCode
},
}
}
const combineScriptSetup = (codes: string[]) =>
`\n<script setup>
${codes.join('\n')}
</script>
`
const combineMarkdown = (
code: string,
headers: string[],
footers: string[]
) => {
// const frontmatterEnds = code.indexOf('---\n\n') + 4
// const firstSubheader = code.search(/\n## \w/)
// const sliceIndex = firstSubheader < 0 ? frontmatterEnds : firstSubheader
// if (headers.length > 0)
// code =
// code.slice(0, sliceIndex) + headers.join('\n') + code.slice(sliceIndex)
// code += footers.join('\n')
return `${code + headers.join('\n')}\n`
}
const vpScriptSetupRE = /<vp-script\s(.*\s)?setup(\s.*)?>([\s\S]*)<\/vp-script>/
const transformVpScriptSetup = (code: string, append: Append) => {
const matches = code.match(vpScriptSetupRE)
if (matches) code = code.replace(matches[0], '')
const scriptSetup = matches?.[3] ?? ''
if (scriptSetup) append.scriptSetups.push(scriptSetup)
return code
}
const transformComponentMarkdown = (
id: string,
componentId: string,
code: string,
append: Append
) => {
return code
}
import path from 'path'
import fs from 'fs'
import MarkdownIt from 'markdown-it'
import mdContainer from 'markdown-it-container'
import tag from '../plugins/tag'
// import { highlight } from '../utils/highlight'
import type Token from 'markdown-it/lib/token'
import type Renderer from 'markdown-it/lib/renderer'
const localMd = MarkdownIt().use(tag)
interface ContainerOpts {
marker?: string | undefined
validate?(params: string): boolean
render?(
tokens: Token[],
index: number,
options: any,
env: any,
self: Renderer
): string
}
export const mdPlugin = (md: MarkdownIt, app) => {
md.use(tag)
md.use(mdContainer, 'demo', {
validate(params) {
return !!params.trim().match(/^demo\s*(.*)$/)
},
render(tokens, idx) {
const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/)
if (tokens[idx].nesting === 1 /* means the tag is opening */) {
const description = m && m.length > 1 ? m[1] : ''
const sourceFileToken = tokens[idx + 2]
let source = ''
const sourceFile = sourceFileToken.children?.[0].content ?? ''
if (sourceFileToken.type === 'inline') {
source = fs.readFileSync(
path.resolve(app.dir.source(), 'examples', `${sourceFile}.vue`),
'utf-8'
)
}
if (!source) throw new Error(`Incorrect source file: ${sourceFile}`)
return `<Demo :demos="demos" source="${encodeURIComponent(
source
)}" path="${sourceFile.split('/')[1]}" raw-source="${encodeURIComponent(
source
)}" description="${encodeURIComponent(localMd.render(description))}">`
} else {
return '</Demo>'
}
},
} as ContainerOpts)
}
import type MarkdownIt from 'markdown-it'
export default (md: MarkdownIt): void => {
md.renderer.rules.table_open = () => '<div class="vp-table"><table>'
md.renderer.rules.table_close = () => '</table></div>'
}
import type MarkdownIt from 'markdown-it'
export default (md: MarkdownIt): void => {
/**
* To enable the plugin to be parsed in the demo description, the content is rendered as span instead of ElTag.
*/
md.renderer.rules.tag = (tokens, idx) => {
const token = tokens[idx]
const value = token.content
/**
* Add styles for some special tags
* vitepress/styles/content/tag-mark-content.scss
*/
const tagClass = ['beta', 'deprecated', 'a11y'].includes(value) ? value : ''
return `<span class="vp-tag ${tagClass}">${value}</span>`
}
md.inline.ruler.before('emphasis', 'tag', (state, silent) => {
const tagRegExp = /^\^\(([^)]*)\)/
const str = state.src.slice(state.pos, state.posMax)
if (!tagRegExp.test(str)) return false
if (silent) return true
const result = str.match(tagRegExp)
if (!result) return false
const token = state.push('tag', 'tag', 0)
token.content = result[1].trim()
token.level = state.level
state.pos += result[0].length
return true
})
}
import type MarkdownIt from 'markdown-it'
export default (md: MarkdownIt): void => {
md.renderer.rules.tooltip = (tokens, idx) => {
const token = tokens[idx]
return `<api-typing type="${token.content}" details="${token.info}" />`
}
md.inline.ruler.before('emphasis', 'tooltip', (state, silent) => {
const tooltipRegExp = /^\^\[([^\]]*)\](`[^`]*`)?/
const str = state.src.slice(state.pos, state.posMax)
if (!tooltipRegExp.test(str)) return false
if (silent) return true
const result = str.match(tooltipRegExp)
if (!result) return false
const token = state.push('tooltip', 'tooltip', 0)
token.content = result[1].replace(/\\\|/g, '|')
token.info = (result[2] || '').replace(/^`(.*)`$/, '$1')
token.level = state.level
state.pos += result[0].length
return true
})
}
// ref https://github.com/vuejs/vitepress/blob/main/src/node/markdown/plugins/highlight.ts
import chalk from 'chalk'
import escapeHtml from 'escape-html'
import prism from 'prismjs'
import consola from 'consola'
// prism is listed as actual dep so it's ok to require
// eslint-disable-next-line @typescript-eslint/no-var-requires
import loadLanguages from 'prismjs/components/index.js'
// required to make embedded highlighting work...
loadLanguages(['markup', 'css', 'javascript'])
function wrap(code: string, lang: string): string {
if (lang === 'text') {
code = escapeHtml(code)
}
return `<pre v-pre><code>${code}</code></pre>`
}
export const highlight = (str: string, lang: string) => {
if (!lang) {
return wrap(str, 'text')
}
lang = lang.toLowerCase()
const rawLang = lang
if (lang === 'vue' || lang === 'html') {
lang = 'markup'
}
if (lang === 'md') {
lang = 'markdown'
}
if (lang === 'ts') {
lang = 'typescript'
}
if (lang === 'py') {
lang = 'python'
}
if (!prism.languages[lang]) {
try {
loadLanguages([lang])
} catch {
// eslint-disable-next-line no-console
consola.warn(
chalk.yellow(
`[vitepress] Syntax highlight for language "${lang}" is not supported.`
)
)
}
}
if (prism.languages[lang]) {
const code = prism.highlight(str, prism.languages[lang], lang)
return wrap(code, rawLang)
}
return wrap(str, 'text')
}
export const define = <T>(value: T): T => value
{
"compilerOptions": {
"baseUrl": ".",
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": [
"node"
],
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "server-dev.js"]
}
\ No newline at end of file
import type { ConfigEnv } from 'vite'
import path from 'path';
import { defineConfig, loadEnv } from 'vite'
export default ({ mode, command }: ConfigEnv) => {
const env = loadEnv(mode, process.cwd())
return defineConfig({
build: {
// target: 'node16',
assetsDir: './',
lib: {
entry: path.resolve(__dirname, './src/index.ts'),
formats: ['es', 'cjs'],
// fileName: 'index'
},
// sourcemap: true,
// rollupOptions: {
// input: path.resolve(__dirname, './src/index.ts'),
// // format: 'es',
// output: {
// // dir: 'a',
// // format: 'es'
// },
// // bundle: {
// // path: `a/es`,
// // },
// }
},
})
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment