知识是备忘录,备忘录不能只有字:手册、日志、导出表格都得能挂上去。
附件本来就落在 item 上(`ALLOWED_KINDS` 早含 knowledge),缺的是"新建时
还没有条目 id"这一段。
- 懒建草稿:`KnowledgeComposer` 第一次传附件(或往正文贴图)时先 `POST /knowledge`
存一条草稿拿到 id,`draftingRef` 去重防止贴图与上传同时打出两条草稿;
之后的保存走 `PATCH /knowledge/{id}`。没传过附件就取消,库里不留空条目。
- `RichTextEditor` 增 `ensureItemId`:没有 itemId 时先把草稿存下来再传图,
于是"新建时不能贴图"这条限制也没了;工具栏上传按钮按是否有落点显隐。
- 新建面板加了附件区:有草稿 id 就是正常的 `AttachmentPanel`(上传/下载/删),
没有就先给一个「添加附件」按钮,点一下建草稿。
- 安卓知识详情加附件区:列表(文件名 + 大小)、`OpenDocument` 上传任意文件、
点「打开」用浏览器看签名直链。编辑器原本只能插图,现在手册/日志也能传。
- 文档:API.md 说明附件三类事项共用同一套接口,以及懒建草稿的先后顺序;
ROADMAP 加 3.13。
验证:后端 pytest 94 passed、ruff 全绿;Web `tsc --noEmit` 通过、`vite build` 通过;
`knowledge-notebook.mjs` 15 项全绿(新增「新建面板里能上传附件」「详情抽屉里能看到
刚传的附件」「附件挂在知识上,详情回读得到」)。安卓端仍是 JDK 25 + Gradle 8.7
不兼容,本次未编译验证。
This commit is contained in:
@@ -20,6 +20,11 @@ interface Props {
|
||||
wsId: string;
|
||||
/** 新建场景还没有条目 id:正文照样能写,只是暂时不能贴图(贴图前必须先存草稿) */
|
||||
itemId?: string;
|
||||
/**
|
||||
* 新建场景的兜底:要贴图时先老老实实把草稿存下来,拿到条目 id 再传。
|
||||
* 返回 null 表示没存成(由调用方自己提示),编辑器不会继续上传。
|
||||
*/
|
||||
ensureItemId?: () => Promise<string | null>;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
/** 编辑器初始高度(px) */
|
||||
@@ -46,6 +51,7 @@ export function RichTextEditor({
|
||||
onChange,
|
||||
wsId,
|
||||
itemId,
|
||||
ensureItemId,
|
||||
placeholder = "写点什么…支持粘贴/拖入图片,也可以用工具栏排版",
|
||||
disabled = false,
|
||||
minHeight = 240,
|
||||
@@ -62,6 +68,7 @@ export function RichTextEditor({
|
||||
const onErrorRef = useRef(onError);
|
||||
const resourcesRef = useRef(resources);
|
||||
const onUploadedRef = useRef(onUploaded);
|
||||
const ensureItemIdRef = useRef(ensureItemId);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
@@ -70,6 +77,7 @@ export function RichTextEditor({
|
||||
onErrorRef.current = onError;
|
||||
resourcesRef.current = resources;
|
||||
onUploadedRef.current = onUploaded;
|
||||
ensureItemIdRef.current = ensureItemId;
|
||||
const disabledRef = useRef(disabled);
|
||||
disabledRef.current = disabled;
|
||||
|
||||
@@ -113,8 +121,8 @@ export function RichTextEditor({
|
||||
"line",
|
||||
"code",
|
||||
"inline-code",
|
||||
// 没有条目 id 就没有上传落点,索性不摆这个按钮,免得点了才报错
|
||||
...(itemId ? ["upload"] : []),
|
||||
// 没有落点就不摆这个按钮,免得点了才报错;新建时靠 ensureItemId 先存草稿
|
||||
...(itemId || ensureItemId ? ["upload"] : []),
|
||||
"table",
|
||||
"|",
|
||||
"undo",
|
||||
@@ -131,9 +139,18 @@ export function RichTextEditor({
|
||||
accept: "image/*",
|
||||
multiple: true,
|
||||
handler: async (files: File[]) => {
|
||||
if (!itemId) {
|
||||
onErrorRef.current?.("先保存这条内容,再回来贴图或传附件");
|
||||
return null;
|
||||
// 新建时还没有条目 id:先让调用方把草稿存下来,拿到落点再传
|
||||
let targetId = itemId;
|
||||
if (!targetId) {
|
||||
const ensured = await ensureItemIdRef.current?.();
|
||||
if (!ensured) {
|
||||
// 存草稿失败的原因由 ensureItemId 自己报,这里不叠一层噪音
|
||||
if (!ensureItemIdRef.current) {
|
||||
onErrorRef.current?.("先保存这条内容,再回来贴图或传附件");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
targetId = ensured;
|
||||
}
|
||||
const images = files.filter((file) => file.type.startsWith("image/"));
|
||||
if (images.length === 0) {
|
||||
@@ -145,7 +162,7 @@ export function RichTextEditor({
|
||||
try {
|
||||
for (const file of images) {
|
||||
const created = await uploadFile<Attachment>(
|
||||
`/workspaces/${wsId}/items/${itemId}/attachments`,
|
||||
`/workspaces/${wsId}/items/${targetId}/attachments`,
|
||||
file,
|
||||
);
|
||||
// 自己插入,不用 Vditor 代劳:它对 handler 的返回值只会
|
||||
@@ -219,7 +236,9 @@ export function RichTextEditor({
|
||||
? "正在上传图片…"
|
||||
: itemId
|
||||
? "支持直接粘贴或拖入图片;内容按 Markdown 保存,模型和导出都读得懂"
|
||||
: "内容按 Markdown 保存;存下来之后就能直接贴图、传附件"}
|
||||
: ensureItemId
|
||||
? "支持直接粘贴或拖入图片:会先自动存成草稿,再传上去"
|
||||
: "内容按 Markdown 保存;存下来之后就能直接贴图、传附件"}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user