Skip to content

Commit ecf2389

Browse files
committed
feat: 增加代码块以及行内代码功能
1 parent 2eabc6a commit ecf2389

23 files changed

+1073
-100
lines changed
Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,109 @@
1-
.writing-editor .tiptap pre {
1+
.writing-editor .tiptap :not(pre) > code {
2+
display: inline-block;
3+
margin: 0 0.1rem;
4+
border: 1px solid oklch(var(--border));
5+
border-radius: 0.375rem;
6+
background: oklch(var(--muted));
7+
color: oklch(var(--foreground));
8+
padding: 0.1rem 0.35rem;
9+
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
10+
font-size: 0.85em;
11+
line-height: 1.4;
12+
vertical-align: baseline;
13+
}
14+
15+
.writing-editor .tiptap pre,
16+
.writing-editor .tiptap pre.ws-code-block {
217
margin: 0.8rem 0;
3-
padding: 0.75rem 0.9rem;
4-
border-radius: 0.5rem;
5-
background: oklch(var(--secondary));
18+
padding: 0.9rem 1rem;
19+
border: 1px solid oklch(var(--border));
20+
border-radius: 0.625rem;
21+
background: oklch(var(--muted));
22+
color: oklch(var(--foreground));
623
overflow-x: auto;
24+
min-height: 4.5rem;
25+
}
26+
27+
.writing-editor .tiptap pre code,
28+
.writing-editor .tiptap pre.ws-code-block code {
29+
display: block;
30+
min-width: max-content;
31+
margin: 0;
32+
border: 0;
33+
border-radius: 0;
34+
background: transparent;
35+
padding: 0;
36+
color: inherit;
37+
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
38+
font-size: 0.875rem;
39+
line-height: 1.6;
40+
}
41+
42+
.writing-editor .tiptap pre[data-wrap="true"],
43+
.writing-editor .tiptap pre.ws-code-block[data-wrap="true"] {
44+
overflow-x: hidden;
45+
}
46+
47+
.writing-editor .tiptap pre[data-wrap="true"] code,
48+
.writing-editor .tiptap pre.ws-code-block[data-wrap="true"] code {
49+
min-width: 0;
50+
white-space: pre-wrap;
51+
word-break: break-word;
52+
}
53+
54+
.writing-editor .tiptap pre code .hljs-comment,
55+
.writing-editor .tiptap pre code .hljs-quote {
56+
color: oklch(var(--muted-foreground));
57+
}
58+
59+
.writing-editor .tiptap pre code .hljs-keyword,
60+
.writing-editor .tiptap pre code .hljs-selector-tag,
61+
.writing-editor .tiptap pre code .hljs-literal,
62+
.writing-editor .tiptap pre code .hljs-type {
63+
color: oklch(var(--primary));
64+
font-weight: 600;
65+
}
66+
67+
.writing-editor .tiptap pre code .hljs-string,
68+
.writing-editor .tiptap pre code .hljs-regexp,
69+
.writing-editor .tiptap pre code .hljs-symbol,
70+
.writing-editor .tiptap pre code .hljs-bullet,
71+
.writing-editor .tiptap pre code .hljs-addition {
72+
color: oklch(0.62 0.17 142);
73+
}
74+
75+
.writing-editor .tiptap pre code .hljs-number,
76+
.writing-editor .tiptap pre code .hljs-meta,
77+
.writing-editor .tiptap pre code .hljs-variable,
78+
.writing-editor .tiptap pre code .hljs-template-variable {
79+
color: oklch(0.68 0.16 35);
80+
}
81+
82+
.writing-editor .tiptap pre code .hljs-title,
83+
.writing-editor .tiptap pre code .hljs-section,
84+
.writing-editor .tiptap pre code .hljs-attr,
85+
.writing-editor .tiptap pre code .hljs-attribute,
86+
.writing-editor .tiptap pre code .hljs-name {
87+
color: oklch(0.64 0.14 250);
88+
}
89+
90+
.ws-code-block-menu {
91+
display: inline-flex;
92+
align-items: center;
93+
gap: 0.25rem;
94+
border: 1px solid oklch(var(--border));
95+
border-radius: 0.625rem;
96+
background: oklch(var(--popover) / 0.98);
97+
padding: 0.25rem;
98+
box-shadow: 0 10px 24px -12px oklch(0 0 0 / 0.45);
99+
backdrop-filter: blur(4px);
100+
}
101+
102+
.ws-code-block-menu .ws-code-block-language-trigger {
103+
min-width: 5rem;
104+
justify-content: space-between;
105+
}
106+
107+
.ws-code-block-menu .ws-code-block-icon-button {
108+
color: oklch(var(--muted-foreground));
7109
}

app/components/paper/WritingStudio.vue

Lines changed: 33 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
<script lang="ts" setup>
22
import { EditorContent } from "@tiptap/vue-3";
3-
import { BubbleMenu } from "@tiptap/vue-3/menus";
43
import { DragHandle } from "@tiptap/extension-drag-handle-vue-3";
54
import {
65
AlignCenter,
76
AlignJustify,
87
AlignLeft,
98
AlignRight,
109
Bold,
11-
Code2,
1210
Highlighter,
1311
Italic,
1412
Link2,
@@ -19,7 +17,10 @@ import {
1917
Type,
2018
Underline as UnderlineIcon,
2119
} from "lucide-vue-next";
20+
import WritingStudioCodeToolbar from "@/components/paper/WritingStudioCodeToolbar.vue";
21+
import WritingStudioCodeBlockBubbleMenu from "@/components/paper/WritingStudioCodeBlockBubbleMenu.vue";
2222
import WritingStudioImageGroup from "@/components/paper/WritingStudioImageGroup.vue";
23+
import WritingStudioImageNodeBubbleMenu from "@/components/paper/WritingStudioImageNodeBubbleMenu.vue";
2324
import { Button } from "@/components/ui/button";
2425
import {
2526
DropdownMenu,
@@ -72,6 +73,8 @@ const {
7273
toggleOrderedList,
7374
toggleTaskList,
7475
toggleCodeBlock,
76+
setCodeBlockLanguage,
77+
setCodeBlockWrap,
7578
toggleDetails,
7679
setHardBreak,
7780
setHorizontalRule,
@@ -106,7 +109,6 @@ type ParagraphHeadingValue =
106109
107110
type ListTypeValue = "bulletList" | "orderedList" | "taskList";
108111
type TextAlignValue = "left" | "center" | "right" | "justify";
109-
type ImageAlignValue = "left" | "center" | "right";
110112
111113
const headingLevelMap: Record<Exclude<ParagraphHeadingValue, "paragraph">, 1 | 2 | 3 | 4 | 5 | 6> = {
112114
heading1: 1,
@@ -200,23 +202,6 @@ const currentTextAlign = (): TextAlignValue => {
200202
return "left";
201203
};
202204
203-
const currentImageAlign = (): ImageAlignValue => {
204-
const alignment = editor.value?.getAttributes("image").align;
205-
206-
if (alignment === "left" || alignment === "center" || alignment === "right") {
207-
return alignment;
208-
}
209-
210-
return "center";
211-
};
212-
213-
const shouldShowImageMenu = ({ editor: currentEditor }: any) => {
214-
return currentEditor.isEditable && currentEditor.isActive("image");
215-
};
216-
217-
const preventImageMenuMouseDown = (event: MouseEvent) => {
218-
event.preventDefault();
219-
};
220205
</script>
221206

222207
<template>
@@ -370,16 +355,14 @@ const preventImageMenuMouseDown = (event: MouseEvent) => {
370355

371356
<Separator orientation="vertical" class="mx-1 h-6" />
372357

373-
<Button
374-
variant="ghost"
375-
size="sm"
358+
<WritingStudioCodeToolbar
376359
:disabled="!editor"
377-
:class="toolbarButtonClass(isMarkActive('code'))"
378-
@click="toggleCode"
379-
>
380-
<Code2 />
381-
{{ t("writingStudio.toolbar.marks.code") }}
382-
</Button>
360+
:is-code-active="isMarkActive('code')"
361+
:is-code-block-active="isNodeActive('codeBlock')"
362+
:dropdown-item-class="dropdownItemClass"
363+
@toggle-code="toggleCode"
364+
@toggle-code-block="toggleCodeBlock"
365+
/>
383366

384367
<Button
385368
variant="ghost"
@@ -454,12 +437,6 @@ const preventImageMenuMouseDown = (event: MouseEvent) => {
454437
>
455438
{{ t("writingStudio.toolbar.block.blockquote") }}
456439
</DropdownMenuItem>
457-
<DropdownMenuItem
458-
:class="dropdownItemClass(isNodeActive('codeBlock'))"
459-
@select.prevent="toggleCodeBlock"
460-
>
461-
{{ t("writingStudio.toolbar.block.codeBlock") }}
462-
</DropdownMenuItem>
463440
<DropdownMenuItem
464441
:class="dropdownItemClass(isNodeActive('details'))"
465442
@select.prevent="toggleDetails"
@@ -588,61 +565,28 @@ const preventImageMenuMouseDown = (event: MouseEvent) => {
588565
</DropdownMenu>
589566
</div>
590567

568+
<WritingStudioImageNodeBubbleMenu
569+
:editor="editor"
570+
:toolbar-button-class="toolbarButtonClass"
571+
:set-image-align="setImageAlign"
572+
/>
573+
<WritingStudioCodeBlockBubbleMenu
574+
:editor="editor"
575+
:set-code-block-language="setCodeBlockLanguage"
576+
:set-code-block-wrap="setCodeBlockWrap"
577+
/>
578+
579+
<DragHandle
580+
v-if="editor"
581+
:editor="editor"
582+
class="ws-drag-handle"
583+
:nested="dragHandleNestedOptions"
584+
:compute-position-config="{ placement: 'left-start' }"
585+
:on-element-drag-start="handleDragHandleStart"
586+
>
587+
<GripVertical class="ws-drag-handle-icon" :size="16" :stroke-width="2.5" aria-hidden="true" />
588+
</DragHandle>
591589
<div class="rounded-lg border bg-background px-4 py-3 shadow-sm">
592-
<BubbleMenu
593-
v-if="editor"
594-
plugin-key="writing-studio-image-menu"
595-
:editor="editor"
596-
:should-show="shouldShowImageMenu"
597-
:options="{ placement: 'top', offset: 10 }"
598-
>
599-
<div class="ws-image-menu" @mousedown="preventImageMenuMouseDown">
600-
<Button
601-
variant="ghost"
602-
size="sm"
603-
:class="toolbarButtonClass(currentImageAlign() === 'left')"
604-
:title="t('writingStudio.toolbar.align.left')"
605-
:aria-label="t('writingStudio.toolbar.align.left')"
606-
@click="setImageAlign('left')"
607-
>
608-
<AlignLeft />
609-
</Button>
610-
611-
<Button
612-
variant="ghost"
613-
size="sm"
614-
:class="toolbarButtonClass(currentImageAlign() === 'center')"
615-
:title="t('writingStudio.toolbar.align.center')"
616-
:aria-label="t('writingStudio.toolbar.align.center')"
617-
@click="setImageAlign('center')"
618-
>
619-
<AlignCenter />
620-
</Button>
621-
622-
<Button
623-
variant="ghost"
624-
size="sm"
625-
:class="toolbarButtonClass(currentImageAlign() === 'right')"
626-
:title="t('writingStudio.toolbar.align.right')"
627-
:aria-label="t('writingStudio.toolbar.align.right')"
628-
@click="setImageAlign('right')"
629-
>
630-
<AlignRight />
631-
</Button>
632-
633-
</div>
634-
</BubbleMenu>
635-
636-
<DragHandle
637-
v-if="editor"
638-
:editor="editor"
639-
class="ws-drag-handle"
640-
:nested="dragHandleNestedOptions"
641-
:compute-position-config="{ placement: 'left-start' }"
642-
:on-element-drag-start="handleDragHandleStart"
643-
>
644-
<GripVertical class="ws-drag-handle-icon" :size="16" :stroke-width="2.5" aria-hidden="true" />
645-
</DragHandle>
646590

647591
<EditorContent :editor="editor" class="writing-editor" />
648592
</div>

0 commit comments

Comments
 (0)