package.json

{
  
    "name": "app",
  
    "version": "1.0.0",
  
    "description": "",
  
    "main": "app.js",
  
    "scripts": {
  
        "test": "echo \"Error: no test specified\" && exit 1"
  
    },
  
    "author": "w5han",
  
    "license": "ISC",
  
    "dependencies": {
  
        "highlight.js": "^11.8.0",
  
        "markdown-it": "^13.0.1"
  
    }
  
}
  

index.html

<!DOCTYPE html>
  
<html lang="zh-CN">
  
<head>
  
    <meta charset="UTF-8">
  
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
    <link rel="icon" href="https://xn--7ct.top/favicon.ico" type="image/x-icon">
  
    <link rel="shortcut icon" href="https://xn--7ct.top/favicon.ico" type="image/x-icon">
  
    <style>
  
        #search {
  
            padding: 10px;
  
            font-size: 18px;
  
        }
  
        body > h3 {
  
            text-align: center;
  
            font-weight: normal;
  
        }
  
        body > p {
  
            text-align: center;
  
        }
  
        body > footer {
  
            width: fit-content;
  
            margin: 0 auto;
  
        }
  

        body {
  
            margin: 0;
  
            padding: 0;
  
            min-height: 100vh;
  
            background: linear-gradient(to bottom, #e0e8da, #8187a1);
  
        }
  
    </style>
  
    <title>砸砸灰</title>
  
</head>
  
<body>
  
    <div style="margin-top: 0px; height: 1px;"></div>
  
    <p>
  
        <input id="search" type="text" placeholder="自用搜索框">
  
    </p>
  
    <h3>若遇加载问题, 多刷新几次</h3>
  
    <p><a href="/html/黑神话悟空影神图/ystHeiShenHua.html">黑神话悟空-影神图</a></p>
  
    <p><a href="/html/俄罗斯方块/game.html">俄罗斯方块</a></p>
  
    <p><a href="/html/轮播图/lunbotu2.html">bilibili轮播图还原</a></p>
  
    <p><a href="/html/抽拉效果/抽拉效果.html">抽拉效果</a></p>
  
    <p><a href="/html/显卡天梯图/显卡天梯图react.html">显卡天梯图</a></p>
  
    <p><a href="/html/立方体/立方体.html">css立方体</a></p>
  
    <p><a href="/html/webgl/webgl07.html">webgl绘制多条闪烁的折线</a></p>
  
    <p><a href="/html/webgl/webgl22.html">webgl跳动的盒子</a></p>
  
    <p><a href="/html/webgl/webgl23.html">webgl一池春水</a></p>
  
    <p><a href="/html/webgl/webgl30.html">webgl多点异色</a></p>
  
    <p><a href="/html/webgl/webgl32.html">webgl一片春色</a></p>
  

    <p><a href="/html/todo/todo.html">我的TODO(练习键盘事件和拖拽事件)</a></p>
  
    <p><a href="/markdown/笔记0.md">建站笔记</a></p>
  
    <p><a href="/html/代码结构.html">代码高亮</a></p>
  
    <p><a href="/html/兰亭序.html">兰亭序(打字特效)</a></p>
  
    
    <p><a href="https://developer.mozilla.org/zh-CN/docs/Web">MDN</a></p>
  
    <p><a href="https://csdiy.wiki/">CS自学指南</a></p>
  
    <p><a href="https://browser.engineering/">浏览器</a></p>
  
    
    <p>欢迎交流</p>
  
    <p>QQ: 30435566</p>
  

    <footer><a href="https://beian.miit.gov.cn/" target="_blank">湘ICP备2024094932号-1</a></footer>
  
</body>
  
</html>
  
<script>
  
    const search = document.querySelector('#search')
  
    search.addEventListener("keyup", function(e){
  
        if (e.key === 'Enter') {
  
            // console.log(e.srcElement.value, '-----e.srcElement.value')
  
            const searchKey = e.srcElement.value
  
            window.location.href = "https://www.bing.com/search?q=" + searchKey
  
        }
  
    })
  
</script>

代码结构.html

<!DOCTYPE html>
  
<html lang="zh">
  

<head>
  
    <meta charset="UTF-8">
  
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
    <style></style>
  
    <title>代码结构</title>
  
</head>
  

<body>
  
    <br>
  
    <img src="../image/代码结构截图.png">
  
    [package.json]
  
    [index.html]
  
    [代码结构.html]
  
    [copyEvent.js]
  
    [app.js]
  
    [utils.js]
  
    [代码结构.js]
  


</body>
  
</html>
  
<script src="./copyEvent.js"></script>

copyEvent.js

const btns = document.querySelectorAll(".copy-btn")
  
for (const btn of btns) {
  
  btn.onclick = function (e) {
  
    // console.log(e.target.dataset.file, '-----e.target.dataset.file')
  
    let key = e.target.dataset.key
  
    key = '.' + key  // 当前路径位于根路径的下一级, 加个点去根目录
  
    let option
  
    if (/.html$/.test(key)) option = {headers : { "Content-Type": "text/plain", }}
  
    fetch(key, option)
  
      .then(response => response.text())
  
      .then((data) => copyText(data))
  
  }
  
}
  

function copyText(str) {
  
  // console.log(str, '-----str')
  
  // 创建一个文本输入框
  
  const textArea = document.createElement("textarea");
  
  textArea.value = str;
  

  // 将文本输入框添加到DOM中
  
  document.body.appendChild(textArea);
  
  // 选择文本
  
  textArea.select();
  
  // 尝试复制文本到剪切板
  
  document.execCommand('copy');
  
  // 从DOM中移除文本输入框
  
  document.body.removeChild(textArea);
  
}
  

app.js

const { 代码结构 } = require('./server/htmljs/代码结构.js')
  
const { MarkDown, fileRead, returnHtml, returnJavaScript, returnCss, returnText, returnImage } = require('./server/utils.js')
  

const http = require('http');
  
const fs = require('fs');
  

const hostname = "127.0.0.1";
  
const port = 3000;
  

const markDown = new MarkDown()
  

const server = http.createServer((req, res) => {
  
    // 修复路由中文字符的问题
  
    req.url = decodeURIComponent(req.url)
  

    if (req.url === '/text') {
  
        returnHtml(res, fileRead('./输入效果.html'))
  

    } else if (/.md$/.test(req.url)) {
  
        let str = fileRead('.' + req.url)
  
        str = markDown.get(str)
  
        returnHtml(res, str)
  

    } else if (/.html$/.test(req.url)) {
  
        // ↓↓↓↓↓↓↓↓↓ 代码结构.html
  
        if (req.headers["content-type"] == 'text/plain') {
  
            // 复制功能, 读取html文件的文本内容, 不按html解析
  
            returnText(res, fileRead('.' + req.url))
  
            return
  
        }
  

        if (req.url === '/html/代码结构.html') {
  
            let str = fileRead('.' + req.url)
  
            str = 代码结构(markDown, str)
  
            returnHtml(res, str)
  
            return
  
        }
  
        // ↑↑↑↑↑↑↑↑↑ 代码结构.html
  

        // 未特殊处理得一般html
  
        returnHtml(res, fileRead('.' + req.url))
  

    } else if (/\.(js|mjs)$/.test(req.url)) {
  
        returnJavaScript(res, fileRead('.' + req.url))
  

    } else if (/\.(css)$/.test(req.url)) {
  
        returnCss(res, fileRead('.' + req.url))
  

    } else if (/\.(css|json)$/.test(req.url)) {
  
        returnText(res, fileRead('.' + req.url))
  

    } else if (req.method === 'GET' && req.url === '/favicon.ico') {
  
        res.setHeader('Content-Type', 'image/x-icon');
  
        fs.createReadStream('./favicon.ico').pipe(res);
  
    } else if (/\.(jpg|png|ico|svg|gif|webp|bmp|tiff)$/.test(req.url)) {
  
        // 读取图片文件
  
        console.log('+++++', req.url)
  
        returnImage(res, fs.readFileSync(`./${req.url}`), req.url)
  
        
    } else if (req.url === '/') {
  
        returnHtml(res, fileRead('./index.html'))
  
        
    } else {
  
        returnText(res, `页面没找到 ${req.url}`)
  
    }
  

});
  

server.listen(port, hostname, () => {
  
    console.log(`Server running at http://${hostname}:${port}/`);
  
});
  

utils.js

const fs = require('fs')
  
const { EOL } = require('os')
  

class MarkDown {
  
  constructor() {
  
    const hljs = this._initHljs()
  
    this.md = this._getMarkDown(hljs)
  
    this.hljsCss = this._getHljsCss()
  
  }
  

  get(str) {
  
    // 修复markdown-it插件问题, 换行没生效
  
    // 使用markdown语法 +两个尾空格表示换行
  
    const reg = new RegExp('(\\S'+EOL+')', 'g')
  
    str = str.replace(reg, '$1  '+EOL)  
    
    const {hljsCss, md} = this
  
    return `${hljsCss}<div class="markdown">${md.render(str)}</div>`
  
  }
  

  _initHljs() {
  
    const hljs = require('highlight.js'); // https://highlightjs.org/
  
    hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript'));
  
    return hljs
  
  }
  

  _getMarkDown(hljs) {
  
    return require('markdown-it')({
  
      html: true,  // markdown中启用html标签
  
      highlight: function (str, lang) {
  
        if (lang && hljs.getLanguage(lang)) {
  
          return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value;
  
        }
  
        return ''; // 使用额外的默认转义
  
      }
  
    })
  
  }
  

  _getHljsCss() {
  
    let hljsCss = fs.readFileSync('node_modules/highlight.js/styles/github.css')
  
    hljsCss = `<head><style>
  
      ${hljsCss.toString()} 
      body { text-align: center; background: #f7f8fc; margin: 0; }
  
      pre { text-align: start; width:fit-content; margin: auto; }
  
      .markdown { 
          width: 70vw; margin:auto; padding: 8px;
  
          background: #ffffff;
  
          position: relative;
  
          overflow: hidden;
  
          margin-bottom: 10px;
  
      }
  
      .markdown .absolute-a {
  
          position: absolute;
  
          top: 0;
  
          right: 0;
  
      }
  
    </style></head>`
  
    return hljsCss
  
  }
  

}
  

function fileRead(filePath, callback) {
  
  try {
  
    return fs.readFileSync(filePath).toString()
  
  } catch (error) {
  
    throw error
  
  }
  
}
  

function returnHtml(res, str) {
  
  res.statusCode = 200
  
  res.setHeader('Content-Type', 'text/html; charset=utf-8')
  
  res.end(str)
  
}
  

function returnJavaScript(res, str) {
  
  res.statusCode = 200
  
  res.setHeader('Content-Type', 'application/javascript; charset=utf-8')
  
  res.end(str)
  
}
  

function returnCss(res, str) {
  
  res.statusCode = 200
  
  res.setHeader('Content-Type', 'text/css; charset=utf-8')
  
  res.end(str)
  
}
  

function returnText(res, str) {
  
  res.statusCode = 200
  
  res.setHeader('Content-Type', 'text/plain; charset=utf-8')
  
  res.end(str)
  
}
  

function returnImage(res, imageContent, url) {
  
  const extension = url.split('.').pop();  // 取出图片后缀(jpg|png|ico)
  
  res.statusCode = 200
  
  res.writeHead(200, { 'Content-Type': `image/${extension}` });
  
  res.end(imageContent)
  
}
  

module.exports = {
  
  MarkDown,
  
  fileRead,
  
  returnHtml,
  
  returnJavaScript,
  
  returnCss,
  
  returnText,
  
  returnImage,
  
} 

代码结构.js

const fs = require('fs')
  

function 代码结构(markDown, content) {
  
  const { hljsCss, get: _mdget } = markDown
  
  const mdget = _mdget.bind(markDown)
  

  content = content.replace(
  
    '<style></style>',
  
    hljsCss,
  
  )
  

  const dict = [
  
    ['[package.json]', './package.json'],
  
    ['[index.html]', './index.html'],
  
    ['[代码结构.html]', './html/代码结构.html'],
  
    ['[copyEvent.js]', './html/copyEvent.js'],
  
    ['[app.js]', './app.js'],
  
    ['[utils.js]', './server/utils.js'],
  
    ['[代码结构.js]', './server/htmljs/代码结构.js'],
  
  ]
  

  for (const item of dict) {
  
    const [tag, filePath] = item
  
    // tag == '[package.json]'
  
    // filePath == './package.json'
  
    const fileName = tag.slice(1, -1)  // 去掉中括号 package.json
  
    const newTag = `{${fileName}}`
  
    const lang = filePath.split('.').slice(-1)[0]  // lang == json | js | 等等
  
    const fileContent = fs.readFileSync(filePath).toString()
  

    let str = "```" + lang + "\r\n" + fileContent + "\r\n ```"
  
    str = mdget(str)
  

    str = `
  
        <p>${fileName}</p>
  
        <div class="markdown">
  
            ${str}
  
            <button class="absolute-a copy-btn" data-key="${filePath}">复制</button>
  
        </div>
  
    `
  

    // ** 由于str中包含tag, 为了避免转换str中的tag, 将原始的tag转换为新标记newTag, 转换完所有的tag再替换为str
  
    content = content.replace(tag, newTag)
  
    item.push(str)
  

  }
  

  for (const item of dict) {
  
    const [tag, filePath, str] = item
  
    const fileName = tag.slice(1, -1)  // 去掉中括号 package.json
  
    const newTag = `{${fileName}}`
  
    // ** 将临时标记newTag替换为str
  
    content = content.replace(newTag, str)
  
  }
  

  return content
  

}
  

module.exports = {
  
  代码结构
  
}