mirror of https://github.com/halo-dev/halo
pref: improve code block styling in editor (#6089)
#### What type of PR is this? /kind improvement /area editor #### What this PR does / why we need it: 优化编辑器代码块样式。 before: <img width="907" alt="image" src="https://github.com/halo-dev/halo/assets/31335418/11ad91a9-75ce-42ec-a947-effca7b42f30"> after: <img width="932" alt="image" src="https://github.com/halo-dev/halo/assets/31335418/d0b3275b-a269-4104-aea8-0d8726ce32e7"> #### How to test it? 测试复制功能是否正常。 #### Does this PR introduce a user-facing change? ```release-note 优化默认编辑器代码块样式 ```pull/6081/head
parent
10f3157258
commit
1e37768b35
|
@ -4,6 +4,10 @@ import type { Editor, Node } from "@/tiptap/vue-3";
|
||||||
import { NodeViewContent, NodeViewWrapper } from "@/tiptap/vue-3";
|
import { NodeViewContent, NodeViewWrapper } from "@/tiptap/vue-3";
|
||||||
import lowlight from "./lowlight";
|
import lowlight from "./lowlight";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
import BxBxsCopy from "~icons/bx/bxs-copy";
|
||||||
|
import IconCheckboxCircle from "~icons/ri/checkbox-circle-line";
|
||||||
|
import { useTimeout } from "@vueuse/core";
|
||||||
|
import { i18n } from "@/locales";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
|
@ -28,24 +32,53 @@ const selectedLanguage = computed({
|
||||||
props.updateAttributes({ language: language });
|
props.updateAttributes({ language: language });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { ready, start } = useTimeout(2000, { controls: true, immediate: false });
|
||||||
|
|
||||||
|
const handleCopyCode = () => {
|
||||||
|
if (!ready.value) return;
|
||||||
|
const code = props.node.textContent;
|
||||||
|
navigator.clipboard.writeText(code).then(() => {
|
||||||
|
start();
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<node-view-wrapper as="div" class="code-node">
|
<node-view-wrapper as="div" class="code-node border-[1px] rounded mt-3">
|
||||||
<div class="py-1.5">
|
<div
|
||||||
<select
|
class="bg-neutral-100 border-b-[1px] border-b-gray-100 py-1 flex items-center justify-between rounded-t"
|
||||||
v-model="selectedLanguage"
|
>
|
||||||
contenteditable="false"
|
<div class="flex-1 flex items-center pl-3">
|
||||||
class="block px-2 py-1.5 text-sm text-gray-900 border border-gray-300 rounded-md bg-gray-50 focus:ring-blue-500 focus:border-blue-500"
|
<select
|
||||||
>
|
v-model="selectedLanguage"
|
||||||
<option :value="null">auto</option>
|
contenteditable="false"
|
||||||
<option
|
class="block !leading-8 text-sm text-gray-900 border select-none border-transparent rounded-md bg-transparent focus:ring-blue-500 focus:border-blue-500 cursor-pointer hover:bg-zinc-200"
|
||||||
v-for="(language, index) in languages"
|
|
||||||
:key="index"
|
|
||||||
:value="language"
|
|
||||||
>
|
>
|
||||||
{{ language }}
|
<option :value="null">auto</option>
|
||||||
</option>
|
<option
|
||||||
</select>
|
v-for="(language, index) in languages"
|
||||||
|
:key="index"
|
||||||
|
:value="language"
|
||||||
|
>
|
||||||
|
{{ language }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="pr-3 flex items-center">
|
||||||
|
<div
|
||||||
|
v-tooltip="
|
||||||
|
ready
|
||||||
|
? i18n.global.t('editor.common.codeblock.copy_code')
|
||||||
|
: i18n.global.t('editor.common.codeblock.copy_code_success')
|
||||||
|
"
|
||||||
|
class="w-8 h-8 cursor-pointer rounded flex items-center justify-center"
|
||||||
|
:class="{ 'hover:bg-zinc-200': ready }"
|
||||||
|
@click="handleCopyCode"
|
||||||
|
>
|
||||||
|
<IconCheckboxCircle v-if="!ready" class="w-4 h-4 text-green-500" />
|
||||||
|
<BxBxsCopy v-else class="w-4 h-4 text-gray-500" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<pre><node-view-content as="code" class="hljs" /></pre>
|
<pre><node-view-content as="code" class="hljs" /></pre>
|
||||||
</node-view-wrapper>
|
</node-view-wrapper>
|
||||||
|
|
|
@ -205,7 +205,7 @@ export default CodeBlockLowlight.extend<
|
||||||
editor,
|
editor,
|
||||||
isActive: editor.isActive("codeBlock"),
|
isActive: editor.isActive("codeBlock"),
|
||||||
icon: markRaw(MdiCodeBracesBox),
|
icon: markRaw(MdiCodeBracesBox),
|
||||||
title: i18n.global.t("editor.common.codeblock"),
|
title: i18n.global.t("editor.common.codeblock.title"),
|
||||||
action: () => editor.chain().focus().toggleCodeBlock().run(),
|
action: () => editor.chain().focus().toggleCodeBlock().run(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -214,7 +214,7 @@ export default CodeBlockLowlight.extend<
|
||||||
return {
|
return {
|
||||||
priority: 80,
|
priority: 80,
|
||||||
icon: markRaw(MdiCodeBracesBox),
|
icon: markRaw(MdiCodeBracesBox),
|
||||||
title: "editor.common.codeblock",
|
title: "editor.common.codeblock.title",
|
||||||
keywords: ["codeblock", "daimakuai"],
|
keywords: ["codeblock", "daimakuai"],
|
||||||
command: ({ editor, range }: { editor: Editor; range: Range }) => {
|
command: ({ editor, range }: { editor: Editor; range: Range }) => {
|
||||||
editor.chain().focus().deleteRange(range).setCodeBlock().run();
|
editor.chain().focus().deleteRange(range).setCodeBlock().run();
|
||||||
|
@ -229,7 +229,7 @@ export default CodeBlockLowlight.extend<
|
||||||
props: {
|
props: {
|
||||||
editor,
|
editor,
|
||||||
icon: markRaw(MdiCodeBracesBox),
|
icon: markRaw(MdiCodeBracesBox),
|
||||||
title: i18n.global.t("editor.common.codeblock"),
|
title: i18n.global.t("editor.common.codeblock.title"),
|
||||||
action: () => {
|
action: () => {
|
||||||
editor.chain().focus().setCodeBlock().run();
|
editor.chain().focus().setCodeBlock().run();
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ import "./styles/index.scss";
|
||||||
import "./styles/tailwind.css";
|
import "./styles/tailwind.css";
|
||||||
import "floating-vue/dist/style.css";
|
import "floating-vue/dist/style.css";
|
||||||
import "github-markdown-css/github-markdown-light.css";
|
import "github-markdown-css/github-markdown-light.css";
|
||||||
import "highlight.js/styles/github-dark.css";
|
import "highlight.js/styles/github.css";
|
||||||
|
|
||||||
const plugin: Plugin = {
|
const plugin: Plugin = {
|
||||||
install(app: App) {
|
install(app: App) {
|
||||||
|
|
|
@ -104,7 +104,10 @@ editor:
|
||||||
code: Code
|
code: Code
|
||||||
superscript: Super Script
|
superscript: Super Script
|
||||||
subscript: Sub Script
|
subscript: Sub Script
|
||||||
codeblock: Code block
|
codeblock:
|
||||||
|
title: Code block
|
||||||
|
copy_code: Copy code
|
||||||
|
copy_code_success: Copy success
|
||||||
image: Image
|
image: Image
|
||||||
heading:
|
heading:
|
||||||
title: Text type
|
title: Text type
|
||||||
|
|
|
@ -104,7 +104,10 @@ editor:
|
||||||
code: 行内代码
|
code: 行内代码
|
||||||
superscript: 上角标
|
superscript: 上角标
|
||||||
subscript: 下角标
|
subscript: 下角标
|
||||||
codeblock: 代码块
|
codeblock:
|
||||||
|
title: 代码块
|
||||||
|
copy_code: 复制代码
|
||||||
|
copy_code_success: 复制成功
|
||||||
image: 图片
|
image: 图片
|
||||||
heading:
|
heading:
|
||||||
title: 文本类型
|
title: 文本类型
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
background: #0d0d0d;
|
background-color: transparent;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,4 @@
|
||||||
@import "./columns.scss";
|
@import "./columns.scss";
|
||||||
@import "./search.scss";
|
@import "./search.scss";
|
||||||
@import "./format-brush.scss";
|
@import "./format-brush.scss";
|
||||||
|
@import "./node-select.scss";
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
.halo-rich-text-editor {
|
||||||
|
$editorNodeCardBorderSelected: rgba(47, 142, 244);
|
||||||
|
|
||||||
|
.has-node-selected {
|
||||||
|
&.code-node {
|
||||||
|
border-color: $editorNodeCardBorderSelected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue