feat: 友链/书库/移动端导航/排行榜等多模块功能
新增模块: - 友链(friendlink)后端处理 + 前端管理页与友链板 - 书库(library):导入、章节、封面、阅读页、横竖版自适应 AdaptiveCoverSlot - 顶栏导航(header_nav)配置与 MobileTabBar/MobileRailDrawers 移动端抽屉 - 排行榜 service 测试、站点页面测试、时间线发布(timeline_release) 其它改动: - 后端 handlers/services 全量小幅调整 - 前端组件、库函数、URL/品牌/站点 URL 工具更新 - Lightbox 图片、MdEntries 条目卡、coverColor 派生色相等前端能力
This commit is contained in:
@@ -33,12 +33,14 @@ import {
|
||||
History,
|
||||
Ellipsis,
|
||||
Smile,
|
||||
LayoutList,
|
||||
} from "lucide-react";
|
||||
import { apiSearchPosts, apiUploadPostImage, type PostListItem } from "@/lib/api";
|
||||
import { toast } from "@/lib/toast";
|
||||
import MarkdownBodyClient from "@/components/MarkdownBodyClient";
|
||||
import ImageInsertModal from "@/components/ImageInsertModal";
|
||||
import TimelineEditor from "@/components/TimelineEditor";
|
||||
import { ENTRIES_TEMPLATE, ENTRIES_TEMPLATE_AUTO } from "@/lib/entryBlocks";
|
||||
import EmojiPicker from "@/components/EmojiPicker";
|
||||
import { resolveTimelineBlockForEdit } from "@/lib/directiveBlocks";
|
||||
import { SHIKI_LANG_IDS } from "@/components/markdown/shikiHighlighter";
|
||||
@@ -79,6 +81,8 @@ type MarkdownEditorProps = {
|
||||
/** 提交失败等原因,显示在编辑器内 */
|
||||
error?: string;
|
||||
onUploadingChange?: (uploading: boolean) => void;
|
||||
/** 书级「全章节条目卡模式」:插入免壳模板、预览按隐式条目渲染 */
|
||||
entriesAuto?: boolean;
|
||||
};
|
||||
|
||||
type EditorMode = "write" | "preview";
|
||||
@@ -159,6 +163,7 @@ export default function MarkdownEditor({
|
||||
maxChars,
|
||||
error,
|
||||
onUploadingChange,
|
||||
entriesAuto = false,
|
||||
}: MarkdownEditorProps) {
|
||||
const taRef = useRef<HTMLTextAreaElement>(null);
|
||||
const urlStyle = useUrlStyle();
|
||||
@@ -212,10 +217,9 @@ export default function MarkdownEditor({
|
||||
}, [uploading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isComment) return;
|
||||
const el = toolbarRef.current;
|
||||
if (!el) return;
|
||||
const apply = () => setToolNarrow(el.clientWidth < 520);
|
||||
const apply = () => setToolNarrow(el.clientWidth < (isComment ? 520 : 640));
|
||||
apply();
|
||||
const ro = new ResizeObserver(apply);
|
||||
ro.observe(el);
|
||||
@@ -742,6 +746,180 @@ export default function MarkdownEditor({
|
||||
l.toLowerCase().includes(codeQuery.trim().toLowerCase())
|
||||
);
|
||||
|
||||
// 代码块语言弹层:常规时挂在行内按钮下,窄屏收进「更多」后挂在其锚点下(居中防溢出)
|
||||
const codePopover =
|
||||
popover === "code" ? (
|
||||
<div
|
||||
className={`j13-md-popover j13-md-popover-wide${toolNarrow ? " j13-md-popover-center" : ""}`}
|
||||
role="dialog"
|
||||
aria-label="选择语言"
|
||||
>
|
||||
<input
|
||||
autoFocus
|
||||
className="j13-md-popover-input"
|
||||
placeholder="搜索语言…"
|
||||
value={codeQuery}
|
||||
onChange={(e) => setCodeQuery(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && filteredLangs[0]) {
|
||||
e.preventDefault();
|
||||
insertCodeBlock(filteredLangs[0]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="j13-md-popover-list">
|
||||
{filteredLangs.map((lang) => (
|
||||
<button key={lang} type="button" onClick={() => insertCodeBlock(lang)}>
|
||||
{lang}
|
||||
</button>
|
||||
))}
|
||||
{filteredLangs.length === 0 && (
|
||||
<p className="meta px-2 py-3 text-center">无匹配语言</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
// 隐藏内容弹层:同上,窄屏时改挂在「更多」锚点下
|
||||
const hidePopover =
|
||||
allowHide && popover === "hide" ? (
|
||||
<div
|
||||
className={`j13-md-popover j13-md-popover-form${toolNarrow ? " j13-md-popover-center" : ""}`}
|
||||
role="dialog"
|
||||
aria-label="插入隐藏内容"
|
||||
>
|
||||
{hideStep === "menu" ? (
|
||||
<>
|
||||
<button type="button" className="j13-md-menu-item" onClick={() => insertHide("login")}>
|
||||
登录可见
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="j13-md-menu-item"
|
||||
disabled={!allowReplyHide}
|
||||
data-tip={allowReplyHide ? undefined : "当前内容不可评论,无法使用回复可见"}
|
||||
aria-disabled={!allowReplyHide}
|
||||
onClick={() => {
|
||||
if (!allowReplyHide) return;
|
||||
insertHide("reply");
|
||||
}}
|
||||
>
|
||||
回复可见
|
||||
{!allowReplyHide ? (
|
||||
<span className="j13-md-menu-item-hint">不可用</span>
|
||||
) : null}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="j13-md-menu-item"
|
||||
onClick={() => setHideStep("points")}
|
||||
>
|
||||
积分可见…
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="j13-md-menu-item"
|
||||
onClick={() => setHideStep("password")}
|
||||
>
|
||||
密码可见…
|
||||
</button>
|
||||
</>
|
||||
) : hideStep === "points" ? (
|
||||
<>
|
||||
<label className="j13-md-field">
|
||||
<span>所需积分</span>
|
||||
<input
|
||||
autoFocus
|
||||
type="number"
|
||||
min={1}
|
||||
max={100000}
|
||||
value={hidePoints}
|
||||
onChange={(e) => setHidePoints(Number(e.target.value) || 1)}
|
||||
className="j13-md-popover-input"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (hidePoints >= 1 && hidePoints <= 100000) {
|
||||
insertHide("points", hidePoints);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<div className="j13-md-popover-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-line btn-sm"
|
||||
onClick={() => setHideStep("menu")}
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => {
|
||||
if (hidePoints < 1 || hidePoints > 100000) {
|
||||
toast("积分须在 1–100000", "error");
|
||||
return;
|
||||
}
|
||||
insertHide("points", hidePoints);
|
||||
}}
|
||||
>
|
||||
插入
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<label className="j13-md-field">
|
||||
<span>查看密码(不含空格,1–64 字)</span>
|
||||
<input
|
||||
autoFocus
|
||||
type="text"
|
||||
value={hidePassword}
|
||||
maxLength={64}
|
||||
placeholder="例如:secret"
|
||||
onChange={(e) => setHidePassword(e.target.value)}
|
||||
className="j13-md-popover-input"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const p = hidePassword.trim();
|
||||
if (p && !/\s/.test(p)) {
|
||||
insertHide("password", undefined, p);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<div className="j13-md-popover-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-line btn-sm"
|
||||
onClick={() => setHideStep("menu")}
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => {
|
||||
const p = hidePassword.trim();
|
||||
if (!p || /\s/.test(p) || p.length > 64) {
|
||||
toast("密码须为 1–64 字符且不含空格", "error");
|
||||
return;
|
||||
}
|
||||
insertHide("password", undefined, p);
|
||||
}}
|
||||
>
|
||||
插入
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const ToolBtn = ({
|
||||
label,
|
||||
icon,
|
||||
@@ -774,7 +952,11 @@ export default function MarkdownEditor({
|
||||
const Sep = () => <span className="j13-md-sep" aria-hidden />;
|
||||
|
||||
return (
|
||||
<div className={`j13-md-editor${fullscreen ? " is-fullscreen" : ""}${isComment ? " is-comment" : ""}`}>
|
||||
<div
|
||||
className={`j13-md-editor${fullscreen ? " is-fullscreen" : ""}${
|
||||
isComment ? " is-comment" : ""
|
||||
}${toolNarrow ? " is-narrow" : ""}`}
|
||||
>
|
||||
<div className="j13-md-toolbar" ref={toolbarRef}>
|
||||
<div className="j13-md-tool-groups flex items-center gap-0.5 flex-1 flex-wrap min-w-0">
|
||||
<ToolBtn
|
||||
@@ -782,21 +964,21 @@ export default function MarkdownEditor({
|
||||
icon={<Bold size={15} />}
|
||||
onClick={() => runWrap({ before: "**", after: "**", placeholder: "粗体" })}
|
||||
/>
|
||||
{!isComment && (
|
||||
{!isComment && !toolNarrow && (
|
||||
<ToolBtn
|
||||
label="斜体 Ctrl+I"
|
||||
icon={<Italic size={15} />}
|
||||
onClick={() => runWrap({ before: "*", after: "*", placeholder: "斜体" })}
|
||||
/>
|
||||
)}
|
||||
{!isComment && (
|
||||
{!isComment && !toolNarrow && (
|
||||
<ToolBtn
|
||||
label="删除线"
|
||||
icon={<Strikethrough size={15} />}
|
||||
onClick={() => runWrap({ before: "~~", after: "~~", placeholder: "删除线" })}
|
||||
/>
|
||||
)}
|
||||
{!isComment && <Sep />}
|
||||
{!isComment && !toolNarrow && <Sep />}
|
||||
{!isComment && (
|
||||
<div className="j13-md-popover-anchor relative">
|
||||
<ToolBtn
|
||||
@@ -838,8 +1020,8 @@ export default function MarkdownEditor({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{(!isComment || !toolNarrow) && <Sep />}
|
||||
{(!isComment || !toolNarrow) && (
|
||||
{!toolNarrow && <Sep />}
|
||||
{!toolNarrow && (
|
||||
<ToolBtn
|
||||
label="引用"
|
||||
icon={<Quote size={15} />}
|
||||
@@ -848,7 +1030,7 @@ export default function MarkdownEditor({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{(!isComment || !toolNarrow) && (
|
||||
{!toolNarrow && (
|
||||
<ToolBtn
|
||||
label="无序列表"
|
||||
icon={<List size={15} />}
|
||||
@@ -857,15 +1039,15 @@ export default function MarkdownEditor({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!isComment && (
|
||||
{!isComment && !toolNarrow && (
|
||||
<ToolBtn
|
||||
label="有序列表"
|
||||
icon={<ListOrdered size={15} />}
|
||||
onClick={insertOrderedList}
|
||||
/>
|
||||
)}
|
||||
{!isComment && <ToolBtn label="表格" icon={<Table size={15} />} onClick={insertTable} />}
|
||||
{!isComment && (
|
||||
{!isComment && !toolNarrow && <ToolBtn label="表格" icon={<Table size={15} />} onClick={insertTable} />}
|
||||
{!isComment && !toolNarrow && (
|
||||
<ToolBtn
|
||||
label="分割线"
|
||||
icon={<Minus size={15} />}
|
||||
@@ -878,7 +1060,7 @@ export default function MarkdownEditor({
|
||||
icon={<Code size={15} />}
|
||||
onClick={() => runWrap({ before: "`", after: "`", placeholder: "code" })}
|
||||
/>
|
||||
{(!isComment || !toolNarrow) && (
|
||||
{!toolNarrow && (
|
||||
<div className="j13-md-popover-anchor relative">
|
||||
<ToolBtn
|
||||
label="代码块 Ctrl+Shift+C"
|
||||
@@ -886,37 +1068,7 @@ export default function MarkdownEditor({
|
||||
active={popover === "code"}
|
||||
onClick={() => openPopover("code")}
|
||||
/>
|
||||
{popover === "code" && (
|
||||
<div className="j13-md-popover j13-md-popover-wide" role="dialog" aria-label="选择语言">
|
||||
<input
|
||||
autoFocus
|
||||
className="j13-md-popover-input"
|
||||
placeholder="搜索语言…"
|
||||
value={codeQuery}
|
||||
onChange={(e) => setCodeQuery(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && filteredLangs[0]) {
|
||||
e.preventDefault();
|
||||
insertCodeBlock(filteredLangs[0]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="j13-md-popover-list">
|
||||
{filteredLangs.map((lang) => (
|
||||
<button
|
||||
key={lang}
|
||||
type="button"
|
||||
onClick={() => insertCodeBlock(lang)}
|
||||
>
|
||||
{lang}
|
||||
</button>
|
||||
))}
|
||||
{filteredLangs.length === 0 && (
|
||||
<p className="meta px-2 py-3 text-center">无匹配语言</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{codePopover}
|
||||
</div>
|
||||
)}
|
||||
<Sep />
|
||||
@@ -1008,6 +1160,7 @@ export default function MarkdownEditor({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{(isComment || !toolNarrow) && (
|
||||
<div className="j13-md-popover-anchor relative">
|
||||
<ToolBtn
|
||||
label="表情"
|
||||
@@ -1021,6 +1174,7 @@ export default function MarkdownEditor({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="j13-md-popover-anchor relative">
|
||||
<ToolBtn
|
||||
label={uploading ? "上传中…" : "图片"}
|
||||
@@ -1030,12 +1184,12 @@ export default function MarkdownEditor({
|
||||
onClick={openImageModal}
|
||||
/>
|
||||
</div>
|
||||
{isComment && (
|
||||
{(isComment || toolNarrow) && (
|
||||
<div className="j13-md-popover-anchor relative">
|
||||
<button
|
||||
ref={moreBtnRef}
|
||||
type="button"
|
||||
className={`j13-md-tool${popover === "more" || (toolNarrow && popover === "code") ? " is-active" : ""}`}
|
||||
className={`j13-md-tool${popover === "more" || (toolNarrow && (popover === "code" || popover === "emoji" || popover === "hide")) ? " is-active" : ""}`}
|
||||
data-tip="更多工具"
|
||||
aria-label="更多工具"
|
||||
aria-haspopup="menu"
|
||||
@@ -1060,55 +1214,49 @@ export default function MarkdownEditor({
|
||||
)}
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { runWrap({ before: "*", after: "*", placeholder: "斜体" }); setPopover(null); }}>斜体</button>
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { runWrap({ before: "~~", after: "~~", placeholder: "删除线" }); setPopover(null); }}>删除线</button>
|
||||
{!isComment && (
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => openPopover("emoji")}>表情</button>
|
||||
)}
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { runWrap({ before: "## ", after: "", placeholder: "二级标题" }); setPopover(null); }}>二级标题</button>
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { runWrap({ before: "### ", after: "", placeholder: "三级标题" }); setPopover(null); }}>三级标题</button>
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { insertOrderedList(); setPopover(null); }}>有序列表</button>
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { insertTable(); setPopover(null); }}>表格</button>
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { insertAtCursor("---"); setPopover(null); }}>分隔线</button>
|
||||
{!isComment && allowHide && (
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => openPopover("hide")}>隐藏内容</button>
|
||||
)}
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { setPopover(null); openTimelineEditor(); }}>时间线</button>
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { insertAtCursor(entriesAuto ? ENTRIES_TEMPLATE_AUTO : ENTRIES_TEMPLATE); setPopover(null); }}>条目卡</button>
|
||||
{toolNarrow && (
|
||||
<button type="button" role="menuitem" className="j13-md-menu-item" onMouseDown={(e) => e.preventDefault()} onClick={() => { setPopover(null); toggleFullscreen(); }}>{fullscreen ? "退出全屏" : "全屏写作"}</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{toolNarrow && popover === "code" && (
|
||||
<div className="j13-md-popover j13-md-popover-wide" role="dialog" aria-label="选择语言">
|
||||
<input
|
||||
autoFocus
|
||||
className="j13-md-popover-input"
|
||||
placeholder="搜索语言…"
|
||||
value={codeQuery}
|
||||
onChange={(e) => setCodeQuery(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && filteredLangs[0]) {
|
||||
e.preventDefault();
|
||||
insertCodeBlock(filteredLangs[0]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="j13-md-popover-list">
|
||||
{filteredLangs.map((lang) => (
|
||||
<button key={lang} type="button" onClick={() => insertCodeBlock(lang)}>
|
||||
{lang}
|
||||
</button>
|
||||
))}
|
||||
{filteredLangs.length === 0 && (
|
||||
<p className="meta px-2 py-3 text-center">无匹配语言</p>
|
||||
)}
|
||||
</div>
|
||||
{toolNarrow && codePopover}
|
||||
{toolNarrow && popover === "emoji" && !isComment && (
|
||||
<div className="j13-md-popover j13-md-emoji-popover j13-md-popover-center" role="dialog" aria-label="插入表情">
|
||||
<EmojiPicker onPick={pickEmoji} />
|
||||
</div>
|
||||
)}
|
||||
{toolNarrow && hidePopover}
|
||||
</div>
|
||||
)}
|
||||
{!isComment && <Sep />}
|
||||
{!isComment && (
|
||||
{!isComment && !toolNarrow && <Sep />}
|
||||
{!isComment && !toolNarrow && (
|
||||
<ToolBtn
|
||||
label="时间线"
|
||||
icon={<History size={15} />}
|
||||
onClick={openTimelineEditor}
|
||||
/>
|
||||
)}
|
||||
{allowHide ? (
|
||||
{!isComment && !toolNarrow && (
|
||||
<ToolBtn
|
||||
label="条目卡"
|
||||
icon={<LayoutList size={15} />}
|
||||
onClick={() => insertAtCursor(entriesAuto ? ENTRIES_TEMPLATE_AUTO : ENTRIES_TEMPLATE)}
|
||||
/>
|
||||
)}
|
||||
{allowHide && !toolNarrow ? (
|
||||
<div className="j13-md-popover-anchor relative">
|
||||
<ToolBtn
|
||||
label="隐藏内容"
|
||||
@@ -1116,139 +1264,7 @@ export default function MarkdownEditor({
|
||||
active={popover === "hide"}
|
||||
onClick={() => openPopover("hide")}
|
||||
/>
|
||||
{popover === "hide" && (
|
||||
<div className="j13-md-popover j13-md-popover-form" role="dialog" aria-label="插入隐藏内容">
|
||||
{hideStep === "menu" ? (
|
||||
<>
|
||||
<button type="button" className="j13-md-menu-item" onClick={() => insertHide("login")}>
|
||||
登录可见
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="j13-md-menu-item"
|
||||
disabled={!allowReplyHide}
|
||||
data-tip={allowReplyHide ? undefined : "当前内容不可评论,无法使用回复可见"}
|
||||
aria-disabled={!allowReplyHide}
|
||||
onClick={() => {
|
||||
if (!allowReplyHide) return;
|
||||
insertHide("reply");
|
||||
}}
|
||||
>
|
||||
回复可见
|
||||
{!allowReplyHide ? (
|
||||
<span className="j13-md-menu-item-hint">不可用</span>
|
||||
) : null}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="j13-md-menu-item"
|
||||
onClick={() => setHideStep("points")}
|
||||
>
|
||||
积分可见…
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="j13-md-menu-item"
|
||||
onClick={() => setHideStep("password")}
|
||||
>
|
||||
密码可见…
|
||||
</button>
|
||||
</>
|
||||
) : hideStep === "points" ? (
|
||||
<>
|
||||
<label className="j13-md-field">
|
||||
<span>所需积分</span>
|
||||
<input
|
||||
autoFocus
|
||||
type="number"
|
||||
min={1}
|
||||
max={100000}
|
||||
value={hidePoints}
|
||||
onChange={(e) => setHidePoints(Number(e.target.value) || 1)}
|
||||
className="j13-md-popover-input"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (hidePoints >= 1 && hidePoints <= 100000) {
|
||||
insertHide("points", hidePoints);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<div className="j13-md-popover-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-line btn-sm"
|
||||
onClick={() => setHideStep("menu")}
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => {
|
||||
if (hidePoints < 1 || hidePoints > 100000) {
|
||||
toast("积分须在 1–100000", "error");
|
||||
return;
|
||||
}
|
||||
insertHide("points", hidePoints);
|
||||
}}
|
||||
>
|
||||
插入
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<label className="j13-md-field">
|
||||
<span>查看密码(不含空格,1–64 字)</span>
|
||||
<input
|
||||
autoFocus
|
||||
type="text"
|
||||
value={hidePassword}
|
||||
maxLength={64}
|
||||
placeholder="例如:secret"
|
||||
onChange={(e) => setHidePassword(e.target.value)}
|
||||
className="j13-md-popover-input"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const p = hidePassword.trim();
|
||||
if (p && !/\s/.test(p)) {
|
||||
insertHide("password", undefined, p);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<div className="j13-md-popover-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-line btn-sm"
|
||||
onClick={() => setHideStep("menu")}
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => {
|
||||
const p = hidePassword.trim();
|
||||
if (!p || /\s/.test(p) || p.length > 64) {
|
||||
toast("密码须为 1–64 字符且不含空格", "error");
|
||||
return;
|
||||
}
|
||||
insertHide("password", undefined, p);
|
||||
}}
|
||||
>
|
||||
插入
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{hidePopover}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1281,7 +1297,7 @@ export default function MarkdownEditor({
|
||||
<Eye size={12} /> 预览
|
||||
</button>
|
||||
</div>
|
||||
{(!isComment || !toolNarrow) && (
|
||||
{!toolNarrow && (
|
||||
<button
|
||||
type="button"
|
||||
className="j13-md-tool"
|
||||
@@ -1354,7 +1370,7 @@ export default function MarkdownEditor({
|
||||
预览
|
||||
</div>
|
||||
{deferredValue.trim() ? (
|
||||
<MarkdownBodyClient content={deferredValue} />
|
||||
<MarkdownBodyClient content={deferredValue} entriesAuto={entriesAuto} />
|
||||
) : (
|
||||
<p className="meta text-center py-16">暂无内容可预览</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user