chaihongjun.me

docsify简单配置应用

docsify是一个很神奇的文档生成工具,利用markdown文档动态生成文档网站。与 GitBook 不同,它不会生成静态的 HTML 文件。相反,它会智能地加载并解析Markdown 文件,并将它们展示为一个网站。这里简要介绍一下一个实例,在官方配置的基础上增加一个手动切换主题的功能,官方文档(https://docsify.js.org/#/zh-cn/)。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>第三方API接口收集文档</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="description" content="第三方API接口收集文档" />
    <!-- 禁止搜索引擎索引 -->
    <meta name="robots" content="noindex, nofollow" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, minimum-scale=1.0"
    />
    <!-- 默认主题 -->
    <link
      id="theme-style"
      rel="stylesheet"
      href="https://cdn.staticfile.net/docsify/4.13.1/themes/vue.min.css"
    />
    <style>
      /* 自定义样式:确保主题切换按钮与 navbar 在同一行 */
      .app-nav {
        display: flex;
        align-items: center;
        justify-content: space-between;
      }
      .theme-switcher {
        cursor: pointer;
        font-size: 16px;
        text-decoration: none;
        border-radius: 4px;
        transition: all 0.3s ease;
        margin: 0 1rem;
      }
      .theme-switcher:hover {
        opacity: 0.8; /* 悬停时透明度变化 */
      }
    </style>
  </head>
  <body>
    <div id="app"></div>
    <script>
      // 动态生成时间戳
      const timestamp = new Date().getTime();
      const themeLink = document.getElementById("theme-style");

      // 加载用户上次选择的主题
      let currentTheme = localStorage.getItem("docsify-theme") || "vue"; // 默认主题为 vue
      themeLink.href = `https://cdn.staticfile.net/docsify/4.13.1/themes/${currentTheme}.min.css?timestamp=${timestamp}`;

      // Docsify 配置
      window.$docsify = {
        name: "第三方API接口收集文档", // 文档站点名称
        loadSidebar: true, // 启用侧边栏
        loadNavbar: true, // 启用导航栏
        autoHeader: true, // 自动生成标题
        subMaxLevel: 6, // 自动生成标题链接的最大层级
        search: "auto", // 启用搜索功能
        auto2top: true, // 启用自动滚动到顶部
        mergeNavbar: true, // 合并导航栏
        externalLinkTarget: "_blank", // 外部链接打开方式
        search: {
          maxAge: 86400000, // 缓存时间(毫秒)
          paths: "auto", // 搜索范围
          placeholder: "Type to Search", // 搜索框占位符
          noData: "No Results!",
        },
        // 启用 lastUpdated 插件
        lastUpdated: true, // 显示文档更新时间
        formatUpdated: "{YYYY}-{MM}-{DD} {HH}:{mm}:{ss}", // 时间格式化
        // 自定义插件:添加主题切换功能
        plugins: [
          function (hook, vm) {
            hook.init(function () {
              // 监听路由变化
              // 使用原生路由监听方式
              window.addEventListener("hashchange", function () {
                setTimeout(addThemeSwitcher, 100);
              });
            });
            hook.doneEach(function () {
              // 确保主题切换按钮只添加一次
              addThemeSwitcher();
            });

            hook.mounted(function () {
              // 页面首次加载时添加主题切换按钮
              addThemeSwitcher();
            });
          },
        ],
      };

      // 添加主题切换按钮
      function addThemeSwitcher() {
        const checkNavbar = () => {
          const navbar = document.querySelector(".app-nav");
          if (navbar) {
            const existingSwitcher = navbar.querySelector(".theme-switcher");
            if (!existingSwitcher) {
              const switcher = document.createElement("div");
              switcher.className = "theme-switcher";
              switcher.textContent = "切换主题";
              switcher.onclick = toggleTheme;
              navbar.appendChild(switcher);
              updateThemeSwitcherColor(); // 同步颜色
            }
          } else {
            // 如果导航栏未加载,延迟重试
            setTimeout(checkNavbar, 50);
            console.log("导航栏未加载,导航容器未生成,请检查配置");
          }
        };
        checkNavbar();
      }

      // 主题切换逻辑
      const themes = ["vue", "dark"]; // 可选主题列表

      function toggleTheme() {
        const nextTheme =
          themes[(themes.indexOf(currentTheme) + 1) % themes.length];
        currentTheme = nextTheme;

        // 更新主题样式
        themeLink.href = `https://cdn.staticfile.net/docsify/4.13.1/themes/${nextTheme}.min.css?timestamp=${timestamp}`;

        // 保存用户选择的主题
        localStorage.setItem("docsify-theme", nextTheme);

        // 更新按钮文字颜色
        updateThemeSwitcherColor();
      }

      // 更新按钮文字颜色以匹配当前主题
      function updateThemeSwitcherColor() {
        const switcher = document.querySelector(".theme-switcher");
        if (switcher) {
          // 根据当前主题设置文字颜色
          switch (currentTheme) {
            case "vue":
              switcher.style.color = "#34495e"; // 默认主题文字颜色
              break;
            case "dark":
              switcher.style.color = "#c8c8c8"; // 深色主题文字颜色
              break;
            default:
              switcher.style.color = "#34495e"; // 默认回退颜色
          }
        }
      }

      // 页面加载完成后初始化按钮颜色
      document.addEventListener("DOMContentLoaded", function () {
        updateThemeSwitcherColor();
      });
    </script>
    <!-- Docsify v4 -->
    <script src="https://cdn.staticfile.net/docsify/4.13.1/docsify.min.js"></script>
    <!-- 引入 Docsify 搜索插件 -->
    <script src="https://cdn.staticfile.net/docsify/4.13.1/plugins/search.min.js"></script>
    <!-- 引入 Docsify 复制到剪切板插件 -->
        <script src="https://cdn.staticfile.net/docsify-copy-code/3.0.0/docsify-copy-code.min.js"></script>
    <!-- 引入时间更新插件 -->
    <script src="./assets/time-updater.min.js"></script>
  </body>
</html>
<script>
  if (typeof navigator.serviceWorker !== "undefined") {
    navigator.serviceWorker.register("sw.js");
    console.log("register serviceWorker successed!");
  }
</script>

再在文档根目录创建sw.js文件,增强离线功能:

/* ===========================================================
 * docsify sw.js
 * ===========================================================
 * Copyright 2016 @huxpro
 * Licensed under Apache 2.0
 * Register service worker.
 * ========================================================== */

const RUNTIME = 'docsify'
const HOSTNAME_WHITELIST = [
  self.location.hostname,
  'fonts.gstatic.com',
  'fonts.googleapis.com',
  'cdn.jsdelivr.net',
  'cdn.staticfile.net'
]

// The Util Function to hack URLs of intercepted requests
const getFixedUrl = (req) => {
  var now = Date.now()
  var url = new URL(req.url)

  // 1. fixed http URL
  // Just keep syncing with location.protocol
  // fetch(httpURL) belongs to active mixed content.
  // And fetch(httpRequest) is not supported yet.
  url.protocol = self.location.protocol

  // 2. add query for caching-busting.
  // Github Pages served with Cache-Control: max-age=600
  // max-age on mutable content is error-prone, with SW life of bugs can even extend.
  // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
  // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
  if (url.hostname === self.location.hostname) {
    url.search += (url.search ? '&' : '?') + 'cache-bust=' + now
  }
  return url.href
}

/**
 *  @Lifecycle Activate
 *  New one activated when old isnt being used.
 *
 *  waitUntil(): activating ====> activated
 */
self.addEventListener('activate', event => {
  event.waitUntil(self.clients.claim())
})

/**
 *  @Functional Fetch
 *  All network requests are being intercepted here.
 *
 *  void respondWith(Promise<Response> r)
 */
self.addEventListener('fetch', event => {
  // Skip some of cross-origin requests, like those for Google Analytics.
  if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
    // Stale-while-revalidate
    // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale
    // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1
    const cached = caches.match(event.request)
    const fixedUrl = getFixedUrl(event.request)
    const fetched = fetch(fixedUrl, { cache: 'no-store' })
    const fetchedCopy = fetched.then(resp => resp.clone())

    // Call respondWith() with whatever we get first.
    // If the fetch fails (e.g disconnected), wait for the cache.
    // If there’s nothing in cache, wait for the fetch.
    // If neither yields a response, return offline pages.
    event.respondWith(
      Promise.race([fetched.catch(_ => cached), cached])
        .then(resp => resp || fetched)
        .catch(_ => { /* eat any errors */ })
    )

    // Update the cache with the version we fetched (only for ok status)
    event.waitUntil(
      Promise.all([fetchedCopy, caches.open(RUNTIME)])
        .then(([response, cache]) => response.ok && cache.put(event.request, response))
        .catch(_ => { /* eat any errors */ })
    )
  }
})


知识共享许可协议本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。作者:柴宏俊»