mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-13 03:52:50 +00:00
perf: 消息api支持markdown
This commit is contained in:
parent
d2e04843a4
commit
a5be461021
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
@ -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: {
|
||||
|
||||
1102
resources/assets/sass/pages/components/dialog-markdown/github.less
Normal file
1102
resources/assets/sass/pages/components/dialog-markdown/github.less
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user