xfy 8493e4c4c4 feat(editor): 代码块语法高亮配色(Catppuccin Latte/Mocha)
色值取自 @catppuccin/highlightjs@1.0.1,与读者侧 highlight.css 同一 palette。
亮/暗双套,.dark 前缀切换,scope 到 .tiptap-editor 避免污染全站。
2026-07-06 15:50:35 +08:00

939 lines
19 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;
}
/* ========== 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;
}
/* 编辑器内 runnable 代码块视觉提示:左侧绿色边框 */
.tiptap-editor pre:has(code[class*='runnable']) {
border-left: 3px solid #40a02b;
padding-left: 12px;
}
/* ========== 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;
}
.dark .tiptap-editor pre:has(code[class*='runnable']) {
border-left-color: #a6e3a1;
}
/* ========== 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: #8839ef;
}
.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);
}