xfy 876cb55e91 style(editor): smooth blur/grayscale transition on placeholder image
给 img 加 transition: filter 0.2s ease,blur 出现和消失(上传中→成功/
失败)平滑过渡,而非瞬间切换。transition 写在基础 img 规则上,
确保 is-uploading class 移除时过渡也能触发。
2026-06-22 16:27:02 +08:00

601 lines
11 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;
color: #2c2c2c;
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;
color: #999;
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;
color: #1a1a1a;
}
.tiptap-editor h2 {
font-size: 1.5em;
font-weight: 600;
margin: 1em 0 0.5em;
line-height: 1.35;
color: #1a1a1a;
}
.tiptap-editor h3 {
font-size: 1.25em;
font-weight: 600;
margin: 0.8em 0 0.4em;
color: #1a1a1a;
}
.tiptap-editor p {
margin: 0.6em 0;
}
.tiptap-editor p:first-child {
margin-top: 0;
}
.tiptap-editor strong {
font-weight: 700;
color: #1a1a1a;
}
.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;
background: #f3f3f3;
padding: 0.15em 0.4em;
border-radius: 3px;
color: #d73a49;
}
.tiptap-editor pre {
background: #f6f8fa;
border-radius: 6px;
padding: 16px;
overflow-x: auto;
margin: 1em 0;
}
.tiptap-editor pre code {
background: none;
padding: 0;
color: #24292e;
font-size: 0.85em;
line-height: 1.6;
}
.tiptap-editor blockquote {
border-left: 4px solid #dfe2e5;
padding-left: 16px;
margin: 1em 0;
color: #6a737d;
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 {
border: none;
border-top: 1px solid #e1e4e8;
margin: 1.5em 0;
}
/* Selection */
.tiptap-editor .ProseMirror ::selection {
background: #b4d7ff;
}
/* Cursor */
.tiptap-editor .ProseMirror-focused .ProseMirror-gapcursor {
display: none;
}
/* ========== Dark Theme ========== */
.dark .tiptap-editor {
color: #dadadb;
}
.dark .tiptap-editor h1,
.dark .tiptap-editor h2,
.dark .tiptap-editor h3,
.dark .tiptap-editor strong {
color: #f0f0f0;
}
.dark .tiptap-editor .ProseMirror p.is-editor-empty:first-child::before {
color: #666;
}
.dark .tiptap-editor code {
background: #2e2e33;
color: #ff7b72;
}
.dark .tiptap-editor pre {
background: #2e2e33;
}
.dark .tiptap-editor pre code {
color: #dadadb;
}
.dark .tiptap-editor blockquote {
border-left-color: #444;
color: #9b9c9d;
}
.dark .tiptap-editor hr {
border-top-color: #333;
}
.dark .tiptap-editor a {
color: #58a6ff;
}
.dark .tiptap-editor .ProseMirror ::selection {
background: #1c4e80;
}
/* ========== Link ========== */
.tiptap-editor a {
color: #0366d6;
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;
border: 1px solid #e1e4e8;
border-radius: 6px;
}
.tiptap-editor table td,
.tiptap-editor table th {
min-width: 80px;
border: 1px solid #e1e4e8;
padding: 8px 12px;
position: relative;
vertical-align: top;
box-sizing: border-box;
}
.tiptap-editor table th {
background: #f6f8fa;
font-weight: 600;
text-align: left;
}
.tiptap-editor table .selectedCell {
background: #b4d7ff;
}
.tiptap-editor table .column-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: -2px;
width: 4px;
background-color: #adf;
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;
background: #fff;
border: 1px solid #e1e4e8;
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 {
background: #f0f4ff;
}
.slash-command-item-icon {
flex-shrink: 0;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: #f3f3f3;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
color: #555;
}
.slash-command-item-text {
flex: 1;
min-width: 0;
}
.slash-command-item-title {
font-size: 14px;
font-weight: 500;
color: #1a1a1a;
line-height: 1.3;
}
.slash-command-item-desc {
font-size: 12px;
color: #999;
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: flex-start;
gap: 0.5em;
margin: 0.3em 0;
}
.tiptap-editor ul[data-type="taskList"] li > label {
flex-shrink: 0;
margin-top: 0.3em;
cursor: pointer;
user-select: none;
}
.tiptap-editor ul[data-type="taskList"] li > label input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: #0366d6;
cursor: pointer;
}
.tiptap-editor ul[data-type="taskList"] li > div {
flex: 1;
}
.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 {
outline: 2px solid #0366d6;
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 ========== */
.dark .tiptap-editor table {
border-color: #333;
}
.dark .tiptap-editor table td,
.dark .tiptap-editor table th {
border-color: #333;
color: #dadadb;
}
.dark .tiptap-editor table th {
background: #2e2e33;
}
.dark .tiptap-editor table .selectedCell {
background: #1c4e80;
}
/* ========== Dark Theme: Slash Command ========== */
.dark .slash-command {
background: #2e2e33;
border-color: #444;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
.dark .slash-command-item:hover,
.dark .slash-command-item.is-selected {
background: #3a3a44;
}
.dark .slash-command-item-icon {
background: #3a3a44;
color: #dadadb;
}
.dark .slash-command-item-title {
color: #f0f0f0;
}
.dark .slash-command-item-desc {
color: #888;
}
/* ========== Dark Theme: Image ========== */
.dark .tiptap-editor img.ProseMirror-selectednode {
outline-color: #58a6ff;
}
.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 ========== */
.dark .tiptap-editor ul[data-type="taskList"] li > label input[type="checkbox"] {
accent-color: #58a6ff;
}
/* ========== 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;
background: rgba(0, 0, 0, 0.04);
color: #6a737d;
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(0, 0, 0, 0.08);
}
.dark .tiptap-toggle-btn {
background: rgba(255, 255, 255, 0.06);
color: #9b9c9d;
}
.dark .tiptap-toggle-btn:hover {
background: rgba(255, 255, 255, 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;
color: #2c2c2c;
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 {
color: #999;
}
.dark .tiptap-source-textarea {
color: #dadadb;
}
.dark .tiptap-source-textarea::placeholder {
color: #666;
}