更新vditor组件版本,优化Editor编辑器HTML过滤,优化vditor文档目录

This commit is contained in:
yangjian 2021-03-16 21:44:41 +08:00
parent 5db59b148a
commit 9a97d8e2ac
12 changed files with 297 additions and 146 deletions

View File

@ -565,6 +565,9 @@
})
})
// 加载DOMPurify过滤HTML
editormd.loadScript(loadPath + 'purify.min',function(){});
editormd.loadCSS(loadPath + "codemirror/lib/codemirror");
if (settings.searchReplace && !settings.readOnly)
@ -2111,12 +2114,14 @@
marked.setOptions(markedOptions);
var newMarkdownDoc = editormd.$marked(cmValue, markedOptions);
// console.info("cmValue", cmValue, newMarkdownDoc);
//console.info("cmValue", cmValue, newMarkdownDoc);
// newMarkdownDoc = editormd.filterHTMLTags(newMarkdownDoc, settings.htmlDecode);
// 加载DOMPurify过滤HTML
newMarkdownDoc = DOMPurify.sanitize(newMarkdownDoc,{ADD_TAGS: ['iframe']})
newMarkdownDoc = editormd.filterHTMLTags(newMarkdownDoc, settings.htmlDecode);
//console.error("cmValue", cmValue, newMarkdownDoc);
// console.log(newMarkdownDoc)
// console.error("cmValue", cmValue, newMarkdownDoc);
this.markdownTextarea.text(cmValue);
@ -3541,7 +3546,7 @@
var editormdLogoReg = regexs.editormdLogo;
var pageBreakReg = regexs.pageBreak;
// 增加引用样式解析规则
// 增加引用样式解析规则
markedRenderer.blockquote = function($quote) {
var quoteBegin = "";
@ -3568,7 +3573,8 @@
}
return '<blockquote class="'+$class+'">\n' + quoteBegin + $quote + '</blockquote>\n';
};
};
// marked 解析图片
markedRenderer.image = function(href,title,text) {
var attr = "";
@ -3612,21 +3618,21 @@
const tedMatch = href.match(/(?:www\.|\/\/)ted\.com\/talks\/(\w+)/);
if (youtubeMatch && youtubeMatch[1].length === 11) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//www.youtube.com/embed/${youtubeMatch[1] + (youtubeMatch[2] ? "?start=" + youtubeMatch[2] : "")}">`
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//www.youtube.com/embed/${youtubeMatch[1] + (youtubeMatch[2] ? "?start=" + youtubeMatch[2] : "")}"></iframe>`
} else if (youkuMatch && youkuMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//player.youku.com/embed/${youkuMatch[1]}">`
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//player.youku.com/embed/${youkuMatch[1]}"></iframe>`
} else if (qqMatch && qqMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="https://v.qq.com/txp/iframe/player.html?vid=${qqMatch[1]}">`
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="https://v.qq.com/txp/iframe/player.html?vid=${qqMatch[1]}"></iframe>`
} else if (coubMatch && coubMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//coub.com/embed/${coubMatch[1]}?muted=false&autostart=false&originalSize=true&startWithHD=true">`
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//coub.com/embed/${coubMatch[1]}?muted=false&autostart=false&originalSize=true&startWithHD=true"></iframe>`
} else if (facebookMatch && facebookMatch[0]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="https://www.facebook.com/plugins/video.php?href=${encodeURIComponent(facebookMatch[0])}">`
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="https://www.facebook.com/plugins/video.php?href=${encodeURIComponent(facebookMatch[0])}"></iframe>`
} else if (dailymotionMatch && dailymotionMatch[2]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="https://www.dailymotion.com/embed/video/${dailymotionMatch[2]}">`
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="https://www.dailymotion.com/embed/video/${dailymotionMatch[2]}"></iframe>`
} else if (bilibiliMatch && bilibiliMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//player.bilibili.com/player.html?bvid=${bilibiliMatch[1]}">`
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//player.bilibili.com/player.html?bvid=${bilibiliMatch[1]}"></iframe>`
} else if (tedMatch && tedMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//embed.ted.com/talks/${tedMatch[1]}">`
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//embed.ted.com/talks/${tedMatch[1]}"></iframe>`
}else{
if(iframe_whitelist.length == 1 && iframe_whitelist[0] == ""){
return href
@ -3678,7 +3684,7 @@
}
}
}
return begin + "<img src=\""+href+"\" title=\""+title+"\" alt=\""+text+"\" "+attr+">" + end;
return begin + "<img src=\""+href+"\" title=\""+title+"\" alt=\""+text+"\" "+attr+" />" + end;
};
// marked emoji 解析
@ -3793,7 +3799,6 @@
// marked 链接解析
markedRenderer.link = function (href, title, text) {
if (this.options.sanitize) {
try {
var prot = decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase();
@ -3870,7 +3875,7 @@
headingHTML += "<span class=\"header-link octicon octicon-link\"></span>";
headingHTML += (hasLinkReg) ? this.atLink(this.mark(this.emoji(text))) : this.mark(this.emoji(text));
headingHTML += "</h" + level + ">";
// console.log(headingHTML)
return headingHTML;
};
@ -3903,7 +3908,6 @@
}
var tocHTML = "<div class=\"markdown-toc editormd-markdown-toc\">" + text + "</div>";
return (isToC) ? ( (isToCMenu) ? "<div class=\"editormd-toc-menu\">" + tocHTML + "</div><br/>" : tocHTML )
: ( (pageBreakReg.test(text)) ? this.pageBreak(text) : "<p" + isTeXAddClass + ">" + this.atLink(this.mark(this.emoji(text))) + "</p>\n" );
};
@ -4250,7 +4254,8 @@
*/
editormd.filterHTMLTags = function(html, filters) {
console.log(html)
console.log(filters)
if (typeof html !== "string") {
html = new String(html).toString();
}
@ -4264,6 +4269,7 @@
var expression = filters.split("|");
var filterTags = expression[0].split(",");
var attrs = expression[1];
console.log(attrs)
if(!filterTags.includes('allowScript') && !filterTags.includes('script'))
{
@ -4278,7 +4284,7 @@
html = html.replace(new RegExp("\<\s*" + tag + "\s*([^\>]*)\>([^\>]*)\<\s*\/" + tag + "\s*\>", "igm"), "");
html = html.replace(new RegExp("\<\s*" + tag + ".*?/?>", "igm"), "") // 过滤单闭合标签
}
//return html;
if (typeof attrs === "undefined")
@ -4304,12 +4310,16 @@
{
html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) {
return "<" + $2 + ">" + $4 + "</" + $5 + ">";
});
});
}
// else if (attrs === "on*")
else if ((attrs === "on*") || filterOn)
{
html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) {
console.log($1)
console.log($2)
console.log($4)
console.log($5)
var el = $("<" + $2 + ">" + $4 + "</" + $5 + ">");
var _attrs = $($1)[0].attributes;
var $attrs = {};
@ -4338,7 +4348,7 @@
// var filterAttrs = attrs.split(",");
var el = $($1);
el.html($4);
$.each(filterAttrs, function(i) {
el.attr(filterAttrs[i], null);
});
@ -4347,7 +4357,7 @@
});
}
}
return html;
};
@ -4435,7 +4445,12 @@
var markdownParsed = marked(markdownDoc, markedOptions);
markdownParsed = editormd.filterHTMLTags(markdownParsed, settings.htmlDecode);
// markdownParsed = editormd.filterHTMLTags(markdownParsed, settings.htmlDecode);
// 加载DOMPurify过滤HTML
editormd.loadScript(settings.plugin_path + 'purify.min',function(){
markdownParsed = DOMPurify.sanitize(markdownParsed,{ADD_TAGS: ['iframe']});
});
// console.log(markdownParsed)
if (settings.markdownSourceCode) {
saveTo.text(markdownDoc);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
static/editor.md/lib/purify.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -165,4 +165,8 @@ ul#doc-tree{
width: 0;
padding-top: 4px;
}
}
/* 覆盖vditor文档目录样式 */
.vditor-outline ul{
padding-left: 0px;
}

View File

@ -295,10 +295,11 @@ copyUrl = function(){
window.clipb
document.execCommand("Copy");
layer.msg("链接复制成功!")
}
};
$("#copy_doc_url").click(function(){
copyUrl();
})
});
// 生成文档链接二维码
doc_qrcode = function(){
new QRCode("url_qrcode", {
@ -312,7 +313,9 @@ doc_qrcode = function(){
};
doc_qrcode();
// 文集水印
/*
文集水印
*/
textBecomeImg = function(text,fontsize,fontcolor){
var canvas = document.createElement('canvas');
canvas.height = 180;
@ -328,6 +331,40 @@ textBecomeImg = function(text,fontsize,fontcolor){
var dataUrl = canvas.toDataURL('image/png');//注意这里背景透明的话需要使用png
return dataUrl;
}
function initWhterMark(value){
var img_base64 = textBecomeImg(value, '14', '#000');
document.getElementById("wm").style.background = 'url('+ img_base64 + ')';
}
// 文集、文档收藏函数
function collect(id,type){
$.ajax({
url:'/my_collect/',
type:'post',
data:{'type':type,'id':id},
success:function(r){
layer.msg(r.data)
},
error:function(){
layer.msg("操作异常")
}
});
}
// 收藏文集
$("#collect_pro").click(function(e){
$(this).toggleClass("layui-icon-star-fill layui-icon-star");
$(this).toggleClass("collected");
collect(pro_id,2);
});
// 收藏文档
$("#collect_doc").click(function(){
$(this).toggleClass("layui-icon-star-fill layui-icon-star");
$(this).toggleClass("collected");
collect(doc_id,1);
});
/*
########################################################
### 文集阅读页面JavaScript函数和变量定义 ###
@ -342,41 +379,96 @@ textBecomeImg = function(text,fontsize,fontcolor){
########################################################
*/
// URL参数解析
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
// 初始化文档内容渲染
function initDocRender(mode){
if(mode == 1){
editormd.markdownToHTML("content", {
emoji : true, //emoji表情
taskList : true, // 任务列表
tex : true, // 科学公式
flowChart : true, // 流程图
sequenceDiagram : true, // 时序图
tocm : true, //目录
toc :true,
tocContainer : "#toc-container",
tocDropdown : false,
atLink : false,//禁用@链接
htmlDecode : "link,style,base,script", //过滤部分HTML标签
});
}else if(mode == 2){
var md_content = $("#content textarea").val()
Vditor.preview(document.getElementById('content'),md_content,
{
"cdn":"/static/vditor/",
markdown:{mark:true},
speech: {enable: true,},
anchor: 1,
after () {
var sub_ele = "<div class='markdown-toc editormd-markdown-toc'></div>"
$("#toc-container").append(sub_ele)
var outlineElement = $("#toc-container div")
Vditor.outlineRender(document.getElementById('content'), outlineElement[0])
$('#toc-container div ul').addClass('markdown-toc-list')
if (outlineElement[0].innerText.trim() !== '') {
outlineElement[0].style.display = 'block';
var toc_cnt = $(".markdown-toc-list ul").children().length;
if(toc_cnt > 0){
//console.log('显示文档目录')
$(".tocMenu").show();
initSidebar('.sidebar', '.doc-content', 2);
}
}
// 图片放大显示
var img_options = {
url: 'data-original',
fullscreen:false,//全屏
rotatable:false,//旋转
scalable:false,//翻转
button:false,//关闭按钮
toolbar:false,
title:false,
};
var img_viewer = new Viewer(document.getElementById('content'), img_options);
},
})
}
};
// 搜索词高亮
function keyLight(id, key, bgColor){
// console.log(id,key,decodeURI(key))
if(key != false){
key = decodeURI(key);
var oDiv = document.getElementById(id),
sText = oDiv.innerHTML,
bgColor = bgColor || "#c00",
sKey = "<span name='addSpan' style='color: "+bgColor+";background:ff0;'>"+key+"</span>",
num = -1,
rStr = new RegExp(key, "ig"),
rHtml = new RegExp("\<.*?\>","ig"), //匹配html元素
aHtml = sText.match(rHtml); //存放html元素的数组
sText = sText.replace(rHtml, '{~}'); //替换html标签
// sText = sText.replace(rStr,sKey); //替换key
sText = sText.replace(rStr,function(text){
return "<span name='addSpan' style='color:#333;background:#ff0;'>"+text+"</span>"
}); //替换key
sText = sText.replace(/{~}/g,function(){ //恢复html标签
num++;
return aHtml[num];
});
oDiv.innerHTML = sText;
}
};
keyLight('doc-content',getQueryVariable("highlight"))
// URL参数解析
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
// 搜索词高亮
function keyLight(id, key, bgColor){
// console.log(id,key,decodeURI(key))
if(key != false){
key = decodeURI(key);
var oDiv = document.getElementById(id),
sText = oDiv.innerHTML,
bgColor = bgColor || "#c00",
sKey = "<span name='addSpan' style='color: "+bgColor+";background:ff0;'>"+key+"</span>",
num = -1,
rStr = new RegExp(key, "ig"),
rHtml = new RegExp("\<.*?\>","ig"), //匹配html元素
aHtml = sText.match(rHtml); //存放html元素的数组
sText = sText.replace(rHtml, '{~}'); //替换html标签
// sText = sText.replace(rStr,sKey); //替换key
sText = sText.replace(rStr,function(text){
return "<span name='addSpan' style='color:#333;background:#ff0;'>"+text+"</span>"
}); //替换key
sText = sText.replace(/{~}/g,function(){ //恢复html标签
num++;
return aHtml[num];
});
oDiv.innerHTML = sText;
}
};
keyLight('doc-content',getQueryVariable("highlight"))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -15,78 +15,116 @@
* @param {string} sidebarQuery - 目录 Element query 字符串
* @param {string} contentQuery - 正文 Element query 字符串
*/
function initSidebar(sidebarQuery, contentQuery) {
function initSidebar(sidebarQuery, contentQuery, mode=1) {
addAllStyle();
var sidebar = document.querySelector(sidebarQuery)
// 遍历文章中的所有 h1或 h2(取决于最大的 h 是多大) , 编辑为li.h3插入 ul
var allHeaders = []
var content = document.querySelector(contentQuery)
for(var i = 1;i < 7; i++){
//console.log(i,content.querySelectorAll('h' + i))
allHeaders.push.apply(allHeaders,content.querySelectorAll('h' + i))
}
// console.log('目录列表:',allHeaders)
//增加 click 点击处理,使用 scrollIntoView,增加控制滚动的 flag
var scrollFlag = 0
var scrollFlagTimer
sidebar.addEventListener('click', function (e) {
e.preventDefault()
// console.log(e.target.dataset.id)
if (e.target.href) {
scrollFlag = 1
clearTimeout(scrollFlagTimer)
scrollFlagTimer = setTimeout(() => scrollFlag = 0, 1500)
setActive(e.target, sidebar)
var target = document.getElementById(e.target.getAttribute('href').slice(1))
// console.log(e,target)
// console.log(e.target.getAttribute('href').slice(1))
target.scrollIntoView({ behavior: 'smooth', block: "start" })
}else if(e.target.dataset.id){
// console.log('vditor目录')
var target = document.getElementById(e.target.dataset.id)
target.scrollIntoView({ behavior: 'smooth', block: "start" })
var sidebar = document.querySelector(sidebarQuery)
if(mode == 1){
// 遍历文章中的所有 h1或 h2(取决于最大的 h 是多大) , 编辑为li.h3插入 ul
var allHeaders = []
var content = document.querySelector(contentQuery)
for(var i = 1;i < 7; i++){
// console.log(i,content.querySelectorAll('h' + i))
allHeaders.push.apply(allHeaders,content.querySelectorAll('h' + i))
}
});
//监听窗口的滚动和缩放事件
window.addEventListener('scroll', updateSidebar)
// window.addEventListener('resize', throttle(updateSidebar))
function updateSidebar() {
if (scrollFlag) return // 如果存在scrollFlag直接返回
var doc = document.documentElement // 定义doc变量值为页面文档元素
var top = doc && doc.scrollTop || document.body.scrollTop // 获取当前页面滚动条纵坐标
if (!allHeaders.length) return // 如果allHeaders的列表长度为空直接返回
var last // 定义一个变量last
// console.log(allHeaders)
// 按照allHeaders的列表长度进行遍历
for (var i = 0; i < allHeaders.length; i++) {
var link = allHeaders[i] // 按索引取出一个目录link
// console.log("当前元素:",link)
// console.log("页面可视区域高度:",document.body.clientHeight)
// console.log("元素距离顶部距离:",link.offsetTop)
// console.log("当前页面滚动条纵坐标:",top)
// console.log("页面元素距离浏览器工作区顶端的距离:", link.offsetTop - document.documentElement.scrollTop)
// link.offsetTop 表示元素距离上方的距离
// top 表示当前页面滚动条的纵坐标
// document.body.clientHeight 表示页面可视区域高度
var link_to_top_offset = link.offsetTop - document.documentElement.scrollTop;
// console.log(link_to_top_offset)
if(link_to_top_offset > 150){
}else if(link_to_top_offset < -150){
}else{
if (!last) { last = link }
break
// console.log('目录列表:',allHeaders)
//增加 click 点击处理,使用 scrollIntoView,增加控制滚动的 flag
var scrollFlag = 0
var scrollFlagTimer
sidebar.addEventListener('click', function (e) {
e.preventDefault()
// console.log(e.target.dataset.id)
if (e.target.href) {
scrollFlag = 1
clearTimeout(scrollFlagTimer)
scrollFlagTimer = setTimeout(() => scrollFlag = 0, 1500)
setActive(e.target, sidebar)
var target = document.getElementById(e.target.getAttribute('href').slice(1))
// console.log(e,target)
// console.log(e.target.getAttribute('href').slice(1))
target.scrollIntoView({ behavior: 'smooth', block: "start" })
}else if(e.target.dataset.id){
// console.log('vditor目录')
var target = document.getElementById(e.target.dataset.id)
target.scrollIntoView({ behavior: 'smooth', block: "start" })
}
});
//监听窗口的滚动和缩放事件
window.addEventListener('scroll', updateSidebar)
// window.addEventListener('resize', throttle(updateSidebar))
function updateSidebar() {
if (scrollFlag) return // 如果存在scrollFlag直接返回
var doc = document.documentElement // 定义doc变量值为页面文档元素
var top = doc && doc.scrollTop || document.body.scrollTop // 获取当前页面滚动条纵坐标
if (!allHeaders.length) return // 如果allHeaders的列表长度为空直接返回
var last // 定义一个变量last
// console.log(allHeaders)
// 按照allHeaders的列表长度进行遍历
for (var i = 0; i < allHeaders.length; i++) {
var link = allHeaders[i] // 按索引取出一个目录link
// console.log("当前元素:",link)
// console.log("页面可视区域高度:",document.body.clientHeight)
// console.log("元素距离顶部距离:",link.offsetTop)
// console.log("当前页面滚动条纵坐标:",top)
// console.log("页面元素距离浏览器工作区顶端的距离:", link.offsetTop - document.documentElement.scrollTop)
// link.offsetTop 表示元素距离上方的距离
// top 表示当前页面滚动条的纵坐标
// document.body.clientHeight 表示页面可视区域高度
var link_to_top_offset = link.offsetTop - document.documentElement.scrollTop;
// console.log(link_to_top_offset)
if(link_to_top_offset > 150){
}else if(link_to_top_offset < -150){
}else{
if (!last) {last = link }
break
}
}
if (last) {
// console.log(last.offsetTop)
setActive(last.id, sidebar)
}
}
}else if(mode == 2){
const headingElements = []
Array.from(document.getElementById('content').children).forEach((item) => {
if (item.tagName.length === 2 && item.tagName !== 'HR' && item.tagName.indexOf('H') === 0) {
headingElements.push(item)
}
})
}
if (last) {
// console.log(last.offsetTop)
setActive(last.id, sidebar)
}
let toc = []
window.addEventListener('scroll', () => {
toc = []
headingElements.forEach((item) => {
toc.push({
id: item.id,
offsetTop: item.offsetTop,
})
})
var last // 定义一个变量last
for (let i = 0, iMax = toc.length; i < iMax; i++) {
var link = headingElements[i] // 按索引取出一个目录link
var link_to_top_offset = link.offsetTop - document.documentElement.scrollTop;
// console.log(link)
// console.log(link_to_top_offset)
if(link_to_top_offset > 150){
}else if(link_to_top_offset < -150){
}else{
if (!last) {
last = link
var index = i > 0 ? i : 0
var previousActives = sidebar.querySelectorAll(`.active`)
;[].forEach.call(previousActives, function (h) {
h.classList.remove('active')
})
document.querySelector('span[data-target-id="' + toc[index].id + '"]').classList.add('active')
}
break
};
};
});
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long