This repository has been archived on 2026-06-21. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MetaLab/templates/MetaLab-2026/html/posts/new.html
Victor_Jay 36b18c0ffe feat: 代码块语法高亮 + 语言选择 + 样式统一
- 引入 @tiptap/extension-code-block-lowlight + lowlight 实现代码语法高亮
- 工具栏新增语言下拉选择器,光标在代码块内时显示,支持 27 种常用语言
- 默认语言 text,高亮主题适配 MetaLab 深蓝背景 (#1a2332)
- 代码块语言标签用 JS 动态注入 DOM 元素(避免 ::before 在 overflow 容器中失效)
- 详情页同步代码块高亮和语言标签显示
- 编辑器与详情页样式统一:代码块暗色背景、引用蓝色边框、链接 accent 蓝
- sanitizePolicy 放行 data-language 属性和 hljs-* class
- 修复 import map 避免 keyed plugin 重复实例
- 修复详情页代码块开头空白行(移除多余的 padding-top)
- 修复语法错误(多余的闭合括号)
2026-05-28 14:34:58 +08:00

286 lines
10 KiB
HTML
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.

{{template "layout/header.html" .}}
<style>
/* Tiptap 编辑器容器适配全高布局 */
#editor-container {
flex: 1;
overflow: hidden;
min-height: 0;
background: #fff;
}
/* Tiptap 编辑器核心样式 */
.tiptap {
padding: 16px 24px;
outline: none;
min-height: 100%;
font-size: 15px;
line-height: 1.7;
color: #24292f;
}
/* 段落间距 */
.tiptap p { margin: 0.5em 0; }
/* 标题 */
.tiptap h1 { margin: 1em 0 0.5em; font-size: 2em; font-weight: 700; }
.tiptap h2 { margin: 0.8em 0 0.4em; font-size: 1.5em; font-weight: 700; }
.tiptap h3 { margin: 0.6em 0 0.3em; font-size: 1.17em; font-weight: 700; }
/* 列表 */
.tiptap ul, .tiptap ol { padding-left: 1.5em; margin: 0.5em 0; }
.tiptap li { margin: 0.2em 0; }
.tiptap ul[data-type="taskList"] { list-style: none; padding-left: 0; }
.tiptap ul[data-type="taskList"] li { display: flex; align-items: flex-start; gap: 0.4em; }
.tiptap ul[data-type="taskList"] li > label { flex-shrink: 0; margin-top: 0.15em; }
/* 引用 */
.tiptap blockquote {
border-left: 3px solid #3498db;
padding-left: 1em;
margin: 0.5em 0;
color: #6b7280;
}
/* 代码块 */
.tiptap pre {
background: #1a2332;
color: #e2e8f0;
padding: 12px 16px;
border-radius: 6px;
font-size: 13px;
line-height: 1.5;
overflow-x: auto;
margin: 0.5em 0;
position: relative;
}
.tiptap pre code { background: transparent; padding: 0; color: inherit; font-size: inherit; white-space: pre; display: block; }
.tiptap code {
background: #f3f4f6;
padding: 2px 5px;
border-radius: 4px;
font-size: 0.9em;
font-family: 'SF Mono', 'Menlo', 'Monaco', monospace;
}
/* 代码块语言标签 */
.tiptap pre .code-lang-label {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 4px 16px;
font-size: 11px;
font-weight: 600;
color: #94a3b8;
background: #2c3e50;
border-radius: 6px 6px 0 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
pointer-events: none;
z-index: 1;
}
/* highlight.js 语法高亮主题 — 基于 One Dark适配 MetaLab 深蓝背景 */
.tiptap pre code .hljs-keyword,
.tiptap pre code .hljs-selector-tag,
.tiptap pre code .hljs-literal,
.tiptap pre code .hljs-section,
.tiptap pre code .hljs-link { color: #c792ea; }
.tiptap pre code .hljs-string,
.tiptap pre code .hljs-title,
.tiptap pre code .hljs-name,
.tiptap pre code .hljs-type,
.tiptap pre code .hljs-attribute,
.tiptap pre code .hljs-symbol,
.tiptap pre code .hljs-bullet,
.tiptap pre code .hljs-addition,
.tiptap pre code .hljs-variable,
.tiptap pre code .hljs-template-tag,
.tiptap pre code .hljs-template-variable { color: #c3e88d; }
.tiptap pre code .hljs-comment,
.tiptap pre code .hljs-quote,
.tiptap pre code .hljs-deletion,
.tiptap pre code .hljs-meta { color: #676e95; }
.tiptap pre code .hljs-number,
.tiptap pre code .hljs-regexp,
.tiptap pre code .hljs-built_in { color: #f78c6c; }
.tiptap pre code .hljs-function .hljs-title,
.tiptap pre code .hljs-doctag,
.tiptap pre code .hljs-formula { color: #82aaff; }
.tiptap pre code .hljs-tag,
.tiptap pre code .hljs-subst { color: #f07178; }
.tiptap pre code .hljs-emphasis { font-style: italic; }
.tiptap pre code .hljs-strong { font-weight: bold; }
.tiptap pre code .hljs-attr,
.tiptap pre code .hljs-selector-attr,
.tiptap pre code .hljs-selector-class,
.tiptap pre code .hljs-selector-id,
.tiptap pre code .hljs-selector-pseudo { color: #ffcb6b; }
/* 水平线 */
.tiptap hr { border: none; border-top: 1px solid #e5e7eb; margin: 1em 0; }
/* 图片 */
.tiptap img { max-width: 100%; height: auto; border-radius: 6px; }
/* 链接 */
.tiptap a { color: #3498db; text-decoration: underline; }
/* Placeholder */
.tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #c4c4c4;
pointer-events: none;
height: 0;
}
/* 工具栏 */
.editor-toolbar {
display: flex;
gap: 2px;
padding: 6px 12px;
background: #fafafa;
border-bottom: 1px solid #e5e7eb;
flex-shrink: 0;
flex-wrap: wrap;
}
.editor-toolbar button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 30px;
border: 1px solid transparent;
border-radius: 4px;
background: transparent;
color: #374151;
font-size: 13px;
cursor: pointer;
transition: background 0.15s;
}
.editor-toolbar button:hover { background: #e5e7eb; }
.editor-toolbar button.is-active { background: #1a2332; color: #fff; }
.code-lang-select {
height: 30px;
padding: 0 6px;
border: 1px solid transparent;
border-radius: 4px;
background: transparent;
color: #374151;
font-size: 12px;
cursor: pointer;
font-family: inherit;
outline: none;
}
.code-lang-select:hover { border-color: #e5e7eb; }
.code-lang-select:focus { border-color: #3498db; }
.editor-toolbar .toolbar-divider {
width: 1px;
background: #e5e7eb;
margin: 3px 4px;
}
</style>
<body>
{{template "layout/nav.html" .}}
<div class="editor-layout">
<form id="postForm" class="editor-form">
<input type="hidden" id="postId" value="{{if .Post}}{{.Post.ID}}{{end}}">
{{/* 标题区域 */}}
<div class="editor-header">
<input type="text" id="postTitle" class="editor-title-input"
value="{{if .Post}}{{.Post.Title}}{{end}}"
placeholder="输入文章标题..."
maxlength="200" required autofocus>
</div>
{{/* 工具栏 */}}
<div class="editor-toolbar" id="toolbar">
<button type="button" data-action="bold" title="加粗 (Ctrl+B)"><strong>B</strong></button>
<button type="button" data-action="italic" title="斜体 (Ctrl+I)"><em>I</em></button>
<button type="button" data-action="strike" title="删除线"><s>S</s></button>
<button type="button" data-action="code" title="行内代码">&lt;/&gt;</button>
<span class="toolbar-divider"></span>
<button type="button" data-action="h1" title="标题 1">H1</button>
<button type="button" data-action="h2" title="标题 2">H2</button>
<button type="button" data-action="h3" title="标题 3">H3</button>
<span class="toolbar-divider"></span>
<button type="button" data-action="bulletList" title="无序列表"></button>
<button type="button" data-action="orderedList" title="有序列表">1.</button>
<button type="button" data-action="blockquote" title="引用">"</button>
<span class="toolbar-divider"></span>
<button type="button" data-action="codeBlock" title="代码块">{ }</button>
<select id="codeLangSelect" class="code-lang-select" style="display:none" title="选择语言">
<option value="text">text</option>
<option value="javascript">JavaScript</option>
<option value="typescript">TypeScript</option>
<option value="python">Python</option>
<option value="go">Go</option>
<option value="rust">Rust</option>
<option value="java">Java</option>
<option value="cpp">C++</option>
<option value="c">C</option>
<option value="csharp">C#</option>
<option value="ruby">Ruby</option>
<option value="php">PHP</option>
<option value="swift">Swift</option>
<option value="kotlin">Kotlin</option>
<option value="sql">SQL</option>
<option value="bash">Bash</option>
<option value="json">JSON</option>
<option value="yaml">YAML</option>
<option value="xml">XML</option>
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="shell">Shell</option>
<option value="dockerfile">Dockerfile</option>
<option value="nginx">Nginx</option>
<option value="diff">Diff</option>
</select>
<button type="button" data-action="horizontalRule" title="分隔线"></button>
<span class="toolbar-divider"></span>
<button type="button" data-action="undo" title="撤销"></button>
<button type="button" data-action="redo" title="重做"></button>
</div>
{{/* 编辑器主体 */}}
<div class="editor-main">
<div class="editor-pane" id="editorPane">
<div id="editor-container"
data-content="{{if .Post}}{{.Post.BodyHTML}}{{end}}"></div>
</div>
</div>
{{/* 状态栏 */}}
<div class="editor-statusbar">
<span class="status-item" id="wordCount">0 字</span>
<span class="status-item status-draft" id="draftStatus" style="display:none">草稿已保存</span>
<span class="status-spacer"></span>
<span class="status-item" id="btnFullscreen" style="cursor:pointer" title="全屏编辑 (F11)">全屏</span>
<button type="submit" class="btn btn-primary btn-submit">{{if .Post}}保存修改{{else}}发布帖子{{end}}</button>
</div>
</form>
</div>
{{template "layout/footer.html" .}}
<script type="importmap">
{
"imports": {
"@tiptap/core": "https://esm.sh/@tiptap/core@3.13.0",
"@tiptap/starter-kit": "https://esm.sh/@tiptap/starter-kit@3.13.0",
"@tiptap/extension-placeholder": "https://esm.sh/@tiptap/extension-placeholder@3.13.0",
"@tiptap/extension-image": "https://esm.sh/@tiptap/extension-image@3.13.0",
"@tiptap/extension-code-block-lowlight": "https://esm.sh/@tiptap/extension-code-block-lowlight@3.13.0",
"lowlight": "https://esm.sh/lowlight@3.3.0",
"highlight.js/lib/languages/": "https://esm.sh/highlight.js@11.11.0/es/languages/"
}
}
</script>
<script src="/static/js/common.js"></script>
<script type="module" src="/static/js/editor.js"></script>
</template>