学习风`宇blog
- md文档转html(markdown-it的使用)
- 语法高亮、行号、一键复制
- toc生成目录
- sticky粘性定位
<style lang="scss">
@import url(//at.alicdn.com/t/c/font_4004562_9v94jccafmc.css);
@import url('https://fonts.font.im/css?family=Roboto');
* {
font-family: 'Roboto';
}
a {
text-decoration: none;
}
ul,
ol,
li {
margin: 0;
padding: 0;
list-style: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
margin-bottom: 10px;
}
/* 行号样式 */
.line-numbers-rows {
position: absolute;
pointer-events: none;
top: 18px;
font-size: 100%;
left: 0.5em;
width: 2.1em;
letter-spacing: -1px;
border-right: 1px solid #0e0f12;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
background-color: #282c34;
}
pre {
position: relative;
padding-left: 2.6em;
line-height: 1.3em;
}
.line-numbers-rows>span {
display: block;
counter-increment: linenumber;
}
pre {
background-color: #282c34;
border-radius: 8px;
}
pre code {
border-radius: 8px;
}
.line-numbers-rows>span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
.language-name {
position: absolute;
top: 9px;
color: #999999;
right: 43px;
font-size: 0.8em;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.copy-btn {
position: absolute;
right: 8px;
top: 8px;
background-color: #525252;
border: none;
padding: 3px 6px;
border-radius: 3px;
color: #cccccc;
cursor: pointer;
display: none;
}
pre:hover .copy-btn {
display: block;
}
.copy-textarea {
position: absolute;
left: -9999px;
top: -9999px;
}
/* 封面图下移效果 */
@keyframes slidedown {
0% {
opacity: 0.3;
transform: translateY(-60px);
}
100% {
opacity: 1;
transform: translateY(0px);
}
}
.slidedown {
animation: slidedown 1s;
}
/* 内容上移效果 */
@keyframes slideup {
0% {
opacity: 0.3;
transform: translateY(60px);
}
100% {
opacity: 1;
transform: translateY(0px);
}
}
.slideup {
animation: slideup 1s;
}
/* 从小变大效果 */
@keyframes scaleup {
0% {
transform: scale(0.3);
}
100% {
transform: scale(1);
}
}
.scaleup {
animation: scaleup 1s;
}
body {
overflow-x: hidden;
overflow-y: scroll;
/* 背景渐变 */
background: linear-gradient(90deg, rgba(247, 149, 51, .1), rgba(243, 112, 85, .1) 15%, rgba(239, 78, 123, .1) 30%, rgba(161, 102, 171, .1) 44%, rgba(80, 115, 184, .1) 58%, rgba(16, 152, 173, .1) 72%, rgba(7, 179, 155, .1) 86%, rgba(109, 186, 130, .1));
}
.verticle-line {
margin: 10px;
}
* {
box-sizing: border-box;
}
.toc-link {
text-overflow: ellipsis;
white-space: nowrap; /* white-space属性为nowrap时,不会因为超出容器宽度而发生换行 */
overflow: hidden;
}
.banner {
height: 400px;
background-image: url(@/assets/bg3.jpg);
background-size: cover;
background-position: center;
position: relative;
color: #eee;
.banner-content {
position: absolute;
bottom: 25%;
width: 100%;
text-align: center;
text-shadow: 0.05rem 0.05rem 0.1rem rgb(0 0 0 / 30%);
.post-title {
font-size: 30px;
margin-bottom: 20px;
}
.post-intro1,
.post-intro2 {
font-size: 14px;
line-height: 1.5;
i {
margin-right: 5px;
}
}
}
}
.post-wrapper {
display: flex;
// border: 1px solid red;
max-width: 1200px;
margin: 40px auto;
.post-content-wrapper {
// border: 1px solid red;
width: 75%;
padding: 10px;
.post-content {
padding: 40px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 8px 6px rgba(7, 17, 27, .06);
}
}
.post-sider-wrapper {
// border: 1px solid red;
padding: 7.5px;
width: 25%;
padding: 12px;
.post-sider {
border-radius: 10px;
position: sticky;
top: 20px;
.toc-wrapper {
flex-basis: 25%;
box-shadow: 0 4px 8px 6px rgba(7, 17, 27, .06);
padding: 20px;
border-radius: 10px;
margin-bottom: 15px;
background-color: #fff;
// border: 1px solid red;
.toc-header {
display: flex;
align-items: center;
margin-bottom: 5px;
}
#toc-content {
position: sticky;
top: 20px;
box-sizing: border-box;
// border: 1px solid red;
a {
transition: all 0.3s;
text-decoration: none;
display: block;
line-height: 1.6em;
padding-left: 10px;
border-left: 4px solid transparent;
color: #666261;
&::before {
display: none;
}
&.is-active-link {
font-weight: normal;
color: #fff;
background-color: #00c4b6;
border-left-color: #009d92;
}
}
}
}
.latest-article {
background-color: #fff;
// border: 1px solid red;
border-radius: 10px;
padding: 20px;
.latest-article-header {
margin-right: 5px;
margin-bottom: 8px;
color: #555555;
i {
font-weight: bold;
margin-right: 8px;
}
}
.latest-article-item {
display: flex;
margin-bottom: 10px;
.latest-post-cover {
width: 60px;
height: 60px;
flex-shrink: 0;
border-radius: 5px;
overflow: hidden;
img {
height: 100%;
width: 100%;
object-fit: cover;
transition: all 0.3s;
&:hover {
transform: scale(1.2);
}
}
}
.latest-post-info {
margin-left: 10px;
overflow: hidden;
flex: 1;
padding-top: 3px;
display: flex;
flex-direction: column;
justify-content: space-between;
line-height: 1.2;
.latest-post-title {
color: #53504f;
display: -webkit-box;
word-break: break-all;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
font-size: 16px;
&:hover {
color: #00c4b6;
}
}
.latest-post-desc {
color: #989898;
font-size: 13px;
}
}
}
}
}
}
}
</style>
<template>
<div>
<navbar />
<div class="banner slidedown">
<div class="banner-content">
<div class="post-title">
Spring Security框架方法注解源码
</div>
<div class="post-intro1">
<i class="iconfont icon-rili"></i>
<span>发表于 2023-03-07</span>
<span class="verticle-line">|</span>
<i class="iconfont icon-gengxinshijian"></i>
<span>更新于 2023-03-07</span>
<span class="verticle-line">|</span>
<i class="iconfont icon-fenlei"></i>
<span>Java</span>
</div>
<div class="post-intro2">
<i class="iconfont icon-wenben"></i>
<span>字数统计: 2.4k</span>
<span class="verticle-line">|</span>
<i class="iconfont icon-shichang"></i>
<span>阅读时长: 6分钟</span>
<span class="verticle-line">|</span>
<i class="iconfont icon-liulan-2"></i>
<span>阅读量: 79</span>
<span class="verticle-line">|</span>
<i class="iconfont icon-wodepinglun"></i>
<span>评论数: 1</span>
</div>
</div>
</div>
<div class="post-wrapper slideup">
<div class="post-content-wrapper">
<div class="post-content">
<div v-html="htmlContent" id="article-content"></div>
</div>
</div>
<div class="post-sider-wrapper">
<div class="post-sider">
<div class="toc-wrapper scaleup">
<div class="toc-header">
<img src="@/assets/svg/menu.svg" style="margin-right: 8px;" width="16px" height="16px" alt="">
<span>目录</span>
</div>
<div id="toc-content"></div>
</div>
<div class="latest-article scaleup">
<div class="latest-article-header">
<i class="iconfont icon-gengxinshijian"></i>
<span>最新文章</span>
</div>
<a href="#" class="latest-article-item">
<div class="latest-post-cover">
<img src="@/assets/post4.jpg" alt="">
</div>
<div class="latest-post-info">
<div class="latest-post-title">
mybatismybatismybatismybatismybatismybatismybatis复杂结果映射mybatis复杂结果映射mybatis复杂结果映射
</div>
<div class="latest-post-desc">
2023-03-07
</div>
</div>
</a>
<a href="#" class="latest-article-item">
<div class="latest-post-cover">
<img src="@/assets/post2.jpg" alt="">
</div>
<div class="latest-post-info">
<div class="latest-post-title">
mybatis
</div>
<div class="latest-post-desc">
2023-03-07
</div>
</div>
</a>
<a href="#" class="latest-article-item">
<div class="latest-post-cover">
<img src="@/assets/post3.jpg" alt="">
</div>
<div class="latest-post-info">
<div class="latest-post-title">
mybatimybatis复杂结果映射mybatis复杂结果映射mybatis复杂结果映射
</div>
<div class="latest-post-desc">
2023-03-07
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<div style="height: 500px;"></div>
</div>
</template>
<script>
import Navbar from './Navbar.vue';
import { getArticle } from '@/api/articleApi'
import ClipboardJS from 'clipboard'
console.log(ClipboardJS);
import MarkdownIt from 'markdown-it'
const MarkdownIt2 = require('markdown-it')
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
import tocbot from 'tocbot'
console.log('tocbot', tocbot);
let md1 = new MarkdownIt()
let md2 = new MarkdownIt2()
console.log(md1);
console.log(md2);
console.log(md1.render('# markdown-it rulezz!'));/* h1>markdown-it rulezz!</h1> */
console.log(md2.render('# markdown-it rulezz!'));/* h1>markdown-it rulezz!</h1> */
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
breaks: true,
highlight: function (str, lang) {
/*
str-> @Configuration
@EnableWebMvc
@EnableGlobalMethodSecurity(prePostEnabled = true) // 因为要控制controller中的方法访问,所以此注解要加到子容器中
@ComponentScan(basePackages = "com.zzhua.controller",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = Service.class)})
public class MyWebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 开启静态资源访问
configurer.enable();
}
}
lang-> java {name: 'Java', aliases: Array(1), keywords: {…}, illegal: /<\/|#/, contains: Array(23), …}
*/
console.log('str->', str, 'lang->', lang, hljs.getLanguage('java'));
/*
<span class="hljs-meta">@Configuration</span>
<span class="hljs-meta">@EnableWebMvc</span>
<span class="hljs-meta">@EnableGlobalMethodSecurity(prePostEnabled = true)</span> <span class="hljs-comment">// 因为要控制controller中的方法访问,所以此注解要加到子容器中</span>
<span class="hljs-meta">@ComponentScan(basePackages = "com.zzhua.controller",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = Service.class)})</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyWebConfig</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">WebMvcConfigurer</span> {
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">configureDefaultServletHandling</span><span class="hljs-params">(DefaultServletHandlerConfigurer configurer)</span> {
<span class="hljs-comment">// 开启静态资源访问</span>
configurer.enable();
}
}
*/
console.log(hljs.highlight(str, { language: lang }).value);
if (lang && hljs.getLanguage(lang)) {
try {
let highlightedHtml = hljs.highlight(str, { language: lang }).value
// 生成行号
let lineNum = highlightedHtml.split('\n').length - 1
let lineNumbersRowsStart = `<span aria-hidden="true" class="line-numbers-rows">`
let lineNumbersRowsEnd = `</span>`
for (let i = 0; i < lineNum; i++) {
lineNumbersRowsStart += `<span></span>`
}
const lineNumbersRows = lineNumbersRowsStart + lineNumbersRowsEnd
let languageName = `<b class="language-name">${lang}</b>`
// 当前时间加随机数生成唯一的id标识
var d = new Date().getTime();
if (window.performance && typeof window.performance.now === "function") {
d += performance.now();
}
const codeIndex = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
}
);
// 复制功能需要一个textarea(这个id需要前面加个字母啥的,不能以数字开头)
let textAreaHtml = `<textarea class="copy-textarea" id="copy${codeIndex}">${str}</textarea>`
let copyButton = `<button class="copy-btn iconfont icon-fuzhi" data-clipboard-action="copy" data-clipboard-target="#copy${codeIndex}" type="button"></button>`
/* 如果返回的不含pre code标签,它会自己加上;如果返回的含有pre code标签,它就不加了 */
return `<pre><code class="language-${lang} hljs">${highlightedHtml}</code>${lineNumbersRows}${languageName}${copyButton}${textAreaHtml}</pre>`;
} catch (__) { }
}
return ""
}
}).use(require("markdown-it-sub"))
.use(require("markdown-it-sup"))
.use(require("markdown-it-mark"))
.use(require("markdown-it-abbr"))
.use(require("markdown-it-container"))
.use(require("markdown-it-deflist"))
.use(require("markdown-it-emoji"))
.use(require("markdown-it-footnote"))
.use(require("markdown-it-ins"))
.use(require("markdown-it-katex-external"))
.use(require("markdown-it-task-lists"));
console.log('未用markdown-it-sub时: ', md1.render('H~2~0')); /* 未用markdown-it-sub时: <p>H~2~0</p> */
const md3 = new MarkdownIt()
.use(require('markdown-it-sub'))
console.log('使用markdown-it-sub时: ', md3.render('H~2~0')); /* 使用markdown-it-sub时: <p>H<sub>2</sub>0</p> */
console.log(md3.render('Hello from mars :satellite:'));
md3.use(require('markdown-it-emoji')) /* <p>Hello from mars :satellite:</p> */
console.log(md3.render('Hello from mars :satellite:'));/* <p>Hello from mars 📡</p> */
export default {
name: 'Post',
components: {
Navbar
},
data() {
return {
mdContent: '',
htmlContent: '',
}
},
mounted() {
getArticle(29).then(data => {
this.mdContent = data.mdContent
})
},
watch: {
mdContent(newVal, oldVal) {
let _this = this
this.htmlContent = md.render(newVal)
this.$nextTick(() => {
var clipboard = new ClipboardJS('.copy-btn');
clipboard.on('success', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
_this.$toast('success', '复制成功了哦');
e.clearSelection();
});
clipboard.on('error', function (e) {
console.error('Action:', e.action);
console.error('Trigger:', e.trigger);
});
// console.log('$nextTick...');
let articleContent = document.querySelector('#article-content')
let headingTag = ['h1', 'h2', 'h3']
let children = Array.from(articleContent.children)
for (let i = 0; i < children.length; i++) {
const e = children[i];
e.id = `h-${i}`
}
hljs.highlightAll()
tocbot.init({
// Where to render the table of contents.
tocSelector: '#toc-content',
// Where to grab the headings to build the table of contents.
contentSelector: '#article-content',
// Which headings to grab inside of the contentSelector element.
headingSelector: 'h1, h2, h3, h4, h5, h6',
// For headings inside relative or absolute positioned containers within content.
hasInnerContainers: true,
});
})
}
}
}
</script>