杂项
杂项
资源大纲问题
大佬们,问个问题:
1. 我的需求
要求同时具有文件资源管理器列和文章大纲列的(通过插件实现的也行),同时要求可以支持嵌套结构比较复杂以及md笔记数比较多的情况。
说人话就是这种效果:
5Y0NQ3HX69~OD.png)
2. 相关插件
关于vuepress资源文件夹和大纲,我只搜出来下面几个相关插件:
- https://github.com/ekoeryanto/vuepress-plugin-table-of-contents
(失效了,这个链接gpt给我的) - https://github.com/vuepress/vuepress-plugin-table-of-contents
这个好像用不了? - https://github.com/shanyuhai123/vuepress-plugin-auto-sidebar
这个我感觉很怪。
一来有bug 结构会莫名其妙乱掉
二来我接受不了文件和大纲共用一个栏,我要分开,而auto-sidebar的是共用的 - https://github.com/xuekai-china/vuepress-plugin-right-anchor
这个应该是能用的
3. 其他补充
vuepress-theme-hope 官网的其实比较接近我想要的(可能还需要自己微调,比如说我要把点开另一个位置时原来的位置自动折叠回去这一特性给咔掉)
官网页面好像不开源,所以我来问一下:这个左栏是自动生成的还是手写的,用的什么解决方案?
开头说了,一千多个.md,且嵌套结构复杂。不适合
如果实在没法,感觉哪怕使用一个批处理工具生成个txt出来,再手动复制进去都更方便(就是每次更新都要重复一次这个动作比较难受)
文档页脚本问题
需求
如果我想要绑定一段js代码,在新的文档页加载出来后执行,可以放在哪?
有对应的钩子吗?
举个具体的例子:
例如检测当前dom元素中有.classA的元素,如果高度>600px则将其折叠
每次页面加载后自动执行这么一段js代码
head属性
https://theme-hope.vuejs.press/zh/guide/customize/external.html
其中可以在配置文件中设置head属性,导入外部脚本。但不知道对应的监听事件,没办法解决
export default defineUserConfig({
// ...
head: [
// ...
// 导入一个外部脚本
["script", { src: "YOUR_SCRIPT_LINK" }],
// 添加一段脚本
[
"script",
{},
`\
// your script here
`,
],
// 添加一个外部 CSS
["link", { rel: "stylesheet", href: "YOUR_STYLE_LINK" }],
// 添加一段样式
// 我们不建议这么做,你应该首选使用 .vuepress/style/index.scss
[
"style",
{},
`\
/* your style here */
`,
],
],
// ...
});
插件API Hook
https://v2.vuepress.vuejs.org/zh/reference/plugin-api.html
然后尝试了下看插件API有没有对应的Hook,但没找到我想要的 “文档被客户端加载” 的事件
只有什么:
- onInitialized: 在 VuePress App 初始化后被立即调用
- onPrepared: 在 VuePress App 完成文件准备后被立即调用
- onWatched: 在 VuePress App 启动开发服务器并开始监听文件修改后被调用
- onGenerated: 在 VuePress App 完成静态文件生成后被立即调用
参考复制button的做法
效果
最后Mr.Hope大佬建议我看复制按钮的实现:
- 参考代码页面:https://github.com/vuepress/ecosystem/blob/b7ab87b954d40036b3ceae05f645191a4dc571d1/plugins/features/plugin-copy-code/src/client/composables/useCopyCode.ts
- 调试页面:https://theme-hope.vuejs.press/zh/guide/customize/external.html
首先通过调试页面可以发现,button元素确实是后来才被加载进去的。浏览器调试工具>网络 中发现 https://theme-hope.vuejs.press/zh/guide/customize/external.html 的dom结构本身并不包含该元素。能实现这个效果也就能实现我想要的效果
实现
既然知道了这种方法可以实现,那么我们再来看他具体是怎么实现的
ecosystem/plugins/features/plugin-copy-code/src/client/composables/useCopyCode.ts
import { useLocaleConfig, wait } from '@vuepress/helper/client'
import { useClipboard, useEventListener, useMediaQuery } from '@vueuse/core'
import { computed, nextTick, watch } from 'vue'
import { usePageData } from 'vuepress/client'
import type { CopyCodePluginLocaleConfig } from '../../shared/index.js'
import '../styles/copy-code.css'
import '../styles/vars.css'
/**
* 配置项
*/
export interface UseCopyCodeOptions {
locales: CopyCodePluginLocaleConfig
: string[]
/** @default 500 */
delay: number
/** @default 2000 */
duration: number
/** @default false */
showInMobile?: boolean
/** @default [] */
ignoreSelector?: string[]
/**
* 在复制前变换预元素
*
* 例如,在复制之前删除某些元素,或插入版权信息
*
* @param preElement `<pre>` clone Node
*
* @example
* ```js
* {
* transform(pre) {
* // Remove all `.ignore` elements
* pre.querySelectorAll('.ignore').remove()
* // insert copyright
* pre.innerHTML += `\n Copied by VuePress`
* }
* }
* ```
*/
transform?: (preElement: HTMLElement) => void
}
const SHELL_RE = /language-(shellscript|shell|bash|sh|zsh)/
/**
* 被./index.ts调用被export
*
* 调用周期:猜测只会在全局被调用一次?
*/
export const useCopyCode = ({
delay = 500,
duration = 2000,
locales,
selector,
showInMobile,
ignoreSelector = [],
transform,
}: UseCopyCodeOptions): void => {
if (__VUEPRESS_SSR__) return
/**
* 一些参数准备
* 在小屏幕设备上,默认情况下不显示复制按钮,以防止它阻碍内容,因为“:hover”效果可以由“touch”事件触发
*/
const is419 = useMediaQuery('(max-width: 419px)')
const enabled = computed(() => !is419.value || showInMobile)
const locale = useLocaleConfig(locales)
const page = usePageData()
/*
* (下一个函数的子函数,被下一个函数递归调用)
*
* 局部 - 插入复制按钮
*
* @param codeBlockElement 对应的代码块
*/
const insertCopyButton = (codeBlockElement: HTMLElement): void => {
if (codeBlockElement.hasAttribute('copy-code')) return
const copyElement = document.createElement('button')
copyElement.type = 'button'
copyElement.classList.add('vp-copy-code-button')
copyElement.setAttribute('aria-label', locale.value.copy)
copyElement.setAttribute('data-copied', locale.value.copied)
codeBlockElement.parentElement?.insertBefore(copyElement, codeBlockElement)
codeBlockElement.setAttribute('copy-code', '')
}
/**
* 全局 - 追加复制按钮
*
* 调用频率:每个页面加载出来后被调用一次
*/
const appendCopyButton = async (): Promise<void> => {
document.body.classList.toggle('no-copy-code', !enabled.value)
if (!enabled.value) return
await nextTick()
await wait(delay)
document
.querySelectorAll<HTMLElement>(selector.join(','))
.forEach(insertCopyButton)
}
/**
* 【重点】
* 事件触发的开始位置 ---------------------------------------------------------
*
* 注意这里的watch需要有vue组件的支持
*
* 每个页面加载出来后会自动调用一次。原理:监听 page.value.path 和 enabled.value,两个值中任意一个发生变化时触发
*/
watch(() => [page.value.path, enabled.value], appendCopyButton, {
immediate: true,
})
const { copy } = useClipboard({ legacy: true })
const timeoutIdMap = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>()
// (下一个函数的子函数)
const copyContent = async (
codeContainer: HTMLDivElement,
codeContent: HTMLPreElement,
button: HTMLButtonElement,
): Promise<void> => {
const clone = codeContent.cloneNode(true) as HTMLPreElement
if (ignoreSelector.length) {
clone.querySelectorAll(ignoreSelector.join(',')).forEach((node) => {
node.remove()
})
}
if (transform) transform(clone)
let text = clone.textContent || ''
if (SHELL_RE.test(codeContainer.className))
text = text.replace(/^ *(\$|>) /gm, '')
await copy(text)
if (duration <= 0) return
button.classList.add('copied')
clearTimeout(timeoutIdMap.get(button))
const timeoutId = setTimeout(() => {
button.classList.remove('copied')
button.blur()
timeoutIdMap.delete(button)
}, duration)
timeoutIdMap.set(button, timeoutId)
}
/* 事件监听:按钮点击事件 (靠class识别是否按钮) */
useEventListener('click', (event) => {
const el = event.target as HTMLElement
if (
enabled.value &&
el.matches('div[class*="language-"] > button.vp-copy-code-button')
) {
const codeContainer = el.parentElement as HTMLDivElement | null
const preBlock = el.nextElementSibling as HTMLPreElement | null
if (!codeContainer || !preBlock) return
void copyContent(codeContainer, preBlock, el as HTMLButtonElement)
}
})
}