上一节编写关于编写使用正则正则转换标题、图片链接,网页链接、代码块的例子,接下继续编写其拓展。
Markdown-富文本转换(1)
粗体&斜体
/**
* **粗体**
* *斜体*
* @param {Object} msg
*/
function transitionFontBoldItalic(msg) {
var reg = /(\*{1,3})(.*?)(\*{1,3})/g;
var objExp = new RegExp(reg);
var now = msg.replace(objExp, function($1, $2, $3, $4) {
if($2 == '***') {//粗体&斜体
$3 = '<b><i>' + $3 + '</i></b>'
} else if($2 == '**') {//粗体
$3 = '<b>' + $3 + '</b>'
} else if($2 == '*') {//斜体
$3 = '<i>' + $3 + '</i>'
}
return $3;
});
return now;
}
调用规则:
斜体:文本文字*
粗体:文本文字
粗体+斜体:文本文字
解析:
1.粗体转换标签采用<b></b>,不使用css来转换,是为了减少代码块。
2.斜体转换标签采用<i></i>,使用原因同理
3.正则/{1,3}/代表可能会有1~3个字符
引用
js代码块
/**
* ^引用
* @param {Object} msg
*/
function transitionQuote(msg) {
var reg = /(\^)(.*?)(<div><br><\/div>)/g;
var objExp = new RegExp(reg);
var now = msg.replace(objExp, function($1, $2, $3, $4) {
console.log($2)
$3 = '<div class="quote-content" ><p class="quote-content-before"></p>' + $3 + '</div>' + $4
return $3;
});
return now;
}
css代码块
.quote-content {
display: flex;
align-items: center;
background-color: #EDEDED;
line-height: 3rem;
width: 100%;
}
.quote-content-before {
width: 5px;
line-height: 3rem;
min-height: 3rem;
background-color: #CCCCCC;
margin: 0;
margin-right: 1rem;
}
*调用规则:^文本文字
*解析:
字体颜色
/**
* @字体颜色@rgb(FFFF00)
* @param {Object} msg
*/
function transitionFontColor(msg) {
var reg = /(@)(.*?)(@rgb)\(([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\)/g;
var objExp = new RegExp(reg);
var now = msg.replace(objExp, function($1, $2, $3, $4, $5) {
$3 = '<span style="color:#'+$5+'" >' + $3 + '</span>';
return $3;
});
return now;
}
*调用规则:@字体颜色@rgb(颜色16进制代码)
eg:@字体颜色@rgb(FF0000)
解析:
1.匹配颜色代码采用[0-9a-fA-F]{6}|[0-9a-fA-F]{3},可以匹配6位16进制数与3位16进制数
2.采用@与@rgb是为了更加形象不会影响其他字数的书写转换
var data="#@我是红色@rgb(FF0000)<div><br></div>"+
"#@我是绿色@rgb(00FF00)<div><br></div>"+
"#@我是蓝色@rgb(0000FF)<div><br></div>";
var content=transitionTitle(data);//转换标题
content=transitionFontColor(content);
document.getElementById("content").innerHTML =content;
Markdown 富文本编辑器实例
编辑器组件——WriteTool.vue
<template>
<div class="write-content-boxs">
<div class="write-header">
<div class="write-title">
<input class="edit-title" v-model.lazy="title" type="text" placeholder="请输入标题" />
</div>
<div class="write-tools">
<span class="item-tool" @click="chooseType">插入图片</span>
<span class="item-tool" @click="onSaveArticle">保存文章</span>
<span class="item-tool" @click="onIssueArticle">发布文章</span>
<span class="item-tool" @click="onPreview">预览模式</span>
</div>
</div>
<div class="write-content-box">
<div id="mediaedit" class="write-content" contenteditable="true" @mouseup="selectText()">
</div>
</div>
<input type="file" id="upload_file" class="input-file" style="display: none;" name="avatar" ref="avatarInput" @change="fileChange($event)"
accept="image/gif,image/jpeg,image/jpg,image/png">
</div>
</template>
<script>
export default {
name: 'write-content',
data() {
return {
imgList: [],
size: 0,
limit: 6,
desc: '',
text: this.content,
cursortPosition: null
}
},
watch: {
//普通的watch监听
title(val, oldVal) {
console.log("a: " + val, oldVal);
},
content(value, oldVal) {
if(this.text!=value){
this.text = value;
this.updataContent(value);
}
}
},
props: {
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
}
},
mounted() {
var that = this;
var mediaedit = document.getElementById("mediaedit");
mediaedit.addEventListener(
"DOMSubtreeModified",
function(evt) {
that.text = mediaedit.innerHTML;
that.cursortPosition= window.getSelection().getRangeAt(0);
setTimeout(function() {
that.$emit('onchange', that.text);
}, 200)
},
false
);
mediaedit.onclick = function() {
that.cursortPosition= window.getSelection().getRangeAt(0);
}
mediaedit.onkeyup = function() {
that.cursortPosition= window.getSelection().getRangeAt(0);
}
},
methods: {
//打开预览视图
onPreview: function() {
this.$emit('preview', 0);
},//保存文章
onSaveArticle: function() {
let content = this.write.startTransition(this.text);
let param = {
title: this.title,
content: content
}
this.$emit('onsave', param);
},//发布文章
onIssueArticle: function() {
let content = this.write.startTransition(this.text);
let param = {
title: this.title,
content: content
}
this.$emit('onissue', param);
},//更新内容
updataContent: function(data) {
var scope = document.getElementById("mediaedit");
scope.innerHTML = data;
// 获取选定对象
var selection = window.getSelection();
// 判断是否有最后光标对象存在
if (this.cursortPosition!=null) {
// 存在最后光标对象,选定对象清除所有光标并添加最后光标还原之前的状态
selection.removeAllRanges()
selection.addRange(this.cursortPosition)
}
//this.setCaretPosition(scope, this.cursortPosition);
}, //获取光标
getCursortPosition: function(ctrl) {
var CaretPos = 0; // IE Support
if (document.selection) {
ctrl.focus();
var Sel = document.selection.createRange();
Sel.moveStart('character', -ctrl.value.length);
CaretPos = Sel.text.length;
}
// Firefox support
else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
CaretPos = ctrl.selectionStart;
}
return CaretPos;
},//设置光标
setCaretPosition: function(ctrl, position) {
if (document.all) {
ctrl.range = document.selection.createRange();
ctrl.range.select();
ctrl.range.moveStart("character", -1);
} else {
try {
ctrl.range = window.getSelection().getRangeAt(0);
//var words = value.replace(/<[^>]+>/g, "");
//words = words.replace(/\s*/g,"");
ctrl.range.setStart(ctrl.range.startContainer, position);
} catch (e) {
//TODO handle the exception
}
}
},
insertHtmlAtCursor: function(html) {
var range, node;
if (window.getSelection && window.getSelection().getRangeAt) {
range = window.getSelection().getRangeAt(0);
node = range.createContextualFragment(html);
range.insertNode(node);
} else if (document.selection && document.selection.createRange) {
document.selection.createRange().pasteHTML(html);
}
},
chooseType() {
document.getElementById("upload_file").click();
},
fileChange(el) {
if (!el.target.files[0].size) return;
this.fileList(el.target);
el.target.value = "";
},
fileList(fileList) {
let files = fileList.files;
for (let i = 0; i < files.length; i++) {
//判断是否为文件夹
if (files[i].type != "") {
this.fileAdd(files[i]);
} else {
//文件夹处理
this.folders(fileList.items[i]);
}
}
},
//文件夹处理
folders(files) {
let _this = this; //判断是否为原生file
if (files.kind) {
files = files.webkitGetAsEntry();
}
files.createReader().readEntries(function(file) {
for (let i = 0; i < file.length; i++) {
if (file[i].isFile) {
_this.foldersAdd(file[i]);
} else {
_this.folders(file[i]);
}
}
});
},
foldersAdd(entry) {
let _this = this;
entry.file(function(file) {
if (_this.limit !== undefined) _this.limit--;
if (_this.limit !== undefined && _this.limit < 0) return; //总大小
_this.size = _this.size + file.size;
_this.fileAdd(file);
});
},
fileAdd(file) {
//判断是否为图片文件
if (file.type.indexOf("image") == -1) {
this.$dialog.toast({
mes: "请选择图片文件"
});
} else {
let reader = new FileReader();
let image = new Image();
let _this = this;
reader.readAsDataURL(file);
reader.onload = function() {
file.src = this.result;
image.onload = function() {
let width = image.width;
let height = image.height;
file.width = width;
file.height = height;
_this.imgList.push({
file
});
console.log(_this.imgList);
};
image.src = file.src;
_this.$emit('loadimg', file.src);
};
}
},
delImg(index) {
this.size = this.size - this.imgList[index].file.size;
//总大小
this.imgList.splice(index, 1);
if (this.limit !== undefined) this.limit = 6 - this.imgList.length;
},
selectText: function() {
if (document.Selection) {
//ie浏览器
this.desc = document.selection.createRange().text;
} else {
//标准浏览器
this.desc = window.getSelection().toString();
}
}
}
}
</script>
<style scoped="scoped">
@import url("../css/write.css");
.write-content-boxs {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #FFFFFF;
overflow-x: hidden;
overflow-y: hidden;
}
.write-content-box {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #FFFFFF;
overflow-x: hidden;
overflow-y: hidden;
}
.edit-title {
width: 100%;
padding: 1.8rem;
font-size: 1.5rem;
border: none;
}
.write-title {
border-bottom: solid 1px gainsboro;
}
.write-tools {
background-color: #545c64;
width: 100%;
text-align: center;
line-height: 1rem;
padding: 1rem;
}
.write-tools span {
color: white;
font-size: 1rem;
border-right: solid 1px white;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.write-content {
width: 96%;
height: 96%;
text-align: left;
font-size: 1.2rem;
-webkit-overflow-scrolling: touch;
overflow-x: hidden;
overflow-y: scroll;
padding: 2%;
}
</style>
markdown转换组件
js代码块——write.vue
<script type="text/javascript">
/**
* 转换标题
* @param {Object} msg
*/
function transitionTitle(msg) {
var reg = /(#{1,5})(.*?)(<div><br><\/div>)/g;
var objExp = new RegExp(reg);
var value = msg.match(objExp);
var now = msg.replace(objExp, function($1, $2, $3, $4) {
if ($2.length == 1) {
$2 = "<h1>"
$4 = "</h1>" + $4;
} else if ($2.length == 2) {
$2 = "<h2>";
$4 = "</h2>" + $4;
} else if ($2.length == 3) {
$2 = "<h3>";
$4 = "</h3>" + $4;
} else if ($2.length == 4) {
$2 = "<h4>";
$4 = "</h4>" + $4;
} else if ($2.length == 5) {
$2 = "<h5>"
$4 = "</h5>" + $4;
}
return $2 + $3 + $4;
});
return now;
}
/**
* ![转换图片](url)
* @param {Object} msg
*/
function transitionImage(msg) {
var reg = /!\[(.*?)]\((.*?)\)/g;
var objExp = new RegExp(reg);
var now = msg.replace(objExp, function($1, $2, $3) {
$3 = '<img class="image-src" src="' + $3 + '" />'
$2 = '<h5 class="image-title">' + $2 + '</h5>';
return $3 + $2;
});
return now;
}
/**
* [转换地址]()
* @param {Object} msg
*/
function transitionHref(msg) {
var reg = /\[(.*?)]\((.*?)\)/g;
var objExp = new RegExp(reg);
var now = msg.replace(objExp, function($1, $2, $3) {
$2 = '<a href="' + $3 + '" >' + $2 + '</a>'
return $2;
});
return now;
}
/**
* **粗体**
* @param {Object} msg
*/
function transitionFontBoldItalic(msg) {
var reg = /(\*{1,2})(.*?)(\*{1,2})/g;
var objExp = new RegExp(reg);
var now = msg.replace(objExp, function($1, $2, $3, $4) {
if ($2 == '***') {
$3 = '<b><i>' + $3 + '</i></b>'
} else if ($2 == '**') {
$3 = '<b>' + $3 + '</b>'
} else if ($2 == '*') {
$3 = '<i>' + $3 + '</i>'
}
return $3;
});
return now;
}
/**
* ```
* 转换代码块
* ```
* @param {Object} msg
*/
function transitionCode(msg) {
var reg = /(```<div><br><\/div>)(.*?)(```<div><br><\/div>)/g;
var objExp = new RegExp(reg);
var now = msg.replace(objExp, function($1, $2, $3, $4) {
$3 = '<div class="code-content" >' + $3 + '</div>'
return $3;
});
return now;
}
/**
* ^引用
* @param {Object} msg
*/
function transitionQuote(msg) {
var reg = /(\^)(.*?)(<div><br><\/div>)/g;
var objExp = new RegExp(reg);
var now = msg.replace(objExp, function($1, $2, $3, $4) {
console.log($2)
$3 = '<div class="quote-content" ><p class="quote-content-before"></p>' + $3 + '</div>' + $4
return $3;
});
return now;
}
/**
* @字体颜色@rgb(FFFF00)
* @param {Object} msg
*/
function transitionFontColor(msg) {
var reg = /(@)(.*?)(@rgb)\(([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\)/g;
var objExp = new RegExp(reg);
var now = msg.replace(objExp, function($1, $2, $3, $4, $5) {
console.log($5)
console.log($3)
$3 = '<span style="color:#'+$5+'" >' + $3 + '</span>';
return $3;
});
return now;
}
function startTransition(value) {
var data = transitionCode(value);
data = transitionTitle(data);
data = transitionFontBoldItalic(data);
data = transitionQuote(data);
data = transitionImage(data);
data = transitionHref(data);
data = transitionFontColor(data);
return data;
}
export default {
startTransition,
transitionCode,
transitionTitle,
transitionFontBoldItalic,
transitionQuote,
transitionImage,
transitionHref,
transitionFontColor
}
</script>
css代码块——write.css
#content {
width: 100%;
padding: 10rem;
height: 100%;
margin: 0;
padding: 0;
}
.code-content {
background-color: #666666;
width: 100%;
padding: 1rem;
color: #CCCCCC;
}
.quote-content {
display: flex;
align-items: center;
background-color: #EDEDED;
line-height: 3rem;
width: 100%;
}
.quote-content-before {
width: 5px;
line-height: 3rem;
min-height: 3rem;
background-color: #CCCCCC;
margin: 0;
margin-right: 1rem;
}
.image-title {
text-align: center;
font-size: 0.75rem;
color: #CCCCCC;
}
.image-src {
width: 100%;
padding: 4px;
}
Markdown预览预览代码——PreView.vue
<template>
<div id="page" class="page write-page">
<el-row>
<el-col :span="12">
<div class="grid-content content-left">
<writetool :title="articleTitle" :content="content" @onchange="onInput" @loadimg="onRequetImage" @onsave="onSaveArticle"
@onissue="onIssueArticle"></writetool>
</div>
</el-col>
<el-col :span="12">
<h3>{{articleTitle}}</h3>
<div id="content" class="write-content"></div>
</el-col>
</el-row>
</div>
</template>
<script>
import writetool from '../element/WriteTool.vue'
export default {
components: {
writetool
},
name: "page",
data() {
return {
size: 0,
articleTitle: "",
content: "",
desc: "测试内容测试内容测试内容测试内容。",
userinfo: {},
artId: 0,
uid: "MnF/9XPJEprPvk/EwSLBAg=="
};
},
watch: {
//普通的watch监听
articleTitle(val, oldVal) {
},
content(val, oldVal) {
// console.log("a: " + val, this.artId);
}
},
created() {
this.artId = this.$route.params.id;
this.initPage();
},
mounted: function() {
},
methods: {
initPage: function() {
let params = {
id: this.artId
};
console.log(params);
var that = this;
this.$axios.post(this.umcons.serviceAddress.ARTICLE, params).then(
function(response) {
console.log(response);
var result = response.data.data;
var article = result.article;
that.artId = article.id;
that.clickItem(article.artTitle, article.artDesc);
}.bind(this)
);
},
onInput: function(text) {
this.content = text;
var data = this.write.startTransition(text);
this.setTextContent(data)
},
handleSelect(key, keyPath) {
console.log(key, keyPath);
},
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
},
handleHrefUrlClose(done) {
},
setTextContent: function(text) {
var mediaedit = document.getElementById("content");
mediaedit.innerHTML = text;
},
clickItem: function(title, content) {
this.articleTitle = title;
this.content = content;
var data = this.write.startTransition(content);
this.setTextContent(data);
}
/**
* 保存文章
*/
,
onSaveArticle: function(data) {
let params = {
id: this.artId,
title: data.title,
context: data.content,
uid: this.uid
};
console.log(params);
var that = this;
this.$axios.post(this.umcons.serviceAddress.UPDATA_ARTICLE, params).then(
function(response) {
var result = response.data.data;
console.log(result);
if (result == undefined) {
that.$message(response.data.msg);
} else {
//that.articleList[that.artId] = result;
that.$message("提交成功");
}
}.bind(this)
);
},
/**
* @param {Object} data发布文章
*/
onIssueArticle: function(data) {
let params = {
id: this.artId,
uid: this.uid,
is_release: 2,
title: data.title,
context: data.content
};
var that = this;
this.$axios.post(this.umcons.serviceAddress.CHANGE_RELEASE, params).then(
function(response) {
var result = response.data.data;
console.log(result);
if (result == undefined) {
that.$message(response.data.msg);
} else {
that.$message("发布成功");
setTimeout(that.toDetails(result.id), 2000);
}
}.bind(this)
);
},
toDetails: function(id) {
this.$router.push({
name: "ArticleDetails",
params: {
id: id
}
});
},
onRequetImage: function(imageSrc) {
let params = {
file: imageSrc
};
var that = this;
this.$axios.post(this.umcons.serviceAddress.UPLOAD_BASE, params).then(
function(response) {
var result = response.data.data;
if (result == undefined) {
that.$message(response.data.msg);
} else {
let title = result.split("/");
that.content += "![" + title[title.length - 1] + "](" + result + ")";
//console.log(that.content);
}
}.bind(this)
);
},
selectText: function() {
if (document.Selection) {
//ie浏览器
this.desc = document.selection.createRange().text;
} else {
//标准浏览器
this.desc = window.getSelection().toString();
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
@import url("../css/app.css");
.el-row {
padding: 0px;
margin: 0px;
width: 100%;
height: 100%;
}
.write-page {
overflow-y: hidden;
}
.el-col {
border-radius: 4px;
padding: 0px;
margin: 0px;
height: 100%;
position: relative;
border-left: solid 1px #DCDCDC;
}
.grid-content {
width: 100%;
overflow-y: hidden;
}
.content-left,
.write-content {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #FFFFFF;
overflow-x: hidden;
}
.content-left {
overflow-y: hidden;
position: relative;
width: 100%;
}
.write-content {
width: 98%;
text-align: left;
font-size: 1.2rem;
-webkit-overflow-scrolling: touch;
overflow-x: scroll;
padding: 1rem;
}
</style>
备注:
1.案例基于vue+element开发,如果需要体验的朋友可以自行搭建环境
2.图片上传可以基于自己服务器上传,每次上传图片会重新刷新内容从而添加图片地址
3.关于光标定位,插入指定光标位置后续再完善。
4.mardown实际转换过程中,还有还有其余问题后期再继续完善更新。
网友评论