面试刷题平台 前端开发 md扩展
修复代码高亮丢失
上期扩展,我将题目详情增加了 select 选择框,但是有一个问题,我们每次切换主题时候,代码的高亮会丢失效果:
代码是引入高亮的:
▼tsx复制代码import highlight from "@bytemd/plugin-highlight"; const plugins = [gfm(), highlight()];
查看页面请求回来的数据:
从这里可以看出,在渲染的时候就没有将代码渲染成高亮的格式,我们用的是服务端渲染,代码高亮的插件是支持 SSR 的所以我们重新安装一个代码高亮的插件:
▼tsx复制代码npm i @bytemd/plugin-highlight-ssr
然后将 MdViewer 的代码高亮度插件替换一下:
▼tsx复制代码import highlight from "@bytemd/plugin-highlight-ssr";
然后再次刷新页面 查看回来的代码:
这次渲染的代码就有高亮的类名了!
通用图片上传功能
官方文档中对于 Editor 是有图片上传功能的地址:https://bytemd.netlify.app/#options
直接在 MdEditor 中增加属性:
▼tsx复制代码uploadImages={async (files: any) => { let imgUrl = ""; const res = await uploadFile( { biz: "user_upload", }, {}, files[0], ); // @ts-ignore if (res.data.code === 0) { // @ts-ignore imgUrl = res.data.data; // 这里是上传成功后,服务端返回的图片地址 } else { console.log("图片上传失败"); } console.log(res); return [ { title: files.map((i: any) => i.name), url: imgUrl, }, ]; }}
这里biz 对应后端的上传类型,自行设置下就好
图片预览
官方文档中的插件中有图片预览功能,这里我也集成下:
- 安装依赖
▼tsx复制代码npm i @bytemd/plugin-medium-zoom
- 引入依赖
▼tsx复制代码import mediumZoom from '@bytemd/plugin-medium-zoom'; const plugins = [ gfm(), highlight(), theme({ themeList, }), mediumZoom(), ];
这里有个小问题,就是我们全屏编辑器后,点击预览图片,图片没有在最上层,我们关闭编辑器后,预览的图片才显示,这里修改下样式文件:
▼tsx复制代码.medium-zoom-image { z-index: 9999999 !important; }
代码块复制功能
浏览面试鸭官网,发现展示代码块时,有一个收缩功能和一个复制功能,这里我也想实现下,于是我发现官网是有一个自定义插件的!(之前没找到.....)效果:
这道题不是仅 VIP 可见,我就粘到这里了... 应该没啥事吧,
在我查询实现的过程中,发现面试鸭和掘金的这个复制功能看着还挺像的
于是,我跟着官网还有搜索到的文档实现了一个 code() 插件
官网:https://bytemd.netlify.app/#authoring-a-plugin
实现
这里实现需要一些学过一些 js 基础在 src 目录下新建bytemdPlugin目录,然后新建一个ByteCode目录,再新建 index.tsx 和 index.css
调用复制代码的方法:
▼tsx复制代码// 拷贝代码 const copyToClipboard = (text: string) => navigator.clipboard.writeText(text);
增加头部布局,左侧是点击缩小,右侧展示语言和复制代码的功能
▼tsx复制代码// innerHTML 布局 const codeText: any = ( innerHTML: HTMLElement, key: number, backgroundColor: string, lan: string, ) => { return ( <div className={"code-block-extension-header code-header-" + key} style={{ backgroundColor }} > <div className="code-block-extension-headerLeft"> <div className={"code-block-extension-foldBtn foldBtn-" + key}> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M16.924 9.617A1 1 0 0 0 16 9H8a1 1 0 0 0-.707 1.707l4 4a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0 .217-1.09z" data-name="Down" ></path> </svg> </div> </div> <div className="code-block-extension-headerRight"> <span className="code-block-extension-lang">{lan}</span> <span className={ "code-block-extension-lang code-block-extension-copy code-block-extension-copy-" + key } > 复制代码 </span> </div> </div> ); };
具体渲染逻辑:
▼tsx复制代码// 渲染页面元素 export const renderEle = function (code: HTMLElement, key: number) { let className: string = code.className; if (!className ?? "") { return; } // 使用正则表达式匹配以 "language-" 开头的类名 const match = className.match(/language-(.+)/); // 如果匹配成功,提取语言部分并转换为大写 let lan = match ? match[1].toUpperCase() : ""; const parentNode: any = code.parentNode; // 获取计算后的样式 const style = window.getComputedStyle(code); // 获取背景色 const backgroundColor = style.backgroundColor; parentNode.removeChild(code); parentNode.innerHTML = ReactDOMServer.renderToString(codeText(code, key, backgroundColor, lan)) + '<code id="code-' + key + '" class="' + code.className + '">' + code.innerHTML + "</code>"; let btn: any = document.getElementsByClassName( "code-block-extension-copy-" + key, )[0]; let timeoutID: any = null; // 用于存储 setTimeout 的 ID const restoreText = () => { btn.innerHTML = "复制代码"; btn.disable = false; }; const timeout = () => { timeoutID = setTimeout(restoreText, 3000); // 设置定时器,三秒后执行 restoreText 函数 }; btn.addEventListener("click", () => { if (btn.disable) { return; } clearTimeout(timeoutID); // 清除之前的定时器 copyToClipboard(code.innerText); // 执行复制操作 btn.innerHTML = "Copied"; // 立即更改按钮文本 btn.disable = true; message.success({ content: "复制成功", }); timeout(); // 设置新的定时器,三秒后恢复按钮文本 }); // 旋转隐藏code let foldBtn: any = document.getElementsByClassName("foldBtn-" + key)[0]; // 最上层的父类的盒子阴影也去掉 let codeHeader: any = document.getElementsByClassName( "code-header-" + key, )[0]; foldBtn.addEventListener("click", () => { let codeEle = document.getElementById("code-" + key); codeEle?.classList.toggle("hidden"); foldBtn.classList.toggle("rotate90"); codeHeader.classList.toggle("boxShadowNone"); }); };
完整代码:
▼tsx复制代码import type { BytemdPlugin } from "bytemd"; import hljs from "highlight.js"; import ReactDOMServer from "react-dom/server"; import "./index.css"; import { message } from "antd"; // 拷贝代码 const copyToClipboard = (text: string) => navigator.clipboard.writeText(text); // 渲染页面元素 export const renderEle = function (code: HTMLElement, key: number) { let className: string = code.className; if (!className ?? "") { return; } // 使用正则表达式匹配以 "language-" 开头的类名 const match = className.match(/language-(.+)/); // 如果匹配成功,提取语言部分并转换为大写 let lan = match ? match[1].toUpperCase() : ""; const parentNode: any = code.parentNode; // 获取计算后的样式 const style = window.getComputedStyle(code); // 获取背景色 const backgroundColor = style.backgroundColor; parentNode.removeChild(code); parentNode.innerHTML = ReactDOMServer.renderToString(codeText(code, key, backgroundColor, lan)) + '<code id="code-' + key + '" class="' + code.className + '">' + code.innerHTML + "</code>"; let btn: any = document.getElementsByClassName( "code-block-extension-copy-" + key, )[0]; let timeoutID: any = null; // 用于存储 setTimeout 的 ID const restoreText = () => { btn.innerHTML = "复制代码"; btn.disable = false; }; const timeout = () => { timeoutID = setTimeout(restoreText, 3000); // 设置定时器,三秒后执行 restoreText 函数 }; btn.addEventListener("click", () => { if (btn.disable) { return; } clearTimeout(timeoutID); // 清除之前的定时器 copyToClipboard(code.innerText); // 执行复制操作 btn.innerHTML = "Copied"; // 立即更改按钮文本 btn.disable = true; message.success({ content: "复制成功", }); timeout(); // 设置新的定时器,三秒后恢复按钮文本 }); // 旋转隐藏code let foldBtn: any = document.getElementsByClassName("foldBtn-" + key)[0]; // 最上层的父类的盒子阴影也去掉 let codeHeader: any = document.getElementsByClassName( "code-header-" + key, )[0]; foldBtn.addEventListener("click", () => { let codeEle = document.getElementById("code-" + key); codeEle?.classList.toggle("hidden"); foldBtn.classList.toggle("rotate90"); codeHeader.classList.toggle("boxShadowNone"); }); }; // innerHTML 布局 const codeText: any = ( innerHTML: HTMLElement, key: number, backgroundColor: string, lan: string, ) => { return ( <div className={"code-block-extension-header code-header-" + key} style={{ backgroundColor }} > <div className="code-block-extension-headerLeft"> <div className={"code-block-extension-foldBtn foldBtn-" + key}> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M16.924 9.617A1 1 0 0 0 16 9H8a1 1 0 0 0-.707 1.707l4 4a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0 .217-1.09z" data-name="Down" ></path> </svg> </div> </div> <div className="code-block-extension-headerRight"> <span className="code-block-extension-lang">{lan}</span> <span className={ "code-block-extension-lang code-block-extension-copy code-block-extension-copy-" + key } > 复制代码 </span> </div> </div> ); }; export default function code(): BytemdPlugin { return { viewerEffect({ markdownBody }): void | (() => void) { const codes = markdownBody.querySelectorAll("code"); codes.forEach((code, key) => { renderEle(code, key); }); } }; }
这里仿照掘金的样式,样式直接 copy 掘金的,使用控制台直接拷贝
▼tsx复制代码.code-block-extension-header { display: flex; user-select: none; height: 28px; align-items: center; justify-content: space-between; box-shadow: 0 5px 4px -6px #888888; margin-bottom: 1px; } .code-block-extension-headerLeft { margin-left: 10px; } .code-block-extension-headerLeft, .code-block-extension-headerRight { display: flex; align-items: center; } .code-block-extension-headerLeft > .code-block-extension-foldBtn { display: flex; align-items: center; justify-content: center; width: 18px; height: 18px; filter: invert(0.8); } .code-block-extension-headerRight { margin-right: 10px; } .code-block-extension-lang { margin-left: 20px; font-size: 12px; filter: invert(0.5); opacity: 0.6; } .code-block-extension-copy{ cursor: pointer; } .code-block-extension-foldBtn:hover{ color: #ffffff; cursor: pointer; } .rotate90{ transform: rotate(-90deg); } .hidden { display: none !important; } .boxShadowNone{ box-shadow: none !important; margin-bottom: 0 !important; }
然后我们在 MdViewer 中引入
▼tsx复制代码import code from "../../bytemdPlugin/ByteCode"; const plugins = [gfm(), highlight(), code(), mediumZoom()];
样式效果:
bug 修复
如果有实现上期扩展,根据选择展示不同的主题,那么在选择时候这个复制的功能就消失了,这里给出修复的代码:(不是专业前段,如有好的解决办法,希望大家指点)▼tsx复制代码const MdViewer = (props: Props) => { const { value = "", theme = "channing-cyan" } = props; const viewerRef = useRef(); useEffect(() => { setTheme(theme); // @ts-ignore plugins[2].viewerEffect({ markdownBody: viewerRef.current }); }, [theme]); return ( // @ts-ignore <div className="md-viewer" ref={viewerRef}> <Viewer value={value} plugins={plugins} /> </div> ); };
评论
问答助学
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
