perf: 消息api支持markdown

This commit is contained in:
kuaifan 2023-04-03 22:43:38 +08:00
parent d2e04843a4
commit a5be461021
9 changed files with 1507 additions and 10 deletions

View File

@ -20,7 +20,6 @@ use Carbon\Carbon;
use DB;
use Redirect;
use Request;
use Str;
/**
* @apiDefine dialog
@ -700,6 +699,7 @@ class DialogController extends AbstractController
$text = trim(Request::input('text'));
$text_type = strtolower(trim(Request::input('text_type')));
$silence = in_array(strtolower(trim(Request::input('silence'))), ['yes', 'true', '1']);
$markdown = in_array($text_type, ['md', 'markdown']);
//
WebSocketDialog::checkDialog($dialog_id);
//
@ -711,11 +711,9 @@ class DialogController extends AbstractController
$action = "";
}
//
if (in_array($text_type, ['md', 'markdown'])) {
$text = Str::markdown($text);
$text = preg_replace("/\>\r?\n\s*+\</", "><", $text);
if (!$markdown) {
$text = WebSocketDialogMsg::formatMsg($text, $dialog_id);
}
$text = WebSocketDialogMsg::formatMsg($text, $dialog_id);
$strlen = mb_strlen($text);
$noimglen = mb_strlen(preg_replace("/<img[^>]*?>/i", "", $text));
if ($strlen < 1) {
@ -735,8 +733,9 @@ class DialogController extends AbstractController
if (empty($size)) {
return Base::retError('消息发送保存失败');
}
$ext = $markdown ? 'md' : 'htm';
$fileData = [
'name' => "LongText-{$strlen}.htm",
'name' => "LongText-{$strlen}.{$ext}",
'size' => $size,
'file' => $file,
'path' => $path,
@ -744,12 +743,16 @@ class DialogController extends AbstractController
'thumb' => '',
'width' => -1,
'height' => -1,
'ext' => 'htm',
'ext' => $ext,
];
return WebSocketDialogMsg::sendMsg($action, $dialog_id, 'file', $fileData, $user->userid, false, false, $silence);
}
//
return WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', ['text' => $text], $user->userid, false, false, $silence);
$msgData = ['text' => $text];
if ($markdown) {
$msgData['type'] = 'md';
}
return WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', $msgData, $user->userid, false, false, $silence);
}
/**

View File

@ -19,6 +19,7 @@
},
"devDependencies": {
"@chenfengyuan/vue-qrcode": "^1.0.2",
"@traptitech/markdown-it-katex": "^3.6.0",
"autoprefixer": "^10.4.13",
"axios": "^0.24.0",
"cross-env": "^7.0.3",
@ -27,6 +28,7 @@
"echarts": "^5.2.2",
"element-ui": "git+https://github.com/kuaifan/element.git#master",
"file-loader": "^6.2.0",
"highlight.js": "^11.7.0",
"inquirer": "^8.2.0",
"internal-ip": "^6.2.0",
"jquery": "^3.6.4",
@ -36,6 +38,8 @@
"less-loader": "^10.2.0",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"markdown-it-link-attributes": "^4.0.1",
"moment": "^2.29.1",
"node-sass": "^6.0.1",
"notification-koro1": "^1.1.1",

View File

@ -0,0 +1,99 @@
<template>
<div class="markdown-body" v-html="html"></div>
</template>
<script>
import '../../../../sass/pages/components/dialog-markdown/markdown.less'
import MarkdownIt from 'markdown-it'
import mdKatex from '@traptitech/markdown-it-katex'
import mila from 'markdown-it-link-attributes'
import hljs from 'highlight.js'
export default {
name: "DialogMarkdown",
props: {
text: {
type: String,
default: ''
},
},
data() {
return {
mdi: null,
}
},
mounted() {
this.copyCodeBlock()
},
updated() {
this.copyCodeBlock()
},
computed: {
html() {
const {text} = this
if (this.mdi === null) {
const {highlightBlock} = this
this.mdi = new MarkdownIt({
linkify: true,
highlight(code, language) {
const validLang = !!(language && hljs.getLanguage(language))
if (validLang) {
const lang = language ?? ''
return highlightBlock(hljs.highlight(code, {language: lang}).value, lang)
}
return highlightBlock(hljs.highlightAuto(code).value, '')
},
})
this.mdi.use(mila, {attrs: {target: '_blank', rel: 'noopener'}})
this.mdi.use(mdKatex, {blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000'})
}
return this.mdi.render(text)
}
},
methods: {
highlightBlock(str, lang = '') {
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${this.$L('复制代码')}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
},
copyCodeBlock() {
const codeBlockWrapper = this.$el.querySelectorAll('.code-block-wrapper')
codeBlockWrapper.forEach((wrapper) => {
const copyBtn = wrapper.querySelector('.code-block-header__copy')
const codeBlock = wrapper.querySelector('.code-block-body')
if (copyBtn && codeBlock && copyBtn.getAttribute("data-copy") !== "click") {
copyBtn.setAttribute("data-copy", "click")
copyBtn.addEventListener('click', () => {
if (navigator.clipboard?.writeText)
navigator.clipboard.writeText(codeBlock.textContent ?? '')
else
this.copyText({text: codeBlock.textContent ?? '', origin: true})
})
}
})
},
copyText(options) {
const props = {origin: true, ...options}
let input
if (props.origin)
input = document.createElement('textarea')
else
input = document.createElement('input')
input.setAttribute('readonly', 'readonly')
input.value = props.text
document.body.appendChild(input)
input.select()
if (document.execCommand('copy'))
document.execCommand('copy')
document.body.removeChild(input)
}
}
}
</script>

View File

@ -18,7 +18,8 @@
<div class="dialog-content" :class="contentClass">
<!--文本-->
<div v-if="msgData.type === 'text'" class="content-text no-dark-content">
<pre @click="viewText" v-html="$A.formatTextMsg(msgData.msg.text, userId)"></pre>
<DialogMarkdown v-if="msgData.msg.type === 'md'" @click="viewText" :text="msgData.msg.text"/>
<pre v-else @click="viewText" v-html="$A.formatTextMsg(msgData.msg.text, userId)"></pre>
</div>
<!--文件-->
<div v-else-if="msgData.type === 'file'" :class="`content-file ${msgData.msg.type}`">
@ -177,10 +178,11 @@ import WCircle from "../../../components/WCircle";
import {mapGetters, mapState} from "vuex";
import {Store} from "le5le-store";
import longpress from "../../../directives/longpress";
import DialogMarkdown from "./DialogMarkdown.vue";
export default {
name: "DialogView",
components: {WCircle},
components: {DialogMarkdown, WCircle},
directives: {longpress},
props: {
msgData: {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,203 @@
body.dark-mode-reverse {
.markdown-body {
pre code.hljs {
display: block;
overflow-x: auto;
}
.hljs {
color: #abb2bf;
background: #282c34
}
.hljs-keyword,
.hljs-operator,
.hljs-pattern-match {
color: #f92672
}
.hljs-function,
.hljs-pattern-match .hljs-constructor {
color: #61aeee
}
.hljs-function .hljs-params {
color: #a6e22e
}
.hljs-function .hljs-params .hljs-typing {
color: #fd971f
}
.hljs-module-access .hljs-module {
color: #7e57c2
}
.hljs-constructor {
color: #e2b93d
}
.hljs-constructor .hljs-string {
color: #9ccc65
}
.hljs-comment,
.hljs-quote {
color: #b18eb1;
font-style: italic
}
.hljs-doctag,
.hljs-formula {
color: #c678dd
}
.hljs-deletion,
.hljs-name,
.hljs-section,
.hljs-selector-tag,
.hljs-subst {
color: #e06c75
}
.hljs-literal {
color: #56b6c2
}
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #98c379
}
.hljs-built_in,
.hljs-class .hljs-title,
.hljs-title.class_ {
color: #e6c07b
}
.hljs-attr,
.hljs-number,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-pseudo,
.hljs-template-variable,
.hljs-type,
.hljs-variable {
color: #d19a66
}
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-symbol,
.hljs-title {
color: #61aeee
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: 700
}
.hljs-link {
text-decoration: underline
}
}
}
body {
.markdown-body {
pre code.hljs {
display: block;
overflow-x: auto;
}
code.hljs {
&::-webkit-scrollbar {
height: 4px;
}
}
.hljs {
color: #383a42;
background: #ffffff
}
.hljs-comment,
.hljs-quote {
color: #a0a1a7;
font-style: italic
}
.hljs-doctag,
.hljs-formula,
.hljs-keyword {
color: #a626a4
}
.hljs-deletion,
.hljs-name,
.hljs-section,
.hljs-selector-tag,
.hljs-subst {
color: #e45649
}
.hljs-literal {
color: #0184bb
}
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #50a14f
}
.hljs-attr,
.hljs-number,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-pseudo,
.hljs-template-variable,
.hljs-type,
.hljs-variable {
color: #986801
}
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-symbol,
.hljs-title {
color: #4078f2
}
.hljs-built_in,
.hljs-class .hljs-title,
.hljs-title.class_ {
color: #c18401
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: 700
}
.hljs-link {
text-decoration: underline
}
}
}

View File

@ -0,0 +1,81 @@
@import "highlight";
@import "github";
body {
&.dark-mode-reverse {
.markdown-body {
color: #ffffff;
.highlight pre,
pre {
background-color: #282c34;
}
}
}
.markdown-body {
color: #303133;
background-color: transparent;
font-size: 14px;
p {
white-space: pre-wrap;
}
ol {
list-style-type: decimal;
}
ul {
list-style-type: disc;
}
pre code,
pre tt {
line-height: 1.65;
}
.highlight pre,
pre {
background-color: #fff;
}
code.hljs {
padding: 0;
}
.code-block {
&-wrapper {
position: relative;
padding-top: 24px;
}
&-header {
position: absolute;
top: 5px;
right: 0;
width: 100%;
padding: 0 1rem;
display: flex;
justify-content: flex-end;
align-items: center;
color: #b3b3b3;
&__copy {
cursor: pointer;
margin-left: 0.5rem;
user-select: none;
&:hover {
color: #65a665;
}
}
}
}
}
.self {
.markdown-body {
color: #ffffff;
}
}
}

View File

@ -510,6 +510,7 @@
min-width: 32px;
border-radius: 2px 8px 8px 8px;
transition: box-shadow 0.3s ease;
max-width: 100%;
&.transparent {
background-color: transparent !important;
@ -586,6 +587,7 @@
.content-text {
color: $primary-title-color;
padding: 2px;
max-width: 100%;
> pre {
display: block;

View File

@ -94,6 +94,7 @@
--header 'token: <span style="color:#84c56a">{机器人Token}</span>' \
--form 'dialog_id="<span style="color:#84c56a">{对话ID}</span>"' \
--form 'text="<span style="color:#84c56a">{消息内容}</span>"'
--form 'text_type="<span style="color:#84c56a">[md|html]</span>"'
--form 'silence="<span style="color:#84c56a">[yes|no]</span>"'
<b>Webhook说明</b>