xfy 5dceb39544 feat(editor): 数学公式节点带 KaTeX 预览,根治编辑器序列化破坏 LaTeX
问题:经 Tiptap 编辑器保存的数学公式,矩阵/分段函数等服务端 KaTeX 渲染失败。
根因:@tiptap/markdown 的 MarkdownManager.encodeTextForMarkdown 对所有非代码
文本节点做 escapeMarkdownSyntax + encodeHtmlEntities,把 LaTeX 的 \→\\、
_→\_、&→&。编辑器内公式走普通文本路径,经 getMarkdown() 序列化后存库即损坏。

解法:把公式建模成 atom Node(InlineMath/DisplayMath),LaTeX 存 attrs.latex
而非子文本;声明 markdownTokenizer/parseMarkdown/renderMarkdown 顶层 spec 字段,
序列化时 renderMarkdown 直接拼 $...$/$$...$$,绕过 encodeTextForMarkdown
转义路径(MarkdownManager 有 handler 的非文本节点不走 escape)。

NodeView 内用客户端 katex.renderToString 渲染预览(类 Notion/Typora),双击编辑
LaTeX 源码。KaTeX CSS 已由 Dioxus.toml 全局注入,无需额外接线。

- 新增 katex ^0.16.22 依赖(workspace 已 hoisted)
- 11 个回归测试覆盖矩阵/分段函数/round-trip 序列化往返
2026-07-17 17:33:37 +08:00

1167 lines
24 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Tiptap Editor - Typora-inspired, theme-aware */
.tiptap-editor {
position: relative;
width: 100%;
height: 100%;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
line-height: 1.75;
/* Catppuccin Latte text */
color: #4c4f69;
outline: none;
}
.tiptap-editor .ProseMirror {
width: 100%;
height: 100%;
padding: 24px 32px;
outline: none;
overflow-y: auto;
}
.tiptap-editor .ProseMirror-focused {
outline: none;
}
/* Placeholder - shown when document has only one empty paragraph */
.tiptap-editor .ProseMirror
p:first-child:last-child:has(br.ProseMirror-trailingBreak:only-child)::before {
content: '在此输入内容...';
float: left;
/* Latte overlay0 */
color: #9ca0b0;
pointer-events: none;
height: 0;
}
/* Typography */
.tiptap-editor h1 {
font-size: 2em;
font-weight: 700;
margin: 1.2em 0 0.6em;
line-height: 1.3;
/* Latte text */
color: #4c4f69;
}
.tiptap-editor h2 {
font-size: 1.5em;
font-weight: 600;
margin: 1em 0 0.5em;
line-height: 1.35;
color: #4c4f69;
}
.tiptap-editor h3 {
font-size: 1.25em;
font-weight: 600;
margin: 0.8em 0 0.4em;
color: #4c4f69;
}
.tiptap-editor p {
margin: 0.6em 0;
}
.tiptap-editor p:first-child {
margin-top: 0;
}
.tiptap-editor strong {
font-weight: 700;
color: #4c4f69;
}
.tiptap-editor em {
font-style: italic;
}
.tiptap-editor s {
text-decoration: line-through;
opacity: 0.7;
}
.tiptap-editor code {
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
font-size: 0.875em;
/* Latte surface0 / red */
background: #ccd0da;
padding: 0.15em 0.4em;
border-radius: 3px;
color: #d20f39;
}
.tiptap-editor pre {
/* Latte crust */
background: #dce0e8;
border-radius: 6px;
padding: 16px;
overflow-x: auto;
margin: 1em 0;
}
.tiptap-editor pre code {
background: none;
padding: 0;
color: #4c4f69;
font-size: 0.85em;
line-height: 1.6;
}
.tiptap-editor blockquote {
/* Latte surface1 / subtext0 */
border-left: 4px solid #bcc0cc;
padding-left: 16px;
margin: 1em 0;
color: #6c6f85;
font-style: italic;
}
.tiptap-editor ul {
list-style-type: disc;
margin: 0.6em 0;
padding-left: 2em;
}
.tiptap-editor ol {
list-style-type: decimal;
margin: 0.6em 0;
padding-left: 2em;
}
.tiptap-editor li {
margin: 0.2em 0;
}
.tiptap-editor li > p {
margin: 0.2em 0;
}
.tiptap-editor hr {
/* Latte surface1 */
border: none;
border-top: 1px solid #bcc0cc;
margin: 1.5em 0;
}
/* Selection */
.tiptap-editor .ProseMirror ::selection {
/* Latte surface0 */
background: #ccd0da;
}
/* Cursor */
.tiptap-editor .ProseMirror-focused .ProseMirror-gapcursor {
display: none;
}
/* ========== Dark Theme (Catppuccin Mocha) ========== */
.dark .tiptap-editor {
/* Mocha text */
color: #cdd6f4;
}
.dark .tiptap-editor h1,
.dark .tiptap-editor h2,
.dark .tiptap-editor h3,
.dark .tiptap-editor strong {
color: #cdd6f4;
}
.dark .tiptap-editor .ProseMirror p.is-editor-empty:first-child::before {
/* Mocha overlay0 */
color: #6c7086;
}
.dark .tiptap-editor code {
/* Mocha surface0 / red */
background: #313244;
color: #f38ba8;
}
.dark .tiptap-editor pre {
background: #313244;
}
.dark .tiptap-editor pre code {
color: #cdd6f4;
}
.dark .tiptap-editor blockquote {
/* Mocha surface1 / subtext0 */
border-left-color: #45475a;
color: #a6adc8;
}
.dark .tiptap-editor hr {
border-top-color: #45475a;
}
.dark .tiptap-editor a {
/* Mocha blue */
color: #89b4fa;
}
.dark .tiptap-editor .ProseMirror ::selection {
background: rgba(30, 102, 245, 0.3);
}
/* ========== Link ========== */
.tiptap-editor a {
/* Latte blue */
color: #1e66f5;
text-decoration: none;
}
.tiptap-editor a:hover {
text-decoration: underline;
}
/* ========== Table ========== */
.tiptap-editor table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 1em 0;
overflow: hidden;
/* Latte surface1 */
border: 1px solid #bcc0cc;
border-radius: 6px;
}
.tiptap-editor table td,
.tiptap-editor table th {
min-width: 80px;
border: 1px solid #bcc0cc;
padding: 8px 12px;
position: relative;
vertical-align: top;
box-sizing: border-box;
}
.tiptap-editor table th {
/* Latte crust */
background: #dce0e8;
font-weight: 600;
text-align: left;
}
.tiptap-editor table .selectedCell {
/* Latte surface0 */
background: #ccd0da;
}
.tiptap-editor table .column-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: -2px;
width: 4px;
/* Latte blue */
background-color: #1e66f5;
pointer-events: none;
}
.tiptap-editor .tableWrapper {
padding: 1rem 0;
overflow-x: auto;
}
.tiptap-editor .resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
/* ========== Slash Command ========== */
.slash-command {
position: fixed;
z-index: 9999;
/* Latte mantle */
background: #e6e9ef;
border: 1px solid #bcc0cc;
border-radius: 8px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
padding: 4px;
min-width: 260px;
max-height: 320px;
overflow-y: auto;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.slash-command-list {
display: flex;
flex-direction: column;
}
.slash-command-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
transition: background 0.15s;
}
.slash-command-item:hover,
.slash-command-item.is-selected {
/* Latte surface0 */
background: #ccd0da;
}
.slash-command-item-icon {
flex-shrink: 0;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: #ccd0da;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
/* Latte subtext1 */
color: #5c5f77;
}
.slash-command-item-text {
flex: 1;
min-width: 0;
}
.slash-command-item-title {
font-size: 14px;
font-weight: 500;
color: #4c4f69;
line-height: 1.3;
}
.slash-command-item-desc {
font-size: 12px;
/* Latte overlay0 */
color: #9ca0b0;
line-height: 1.3;
}
.slash-command-empty {
padding: 12px 16px;
font-size: 13px;
/* Latte overlay0 */
color: #9ca0b0;
text-align: center;
}
.dark .slash-command-empty {
/* Mocha overlay0 */
color: #6c7086;
}
/* ========== Task List (Enhanced) ========== */
.tiptap-editor ul[data-type='taskList'] {
list-style: none;
padding-left: 0;
}
.tiptap-editor ul[data-type='taskList'] li {
display: flex;
align-items: center;
gap: 0.5em;
margin: 0.3em 0;
}
.tiptap-editor ul[data-type='taskList'] li > label {
flex-shrink: 0;
cursor: pointer;
user-select: none;
/* label 默认 inline,内部 checkbox 是 inline-block 会受 line-height 撑出
基线间距导致 checkbox 在 label 内偏上。用 flex 居中并清零行高,
让 checkbox 严格在 label 盒子中心。 */
display: flex;
align-items: center;
line-height: 0;
}
.tiptap-editor ul[data-type='taskList'] li > label input[type='checkbox'] {
width: 16px;
height: 16px;
/* 主强调色 Latte green */
accent-color: #40a02b;
cursor: pointer;
}
.tiptap-editor ul[data-type='taskList'] li > div {
flex: 1;
}
/* task list 项内的段落去掉上下 margin.tiptap-editor p 默认 .6em 0
否则 margin 会撑高 div使 checkbox 与文本垂直中线错位、项间距过大。 */
.tiptap-editor ul[data-type='taskList'] li > div > p {
margin: 0;
}
.tiptap-editor ul[data-type='taskList'] li[data-checked='true'] > div {
text-decoration: line-through;
opacity: 0.6;
}
/* ========== Image ========== */
.tiptap-editor img {
max-width: 100%;
height: auto;
border-radius: 6px;
margin: 1em 0;
display: block;
}
.tiptap-editor img.ProseMirror-selectednode {
/* 主强调色 Latte green */
outline: 2px solid #40a02b;
border-radius: 6px;
}
/* ========== Upload Image Placeholder NodeView ========== */
.upload-image-container {
position: relative;
display: inline-block;
max-width: 100%;
}
.upload-image-container img {
max-width: 100%;
height: auto;
border-radius: 6px;
margin: 1em 0;
display: block;
/* blur/grayscale 出现与消失的过渡(上传中→成功/失败的 filter 切换) */
transition: filter 0.2s ease;
}
.upload-image-container.is-error img {
opacity: 0.5;
filter: grayscale(0.5);
}
/* 上传中图片高斯模糊遮罩文字spinner/"上传中…")在独立 overlay 层不受影响 */
.upload-image-container.is-uploading {
overflow: hidden;
}
.upload-image-container.is-uploading img {
filter: blur(8px);
}
.upload-image-overlay {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
border-radius: 6px;
pointer-events: none;
}
.upload-image-container.is-error .upload-image-overlay {
background: rgba(0, 0, 0, 0.35);
pointer-events: auto;
}
.upload-spinner {
width: 28px;
height: 28px;
border: 3px solid rgba(255, 255, 255, 0.4);
border-top-color: #fff;
border-radius: 50%;
animation: upload-spin 0.8s linear infinite;
}
@keyframes upload-spin {
to {
transform: rotate(360deg);
}
}
.upload-overlay-text {
color: #fff;
font-size: 13px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.upload-error-icon {
color: #fff;
font-size: 22px;
line-height: 1;
}
.upload-error-msg {
color: #fff;
font-size: 13px;
text-align: center;
padding: 0 12px;
max-width: 90%;
word-break: break-word;
}
.upload-error-actions {
display: flex;
gap: 8px;
}
.upload-btn {
padding: 3px 12px;
font-size: 12px;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.6);
background: rgba(255, 255, 255, 0.15);
color: #fff;
cursor: pointer;
}
.upload-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.upload-btn-remove {
border-color: rgba(254, 202, 202, 0.7);
color: #fecaca;
}
.upload-btn-remove:hover {
background: rgba(239, 68, 68, 0.3);
}
/* ========== Dark Theme: Table (Mocha) ========== */
.dark .tiptap-editor table {
/* Mocha surface1 */
border-color: #45475a;
}
.dark .tiptap-editor table td,
.dark .tiptap-editor table th {
border-color: #45475a;
color: #cdd6f4;
}
.dark .tiptap-editor table th {
/* Mocha surface0 */
background: #313244;
}
.dark .tiptap-editor table .selectedCell {
background: rgba(30, 102, 245, 0.25);
}
/* ========== Dark Theme: Slash Command (Mocha) ========== */
.dark .slash-command {
/* Mocha mantle */
background: #181825;
border-color: #45475a;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
.dark .slash-command-item:hover,
.dark .slash-command-item.is-selected {
background: #313244;
}
.dark .slash-command-item-icon {
background: #313244;
color: #cdd6f4;
}
.dark .slash-command-item-title {
color: #cdd6f4;
}
.dark .slash-command-item-desc {
/* Mocha overlay0 */
color: #6c7086;
}
/* ========== Dark Theme: Image (Mocha) ========== */
.dark .tiptap-editor img.ProseMirror-selectednode {
/* 主强调色 Mocha green */
outline-color: #a6e3a1;
}
.dark .upload-image-container.is-error img {
opacity: 0.45;
}
.dark .upload-btn {
border-color: rgba(200, 200, 200, 0.4);
}
.dark .upload-btn-remove {
border-color: rgba(239, 68, 68, 0.5);
}
/* ========== Dark Theme: Task List (Mocha) ========== */
.dark .tiptap-editor ul[data-type='taskList'] li > label input[type='checkbox'] {
/* 主强调色 Mocha green */
accent-color: #a6e3a1;
}
/* ========== Source Toggle Button ========== */
.tiptap-toggle-btn {
position: absolute;
top: 12px;
right: 12px;
z-index: 10;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 6px;
/* Latte text 半透明底 */
background: rgba(76, 79, 105, 0.05);
color: #6c6f85;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
font-size: 13px;
font-weight: 600;
line-height: 1;
cursor: pointer;
opacity: 0.55;
transition:
opacity 0.15s,
background 0.15s;
}
.tiptap-toggle-btn:hover {
opacity: 1;
background: rgba(76, 79, 105, 0.1);
}
.dark .tiptap-toggle-btn {
/* Mocha text 半透明底 */
background: rgba(205, 214, 244, 0.06);
color: #6c7086;
}
.dark .tiptap-toggle-btn:hover {
background: rgba(205, 214, 244, 0.12);
}
/* ========== Source Textarea ========== */
.tiptap-source-textarea {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
padding: 24px 32px;
box-sizing: border-box;
border: none;
background: transparent;
/* Latte text */
color: #4c4f69;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
font-size: 14px;
line-height: 1.7;
resize: none;
outline: none;
overflow-y: auto;
z-index: 5;
}
.tiptap-source-textarea::placeholder {
/* Latte overlay0 */
color: #9ca0b0;
}
.dark .tiptap-source-textarea {
/* Mocha text */
color: #cdd6f4;
}
.dark .tiptap-source-textarea::placeholder {
/* Mocha overlay0 */
color: #6c7086;
}
/* ========== Runnable Code Block Modal ========== */
.tiptap-runnable-modal-mask {
position: fixed;
inset: 0;
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.3);
}
.tiptap-runnable-modal {
/* Latte mantle —— 复用 .slash-command 配色 */
background: #e6e9ef;
border: 1px solid #bcc0cc;
border-radius: 8px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
padding: 16px;
min-width: 320px;
max-width: 90vw;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.tiptap-runnable-modal-title {
font-size: 15px;
font-weight: 600;
/* Latte text */
color: #4c4f69;
margin-bottom: 12px;
}
.tiptap-runnable-field {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-size: 13px;
/* Latte subtext0 */
color: #6c6f85;
}
.tiptap-runnable-field input[type='number'],
.tiptap-runnable-field select {
flex: 1;
padding: 4px 8px;
border: 1px solid #bcc0cc;
border-radius: 4px;
background: #fff;
/* Latte text */
color: #4c4f69;
font-size: 13px;
font-family: inherit;
outline: none;
}
.tiptap-runnable-field input[type='checkbox'] {
accent-color: #40a02b;
}
.tiptap-runnable-preview {
margin: 12px 0;
padding: 8px;
/* Latte surface0 */
background: #ccd0da;
border-radius: 4px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
font-size: 12px;
/* Latte subtext1 */
color: #5c5f77;
word-break: break-all;
}
.tiptap-runnable-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.tiptap-runnable-actions button {
padding: 6px 16px;
border: none;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
}
.tiptap-runnable-actions .cancel {
/* Latte surface0 */
background: #ccd0da;
/* Latte text */
color: #4c4f69;
}
.tiptap-runnable-actions .insert {
/* Catppuccin green —— 与 Code Runner 运行按钮主色一致 */
background: #40a02b;
color: #fff;
}
.tiptap-runnable-actions .insert:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ========== Dark Theme: Runnable Modal (Mocha) ========== */
.dark .tiptap-runnable-modal {
/* Mocha mantle */
background: #181825;
border-color: #45475a;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
.dark .tiptap-runnable-modal-title {
/* Mocha text */
color: #cdd6f4;
}
.dark .tiptap-runnable-field {
/* Mocha subtext0 */
color: #a6adc8;
}
.dark .tiptap-runnable-field input[type='number'],
.dark .tiptap-runnable-field select {
border-color: #45475a;
/* Mocha base */
background: #1e1e2e;
/* Mocha text */
color: #cdd6f4;
}
.dark .tiptap-runnable-field input[type='checkbox'] {
/* Mocha green */
accent-color: #a6e3a1;
}
.dark .tiptap-runnable-preview {
/* Mocha surface0 */
background: #313244;
/* Mocha subtext1 */
color: #bac2de;
}
.dark .tiptap-runnable-actions .cancel {
/* Mocha surface0 */
background: #313244;
/* Mocha text */
color: #cdd6f4;
}
.dark .tiptap-runnable-actions .insert {
/* Mocha green */
background: #a6e3a1;
/* Mocha base */
color: #1e1e2e;
}
/* ========== Code Block Syntax Highlighting (Catppuccin Latte) ========== */
/* 色值取自 @catppuccin/highlightjs@1.0.1,与读者侧 public/highlight.css(syntect Catppuccin)同一 palette。
scope 到 .tiptap-editor pre code,避免污染全站 code 元素。
基础文字色/背景由现有 .tiptap-editor pre code 规则覆盖,未命中 token 回退到基础色。 */
.tiptap-editor pre code .hljs-keyword {
color: #8839ef;
}
.tiptap-editor pre code .hljs-string,
.tiptap-editor pre code .hljs-char.escape_ {
color: #40a02b;
}
.tiptap-editor pre code .hljs-comment {
color: #7c7f93;
font-style: italic;
}
.tiptap-editor pre code .hljs-number,
.tiptap-editor pre code .hljs-literal {
color: #fe640b;
}
.tiptap-editor pre code .hljs-title,
.tiptap-editor pre code .hljs-title.function_ {
color: #1e66f5;
}
.tiptap-editor pre code .hljs-type,
.tiptap-editor pre code .hljs-title.class_ {
color: #df8e1d;
}
.tiptap-editor pre code .hljs-built_in,
.tiptap-editor pre code .hljs-doctag {
color: #d20f39;
}
.tiptap-editor pre code .hljs-operator {
color: #04a5e5;
}
.tiptap-editor pre code .hljs-property,
.tiptap-editor pre code .hljs-tag {
color: #179299;
}
.tiptap-editor pre code .hljs-regexp,
.tiptap-editor pre code .hljs-meta {
color: #ea76cb;
}
.tiptap-editor pre code .hljs-variable,
.tiptap-editor pre code .hljs-symbol {
color: #dd7878;
}
.tiptap-editor pre code .hljs-attr,
.tiptap-editor pre code .hljs-attribute {
color: #1e66f5;
}
.tiptap-editor pre code .hljs-addition {
color: #40a02b;
background: rgba(64, 160, 43, 0.15);
}
.tiptap-editor pre code .hljs-deletion {
color: #d20f39;
background: rgba(210, 15, 57, 0.15);
}
/* ========== Dark Theme: Code Block Syntax Highlighting (Catppuccin Mocha) ========== */
.dark .tiptap-editor pre code .hljs-keyword {
color: #cba6f7;
}
.dark .tiptap-editor pre code .hljs-string,
.dark .tiptap-editor pre code .hljs-char.escape_ {
color: #a6e3a1;
}
.dark .tiptap-editor pre code .hljs-comment {
color: #9399b2;
font-style: italic;
}
.dark .tiptap-editor pre code .hljs-number,
.dark .tiptap-editor pre code .hljs-literal {
color: #fab387;
}
.dark .tiptap-editor pre code .hljs-title,
.dark .tiptap-editor pre code .hljs-title.function_ {
color: #89b4fa;
}
.dark .tiptap-editor pre code .hljs-type,
.dark .tiptap-editor pre code .hljs-title.class_ {
color: #f9e2af;
}
.dark .tiptap-editor pre code .hljs-built_in,
.dark .tiptap-editor pre code .hljs-doctag {
color: #f38ba8;
}
.dark .tiptap-editor pre code .hljs-operator {
color: #89dceb;
}
.dark .tiptap-editor pre code .hljs-property,
.dark .tiptap-editor pre code .hljs-tag {
color: #94e2d5;
}
.dark .tiptap-editor pre code .hljs-regexp,
.dark .tiptap-editor pre code .hljs-meta {
color: #f5c2e7;
}
.dark .tiptap-editor pre code .hljs-variable,
.dark .tiptap-editor pre code .hljs-symbol {
color: #f2cdcd;
}
.dark .tiptap-editor pre code .hljs-attr,
.dark .tiptap-editor pre code .hljs-attribute {
color: #89b4fa;
}
.dark .tiptap-editor pre code .hljs-addition {
color: #a6e3a1;
background: rgba(166, 227, 161, 0.15);
}
.dark .tiptap-editor pre code .hljs-deletion {
color: #f38ba8;
background: rgba(243, 139, 168, 0.15);
}
/* ========== Code Block NodeView (toolbar + result) ========== */
.tiptap-codeblock {
position: relative;
margin: 1em 0;
}
.tiptap-codeblock-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px 12px;
/* Latte crust与 pre 背景协调) */
background: #dce0e8;
border-radius: 6px 6px 0 0;
font-size: 12px;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* pre 紧贴 toolbar 下方,合并圆角 */
.tiptap-codeblock > pre {
margin: 0;
border-radius: 0 0 6px 6px;
}
/* runnable 块绿条移到容器左侧(避免与 pre padding 冲突) */
.tiptap-codeblock:has(code[class*='runnable']) {
border-left: 3px solid #40a02b;
padding-left: 0;
}
.tiptap-codeblock:has(code[class*='runnable']) > pre {
padding-left: 13px;
}
.tiptap-codeblock-lang {
/* Latte subtext0 */
color: #6c6f85;
font-weight: 500;
text-transform: lowercase;
}
/* runnable 块的语言标签可点击(触发编辑模态框) */
.tiptap-codeblock-lang-editable {
cursor: pointer;
padding: 1px 6px;
border-radius: 3px;
}
.tiptap-codeblock-lang-editable:hover {
/* Latte surface0 */
background: #ccd0da;
/* Catppuccin green暗示可改 */
color: #40a02b;
}
.dark .tiptap-codeblock-lang-editable:hover {
/* Mocha surface0 / green */
background: #313244;
color: #a6e3a1;
}
.tiptap-codeblock-run {
border: none;
border-radius: 4px;
padding: 2px 10px;
font-size: 12px;
cursor: pointer;
/* Catppuccin green */
background: #40a02b;
color: #fff;
}
.tiptap-codeblock-run:hover:not(:disabled) {
background: #3a8f26;
}
.tiptap-codeblock-run:disabled {
opacity: 0.6;
cursor: wait;
}
.tiptap-codeblock-result {
margin-top: 4px;
padding: 8px 12px;
border-radius: 6px;
/* Latte surface0 */
background: #ccd0da;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
font-size: 12px;
/* Latte text */
color: #4c4f69;
white-space: pre-wrap;
word-break: break-all;
max-height: 300px;
overflow-y: auto;
}
/* ========== Dark Theme: Code Block NodeView (Mocha) ========== */
.dark .tiptap-codeblock-toolbar {
/* Mocha crust */
background: #11111b;
}
.dark .tiptap-codeblock:has(code[class*='runnable']) {
border-left-color: #a6e3a1;
}
.dark .tiptap-codeblock-lang {
/* Mocha subtext0 */
color: #a6adc8;
}
.dark .tiptap-codeblock-run {
/* Mocha green */
background: #a6e3a1;
/* Mocha base */
color: #1e1e2e;
}
.dark .tiptap-codeblock-run:hover:not(:disabled) {
background: #95d88f;
}
.dark .tiptap-codeblock-result {
/* Mocha surface0 */
background: #313244;
/* Mocha text */
color: #cdd6f4;
}
/* ========== Math Node (KaTeX preview) ========== */
.tiptap-editor .math-node {
/* inline 公式随文,block 公式独占行;KaTeX 字体由全局 katex.min.css 提供。 */
cursor: pointer;
border-radius: 4px;
transition:
background-color 0.15s ease,
box-shadow 0.15s ease;
}
.tiptap-editor .math-node:hover {
/* Latte surface0:轻底色提示「可交互」。 */
background-color: rgba(204, 208, 218, 0.4);
}
.tiptap-editor .math-node-block {
display: block;
margin: 0.75em 0;
padding: 0.5em 0.75em;
text-align: center;
}
.tiptap-editor .math-node-inline {
display: inline;
padding: 0 0.15em;
}
.tiptap-editor .math-node-render-hidden {
display: none;
}
.tiptap-editor .math-node-error .math-node-render {
/* 坏 TeX:红色提示,对齐服务端 katex-error 的 #cc0000。 */
color: #d20f2f;
}
.tiptap-editor .math-node-edit {
width: 100%;
min-width: 200px;
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', Menlo, Monaco, Consolas, monospace;
font-size: 0.9em;
line-height: 1.5;
padding: 0.5em 0.75em;
border: 1px solid #ccd0da;
border-radius: 6px;
/* Latte base */
background: #eff1f5;
/* Latte text */
color: #4c4f69;
resize: vertical;
outline: none;
}
.tiptap-editor .math-node-edit:focus {
/* Latte blue */
border-color: #1e66f5;
box-shadow: 0 0 0 2px rgba(30, 102, 245, 0.2);
}
/* ProseMirror 选中态:公式被选中时高亮。 */
.tiptap-editor .ProseMirror-selectednode .math-node {
/* Latte blue 透明底。 */
background-color: rgba(30, 102, 245, 0.12);
box-shadow: 0 0 0 1px rgba(30, 102, 245, 0.4);
}
/* Dark theme (Mocha) */
.dark .tiptap-editor .math-node:hover {
background-color: rgba(49, 50, 68, 0.5);
}
.dark .tiptap-editor .math-node-error .math-node-render {
/* Mocha red */
color: #f38ba8;
}
.dark .tiptap-editor .math-node-edit {
/* Mocha surface0 */
border-color: #313244;
background: #181825;
/* Mocha text */
color: #cdd6f4;
}
.dark .tiptap-editor .math-node-edit:focus {
/* Mocha blue */
border-color: #89b4fa;
box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.25);
}
.dark .tiptap-editor .ProseMirror-selectednode .math-node {
background-color: rgba(137, 180, 250, 0.15);
box-shadow: 0 0 0 1px rgba(137, 180, 250, 0.5);
}