export+sidebar: กรองตามรายคนที่เลือกดูครบทั้งจอและ export + REDACTION คงไว้เสมอ (คำขอผู้ใช้ 2026-09-22)
export PDF (F8): - route รับ ?user=<userId> เพิ่ม: กรอง annotation ตาม userId นั้น ยกเว้น REDACTION ติดไปเสมอ (D4 — กล่องดำหาย = เนื้อหาที่ปิดกั้นกลับมาอ่านได้) และ bookmarks → PDF outline กรองตามผู้บันทึกด้วย (หัวข้อแม่โดนกรองแต่ลูกยังอยู่ ลูกขึ้น root ตามตรรกะ buildOutline เดิม) ไม่ส่ง param = ทั้งหมด เหมือนเดิม - ชื่อไฟล์เติมชื่อผู้ใช้ท้าย: <title>-annotated-<ชื่อ>.pdf — ชื่อจาก user_name ที่ mirror ในตาราง (ไม่รับ text จาก client) ผ่าน encodeRFC5987 รองรับชื่อไทย - PDFViewer handleExportPdf: resolve filter ปัจจุบันก่อนยิง route (mine → user.userId) ส่ง &user= เมื่อไม่ใช่ ทุกคน จอ (F4): - visibleAnnotations คง REDACTION ของทุกคนไว้เสมอแม้กรองดูของคนอื่น — สูตรเดียวกับ export: กันเนื้อหาที่ปิดกั้นกลับมาอ่านได้บนจอ (เดิมหายไปตาม filter) และจอตรงกับ ไฟล์ export / ผลค้นหา F14 (visibleSearchResults) ใช้สูตรเดียวกัน - sidebar แท็บ Bookmark กรองตามเจ้าของหมายเหตุเหมือน annotation (เดิม F4 คุมแค่ annotation — bug report 2026-09-22): ต้นไม้ ตามหัวข้อ มุมมอง ตามหน้า ตัวเลข แท็บ และ badge จำนวนลูก ใช้ visibleBookmarks ชุดเดียวกัน — ลูกที่แม่โดนกรองขึ้น root เหมือน export; empty state แยก ไม่มีที่คั่นหน้าของเจ้าของหมายเหตุที่เลือก ออกจากไฟล์ว่างจริง - popup ย้ายหัวข้อ (movableTo) และ bookmarkById ยังเห็นต้นไม้เต็ม — ย้ายไปใต้หัวข้อ ของคนอื่นได้ตามเดิม verify: tsc + Playwright e2e (สร้าง redaction จริงเป็น user1 → สลับ user3 กรองของ ตัวเอง: rect ดำของ user1 ยัง render ครบ, tab Bookmark (10)→(2) และแถวต้นไม้ 2 ตรง DB เป๊ะ, ลบ redaction ทดสอบทิ้งจน DB เหลือ 0) + export จริง 4 filter นับ PDF outline ตรงจำนวน bookmarks ของผู้ใช้นั้นใน DB ทุกแบบ — sync docs/PLAN-FEAT.md §F8 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
9ba8f0a8ec
commit
0aef3ff028
4 changed files with 73 additions and 16 deletions
|
|
@ -374,13 +374,14 @@ export default function AnnotationSidebar({
|
|||
}
|
||||
|
||||
/** ผลการค้นหา F14 หลังกรองตามเจ้าของหมายเหตุ (F4) ฝั่ง client — searchAnnotations
|
||||
* ไม่รับ userId และห้ามแต้ server action: สูตรเดียวกับ visibleAnnotations ของ
|
||||
* PDFViewer — เปลี่ยนตัวกรองหลังค้นแล้ว ผลกรองทันทีไม่ต้องค้นใหม่ */
|
||||
* ไม่รับ userId และห้ามแต้ server action: สูตรเดียวกันกับ visibleAnnotations ของ
|
||||
* PDFViewer (รวม REDACTION ของทุกคนไว้เสมอ — D4) — เปลี่ยนตัวกรองหลังค้นแล้ว
|
||||
* ผลกรองทันทีไม่ต้องค้นใหม่ */
|
||||
const visibleSearchResults = useMemo(() => {
|
||||
if (searchResults === null) return null;
|
||||
if (userFilter === "all") return searchResults;
|
||||
const target = userFilter === "mine" ? userId : userFilter;
|
||||
return searchResults.filter((a) => a.userId === target);
|
||||
return searchResults.filter((a) => a.userId === target || a.type === "REDACTION");
|
||||
}, [searchResults, userFilter, userId]);
|
||||
|
||||
// ─── F19/A7: ประวัติ/กู้คืน — dialog ต่อรายการ (แสดงเฉพาะรายการของตัวเอง) ─────
|
||||
|
|
@ -435,9 +436,25 @@ export default function AnnotationSidebar({
|
|||
const pendingItems = annotations.filter((a) => a.type !== "COMMENT" || !a.resolved);
|
||||
const resolvedItems = annotations.filter((a) => a.type === "COMMENT" && a.resolved);
|
||||
|
||||
// F4: bookmarks ที่แสดง กรองตามเจ้าของหมายเหตุเหมือน annotation (คำขอผู้ใช้
|
||||
// 2026-09-22 ให้ตรงกับ export ที่กรอง PDF outline ตามผู้บันทึก) — หัวข้อแม่โดนกรอง
|
||||
// แต่ลูกยังอยู่ ลูกขึ้น root (พฤติกรรมเดียวกับ buildOutline ของ export) — ใช้เฉพาะจุด
|
||||
// แสดงผล (ต้นไม้/กลุ่มหน้า/ตัวเลขแท็บ): popup ย้ายหัวข้อ (movableTo) กับ bookmarkById
|
||||
// ยังเห็นต้นไม้เต็ม ย้ายไปใต้หัวข้อของคนอื่นได้ตามเดิม
|
||||
const keptBookmarks =
|
||||
userFilter === "all"
|
||||
? bookmarks
|
||||
: bookmarks.filter(
|
||||
(b) => b.userId === (userFilter === "mine" ? userId : userFilter),
|
||||
);
|
||||
const keptIds = new Set(keptBookmarks.map((b) => b.id));
|
||||
const visibleBookmarks = keptBookmarks.map((b) =>
|
||||
b.parentId && !keptIds.has(b.parentId) ? { ...b, parentId: null } : b,
|
||||
);
|
||||
|
||||
// มุมมอง "ตามหน้า": จัดกลุ่มตามหน้า เรียงน้อย→มาก — bookmark เอาเฉพาะ root
|
||||
// (sub ดูในต้นไม้ "ตามหัวข้อ") / annotation เก็บลำดับเดิมตามเวลาสร้าง
|
||||
const bmRootPages = groupByPage(bookmarks.filter((b) => !b.parentId).sort(byPageOrder));
|
||||
const bmRootPages = groupByPage(visibleBookmarks.filter((b) => !b.parentId).sort(byPageOrder));
|
||||
const annPages = groupByPage(annotations);
|
||||
|
||||
// F12/C4: คัดลอก deep link ไปยัง annotation นี้ (?page= + ?ann=)
|
||||
|
|
@ -715,7 +732,7 @@ export default function AnnotationSidebar({
|
|||
}
|
||||
|
||||
const renderBookmarkNode = (b: BookmarkDTO, depth: number) => {
|
||||
const children = bookmarks.filter((x) => x.parentId === b.id).sort(byPageOrder);
|
||||
const children = visibleBookmarks.filter((x) => x.parentId === b.id).sort(byPageOrder);
|
||||
const hasChildren = children.length > 0;
|
||||
const collapsed = collapsedIds.has(b.id);
|
||||
const editing = editBookmark?.id === b.id;
|
||||
|
|
@ -1027,7 +1044,7 @@ export default function AnnotationSidebar({
|
|||
<div role="tablist" aria-label="รายการในแผงข้าง" className="flex shrink-0 border-b border-slate-200">
|
||||
{(
|
||||
[
|
||||
["bookmarks", `Bookmark (${bookmarks.length})`],
|
||||
["bookmarks", `Bookmark (${visibleBookmarks.length})`],
|
||||
["annotations", `Annotation (${annotations.length})`],
|
||||
] as const
|
||||
).map(([key, label]) => (
|
||||
|
|
@ -1318,6 +1335,10 @@ export default function AnnotationSidebar({
|
|||
<p className="p-4 text-center text-xs text-slate-500">
|
||||
ยังไม่มีที่คั่นหน้า — กดปุ่มคั่นหน้าบนแถบเครื่องมือเพื่อเพิ่ม
|
||||
</p>
|
||||
) : visibleBookmarks.length === 0 ? (
|
||||
<p className="p-4 text-center text-xs text-slate-500">
|
||||
ไม่มีที่คั่นหน้าของเจ้าของหมายเหตุที่เลือก
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
{/* สลับมุมมองแท็บนี้: ต้นไม้หัวข้อ ↔ root แยกตามหน้า */}
|
||||
|
|
@ -1345,7 +1366,7 @@ export default function AnnotationSidebar({
|
|||
{!collapsed && (
|
||||
<ul className="space-y-0.5">
|
||||
{roots.map((b) => {
|
||||
const childCount = bookmarks.filter(
|
||||
const childCount = visibleBookmarks.filter(
|
||||
(x) => x.parentId === b.id,
|
||||
).length;
|
||||
return (
|
||||
|
|
@ -1388,7 +1409,7 @@ export default function AnnotationSidebar({
|
|||
) : (
|
||||
<ul className="space-y-0.5">
|
||||
{/* N2: root = หมุดหัวข้อใหญ่ เรียงตามลำดับหน้า (N5) — sub render ซ้อนใต้ผ่าน renderBookmarkNode */}
|
||||
{bookmarks
|
||||
{visibleBookmarks
|
||||
.filter((b) => !b.parentId)
|
||||
.sort(byPageOrder)
|
||||
.map((b) => renderBookmarkNode(b, 1))}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue