genesis+worker: design system Genesis ทั้งชุด + patch pdf.worker แก้ fake-space ไทย + ปิด review design/responsive/quality รอบ 2026-09-24 — (1) Genesis: ฟอนต์ General Sans self-host + DM Sans + Sarabun, AppNav 56px backdrop-blur แทน SessionBar, DotGrid หน้า login, palette/รัศมี/เงา/focus ตาม docs/DESIGN.md ครบทุกหน้า (2) postinstall scripts/patch-pdf-worker.mjs ถอด if (scaledDim) รอบ prevTransform ให้อัปเดตทุก glyph — กัน addFakeSpaces แทรก space กลางคำไทยใน PDF ที่ justify ด้วย letter-spacing + test กัน regression (tests/scripts/ + fixture thai_letterspacing.pdf, invariant #6) (3) ฟอร์มอัปโหลด + ช่องกลุ่ม typeId ทางเลือก — normDocumentGroup ที่ utils/external-file.ts เป็น single source ร่วมกับ N8 (4) token fixes: focus ring ปุ่ม pin + แถว bookmark ตามหน้า, ปุ่ม action ใน popup/composer/dialog เป็น rounded-md ทั้งคู่, selection preview เป็น tint ของ primary แทน blue-500 ดิบ, default สี shape/highlight รวมที่ utils/annotation-display, loading animate-pulse + จดข้อยกเว้น (เงา pin, font weights ของ viewer) ลง DESIGN.md (5) responsive: Export PDF max-lg:order-first ให้เห็นบน iPad, แถวบน header แถวเดียวเลื่อนได้บนจอแคบ, re-fit ความกว้างตอน toggle sidebar เฉพาะเมื่อยังไม่ซูมเอง, pin popover flip ขึ้นบนเมื่อใกล้ท้ายหน้า, toast hint ตอนสลับ tool กลางเอกสาร, ป้าย "ชนิด · ผู้ทำ" ของรายการผู้อื่นสำหรับจอสัมผัส (6) คุณภาพ: @layer ครอบ @media กันกฎ touch รั่ยไป desktop (Tailwind v3 ยก @layer ออกจาก media ทิ้ง wrapper — ยืนยันด้วย build จริง + วัดบนจอ), กฎ M5 ครอบ contenteditable กัน Safari zoom ตอนแก้ไข TEXTBOX บน iPad, COMMENT ใช้ box มาตราฐานเดียวตาม pin px จริง (render/scroll target/move-clamp/bookmark), ตัดหัวการ์ดซ้ำกับ h2 หน้ารายการ — พร้อมไฟล์ untracked ที่ postinstall/โค้ดอ้างถึงครบชุด (app/fonts/, AppNav, DotGrid, tests/) กัน fresh clone fail ที่ npm install (คำขอผู้ใช้ 2026-09-24)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
e254b935ce
commit
c5c6b65bd2
42 changed files with 1429 additions and 745 deletions
|
|
@ -54,7 +54,7 @@ import { formatThaiDate } from "@/utils/date-format";
|
|||
* ของ PDFViewer.tsx (ห้าม import ข้ามไฟล์: PDFViewer → sidebar → PDFViewer เป็น
|
||||
* circular import — แก้ต้องแก้ทั้งสองที่พร้อมกัน) */
|
||||
const FOCUS_RING_CLS =
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 focus-visible:z-10";
|
||||
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary focus-visible:z-10";
|
||||
|
||||
/** ใส่/ล้างโน้ตคำอธิบายใน data ของ annotation (ทุกชนิดยกเว้น COMMENT — message ทำหน้าที่นี้แล้ว) */
|
||||
function withNote(data: Exclude<AnnotationData, CommentData>, note: string): AnnotationData {
|
||||
|
|
@ -139,7 +139,7 @@ function ViewSwitch<T extends string>({
|
|||
onChange: (v: T) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex rounded-md bg-slate-100 p-0.5">
|
||||
<div className="flex rounded-md bg-ink/[0.05] p-0.5">
|
||||
{options.map((o) => (
|
||||
<button
|
||||
key={o.key}
|
||||
|
|
@ -147,8 +147,8 @@ function ViewSwitch<T extends string>({
|
|||
aria-pressed={value === o.key}
|
||||
className={`flex-1 rounded px-2 py-1 text-[11px] font-medium transition ${FOCUS_RING_CLS} ${
|
||||
value === o.key
|
||||
? "bg-white text-slate-900 shadow-sm"
|
||||
: "text-slate-500 hover:text-slate-600"
|
||||
? "bg-white text-ink ring-1 ring-line"
|
||||
: "text-ink-soft hover:text-ink"
|
||||
}`}
|
||||
onClick={() => onChange(o.key)}
|
||||
>
|
||||
|
|
@ -178,7 +178,7 @@ function MenuItem({
|
|||
type="button"
|
||||
role="menuitem"
|
||||
className={`flex w-full items-start gap-2 rounded px-2 py-1.5 text-left text-xs transition ${FOCUS_RING_CLS} ${
|
||||
danger ? "text-red-600 hover:bg-red-50" : "text-slate-600 hover:bg-slate-100"
|
||||
danger ? "text-error-ink hover:bg-error/10" : "text-ink-soft hover:bg-ink/[0.05]"
|
||||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
|
|
@ -204,17 +204,17 @@ function PageGroupHeader({
|
|||
<button
|
||||
type="button"
|
||||
aria-label={collapsed ? `กางหน้า ${pageIndex + 1}` : `หุบหน้า ${pageIndex + 1}`}
|
||||
className={`flex w-full items-center gap-1.5 rounded px-1 py-1 text-left hover:bg-slate-100 ${FOCUS_RING_CLS}`}
|
||||
className={`flex w-full items-center gap-1.5 rounded px-1 py-1 text-left hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
|
||||
onClick={onToggle}
|
||||
>
|
||||
{collapsed ? (
|
||||
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-slate-500" />
|
||||
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-ink-soft" />
|
||||
) : (
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-slate-500" />
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-ink-soft" />
|
||||
)}
|
||||
<span className="text-xs font-medium text-slate-700">
|
||||
<span className="text-xs font-medium text-ink">
|
||||
หน้า {pageIndex + 1}
|
||||
<span className="ml-1 font-normal text-slate-500">({count})</span>
|
||||
<span className="ml-1 font-normal text-ink-soft">({count})</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
|
@ -774,7 +774,7 @@ export default function AnnotationSidebar({
|
|||
if (e.key === "Escape") setEditBookmark(null);
|
||||
}}
|
||||
placeholder="ชื่อหัวข้อ"
|
||||
className="w-full rounded border border-slate-300 px-1.5 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
className="w-full rounded-md border border-line px-1.5 py-1 text-xs focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
/>
|
||||
<textarea
|
||||
rows={2}
|
||||
|
|
@ -787,16 +787,16 @@ export default function AnnotationSidebar({
|
|||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) saveBookmarkEdit(b);
|
||||
}}
|
||||
placeholder="โน้ต (ไม่บังคับ)"
|
||||
className="mt-1 w-full resize-none rounded border border-slate-300 p-1.5 text-[11px] focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
className="mt-1 w-full resize-none rounded-md border border-line p-1.5 text-[11px] focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
/>
|
||||
<p className="mt-1 text-[11px] text-slate-500">วางที่</p>
|
||||
<p className="mt-1 text-[11px] text-ink-soft">วางที่</p>
|
||||
<Select
|
||||
value={eb.parentId ?? ""}
|
||||
onChange={(e) =>
|
||||
setEditBookmark({ ...eb, parentId: e.target.value || null })
|
||||
}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
className="mt-0.5 w-full rounded border border-slate-300 bg-white px-1.5 py-1 text-[11px] focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
className="mt-0.5 w-full rounded-md border border-line bg-white px-1.5 py-1 text-[11px] focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
>
|
||||
<option value="">ระดับบนสุด (หมุดหัวข้อใหญ่)</option>
|
||||
{movableTo.map((h) => (
|
||||
|
|
@ -808,14 +808,14 @@ export default function AnnotationSidebar({
|
|||
<div className="mt-1.5 flex justify-end gap-1.5 text-[11px]">
|
||||
<button
|
||||
type="button"
|
||||
className={`rounded px-2 py-0.5 text-slate-500 hover:bg-slate-100 ${FOCUS_RING_CLS}`}
|
||||
className={`rounded-md px-2 py-0.5 text-ink-soft hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
|
||||
onClick={() => setEditBookmark(null)}
|
||||
>
|
||||
ยกเลิก
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`rounded bg-primary px-2 py-0.5 font-medium text-white ${FOCUS_RING_CLS}`}
|
||||
className={`rounded-md bg-primary px-2 py-0.5 font-medium text-white ${FOCUS_RING_CLS}`}
|
||||
onClick={() => saveBookmarkEdit(b)}
|
||||
>
|
||||
บันทึก
|
||||
|
|
@ -823,7 +823,7 @@ export default function AnnotationSidebar({
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`group flex items-center gap-1.5 rounded py-1 hover:bg-slate-100 ${pad}`}>
|
||||
<div className={`group flex items-center gap-1.5 rounded py-1 hover:bg-ink/[0.05] ${pad}`}>
|
||||
{hasChildren ? (
|
||||
// bm-toggle: คอลัมน์นี้เป็น "ตาราง" ของต้นไม้ ต้องกว้าง 14px เท่า spacer
|
||||
// ของแถวลูกเสมอ — บนจอสัมผัส rule coarse ที่ขยายปุ่มเป็น 32px จะดันไอคอน
|
||||
|
|
@ -832,7 +832,7 @@ export default function AnnotationSidebar({
|
|||
<button
|
||||
type="button"
|
||||
aria-label={collapsed ? `กางหัวข้อ ${b.title}` : `หุบหัวข้อ ${b.title}`}
|
||||
className={`shrink-0 bm-toggle text-slate-500 hover:text-slate-600 ${FOCUS_RING_CLS}`}
|
||||
className={`shrink-0 bm-toggle text-ink-soft hover:text-ink ${FOCUS_RING_CLS}`}
|
||||
onClick={() => toggleCollapsed(b.id)}
|
||||
>
|
||||
{collapsed ? (
|
||||
|
|
@ -850,26 +850,25 @@ export default function AnnotationSidebar({
|
|||
className={`flex min-w-0 flex-1 items-start gap-1.5 text-left ${FOCUS_RING_CLS}`}
|
||||
onClick={() => onGoToBookmark(b)}
|
||||
>
|
||||
<Bookmark
|
||||
className={`h-3.5 w-3.5 shrink-0 ${
|
||||
depth === 1 ? "text-amber-500" : "text-amber-300"
|
||||
}`}
|
||||
/>
|
||||
{/* ไอคอน bookmark สีเดียวทุกระดับ (warning-ink ผ่าน 5:1 บนขาว — เดิมแยก
|
||||
root/ลูกด้วย amber-500/300 แต่ตัว 300 จาง 1.6:1 แทบมองไม่เห็น;
|
||||
ลำดับชั้นของต้นไม้สื่อด้วย font-semibold + indent อยู่แล้ว) */}
|
||||
<Bookmark className="h-3.5 w-3.5 shrink-0 text-warning-ink" />
|
||||
<span className="min-w-0">
|
||||
<span
|
||||
className={`block truncate text-[13px] leading-snug ${
|
||||
depth === 1 ? "font-semibold text-slate-700" : ""
|
||||
depth === 1 ? "font-semibold text-ink" : ""
|
||||
}`}
|
||||
>
|
||||
{b.title}
|
||||
</span>
|
||||
{/* แสดงผู้บันทึกด้วย — เหมือนแถวผลการค้นหา (N6) และแถวหมายเหตุ
|
||||
(แถวเดิมมีแค่ "หน้า N" ไม่รู้ว่าใครเป็นคนคั่นไว้) */}
|
||||
<span className="block text-xs text-slate-500">
|
||||
<span className="block text-xs text-ink-soft">
|
||||
หน้า {b.pageIndex + 1} · {b.userName || "ไม่ทราบชื่อ"}
|
||||
</span>
|
||||
{b.note && (
|
||||
<span className="mt-0.5 block line-clamp-2 whitespace-pre-wrap break-words text-[11px] text-slate-500">
|
||||
<span className="mt-0.5 block line-clamp-2 whitespace-pre-wrap break-words text-[11px] text-ink-soft">
|
||||
{b.note}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -877,7 +876,7 @@ export default function AnnotationSidebar({
|
|||
</button>
|
||||
{/* จำนวนหมุดย่อย — badge ขวาสุด (UX audit: แยกจากชื่อ ไม่ปนตอนอ่านไว) */}
|
||||
{hasChildren && (
|
||||
<span className="shrink-0 self-center rounded-full bg-slate-100 px-1.5 text-[10px] tabular-nums leading-4 text-slate-500">
|
||||
<span className="shrink-0 self-center rounded-full bg-ink/[0.05] px-1.5 text-[10px] tabular-nums leading-4 text-ink-soft">
|
||||
{children.length}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -888,7 +887,7 @@ export default function AnnotationSidebar({
|
|||
aria-label="จัดการหัวข้อนี้"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={menuFor?.kind === "bookmark" && menuFor.id === b.id}
|
||||
className={`shrink-0 text-slate-500 hover:text-slate-600 group-hover:text-slate-600 ${FOCUS_RING_CLS}`}
|
||||
className={`shrink-0 text-ink-soft hover:text-ink group-hover:text-ink-soft ${FOCUS_RING_CLS}`}
|
||||
onClick={(e) => {
|
||||
if (menuFor?.kind === "bookmark" && menuFor.id === b.id) closeRowMenu();
|
||||
else openRowMenu(e.currentTarget, { kind: "bookmark", id: b.id });
|
||||
|
|
@ -902,7 +901,7 @@ export default function AnnotationSidebar({
|
|||
{hasChildren && !collapsed && (
|
||||
// เส้น guide แนวตั้งนำสายตากลับหา parent แบบ file tree (indent ~17px/ระดับ
|
||||
// ≈ ของเดิม 16px — spacer/chevron w-3.5 คงเดิมจึงตรงแนวกันทุกระดับ)
|
||||
<ul className="ml-3 space-y-0.5 border-l border-slate-200 pl-1">
|
||||
<ul className="ml-3 space-y-0.5 border-l border-line pl-1">
|
||||
{children.map((c) => renderBookmarkNode(c, depth + 1))}
|
||||
</ul>
|
||||
)}
|
||||
|
|
@ -920,7 +919,7 @@ export default function AnnotationSidebar({
|
|||
className={`group rounded px-2 py-1.5 ${
|
||||
a.id === selectedId
|
||||
? "bg-primary/10 ring-1 ring-inset ring-primary/30"
|
||||
: "hover:bg-slate-100"
|
||||
: "hover:bg-ink/[0.05]"
|
||||
} ${a.resolved ? "opacity-60" : ""}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -938,7 +937,7 @@ export default function AnnotationSidebar({
|
|||
{/* UX audit: อักษรไทยมีสระ/วรรณยุกต์ซ้อนหลายชั้น — ข้อความหลักต้อง 13px
|
||||
จึงอ่านได้สบาย (บรรทัดรอง/โน้ตคง 11px) */}
|
||||
<span className="block truncate text-[13px] leading-snug">{annotationPreviewText(a)}</span>
|
||||
<span className="block text-xs text-slate-500">
|
||||
<span className="block text-xs text-ink-soft">
|
||||
หน้า {a.pageIndex + 1} · {a.userName}
|
||||
{a.type === "COMMENT" && (replyCounts[a.id] ?? 0) > 0
|
||||
? ` · ตอบ ${replyCounts[a.id]}`
|
||||
|
|
@ -957,10 +956,10 @@ export default function AnnotationSidebar({
|
|||
title={copyFailedId === a.id ? "คัดลอกไม่ได้ (เบราว์เซอร์บล็อกคลิปบอร์ด)" : "จัดการหมายเหตุนี้"}
|
||||
className={`shrink-0 ${FOCUS_RING_CLS} ${
|
||||
copiedId === a.id
|
||||
? "text-emerald-500"
|
||||
? "text-success-ink"
|
||||
: copyFailedId === a.id
|
||||
? "text-red-500"
|
||||
: "text-slate-500 hover:text-slate-600 group-hover:text-slate-600"
|
||||
? "text-error-ink"
|
||||
: "text-ink-soft hover:text-ink group-hover:text-ink-soft"
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
if (menuFor?.kind === "annotation" && menuFor.id === a.id) closeRowMenu();
|
||||
|
|
@ -989,19 +988,19 @@ export default function AnnotationSidebar({
|
|||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) saveNote(a);
|
||||
}}
|
||||
placeholder="โน้ต เช่น เหตุผลที่ทำรายการนี้…"
|
||||
className="w-full resize-none rounded border border-slate-300 p-1.5 text-[11px] focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
className="w-full resize-none rounded-md border border-line p-1.5 text-[11px] focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
/>
|
||||
<div className="mt-1 flex justify-end gap-1.5 text-[11px]">
|
||||
<button
|
||||
type="button"
|
||||
className={`rounded px-2 py-0.5 text-slate-500 hover:bg-slate-100 ${FOCUS_RING_CLS}`}
|
||||
className={`rounded-md px-2 py-0.5 text-ink-soft hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
|
||||
onClick={() => setNoteFor(null)}
|
||||
>
|
||||
ยกเลิก
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`rounded bg-primary px-2 py-0.5 font-medium text-white ${FOCUS_RING_CLS}`}
|
||||
className={`rounded-md bg-primary px-2 py-0.5 font-medium text-white ${FOCUS_RING_CLS}`}
|
||||
onClick={() => saveNote(a)}
|
||||
>
|
||||
บันทึก
|
||||
|
|
@ -1010,8 +1009,8 @@ export default function AnnotationSidebar({
|
|||
</div>
|
||||
) : (
|
||||
note && (
|
||||
<p className="mt-0.5 flex items-start gap-1 pl-6 text-[11px] text-slate-500">
|
||||
<StickyNote className="mt-0.5 h-3 w-3 shrink-0 text-amber-400" />
|
||||
<p className="mt-0.5 flex items-start gap-1 pl-6 text-[11px] text-ink-soft">
|
||||
<StickyNote className="mt-0.5 h-3 w-3 shrink-0 text-warning-ink" />
|
||||
<span className="line-clamp-2 whitespace-pre-wrap break-words">{note}</span>
|
||||
</p>
|
||||
)
|
||||
|
|
@ -1023,10 +1022,10 @@ export default function AnnotationSidebar({
|
|||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* F4: กรองแสดง annotation ตามผู้เขียน (viewer + sidebar พร้อมกัน) */}
|
||||
<div className="shrink-0 border-b border-slate-200 p-2">
|
||||
<div className="shrink-0 border-b border-line p-2">
|
||||
<Select
|
||||
aria-label="เลือกเจ้าของหมายเหตุที่แสดง"
|
||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-xs text-slate-600 focus:outline-none focus:ring-2 focus:ring-slate-400"
|
||||
className="w-full rounded-md border border-line bg-white px-2 py-1.5 text-xs text-ink-soft focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
value={userFilter}
|
||||
onChange={(e) => onUserFilterChange(e.target.value)}
|
||||
>
|
||||
|
|
@ -1041,7 +1040,7 @@ export default function AnnotationSidebar({
|
|||
</div>
|
||||
|
||||
{/* แท็บหลัก — tablist/tab semantics ให้ screen reader ประกาศว่าเป็นแท็บ */}
|
||||
<div role="tablist" aria-label="รายการในแผงข้าง" className="flex shrink-0 border-b border-slate-200">
|
||||
<div role="tablist" aria-label="รายการในแผงข้าง" className="flex shrink-0 border-b border-line">
|
||||
{(
|
||||
[
|
||||
["bookmarks", `Bookmark (${visibleBookmarks.length})`],
|
||||
|
|
@ -1055,8 +1054,8 @@ export default function AnnotationSidebar({
|
|||
aria-selected={tab === key}
|
||||
className={`flex-1 px-2 py-2 text-xs font-medium tabular-nums transition ${FOCUS_RING_CLS} ${
|
||||
tab === key
|
||||
? "border-b-2 border-primary text-slate-900"
|
||||
: "text-slate-500 hover:text-slate-600"
|
||||
? "border-b-2 border-primary text-ink"
|
||||
: "text-ink-soft hover:text-ink"
|
||||
}`}
|
||||
onClick={() => setTab(key)}
|
||||
>
|
||||
|
|
@ -1068,25 +1067,25 @@ export default function AnnotationSidebar({
|
|||
{/* N6: ค้นหา bookmark พร้อมเงื่อนไข — เฉพาะแท็บที่คั่นหน้า
|
||||
(แยกจากช่อง F14 ที่ค้นหมายเหตุ ซึ่งอยู่เหนือแท็บ) */}
|
||||
{tab === "bookmarks" && (
|
||||
<div className="shrink-0 space-y-1.5 border-b border-slate-200 p-2">
|
||||
<div className="shrink-0 space-y-1.5 border-b border-line p-2">
|
||||
{/* แถวคำค้น — โครงเดียวกับ F14 ของแท็บหมายเหตุ: input เต็มกว้าง + ปุ่มเดียว
|
||||
(เดิมปุ่ม "เงื่อนไข"+badge แย่งแถวเดียวกันจน input เหลือพื้นที่ข้อความ
|
||||
~80px ≈ 6–7 ตัวอักษรไทย พิมพ์คำค้นจริงไม่พอ) */}
|
||||
<form className="flex gap-1" onSubmit={(e) => void runBookmarkSearch(e)}>
|
||||
<div className="relative min-w-0 flex-1">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-500" />
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-ink-soft" />
|
||||
<input
|
||||
value={bmQuery}
|
||||
onChange={(e) => setBmQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.stopPropagation()} // ไม่ให้ปุ่มในช่องค้นหาชนคีย์ลัดของ viewer
|
||||
placeholder="ชื่อ หรือ โน้ต…"
|
||||
className="w-full rounded-md border border-slate-300 py-1.5 pl-7 pr-2 text-xs focus:outline-none focus:ring-2 focus:ring-slate-400"
|
||||
className="w-full rounded-md border border-line py-1.5 pl-7 pr-2 text-xs focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={activeConditionCount === 0 || bmSearching}
|
||||
className={`shrink-0 rounded-md border border-slate-300 px-2 py-1.5 text-xs text-slate-600 hover:bg-slate-100 disabled:opacity-40 ${FOCUS_RING_CLS}`}
|
||||
className={`shrink-0 rounded-md border border-line px-2 py-1.5 text-xs text-ink-soft hover:bg-ink/[0.05] disabled:opacity-40 ${FOCUS_RING_CLS}`}
|
||||
>
|
||||
{bmSearching ? "…" : "หา"}
|
||||
</button>
|
||||
|
|
@ -1099,8 +1098,8 @@ export default function AnnotationSidebar({
|
|||
type="button"
|
||||
aria-expanded={bmShowConditions}
|
||||
onClick={() => setBmShowConditions((v) => !v)}
|
||||
className={`flex min-w-0 flex-1 items-center gap-1.5 rounded px-1.5 py-1 text-[11px] hover:bg-slate-100 ${FOCUS_RING_CLS} ${
|
||||
hasBmConditionValues ? "text-primary" : "text-slate-500"
|
||||
className={`flex min-w-0 flex-1 items-center gap-1.5 rounded px-1.5 py-1 text-[11px] hover:bg-ink/[0.05] ${FOCUS_RING_CLS} ${
|
||||
hasBmConditionValues ? "text-primary" : "text-ink-soft"
|
||||
}`}
|
||||
>
|
||||
<SlidersHorizontal className="h-3 w-3 shrink-0" />
|
||||
|
|
@ -1119,7 +1118,7 @@ export default function AnnotationSidebar({
|
|||
href={withBasePath(`/api/bookmarks/export?fileId=${fileId}`)}
|
||||
title="ส่งออกทุกที่คั่นหน้าของไฟล์นี้เป็น JSON"
|
||||
aria-label="ส่งออกทุกที่คั่นหน้าของไฟล์นี้เป็น JSON"
|
||||
className={`coarse-36 shrink-0 rounded-md p-1.5 text-slate-500 hover:bg-slate-100 hover:text-slate-700 ${FOCUS_RING_CLS}`}
|
||||
className={`coarse-36 shrink-0 rounded-md p-1.5 text-ink-soft hover:bg-ink/[0.05] hover:text-ink ${FOCUS_RING_CLS}`}
|
||||
>
|
||||
<Braces className="h-3.5 w-3.5" />
|
||||
</a>
|
||||
|
|
@ -1128,7 +1127,7 @@ export default function AnnotationSidebar({
|
|||
// จังหวะเดียวทุกแถว: label บนบรรทัด + คู่ input แบ่งความกว้าง grid 1fr–1fr
|
||||
// (เดิม label inline 2 แถว + label บน 1 แถว อ่านไม่เป็นระเบียบ ขอบ input
|
||||
// ไม่ตรงแนวกัน และคู่ date ในแถว inline ล้น 288px จนต้องหนีขึ้นบรรทัดบน)
|
||||
<div className="space-y-1.5 text-[11px] text-slate-600">
|
||||
<div className="space-y-1.5 text-[11px] text-ink-soft">
|
||||
<div>
|
||||
<span className="block pb-0.5">หน้า</span>
|
||||
<div className="grid grid-cols-[1fr_auto_1fr] items-center gap-1">
|
||||
|
|
@ -1143,9 +1142,9 @@ export default function AnnotationSidebar({
|
|||
}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
placeholder="จาก"
|
||||
className="w-full min-w-0 rounded border border-slate-300 px-1.5 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
className="w-full min-w-0 rounded-md border border-line px-1.5 py-1 text-xs focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
/>
|
||||
<span className="text-slate-400">–</span>
|
||||
<span className="text-ink-faint">–</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
|
|
@ -1157,7 +1156,7 @@ export default function AnnotationSidebar({
|
|||
}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
placeholder="ถึง"
|
||||
className="w-full min-w-0 rounded border border-slate-300 px-1.5 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
className="w-full min-w-0 rounded-md border border-line px-1.5 py-1 text-xs focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1170,7 +1169,7 @@ export default function AnnotationSidebar({
|
|||
setBmConditions({ ...bmConditions, userId: e.target.value })
|
||||
}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
className="w-full rounded border border-slate-300 bg-white px-1.5 py-1 text-[11px] focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
className="w-full rounded-md border border-line bg-white px-1.5 py-1 text-[11px] focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
>
|
||||
<option value="">ทุกคน</option>
|
||||
{bookmarkAuthors.map((u) => (
|
||||
|
|
@ -1182,7 +1181,11 @@ export default function AnnotationSidebar({
|
|||
</div>
|
||||
<div>
|
||||
<span className="block pb-0.5">บันทึกวันที่</span>
|
||||
<div className="grid grid-cols-[1fr_auto_1fr] items-center gap-1">
|
||||
{/* เรียง 2 แถวเต็มกว้าง (ไม่ใช่คู่แนวนอน + "–" คั่น): คอลัมน์คู่แคบ
|
||||
~122px ของ sidebar ใส่ค่า "23 ก.ย. 2026" ที่ 16px (rule จอสัมผัส
|
||||
กัน iOS zoom-on-focus) + icon ปฏิทินไม่พอ ปีโดนตัด (code review
|
||||
2026-09-23) — aria-label กำกับช่วงเริ่ม/สุดท้ายอยู่แล้ว */}
|
||||
<div className="grid gap-1">
|
||||
<input
|
||||
type="date"
|
||||
aria-label="วันที่เริ่ม"
|
||||
|
|
@ -1191,9 +1194,8 @@ export default function AnnotationSidebar({
|
|||
setBmConditions({ ...bmConditions, dateFrom: e.target.value })
|
||||
}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
className="w-full min-w-0 rounded border border-slate-300 px-1.5 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
className="w-full min-w-0 rounded-md border border-line px-1.5 py-1 text-xs focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
/>
|
||||
<span className="text-slate-400">–</span>
|
||||
<input
|
||||
type="date"
|
||||
aria-label="วันที่สุดท้าย"
|
||||
|
|
@ -1202,14 +1204,14 @@ export default function AnnotationSidebar({
|
|||
setBmConditions({ ...bmConditions, dateTo: e.target.value })
|
||||
}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
className="w-full min-w-0 rounded border border-slate-300 px-1.5 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
className="w-full min-w-0 rounded-md border border-line px-1.5 py-1 text-xs focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
className={`rounded px-1.5 py-1 text-slate-500 hover:bg-slate-100 ${FOCUS_RING_CLS}`}
|
||||
className={`rounded-md px-1.5 py-1 text-ink-soft hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
|
||||
onClick={() => {
|
||||
// UX audit: ล้างให้สิ้นเชิง — คำค้น + เงื่อนไข + ผลที่แสดง พร้อมกัน
|
||||
setBmConditions(EMPTY_BM_CONDITIONS);
|
||||
|
|
@ -1225,12 +1227,12 @@ export default function AnnotationSidebar({
|
|||
{/* error กับ hint เป็นคำเตือนคนละเรื่อง (ฟอร์มผิด vs ผลเป็นของเงื่อนไขเก่า)
|
||||
— มี error แสดงเดี่ยว ไม่ซ้อนสองข้อความพร้อมกัน */}
|
||||
{bmSearchError ? (
|
||||
<p className="px-1 text-[11px] text-red-600">{bmSearchError}</p>
|
||||
<p className="px-1 text-[11px] text-error-ink">{bmSearchError}</p>
|
||||
) : (
|
||||
bmResults !== null &&
|
||||
bmSearchedSig !== null &&
|
||||
bmSearchedSig !== JSON.stringify([bmQuery.trim(), bmConditions]) && (
|
||||
<p className="px-1 text-[11px] text-amber-600">
|
||||
<p className="px-1 text-[11px] text-warning-ink">
|
||||
เงื่อนไขเปลี่ยนแล้ว กด "หา" เพื่อค้นใหม่
|
||||
</p>
|
||||
)
|
||||
|
|
@ -1243,23 +1245,23 @@ export default function AnnotationSidebar({
|
|||
เนื้อหาใน list ด้านล่าง คลิกแถว = กระโดดไปตำแหน่ง + เลือก */}
|
||||
{tab === "annotations" && (
|
||||
<form
|
||||
className="flex shrink-0 gap-1 border-b border-slate-200 p-2"
|
||||
className="flex shrink-0 gap-1 border-b border-line p-2"
|
||||
onSubmit={(e) => void runSearch(e)}
|
||||
>
|
||||
<div className="relative min-w-0 flex-1">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-500" />
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-ink-soft" />
|
||||
<input
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.stopPropagation()} // ไม่ให้ปุ่มในช่องค้นหาชนคีย์ลัดของ viewer
|
||||
placeholder="ค้นหาในหมายเหตุ…"
|
||||
className="w-full rounded-md border border-slate-300 py-1.5 pl-7 pr-2 text-xs focus:outline-none focus:ring-2 focus:ring-slate-400"
|
||||
className="w-full rounded-md border border-line py-1.5 pl-7 pr-2 text-xs focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!searchQuery.trim() || searching}
|
||||
className={`shrink-0 rounded-md border border-slate-300 px-2 py-1.5 text-xs text-slate-600 hover:bg-slate-100 disabled:opacity-40 ${FOCUS_RING_CLS}`}
|
||||
className={`shrink-0 rounded-md border border-line px-2 py-1.5 text-xs text-ink-soft hover:bg-ink/[0.05] disabled:opacity-40 ${FOCUS_RING_CLS}`}
|
||||
>
|
||||
{searching ? "…" : "หา"}
|
||||
</button>
|
||||
|
|
@ -1271,14 +1273,14 @@ export default function AnnotationSidebar({
|
|||
bmResults !== null ? (
|
||||
<div>
|
||||
<div className="flex items-center justify-between px-1 pb-1">
|
||||
<p className="text-[11px] font-semibold tabular-nums text-slate-500">
|
||||
<p className="text-[11px] font-semibold tabular-nums text-ink-soft">
|
||||
ผลการค้นหา ({bmResults.length})
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="ปิดผลการค้นหา"
|
||||
title="กลับไปรายการที่คั่นหน้า"
|
||||
className={`text-slate-500 hover:text-slate-600 ${FOCUS_RING_CLS}`}
|
||||
className={`text-ink-soft hover:text-ink ${FOCUS_RING_CLS}`}
|
||||
onClick={() => setBmResults(null)}
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
|
|
@ -1288,14 +1290,14 @@ export default function AnnotationSidebar({
|
|||
แชร์จอให้คนอื่นดู (แก้ฟอร์มหลังค้นบรรทัดนี้ไม่เปลี่ยน จนกด "หา" ใหม่) */}
|
||||
{bmSearchedSummary && (
|
||||
<p
|
||||
className="truncate px-1 pb-1 text-[11px] text-slate-500"
|
||||
className="truncate px-1 pb-1 text-[11px] text-ink-soft"
|
||||
title={bmSearchedSummary}
|
||||
>
|
||||
ค้นด้วย: {bmSearchedSummary}
|
||||
</p>
|
||||
)}
|
||||
{bmResults.length === 0 ? (
|
||||
<p className="px-1 pb-1 text-xs text-slate-500">ไม่พบ</p>
|
||||
<p className="px-1 pb-1 text-xs text-ink-soft">ไม่พบ</p>
|
||||
) : (
|
||||
<ul className="space-y-0.5">
|
||||
{bmResults.map((b) => {
|
||||
|
|
@ -1304,22 +1306,22 @@ export default function AnnotationSidebar({
|
|||
<li key={b.id}>
|
||||
<button
|
||||
type="button"
|
||||
className={`flex w-full items-start gap-1.5 rounded px-1 py-1 text-left hover:bg-slate-100 ${FOCUS_RING_CLS}`}
|
||||
className={`flex w-full items-start gap-1.5 rounded px-1 py-1 text-left hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
|
||||
onClick={() => onGoToBookmark(b)}
|
||||
>
|
||||
<Bookmark className="mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-500" />
|
||||
<Bookmark className="mt-0.5 h-3.5 w-3.5 shrink-0 text-warning-ink" />
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate text-[13px] leading-snug">{b.title}</span>
|
||||
<span className="block text-xs text-slate-500">
|
||||
<span className="block text-xs text-ink-soft">
|
||||
หน้า {b.pageIndex + 1} · {b.userName || "ไม่ทราบชื่อ"}
|
||||
</span>
|
||||
{parent && (
|
||||
<span className="block truncate text-[11px] text-slate-500">
|
||||
<span className="block truncate text-[11px] text-ink-soft">
|
||||
ใน: {parent.title}
|
||||
</span>
|
||||
)}
|
||||
{b.note && (
|
||||
<span className="mt-0.5 block line-clamp-2 whitespace-pre-wrap break-words text-[11px] text-slate-500">
|
||||
<span className="mt-0.5 block line-clamp-2 whitespace-pre-wrap break-words text-[11px] text-ink-soft">
|
||||
{b.note}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -1332,11 +1334,11 @@ export default function AnnotationSidebar({
|
|||
)}
|
||||
</div>
|
||||
) : bookmarks.length === 0 ? (
|
||||
<p className="p-4 text-center text-xs text-slate-500">
|
||||
<p className="p-4 text-center text-xs text-ink-soft">
|
||||
ยังไม่มีที่คั่นหน้า — กดปุ่มคั่นหน้าบนแถบเครื่องมือเพื่อเพิ่ม
|
||||
</p>
|
||||
) : visibleBookmarks.length === 0 ? (
|
||||
<p className="p-4 text-center text-xs text-slate-500">
|
||||
<p className="p-4 text-center text-xs text-ink-soft">
|
||||
ไม่มีที่คั่นหน้าของเจ้าของหมายเหตุที่เลือก
|
||||
</p>
|
||||
) : (
|
||||
|
|
@ -1373,27 +1375,27 @@ export default function AnnotationSidebar({
|
|||
<li key={b.id}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-start gap-1.5 rounded py-1 pl-5 pr-1 text-left hover:bg-slate-100"
|
||||
className={`flex w-full items-start gap-1.5 rounded py-1 pl-5 pr-1 text-left hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
|
||||
onClick={() => onGoToBookmark(b)}
|
||||
>
|
||||
<Bookmark className="mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-500" />
|
||||
<Bookmark className="mt-0.5 h-3.5 w-3.5 shrink-0 text-warning-ink" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-[13px] leading-snug font-medium text-slate-700">
|
||||
<span className="block truncate text-[13px] leading-snug font-medium text-ink">
|
||||
{b.title}
|
||||
</span>
|
||||
{/* ผู้บันทึกเหมือนแถวผลการค้นหา — ไม่ใส่ "หน้า N"
|
||||
เพราะหัวกลุ่มของมุมมองนี้บอกหน้าอยู่แล้ว */}
|
||||
<span className="block text-xs text-slate-500">
|
||||
<span className="block text-xs text-ink-soft">
|
||||
{b.userName || "ไม่ทราบชื่อ"}
|
||||
</span>
|
||||
{b.note && (
|
||||
<span className="mt-0.5 block line-clamp-2 whitespace-pre-wrap break-words text-[11px] text-slate-500">
|
||||
<span className="mt-0.5 block line-clamp-2 whitespace-pre-wrap break-words text-[11px] text-ink-soft">
|
||||
{b.note}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{childCount > 0 && (
|
||||
<span className="ml-auto shrink-0 self-center rounded-full bg-slate-100 px-1.5 text-[10px] tabular-nums leading-4 text-slate-500">
|
||||
<span className="ml-auto shrink-0 self-center rounded-full bg-ink/[0.05] px-1.5 text-[10px] tabular-nums leading-4 text-ink-soft">
|
||||
{childCount}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -1420,29 +1422,29 @@ export default function AnnotationSidebar({
|
|||
) : visibleSearchResults !== null ? (
|
||||
<div>
|
||||
<div className="flex items-center justify-between px-1 pb-1">
|
||||
<p className="text-[11px] font-semibold tabular-nums text-slate-500">
|
||||
<p className="text-[11px] font-semibold tabular-nums text-ink-soft">
|
||||
ผลการค้นหา ({visibleSearchResults.length})
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="ปิดผลการค้นหา"
|
||||
title="กลับไปรายการหมายเหตุ"
|
||||
className={`text-slate-500 hover:text-slate-600 ${FOCUS_RING_CLS}`}
|
||||
className={`text-ink-soft hover:text-ink ${FOCUS_RING_CLS}`}
|
||||
onClick={() => setSearchResults(null)}
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
{visibleSearchResults.length === 0 ? (
|
||||
<p className="px-1 pb-1 text-xs text-slate-500">ไม่พบ</p>
|
||||
<p className="px-1 pb-1 text-xs text-ink-soft">ไม่พบ</p>
|
||||
) : (
|
||||
// reuse แถวเดิม — li มี data-ann-id ให้ effect scrollIntoView หาเจอ
|
||||
<ul className="space-y-1">{visibleSearchResults.map(renderAnnotationRow)}</ul>
|
||||
)}
|
||||
</div>
|
||||
) : annotations.length === 0 ? (
|
||||
<p className="p-4 text-center text-xs text-slate-500">
|
||||
ยังไม่มีหมายเหตุ — คลุมข้อความ / วาดเส้น / คลิกวางคอมเมนต์บนหน้าเอกสาร
|
||||
<p className="p-4 text-center text-xs text-ink-soft">
|
||||
ยังไม่มีหมายเหตุ — คลุมข้อความ / วาดเส้น / คอมเมนต์บนหน้าเอกสาร
|
||||
<span className="mt-1 block text-[11px]">
|
||||
คีย์ลัด: H มาร์ก · D วาด · C comment · B คั่นหน้า · Ctrl+Z ย้อน
|
||||
</span>
|
||||
|
|
@ -1481,7 +1483,7 @@ export default function AnnotationSidebar({
|
|||
<>
|
||||
{pendingItems.length > 0 && (
|
||||
<section>
|
||||
<p className="px-2 pb-1 pt-2 text-[11px] font-semibold text-slate-500">
|
||||
<p className="px-2 pb-1 pt-2 text-[11px] font-semibold text-ink-soft">
|
||||
ค้างดำเนินการ
|
||||
</p>
|
||||
<ul className="space-y-1">{pendingItems.map(renderAnnotationRow)}</ul>
|
||||
|
|
@ -1489,7 +1491,7 @@ export default function AnnotationSidebar({
|
|||
)}
|
||||
{resolvedItems.length > 0 && (
|
||||
<section>
|
||||
<p className="px-2 pb-1 pt-3 text-[11px] font-semibold text-emerald-500">
|
||||
<p className="px-2 pb-1 pt-3 text-[11px] font-semibold text-success-ink">
|
||||
จัดการแล้ว
|
||||
</p>
|
||||
<ul className="space-y-1">{resolvedItems.map(renderAnnotationRow)}</ul>
|
||||
|
|
@ -1511,35 +1513,35 @@ export default function AnnotationSidebar({
|
|||
{/* F19/A7: dialog ประวัติ — รายการ version (เวลา + ชนิดการเปลี่ยนแปลง) + ปุ่มกู้คืน */}
|
||||
{historyFor && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/40 p-4"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-ink/40 p-4"
|
||||
onClick={() => setHistoryFor(null)}
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="ประวัติการเปลี่ยนแปลง"
|
||||
className="w-96 max-w-full rounded-lg border border-slate-200 bg-white p-4 shadow-xl"
|
||||
className="w-96 max-w-full rounded-lg border border-line bg-white p-4 shadow-xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<p className="text-sm font-semibold">ประวัติการเปลี่ยนแปลง</p>
|
||||
<p className="mt-0.5 truncate text-[11px] text-slate-500">
|
||||
<p className="mt-0.5 truncate text-[11px] text-ink-soft">
|
||||
{annotationPreviewText(historyFor)}
|
||||
</p>
|
||||
{historyError && (
|
||||
<p className="mt-2 rounded bg-red-50 px-2 py-1 text-[11px] text-red-600">
|
||||
<p className="mt-2 rounded bg-error/10 px-2 py-1 text-[11px] text-error-ink">
|
||||
{historyError}
|
||||
</p>
|
||||
)}
|
||||
{versions === null && !historyError && (
|
||||
<p className="mt-3 text-xs text-slate-500">กำลังโหลด…</p>
|
||||
<p className="mt-3 text-xs text-ink-soft">กำลังโหลด…</p>
|
||||
)}
|
||||
{versions !== null && versions.length === 0 && (
|
||||
<p className="mt-3 text-xs text-slate-500">
|
||||
<p className="mt-3 text-xs text-ink-soft">
|
||||
ยังไม่มีบันทึกการเปลี่ยนแปลง (เกิดขึ้นเมื่อแก้ไขหรือลบรายการนี้)
|
||||
</p>
|
||||
)}
|
||||
{versions !== null && versions.length > 0 && (
|
||||
<ul className="mt-3 max-h-[60vh] divide-y divide-slate-100 overflow-y-auto">
|
||||
<ul className="mt-3 max-h-[60vh] divide-y divide-line/60 overflow-y-auto">
|
||||
{versions.map((v, i) => {
|
||||
// สิ่งที่เปลี่ยนของรอบนี้ = snapshot ก่อนแก้ (v.data) เทียบ state
|
||||
// ถัดไป: snapshot ของรอบแก้ที่ใหม่กว่าถัดไป (i-1) หรือค่าปัจจุบัน
|
||||
|
|
@ -1559,11 +1561,11 @@ export default function AnnotationSidebar({
|
|||
<span className="block text-xs font-medium">
|
||||
{v.kind === "delete" ? "ถูกลบ" : "ถูกแก้ไข"}
|
||||
</span>
|
||||
<span className="block text-[11px] text-slate-500" suppressHydrationWarning>
|
||||
<span className="block text-[11px] text-ink-soft" suppressHydrationWarning>
|
||||
{formatThaiDate(v.createdAt, true)} · โดย {v.userName}
|
||||
</span>
|
||||
{changes.map((c) => (
|
||||
<span key={c} className="mt-0.5 block text-[11px] text-slate-600">
|
||||
<span key={c} className="mt-0.5 block text-[11px] text-ink-soft">
|
||||
{c}
|
||||
</span>
|
||||
))}
|
||||
|
|
@ -1571,7 +1573,7 @@ export default function AnnotationSidebar({
|
|||
<button
|
||||
type="button"
|
||||
disabled={restoringId !== null}
|
||||
className={`mt-0.5 shrink-0 rounded border border-slate-300 px-2 py-1 text-[11px] text-slate-600 hover:bg-slate-100 disabled:opacity-40 ${FOCUS_RING_CLS}`}
|
||||
className={`mt-0.5 shrink-0 rounded-md border border-line px-2 py-1 text-[11px] text-ink-soft hover:bg-ink/[0.05] disabled:opacity-40 ${FOCUS_RING_CLS}`}
|
||||
onClick={() => void handleRestore(v.id)}
|
||||
>
|
||||
{restoringId === v.id ? "กำลังกู้คืน…" : "กู้คืน"}
|
||||
|
|
@ -1585,7 +1587,7 @@ export default function AnnotationSidebar({
|
|||
<button
|
||||
type="button"
|
||||
autoFocus
|
||||
className={`rounded px-3 py-1.5 text-slate-500 hover:bg-slate-100 ${FOCUS_RING_CLS}`}
|
||||
className={`rounded-md px-3 py-1.5 text-ink-soft hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
|
||||
onClick={() => setHistoryFor(null)}
|
||||
>
|
||||
ปิด
|
||||
|
|
@ -1605,7 +1607,7 @@ export default function AnnotationSidebar({
|
|||
data-tool-menu=""
|
||||
role="menu"
|
||||
aria-label={menuFor.kind === "annotation" ? "จัดการหมายเหตุ" : "จัดการหัวข้อ"}
|
||||
className="w-60 rounded-lg border border-slate-200 bg-white p-1.5 shadow-lg"
|
||||
className="w-60 rounded-lg border border-line bg-white p-1.5 shadow-lg"
|
||||
style={{
|
||||
position: "fixed",
|
||||
left: menuPos.left,
|
||||
|
|
@ -1619,20 +1621,20 @@ export default function AnnotationSidebar({
|
|||
>
|
||||
{menuConfirmDelete ? (
|
||||
<div className="p-1">
|
||||
<p className="px-1 text-xs text-slate-700">
|
||||
<p className="px-1 text-xs text-ink">
|
||||
ลบ{menuFor.kind === "annotation" ? "รายการ" : "หัวข้อ"}นี้?
|
||||
</p>
|
||||
<div className="mt-1.5 flex justify-end gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
className={`rounded px-2 py-1 text-xs text-slate-500 hover:bg-slate-100 ${FOCUS_RING_CLS}`}
|
||||
className={`rounded-md px-2 py-1 text-xs text-ink-soft hover:bg-ink/[0.05] ${FOCUS_RING_CLS}`}
|
||||
onClick={closeRowMenu}
|
||||
>
|
||||
ยกเลิก
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`rounded bg-red-600 px-2 py-1 text-xs font-medium text-white hover:bg-red-700 ${FOCUS_RING_CLS}`}
|
||||
className={`rounded-md border border-error px-2 py-1 text-xs font-medium text-error-ink hover:bg-error/10 ${FOCUS_RING_CLS}`}
|
||||
onClick={() => {
|
||||
// อ่าน kind/id ก่อน close — setMenuFor(null) แล้วเงื่อนไขเดิมหาย
|
||||
if (menuFor.kind === "annotation") onDeleteAnnotation(menuFor.id);
|
||||
|
|
@ -1678,7 +1680,7 @@ export default function AnnotationSidebar({
|
|||
)}
|
||||
{menuAnn.userId === userId && (
|
||||
<>
|
||||
<div className="my-1 border-t border-slate-100" />
|
||||
<div className="my-1 border-t border-line/60" />
|
||||
<MenuItem
|
||||
danger
|
||||
Icon={Trash2}
|
||||
|
|
@ -1713,7 +1715,7 @@ export default function AnnotationSidebar({
|
|||
)}
|
||||
{menuBm.userId === userId && (
|
||||
<>
|
||||
<div className="my-1 border-t border-slate-100" />
|
||||
<div className="my-1 border-t border-line/60" />
|
||||
<MenuItem
|
||||
danger
|
||||
Icon={Trash2}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue