Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94e23bb916 | ||
|
|
21fb4f817c | ||
|
|
2c509c329f | ||
|
|
4769c31d09 |
16
.github/workflows/build_docker.yml
vendored
16
.github/workflows/build_docker.yml
vendored
@@ -3,6 +3,8 @@ name: build_docker
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
release:
|
||||
types: [created] # 表示在创建新的 Release 时触发
|
||||
|
||||
jobs:
|
||||
build_docker:
|
||||
@@ -11,11 +13,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: chenzhaoyu94/chatgpt-web
|
||||
|
||||
- run: |
|
||||
echo "本次构建的版本为:${GITHUB_REF_NAME} (但是这个变量目前上下文中无法获取到)"
|
||||
echo 本次构建的版本为:${{ github.ref_name }}
|
||||
env
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
@@ -32,6 +34,8 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:${{ github.ref_name }}
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:latest
|
||||
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -29,6 +29,7 @@
|
||||
"dockerhub",
|
||||
"esno",
|
||||
"GPTAPI",
|
||||
"hljs",
|
||||
"iconify",
|
||||
"logprobs",
|
||||
"nodata",
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,3 +1,18 @@
|
||||
## v2.8.2
|
||||
|
||||
`2023-02-28`
|
||||
### Enhancement
|
||||
- 代码主题调整为 `One Dark - light|dark` 适配深色模式
|
||||
### BugFix
|
||||
- 修复普通文本代码渲染和深色模式下的问题[#139][#154]
|
||||
|
||||
## v2.8.1
|
||||
|
||||
`2023-02-27`
|
||||
|
||||
### BugFix
|
||||
- 修复 `API` 版本不是 `Markdown` 时,普通 `HTML` 代码会被渲染的问题 [#146]
|
||||
|
||||
## v2.8.0
|
||||
|
||||
`2023-02-27`
|
||||
|
||||
@@ -174,7 +174,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
image: chenzhaoyu94/chatgpt-web:main
|
||||
image: chenzhaoyu94/chatgpt-web # 总是使用latest,更新时重新pull该tag镜像即可
|
||||
ports:
|
||||
- 3002:3002
|
||||
environment:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chatgpt-web",
|
||||
"version": "2.8.0",
|
||||
"version": "2.8.2",
|
||||
"private": false,
|
||||
"description": "ChatGPT Web",
|
||||
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
||||
|
||||
23
src/directives/highlight.ts
Normal file
23
src/directives/highlight.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { App, Directive } from 'vue'
|
||||
import hljs from 'highlight.js'
|
||||
import { includeCode } from '@/utils/format'
|
||||
|
||||
hljs.configure({ ignoreUnescapedHTML: true })
|
||||
|
||||
function highlightCode(el: HTMLElement) {
|
||||
if (includeCode(el.textContent))
|
||||
hljs.highlightBlock(el)
|
||||
}
|
||||
|
||||
export default function setupHighlightDirective(app: App) {
|
||||
const highLightDirective: Directive<HTMLElement> = {
|
||||
mounted(el: HTMLElement) {
|
||||
highlightCode(el)
|
||||
},
|
||||
updated(el: HTMLElement) {
|
||||
highlightCode(el)
|
||||
},
|
||||
}
|
||||
|
||||
app.directive('highlight', highLightDirective)
|
||||
}
|
||||
@@ -1 +1,6 @@
|
||||
export function setupDirectives() {}
|
||||
import type { App } from 'vue'
|
||||
import setupHighlightDirective from './highlight'
|
||||
|
||||
export function setupDirectives(app: App) {
|
||||
setupHighlightDirective(app)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { setupDirectives } from './directives'
|
||||
import { setupAssets } from '@/plugins'
|
||||
import { setupStore } from '@/store'
|
||||
import { setupRouter } from '@/router'
|
||||
@@ -10,6 +11,8 @@ async function bootstrap() {
|
||||
|
||||
setupStore(app)
|
||||
|
||||
setupDirectives(app)
|
||||
|
||||
await setupRouter(app)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'highlight.js/styles/xcode.css'
|
||||
import '@/styles/lib/tailwind.css'
|
||||
import '@/styles/lib/highlight.less'
|
||||
import '@/styles/lib/github-markdown.less'
|
||||
import '@/styles/global.less'
|
||||
|
||||
|
||||
203
src/styles/lib/highlight.less
Normal file
203
src/styles/lib/highlight.less
Normal file
@@ -0,0 +1,203 @@
|
||||
html.dark {
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em
|
||||
}
|
||||
|
||||
code.hljs {
|
||||
padding: 3px 5px
|
||||
}
|
||||
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em
|
||||
}
|
||||
|
||||
code.hljs {
|
||||
padding: 3px 5px
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: #383a42;
|
||||
background: #fafafa
|
||||
}
|
||||
|
||||
.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
|
||||
}
|
||||
}
|
||||
15
src/utils/format/index.ts
Normal file
15
src/utils/format/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// 转义 HTML 字符
|
||||
export function encodeHTML(source: string) {
|
||||
return source
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
// 判断是否为代码块
|
||||
export function includeCode(text: string | null | undefined) {
|
||||
const regexp = /^(?:\s{4}|\t).+/gm
|
||||
return !!(text?.includes(' = ') || text?.match(regexp))
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
function includeCode(text: string | null | undefined) {
|
||||
const regexp = /^(?:\s{4}|\t).+/gm
|
||||
return !!(text?.includes(' = ') || text?.match(regexp))
|
||||
}
|
||||
|
||||
export default includeCode
|
||||
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import hljs from 'highlight.js'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
import { encodeHTML } from '@/utils/format'
|
||||
|
||||
interface Props {
|
||||
inversion?: boolean
|
||||
@@ -15,8 +16,21 @@ const props = defineProps<Props>()
|
||||
|
||||
const { isMobile } = useBasicLayout()
|
||||
|
||||
const renderer = new marked.Renderer()
|
||||
|
||||
renderer.html = (html) => {
|
||||
return `<p>${encodeHTML(html)}</p>`
|
||||
}
|
||||
|
||||
renderer.code = (code, language) => {
|
||||
const validLang = !!(language && hljs.getLanguage(language))
|
||||
if (validLang)
|
||||
return `<pre><code class="hljs ${language}">${hljs.highlight(language, code).value}</code></pre>`
|
||||
return `<pre style="background: none">${hljs.highlightAuto(code).value}</pre>`
|
||||
}
|
||||
|
||||
marked.setOptions({
|
||||
renderer: new marked.Renderer(),
|
||||
renderer,
|
||||
highlight(code) {
|
||||
return hljs.highlightAuto(code).value
|
||||
},
|
||||
@@ -35,9 +49,10 @@ const wrapClass = computed(() => {
|
||||
})
|
||||
|
||||
const text = computed(() => {
|
||||
if (props.text && !props.inversion)
|
||||
return marked(props.text)
|
||||
return props.text
|
||||
const value = props.text ?? ''
|
||||
if (!props.inversion)
|
||||
return marked(value)
|
||||
return value
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -48,7 +63,7 @@ const text = computed(() => {
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="leading-relaxed break-all">
|
||||
<pre v-if="!inversion" class="markdown-body" v-html="text" />
|
||||
<div v-if="!inversion" class="markdown-body" v-html="text" />
|
||||
<div v-else class="whitespace-pre-wrap" v-text="text" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
background-color: transparent;
|
||||
font-size: 14px;
|
||||
|
||||
p{
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -23,13 +23,16 @@
|
||||
pre {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.dark{
|
||||
.markdown-body{
|
||||
.highlight pre,
|
||||
pre {
|
||||
background-color: #18181c;
|
||||
}
|
||||
code.hljs{
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
html.dark {
|
||||
|
||||
.highlight pre,
|
||||
pre {
|
||||
background-color: #282c34;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user