支持代码高亮

This commit is contained in:
linghaihui 2023-03-23 14:06:20 +08:00
parent f93d65abe6
commit d0cb25240b
14 changed files with 775 additions and 5 deletions

View File

@ -1,4 +1,4 @@
page {
background-color: #fff;
font-size: 28rpx;
font-size: 28rpx;
}

View File

@ -1,4 +1,6 @@
{
"component": true,
"usingComponents": {}
"usingComponents": {
"wemark": "../wemark/wemark"
}
}

View File

@ -1,3 +1,4 @@
<wxs src="../../tools.wxs" module="tools" />
<view wx:if="{{chatList.length == 0}}" style="text-align:center;color: rgb(180, 187, 196);font-size: 28rpx;">输入问题开始和New Bing聊天吧~</view>
<scroll-view class="chat" scroll-y="true" scroll-into-view="{{scrollId}}" style="height:{{systemInfo.windowHeight - 70}}px;" enable-back-to-top="{{true}}" scroll-anchoring="{{true}}" enhanced="{{true}}" enable-flex="{{true}}">
<view wx:for="{{chatList}}" wx:key="index" wx:for-item="item" id="{{'item'+index}}">
@ -5,7 +6,7 @@
<image class="avatar" src="{{item.avatarUrl}}" style="display: flex;" bindlongpress="clearChat" data-index="{{index}}"></image>
<view class="chat-box" style="margin-left: 20rpx;">
<text class="dt">{{item.dt}}</text>
<view class="content bg-white" bindlongpress="copyContent" data-index="{{index}}"><view class="{{item.blink ? 'blinking': ''}}">{{item.originContent}}</view></view>
<view class="content bg-white" bindlongpress="copyContent" data-index="{{index}}"><view class="{{item.blink ? 'blinking': ''}}"><wemark md="{{item.originContent}}" link="{{false}}" highlight type="wemark" wx:if="{{tools.indexOf(item.originContent, '```') || tools.indexOf(item.originContent, '**')}}"></wemark><view wx:else>{{item.originContent}}</view></view></view>
<view class="suggest" >
<text class="suggest-item" bindtap="suggestSubmit" data-suggest="{{suggest}}" wx:for="{{item.suggests}}" wx:for-item="suggest">{{suggest}}</text>
</view>

View File

@ -0,0 +1,262 @@
var Remarkable = require('./remarkable');
var parser = new Remarkable({
html: true
});
var prism = require('./prism');
function parse(md, options){
if(!options) options = {};
var tokens = parser.parse(md, {});
// markdwon渲染列表
var renderList = [];
var env = [];
// 记录当前list深度
var listLevel = 0;
// 记录第N级ol的顺序
var orderNum = [0, 0];
var tmp;
// 获取inline内容
var getInlineContent = function(inlineToken){
var ret = [];
var env;
var tokenData = {};
if(inlineToken.type === 'htmlblock'){
// 匹配video
// 兼容video[src]和video > source[src]
var videoRegExp = /<video.*?src\s*=\s*['"]*([^\s^'^"]+).*?(poster\s*=\s*['"]*([^\s^'^"]+).*?)?(?:\/\s*>|<\/video>)/g;
var match;
var html = inlineToken.content.replace(/\n/g, '');
while(match = videoRegExp.exec(html)){
if(match[1]){
var retParam = {
type: 'video',
src: match[1]
};
if(match[3]) {
retParam.poster = match[3];
}
ret.push(retParam);
}
}
}else{
// console.log(inlineToken);
inlineToken.children && inlineToken.children.forEach(function(token, index){
if(['text', 'code'].indexOf(token.type) > -1){
ret.push({
type: env || token.type,
content: token.content,
data: tokenData
});
env = '';
tokenData = {};
}else if(token.type === 'del_open'){
env = 'deleted';
}else if (token.type === 'softbreak') {
// todo:处理li的问题
/* ret.push({
type: 'text',
content: ' '
}); */
}else if (token.type === 'hardbreak') {
ret.push({
type: 'text',
content: '\n'
});
}else if(token.type === 'strong_open'){
if(env === 'em') {
env = 'strong_em';
}else {
env = 'strong';
}
}else if (token.type === 'em_open') {
if(env === 'strong') {
env = 'strong_em';
}else {
env = 'em';
}
}else if (token.type === 'link_open') {
if(options.link){
env = 'link';
tokenData = {
href: token.href
};
}
}else if(token.type === 'image'){
ret.push({
type: token.type,
src: token.src
});
}
});
}
return ret;
};
var getBlockContent = function(blockToken, index, firstInLi){
if(blockToken.type === 'htmlblock'){
return getInlineContent(blockToken);
}else if(blockToken.type === 'heading_open'){
return {
type: 'h' + blockToken.hLevel,
content: getInlineContent(tokens[index+1])
};
}else if(blockToken.type === 'paragraph_open'){
// var type = 'p';
var prefix = '';
if(env.length){
prefix = env.join('_') + '_';
}
var content = getInlineContent(tokens[index+1]);
// 处理ol前的数字
if(env[env.length - 1] === 'li' && env[env.length - 2] === 'ol'){
let prefix = ' ';
if (firstInLi){
prefix = orderNum[listLevel - 1] + '. ';
}
content.unshift({
type:'text',
content: prefix
});
}
return {
type: prefix + 'p',
content: content
};
}else if(blockToken.type === 'fence' || blockToken.type === 'code'){
content = blockToken.content;
var highlight = false;
if(options.highlight && blockToken.params && prism.languages[blockToken.params]){
content = prism.tokenize(content, prism.languages[blockToken.params]);
highlight = true;
}
const flattenTokens = (tokensArr, result = [], parentType = '') => {
if (Array.isArray(tokensArr)) {
tokensArr.forEach(el => {
if (typeof el === 'object') {
// el.type = parentType + ' wemark_inline_code_' + el.type;
if(Array.isArray(el.content)){
flattenTokens(el.content, result, el.type);
}else{
flattenTokens(el, result, el.type);
}
} else {
const obj = {};
obj.type = parentType || 'text';
// obj.type = parentType + ' wemark_inline_code_';
obj.content = el;
result.push(obj);
}
})
return result
} else {
result.push(tokensArr)
return result
}
}
if(highlight){
var tokenList = content;
content = [];
tokenList.forEach((token) => {
// let contentListForToken = [];
if(Array.isArray(token.content)){
content = content.concat(flattenTokens(token.content, [], ''));
}else{
content.push(token);
}
});
}
// flatten nested tokens in html
// if (blockToken.params === 'html') {
// content = flattenTokens(content)
// }
// console.log(content);
return {
type: 'code',
highlight: highlight,
content: content
};
}else if(blockToken.type === 'bullet_list_open'){
env.push('ul');
listLevel++;
}else if(blockToken.type === 'ordered_list_open'){
env.push('ol');
listLevel++;
}else if(blockToken.type === 'list_item_open'){
env.push('li');
if(env[env.length - 2] === 'ol' ){
orderNum[listLevel - 1]++;
}
}else if(blockToken.type === 'list_item_close'){
env.pop();
}else if(blockToken.type === 'bullet_list_close'){
env.pop();
listLevel--;
}else if(blockToken.type === 'ordered_list_close'){
env.pop();
listLevel--;
orderNum[listLevel] = 0;
}else if(blockToken.type === 'blockquote_open'){
env.push('blockquote');
}else if(blockToken.type === 'blockquote_close'){
env.pop();
}else if(blockToken.type === 'tr_open'){
tmp = {
type: 'table_tr',
content:[]
};
return tmp;
}else if(blockToken.type === 'th_open'){
tmp.content.push({
type: 'table_th',
content: getInlineContent(tokens[index+1]).map(function(inline){return inline.content;}).join('')
});
}else if(blockToken.type === 'td_open'){
tmp.content.push({
type: 'table_td',
content: getInlineContent(tokens[index+1]).map(function(inline){return inline.content;}).join('')
});
}
};
tokens.forEach(function(token, index){
// 标记是否刚进入li如果刚进入可以加符号/序号,否则不加
var firstInLi = false;
if(token.type === 'paragraph_open' && tokens[index-1] && tokens[index-1].type === 'list_item_open'){
firstInLi = true;
}
var blockContent = getBlockContent(token, index, firstInLi);
if(!blockContent) return;
if(!Array.isArray(blockContent)){
blockContent = [blockContent];
}
blockContent.forEach(function(block){
if(Array.isArray(block.content)){
block.isArray = true;
}else{
block.isArray = false;
}
renderList.push(block);
});
});
return renderList;
}
module.exports = {
parse: parse
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,81 @@
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+basic+markup-templating+go+java+json+php+sql+python+typescript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
.wemark_inline_code_comment,
.wemark_inline_code_prolog,
.wemark_inline_code_doctype,
.wemark_inline_code_cdata {
color: slategray;
}
.wemark_inline_code_punctuation,
.wemark_inline_code_interpolation-punctuation {
color: #999;
}
.wemark_inline_code_namespace {
opacity: .7;
}
.wemark_inline_code_property,
.wemark_inline_code_tag,
.wemark_inline_code_boolean,
.wemark_inline_code_number,
.wemark_inline_code_constant,
.wemark_inline_code_symbol,
.wemark_inline_code_deleted {
color: #905;
}
.wemark_inline_code_selector,
.wemark_inline_code_attr-name,
.wemark_inline_code_string,
.wemark_inline_code_char,
.wemark_inline_code_builtin,
.wemark_inline_code_inserted {
color: #690;
}
.wemark_inline_code_operator,
.wemark_inline_code_entity,
.wemark_inline_code_url,
.language-css .wemark_inline_code_string,
.style .wemark_inline_code_string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.wemark_inline_code_atrule,
.wemark_inline_code_attr-value,
.wemark_inline_code_keyword {
color: #07a;
}
.wemark_inline_code_function,
.wemark_inline_code_class-name {
color: #DD4A68;
}
.wemark_inline_code_regex,
.wemark_inline_code_important,
.wemark_inline_code_variable,
.wemark_inline_code_interpolation {
color: #e90;
}
.wemark_inline_code_important,
.wemark_inline_code_bold {
font-weight: bold;
}
.wemark_inline_code_italic {
font-style: italic;
}
.wemark_inline_code_entity {
cursor: help;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,155 @@
exports.getRichTextNodes = function(parsedData){
var richTextNodes = [];
var getNodeName = (function(){
var stack = [];
return function(type, nodeType = 'inline'){
if(type === 'table_tr'){
return 'tr';
}else{
// 有多级的block返回第一级inline返回最后一级
if(type.indexOf('_') > -1){
var typePart = type.split('_');
if(nodeType === 'inline'){
return typePart.pop();
}else{
return typePart[0];
}
}
}
return type;
};
})();
var getBlockNode = function(node){
var nodeType = node.type;
// console.log('nodeType:', nodeType);
var richTextNode = {
name: getNodeName(nodeType, 'inline'),
attrs: {
class: 'wemark_block_' + nodeType
},
children: []
};
if(node.isArray){
node.content.forEach((childNode) => {
if(['text','code','strong','deleted','em'].indexOf(childNode.type) > -1){
richTextNode.children.push({
name: 'span',
attrs: {
class: 'wemark_inline_' + childNode.type
},
children:[{
type: 'text',
text: childNode.content
}]
});
}else if(node.highlight){
if(typeof childNode === 'string'){
richTextNode.children.push({
name: 'span',
attrs: {
class: 'wemark_inline_code_text'
},
children: [{
type: 'text',
text: childNode
}]
});
}else{
richTextNode.children.push({
name: 'span',
attrs: {
class: 'wemark_inline_code_' + childNode.type
},
children: [{
type: 'text',
text: childNode.content
}]
});
}
}else if(childNode.type === 'link'){
richTextNode.children.push({
name: 'a',
attrs: {
class: 'wemark_inline_link',
href: childNode.data.href
},
children:[{
type: 'text',
text: childNode.content
}]
});
}else if(childNode.type === 'image'){
richTextNode.children.push({
name: 'img',
attrs: {
mode: 'widthFix',
class: 'wemark_inline_image',
src: childNode.src
}
});
}else if(childNode.type === 'table_th'){
richTextNode.children.push({
name: 'th',
attrs: {
class: 'wemark_inline_table_th',
},
children: [{
type: 'text',
text: childNode.content
}]
});
}else if(childNode.type === 'table_td'){
richTextNode.children.push({
name: 'td',
attrs: {
class: 'wemark_inline_table_td',
},
children: [{
type: 'text',
text: childNode.content
}]
});
}
});
}else if(node.type === 'code'){
richTextNode.children = [{
name: 'code',
children: [{
type: 'text',
text: node.content
}]
}];
}
return richTextNode;
}
for(var i=0; i<parsedData.length;i++){
var node = parsedData[i];
if(node.type === 'table_tr'){
var tableNode = {
name: 'table',
attrs: {
class: 'wemark_block_table'
},
children: []
};
var tmpNode = node;
while(tmpNode.type === 'table_tr'){
tableNode.children.push(getBlockNode(tmpNode));
tmpNode = parsedData[++i];
}
richTextNodes.push(tableNode);
}else{
richTextNodes.push(getBlockNode(node));
}
}
return richTextNodes;
}

View File

@ -0,0 +1,71 @@
const parser = require('./parser');
const getRichTextNodes = require('./richtext').getRichTextNodes;
Component({
properties: {
md: {
type: String,
value: '',
observer(){
this.parseMd();
}
},
type: {
type: String,
value: 'wemark'
},
link: {
type: Boolean,
value: false
},
highlight: {
type: Boolean,
value: false
}
},
data: {
parsedData: {},
richTextNodes: []
},
methods: {
parseMd(){
if (this.data.md) {
var parsedData = parser.parse(this.data.md, {
link: this.data.link,
highlight: this.data.highlight
});
// console.log('parsedData:', parsedData);
if(this.data.type === 'wemark'){
this.setData({
parsedData
});
}else{
// var inTable = false;
var richTextNodes = getRichTextNodes(parsedData);
// console.log('richTextNodes:', richTextNodes);
this.setData({
richTextNodes
});
/* // 分批更新
var update = {};
var batchLength = 1000;
console.log(batchLength);
for(var i=0; i<richTextNodes.length; i++){
update['richTextNodes.' + i] = richTextNodes[i];
if(i%batchLength === batchLength - 1){
console.log(update);
this.setData(update);
update = {};
}
}
this.setData(update);
update = {}; */
}
}
}
}
});

View File

@ -0,0 +1,3 @@
{
"component": true
}

View File

@ -0,0 +1,19 @@
<view class="wemark_wrapper">
<block wx:if="{{type === 'wemark'}}" wx:for="{{parsedData}}" wx:key="blockIndex" wx:for-index="blockIndex" wx:for-item="renderBlock">
<view class="wemark_block_{{renderBlock.type}}">
<block wx:if="{{renderBlock.isArray}}" wx:for="{{renderBlock.content}}" wx:key="inlineIndex" wx:for-index="inlineIndex" wx:for-item="renderInline">
<text class="wemark_inline_{{renderInline.type}}" wx:if="{{renderInline.type === 'text' || renderInline.type === 'code' || renderInline.type === 'strong' || renderInline.type === 'strong_em' || renderInline.type === 'deleted' || renderInline.type === 'em' || renderInline.type === 'table_th' || renderInline.type === 'table_td'}}">{{renderInline.content}}</text>
<!-- 代码高亮 -->
<text class="wemark_inline_code_{{renderInline.type}}" wx:if="{{renderInline.type&&renderBlock.highlight}}">{{renderInline.content}}</text>
<text class="wemark_inline_code_text" wx:if="{{!renderInline.type}}">{{renderInline}}</text>
<navigator class="wemark_inline_link" url="{{renderInline.data.href}}" wx:if="{{renderInline.type === 'link'}}">{{renderInline.content}}</navigator>
<image mode="widthFix" class="wemark_inline_image" src="{{renderInline.src}}" wx:if="{{renderInline.type === 'image'}}"></image>
</block>
<block wx:if="{{!renderBlock.isArray}}">
<view wx:if="{{renderBlock.type === 'code'}}">{{renderBlock.content}}</view>
<video wx:if="{{renderBlock.type == 'video'}}" class="wemark_block_video" src="{{renderBlock.src}}" poster="{{renderBlock.poster}}" controls></video>
</block>
</view>
</block>
<rich-text class="wemark_wrapper_richtext" wx:if="{{type === 'rich-text'}}" nodes="{{richTextNodes}}"></rich-text>
</view>

View File

@ -0,0 +1,148 @@
@import "prism.wxss";
.wemark_wrapper{
/* margin:10px 0;
font-size:32rpx;
line-height: 1.8em; */
}
.wemark_block_h1{
font-size:40rpx;
text-align: center;
margin-bottom:1em;
}
.wemark_block_h2{
font-size:40rpx;
padding-bottom:.5em;
margin-top:1em;
margin-bottom:1em;
border-bottom:1px solid #f8f8f8;
}
.wemark_block_h3{
font-size:36rpx;
margin-top: 1em;
margin-bottom: 1em;
}
.wemark_block_h4,
.wemark_block_h5,
.wemark_block_h6{
font-weight: bold;
margin-top: 1em;
margin-bottom: 1em;
}
.wemark_block_p{
margin-bottom: 4px;
}
.wemark_block_video{
margin-top:1em;
margin-bottom:1em;
width:100%;
}
.wemark_block_blockquote_p{
margin-top:1em;
margin-bottom:1em;
padding:10px 0 10px 1em;
font-size:28rpx;
background:#f8f8f8;
border-left:5px solid #e0e0e0;
}
.wemark_block_ul_li_p::before{
content:'• ';
}
.wemark_block_ul_li_ul_li_p::before,
.wemark_block_ol_li_ul_li_p::before{
content:'◦ ';
}
.wemark_block_ul_li_ul_li_p,
.wemark_block_ul_li_ol_li_p,
.wemark_block_ol_li_ul_li_p,
.wemark_block_ol_li_ol_li_p{
padding-left: 1em;
}
.wemark_block_ul_li_p:last{
margin-bottom:1em;
}
.wemark_block_code{
display: block;
padding:10px;
font-size:28rpx;
line-height: 1.5em;
border-radius: 10rpx;
white-space: pre;
overflow: auto;
background-color: #f8f8f8;
}
.wemark_block_table{
width: 100%;
border-spacing: 0;
border-collapse: collapse;
/* border-left: 1px solid #e0e0e0; */
/* border-top: 1px solid #e0e0e0; */
}
.wemark_block_table_tr{
display: flex;
}
.wemark_wrapper_richtext .wemark_block_table_tr{
display: table-row;
}
.wemark_inline_table_th,
.wemark_inline_table_td{
flex:1;
padding:5px;
font-size:28rpx;
word-break: break-all;
border: 1px solid #e0e0e0;
}
.wemark_wrapper_richtext .wemark_inline_table_th,
.wemark_wrapper_richtext .wemark_inline_table_td{
display: table-cell;
/* background:red;
border-right:1px solid #e0e0e0;
border-bottom:1px solid #e0e0e0; */
}
.wemark_inline_table_td:last{
/* border-top:1px solid #e0e0e0; */
}
.wemark_inline_table_th{
background:#f8f8f8;
/* border-top:1px solid #e0e0e0; */
}
.wemark_inline_strong{
font-weight: bold;
padding:0 5px;
word-wrap:break-word;
}
.wemark_inline_em{
font-style: italic;
padding:0 5px;
word-wrap:break-word;
}
.wemark_inline_strong_em{
font-style: italic;
font-weight: bold;
padding:0 5px;
word-wrap:break-word;
}
.wemark_inline_deleted{
text-decoration: line-through;
padding:0 5px;
word-wrap:break-word;
}
.wemark_inline_image{
width:100%;
height:auto;
}
.wemark_inline_code{
background-color: #fff5f5;
padding: 3px;
word-wrap:break-word;
border-radius: 5px;
color: #ff502c;
}
.wemark_inline_text{
word-wrap:break-word;
}
.wemark_inline_link{
display: inline;
color: blue;
word-wrap:break-word;
}

View File

@ -48,7 +48,7 @@ Page({
InputBlur(e) {
this.setData({
InputBottom: 24,
});
});
},
processContent(content) {
return content.replace(/\\n/g, "\n");

8
bingchat/tools.wxs Normal file
View File

@ -0,0 +1,8 @@
function indexOf(s, value) {
if (s.indexOf(value) < 0) {
return false
} else {
return true
}
}
module.exports.indexOf = indexOf