wiki-viewer 1.14.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/agent/activity/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js +9 -9
- package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/move/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/search/route.js +9 -9
- package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/internal/span/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/settings/route.js +7 -7
- package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/assets/[...path]/route.js +5 -5
- package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/share/[token]/route.js +6 -5
- package/.next/standalone/.next/server/app/api/share/[token]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/share/route.js +4 -4
- package/.next/standalone/.next/server/app/api/share/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/reveal/route.js +6 -6
- package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/set-root/route.js +6 -5
- package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js +5 -4
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/refresh/route.js +6 -5
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/refresh/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js +5 -4
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/route.js +7 -6
- package/.next/standalone/.next/server/app/api/system/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/upload/[...path]/route.js +5 -5
- package/.next/standalone/.next/server/app/api/upload/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/app/route.js +7 -7
- package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/backlinks/route.js +7 -7
- package/.next/standalone/.next/server/app/api/wiki/backlinks/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/content/route.js +7 -7
- package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/download/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/download/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/file/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/folder/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-branches/route.js +4 -4
- package/.next/standalone/.next/server/app/api/wiki/git-branches/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-checkout/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/git-checkout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-diff/route.js +4 -4
- package/.next/standalone/.next/server/app/api/wiki/git-diff/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-file-info/route.js +4 -4
- package/.next/standalone/.next/server/app/api/wiki/git-file-info/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-history/route.js +4 -4
- package/.next/standalone/.next/server/app/api/wiki/git-history/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-pull/route.js +8 -8
- package/.next/standalone/.next/server/app/api/wiki/git-pull/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/move/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/new-file/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/outlinks/route.js +7 -7
- package/.next/standalone/.next/server/app/api/wiki/outlinks/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/page/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/presence/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/presence/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/search/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/slugs/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/upload/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/watch/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/s/[token]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/signin/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-jzw2_._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.-oq-7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__000spch._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01oin-a._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01qcera._.js +16 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__02.uul~._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__024967n._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06booxp._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__071_pmz._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0bqpg._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_1f2cw._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0a6j6r4._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0aoqat-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0as-u6z._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bvtbax._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0c-ee48._.js +5 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e_9pc5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0esmskc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fshk._._.js +20 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fu72o7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0k9mili._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l-wu5u._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lvz22t._.js +20 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0n-chxk._.js +16 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0n9zdf0._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nmx3k2._.js +12 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nzlmkb._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o0v6hf._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s38pid._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s_8poz._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sfrx~.._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sggstu._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0u81k~2._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uibxf_._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wfv_by._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wlj-yw._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w~__xz._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yat~ul._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yxauu6._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zbkgnr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zv_j05._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0~rntxj._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_lib_search_0d-_j3u._.js +4 -4
- package/.next/standalone/.next/server/chunks/src_lib_search_0ymmp0c._.js +3 -3
- package/.next/standalone/.next/server/chunks/src_lib_workspaces_ts_0qon40u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{0~w0_katex_dist_katex_mjs_029cdbg._.js → 0~w0_katex_dist_katex_mjs_0s4yb1t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0z41nrr._.js → [root-of-the-server]__10te1ye._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_13p348f._.js → node_modules__pnpm_0-a_zjq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0gos._c._.js → node_modules__pnpm_02l_1_8._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ru1i2-._.js → node_modules__pnpm_03rzttw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_08xpp3w._.js → node_modules__pnpm_04frxp9._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0b8odbc._.js → node_modules__pnpm_057-t6m._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0f_n_zp._.js → node_modules__pnpm_05_oahi._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0~i3a.i._.js → node_modules__pnpm_05ans~m._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0i2m0b.._.js → node_modules__pnpm_06-2ia.._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0aha1~u._.js → node_modules__pnpm_086.ow4._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0w.kwue._.js → node_modules__pnpm_0cahog3._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0e0r_qk._.js → node_modules__pnpm_0cxanql._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ajqqlq._.js → node_modules__pnpm_0e09gmf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0hzzx9d._.js → node_modules__pnpm_0eqjo2f._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_02bje_g._.js → node_modules__pnpm_0f-yazg._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xk042~._.js → node_modules__pnpm_0iu44jv._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_11ede0p._.js → node_modules__pnpm_0jh8q66._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0k-7uzm._.js → node_modules__pnpm_0m39f~t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xr.ztu._.js → node_modules__pnpm_0ma-8l.._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0i6_kfe._.js → node_modules__pnpm_0met6sx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_04v1_ty._.js → node_modules__pnpm_0mtrsi0._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0607~kg._.js → node_modules__pnpm_0n_swwn._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0t4l89_._.js → node_modules__pnpm_0ndyy93._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-_58b8._.js → node_modules__pnpm_0s-cj7c._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0y2h_cz._.js → node_modules__pnpm_0tr41-t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_08edl1n._.js → node_modules__pnpm_0u.uija._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0026mgr._.js → node_modules__pnpm_0wiyn6d._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_09o_spj._.js → node_modules__pnpm_0ygzt82._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_app_page_tsx_0ss2.w7._.js +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/static/chunks/0ci7khnv7i~7_.js +17 -0
- package/.next/standalone/.next/static/chunks/0sx~65~lg4mgl.css +5 -0
- package/.next/standalone/package.json +1 -1
- package/README.md +88 -17
- package/bin/postbuild.js +21 -0
- package/bin/wiki-viewer.js +198 -6
- package/package.json +1 -1
- package/.next/standalone/.github/workflows/publish.yml +0 -37
- package/.next/standalone/.handoffs/archive/handoff-20260612-113332.md +0 -98
- package/.next/standalone/.handoffs/archive/handoff-20260612-141032.md +0 -73
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-.y6im._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-3_t2y._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-kjkzt._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-puax4._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01h996g._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0329ktu._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__03_xj4r._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05398dt._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06lfvxh._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06w0aet._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__070_p2a._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0905hi1._.js +0 -21
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0aa63~j._.js +0 -5
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0avcplk._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b8_kyo._.js +0 -16
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bticq1._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bwevvu._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0c__k3q._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cp~z4x._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fmdv9h._.js +0 -20
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0h0qbwr._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jk~oyi._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ndq77b._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nvgpuk._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ocvng2._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ps4h~.._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q7jqwp._.js +0 -12
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qew~6p._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s_9f-a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uv7b8u._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uv8.9i._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vfe2io._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w11kjy._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wc-.e4._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ymovmp._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0~c6ouv._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0~rhs0u._.js +0 -16
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10h9tid._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10yk-u1._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__11105ew._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__11a3dhw._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13420tt._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13h~pdf._.js +0 -3
- package/.next/standalone/.next/static/chunks/02kg.lbkfi1yc.css +0 -5
- package/.next/standalone/.next/static/chunks/0rcwd6-.iu~nd.js +0 -17
- package/.next/standalone/.pi/ownership.json +0 -3
- package/.next/standalone/AGENTS.md +0 -114
- package/.next/standalone/DESIGN.md +0 -521
- package/.next/standalone/README.md +0 -821
- package/.next/standalone/TODO.md +0 -20
- package/.next/standalone/agents/bootstrap-prompt.md +0 -14
- package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +0 -456
- package/.next/standalone/bin/postbuild.js +0 -78
- package/.next/standalone/bin/wiki-viewer.js +0 -922
- package/.next/standalone/certificates/localhost-key.pem +0 -28
- package/.next/standalone/certificates/localhost.pem +0 -24
- package/.next/standalone/docs/agent-collab-plan.md +0 -1615
- package/.next/standalone/docs/agent-collab-v2-plan.md +0 -771
- package/.next/standalone/docs/agent-fs-plan.md +0 -232
- package/.next/standalone/docs/editorial-skin-spec.md +0 -242
- package/.next/standalone/docs/file-vs-collab-authority.md +0 -124
- package/.next/standalone/docs/mobile-readiness-spec.md +0 -229
- package/.next/standalone/docs/workspacing-plan.md +0 -364
- package/.next/standalone/packages/wiki-viewer-mcp/README.md +0 -195
- package/.next/standalone/packages/wiki-viewer-mcp/bench-e2e.mjs +0 -166
- package/.next/standalone/packages/wiki-viewer-mcp/bench-latency.mjs +0 -87
- package/.next/standalone/packages/wiki-viewer-mcp/bench-payload.mjs +0 -45
- package/.next/standalone/packages/wiki-viewer-mcp/dist/http-client.js +0 -331
- package/.next/standalone/packages/wiki-viewer-mcp/dist/index.js +0 -531
- package/.next/standalone/packages/wiki-viewer-mcp/dist/register.js +0 -94
- package/.next/standalone/packages/wiki-viewer-mcp/dist/state-cache.js +0 -28
- package/.next/standalone/packages/wiki-viewer-mcp/package-lock.json +0 -1716
- package/.next/standalone/packages/wiki-viewer-mcp/package.json +0 -41
- package/.next/standalone/packages/wiki-viewer-mcp/src/http-client.ts +0 -459
- package/.next/standalone/packages/wiki-viewer-mcp/src/index.ts +0 -638
- package/.next/standalone/packages/wiki-viewer-mcp/src/register.ts +0 -144
- package/.next/standalone/packages/wiki-viewer-mcp/src/state-cache.ts +0 -55
- package/.next/standalone/packages/wiki-viewer-mcp/tsconfig.json +0 -19
- package/.next/standalone/pnpm-lock.yaml +0 -6697
- package/.next/standalone/pnpm-workspace.yaml +0 -8
- package/.next/standalone/postcss.config.mjs +0 -2
- package/.next/standalone/src/app/api/agent/activity/route.ts +0 -59
- package/.next/standalone/src/app/api/agent/admin/agents/[agentId]/revoke/route.ts +0 -40
- package/.next/standalone/src/app/api/agent/admin/agents/route.ts +0 -33
- package/.next/standalone/src/app/api/agent/admin/registrations/[regId]/approve/route.ts +0 -83
- package/.next/standalone/src/app/api/agent/admin/registrations/[regId]/deny/route.ts +0 -36
- package/.next/standalone/src/app/api/agent/admin/registrations/route.ts +0 -27
- package/.next/standalone/src/app/api/agent/events/[...path]/route.ts +0 -148
- package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +0 -219
- package/.next/standalone/src/app/api/agent/fs/file/[...path]/route.ts +0 -446
- package/.next/standalone/src/app/api/agent/fs/ls/[[...path]]/route.ts +0 -152
- package/.next/standalone/src/app/api/agent/fs/move/route.ts +0 -130
- package/.next/standalone/src/app/api/agent/fs/search/route.ts +0 -272
- package/.next/standalone/src/app/api/agent/internal/span/route.ts +0 -121
- package/.next/standalone/src/app/api/agent/register/[regId]/route.ts +0 -50
- package/.next/standalone/src/app/api/agent/register/route.ts +0 -118
- package/.next/standalone/src/app/api/agent/settings/route.ts +0 -34
- package/.next/standalone/src/app/api/agent/settings/token/regenerate/route.ts +0 -20
- package/.next/standalone/src/app/api/agent/sidecar/[...path]/route.ts +0 -53
- package/.next/standalone/src/app/api/agents/install/route.ts +0 -143
- package/.next/standalone/src/app/api/agents/skill/route.ts +0 -20
- package/.next/standalone/src/app/api/agents/skill.tar.gz/route.ts +0 -87
- package/.next/standalone/src/app/api/app-proxy/[...path]/route.ts +0 -247
- package/.next/standalone/src/app/api/assets/[...path]/route.ts +0 -75
- package/.next/standalone/src/app/api/auth/[...all]/route.ts +0 -7
- package/.next/standalone/src/app/api/owner/init/route.ts +0 -14
- package/.next/standalone/src/app/api/share/[token]/route.ts +0 -269
- package/.next/standalone/src/app/api/share/route.ts +0 -127
- package/.next/standalone/src/app/api/system/admins/route.ts +0 -103
- package/.next/standalone/src/app/api/system/auth-config/route.ts +0 -22
- package/.next/standalone/src/app/api/system/auth-settings/route.ts +0 -86
- package/.next/standalone/src/app/api/system/browse/route.ts +0 -82
- package/.next/standalone/src/app/api/system/clear-root/route.ts +0 -16
- package/.next/standalone/src/app/api/system/config/route.ts +0 -14
- package/.next/standalone/src/app/api/system/pins/route.ts +0 -38
- package/.next/standalone/src/app/api/system/reveal/route.ts +0 -36
- package/.next/standalone/src/app/api/system/root-status/route.ts +0 -13
- package/.next/standalone/src/app/api/system/set-root/route.ts +0 -53
- package/.next/standalone/src/app/api/system/users/route.ts +0 -120
- package/.next/standalone/src/app/api/system/workspaces/[id]/open/route.ts +0 -34
- package/.next/standalone/src/app/api/system/workspaces/[id]/refresh/route.ts +0 -32
- package/.next/standalone/src/app/api/system/workspaces/[id]/route.ts +0 -78
- package/.next/standalone/src/app/api/system/workspaces/route.ts +0 -128
- package/.next/standalone/src/app/api/upload/[...path]/route.ts +0 -85
- package/.next/standalone/src/app/api/wiki/app/route.ts +0 -60
- package/.next/standalone/src/app/api/wiki/backlinks/route.ts +0 -29
- package/.next/standalone/src/app/api/wiki/content/route.ts +0 -159
- package/.next/standalone/src/app/api/wiki/download/route.ts +0 -100
- package/.next/standalone/src/app/api/wiki/file/route.ts +0 -57
- package/.next/standalone/src/app/api/wiki/folder/route.ts +0 -34
- package/.next/standalone/src/app/api/wiki/git-branches/route.ts +0 -32
- package/.next/standalone/src/app/api/wiki/git-checkout/route.ts +0 -45
- package/.next/standalone/src/app/api/wiki/git-diff/route.ts +0 -33
- package/.next/standalone/src/app/api/wiki/git-file-info/route.ts +0 -27
- package/.next/standalone/src/app/api/wiki/git-history/route.ts +0 -27
- package/.next/standalone/src/app/api/wiki/git-pull/route.ts +0 -48
- package/.next/standalone/src/app/api/wiki/move/route.ts +0 -61
- package/.next/standalone/src/app/api/wiki/new-file/route.ts +0 -56
- package/.next/standalone/src/app/api/wiki/outlinks/route.ts +0 -76
- package/.next/standalone/src/app/api/wiki/page/route.ts +0 -92
- package/.next/standalone/src/app/api/wiki/presence/route.ts +0 -56
- package/.next/standalone/src/app/api/wiki/route.ts +0 -116
- package/.next/standalone/src/app/api/wiki/search/route.ts +0 -44
- package/.next/standalone/src/app/api/wiki/slugs/route.ts +0 -54
- package/.next/standalone/src/app/api/wiki/upload/route.ts +0 -95
- package/.next/standalone/src/app/api/wiki/watch/route.ts +0 -67
- package/.next/standalone/src/app/globals.css +0 -1249
- package/.next/standalone/src/app/icon.svg +0 -11
- package/.next/standalone/src/app/layout.tsx +0 -78
- package/.next/standalone/src/app/manifest.ts +0 -30
- package/.next/standalone/src/app/page.tsx +0 -3260
- package/.next/standalone/src/app/s/[token]/page.tsx +0 -357
- package/.next/standalone/src/app/signin/page.tsx +0 -16
- package/.next/standalone/src/app/signin/signin-form.tsx +0 -238
- package/.next/standalone/src/components/ai-panel/activity-row.tsx +0 -284
- package/.next/standalone/src/components/ai-panel/ai-panel.tsx +0 -311
- package/.next/standalone/src/components/ai-panel/token-section.tsx +0 -313
- package/.next/standalone/src/components/auth-settings-sheet.tsx +0 -474
- package/.next/standalone/src/components/confirm-dialog.tsx +0 -56
- package/.next/standalone/src/components/dir-picker.tsx +0 -606
- package/.next/standalone/src/components/editor/backlinks-panel.tsx +0 -139
- package/.next/standalone/src/components/editor/bubble-menu.tsx +0 -346
- package/.next/standalone/src/components/editor/callout-extension.ts +0 -36
- package/.next/standalone/src/components/editor/comment-pip.tsx +0 -56
- package/.next/standalone/src/components/editor/comment-thread.tsx +0 -254
- package/.next/standalone/src/components/editor/csv-viewer.tsx +0 -379
- package/.next/standalone/src/components/editor/document-outline.tsx +0 -230
- package/.next/standalone/src/components/editor/editor-toolbar.tsx +0 -564
- package/.next/standalone/src/components/editor/editor.tsx +0 -1327
- package/.next/standalone/src/components/editor/extensions/drag-handle.ts +0 -325
- package/.next/standalone/src/components/editor/extensions/heading-anchors.ts +0 -55
- package/.next/standalone/src/components/editor/extensions/proof-span.ts +0 -60
- package/.next/standalone/src/components/editor/extensions/resizable-image.tsx +0 -159
- package/.next/standalone/src/components/editor/extensions.ts +0 -150
- package/.next/standalone/src/components/editor/file-fallback-viewer.tsx +0 -75
- package/.next/standalone/src/components/editor/folder-index.tsx +0 -236
- package/.next/standalone/src/components/editor/google-doc-viewer.tsx +0 -73
- package/.next/standalone/src/components/editor/image-viewer.tsx +0 -57
- package/.next/standalone/src/components/editor/large-file-gate.tsx +0 -108
- package/.next/standalone/src/components/editor/link-popover.tsx +0 -111
- package/.next/standalone/src/components/editor/media-popover.tsx +0 -212
- package/.next/standalone/src/components/editor/media-viewer.tsx +0 -70
- package/.next/standalone/src/components/editor/mermaid-viewer.tsx +0 -286
- package/.next/standalone/src/components/editor/node-app-viewer.tsx +0 -328
- package/.next/standalone/src/components/editor/notebook-viewer.tsx +0 -371
- package/.next/standalone/src/components/editor/office/docx-viewer.tsx +0 -93
- package/.next/standalone/src/components/editor/office/office-chrome.tsx +0 -83
- package/.next/standalone/src/components/editor/office/pptx-viewer.tsx +0 -104
- package/.next/standalone/src/components/editor/office/xlsx-viewer.tsx +0 -192
- package/.next/standalone/src/components/editor/pdf-viewer.tsx +0 -32
- package/.next/standalone/src/components/editor/proof-span-popover.tsx +0 -162
- package/.next/standalone/src/components/editor/slash-commands.tsx +0 -629
- package/.next/standalone/src/components/editor/source-viewer.tsx +0 -289
- package/.next/standalone/src/components/editor/suggest-edit-popover.tsx +0 -229
- package/.next/standalone/src/components/editor/suggestion-card.tsx +0 -206
- package/.next/standalone/src/components/editor/table-menu.tsx +0 -232
- package/.next/standalone/src/components/editor/view-mode-comment-button.tsx +0 -77
- package/.next/standalone/src/components/editor/website-viewer.tsx +0 -75
- package/.next/standalone/src/components/editor/wiki-link-create-dialog.tsx +0 -189
- package/.next/standalone/src/components/editor/wiki-link-decorator.ts +0 -79
- package/.next/standalone/src/components/editor/wiki-link-extension.ts +0 -120
- package/.next/standalone/src/components/editor/wiki-link-picker.tsx +0 -250
- package/.next/standalone/src/components/layout/viewer-toolbar.tsx +0 -54
- package/.next/standalone/src/components/search/search-command-dialog.tsx +0 -246
- package/.next/standalone/src/components/search/sidebar-search-box.tsx +0 -121
- package/.next/standalone/src/components/search/snippet-text.tsx +0 -48
- package/.next/standalone/src/components/share-dialog.tsx +0 -307
- package/.next/standalone/src/components/theme-provider.tsx +0 -16
- package/.next/standalone/src/components/theme-toggle.tsx +0 -24
- package/.next/standalone/src/components/ui/badge.tsx +0 -35
- package/.next/standalone/src/components/ui/button.tsx +0 -56
- package/.next/standalone/src/components/ui/card.tsx +0 -63
- package/.next/standalone/src/components/ui/collapsible.tsx +0 -9
- package/.next/standalone/src/components/ui/command.tsx +0 -151
- package/.next/standalone/src/components/ui/context-menu.tsx +0 -191
- package/.next/standalone/src/components/ui/dialog.tsx +0 -119
- package/.next/standalone/src/components/ui/dropdown-menu.tsx +0 -194
- package/.next/standalone/src/components/ui/input.tsx +0 -22
- package/.next/standalone/src/components/ui/label.tsx +0 -25
- package/.next/standalone/src/components/ui/popover.tsx +0 -33
- package/.next/standalone/src/components/ui/scroll-area.tsx +0 -47
- package/.next/standalone/src/components/ui/select.tsx +0 -156
- package/.next/standalone/src/components/ui/separator.tsx +0 -30
- package/.next/standalone/src/components/ui/sheet.tsx +0 -140
- package/.next/standalone/src/components/ui/skeleton.tsx +0 -15
- package/.next/standalone/src/components/ui/switch.tsx +0 -28
- package/.next/standalone/src/components/ui/tabs.tsx +0 -54
- package/.next/standalone/src/components/ui/textarea.tsx +0 -21
- package/.next/standalone/src/components/ui/tip.tsx +0 -23
- package/.next/standalone/src/components/ui/toggle-group.tsx +0 -41
- package/.next/standalone/src/components/ui/tooltip.tsx +0 -27
- package/.next/standalone/src/components/view-width-toggle.tsx +0 -70
- package/.next/standalone/src/components/wiki/frontmatter-header.tsx +0 -164
- package/.next/standalone/src/hooks/use-is-mobile.ts +0 -14
- package/.next/standalone/src/lib/app-runner.ts +0 -272
- package/.next/standalone/src/lib/auth/admin.ts +0 -120
- package/.next/standalone/src/lib/auth/allowlist.ts +0 -50
- package/.next/standalone/src/lib/auth/client.ts +0 -4
- package/.next/standalone/src/lib/auth/csrf.ts +0 -68
- package/.next/standalone/src/lib/auth/server.ts +0 -215
- package/.next/standalone/src/lib/cabinets/tree.ts +0 -75
- package/.next/standalone/src/lib/config.ts +0 -114
- package/.next/standalone/src/lib/embeds/detect.ts +0 -142
- package/.next/standalone/src/lib/git-secrets.ts +0 -65
- package/.next/standalone/src/lib/git.ts +0 -503
- package/.next/standalone/src/lib/google/detect.ts +0 -107
- package/.next/standalone/src/lib/markdown/parse-frontmatter.ts +0 -104
- package/.next/standalone/src/lib/markdown/remark-wikilinks.ts +0 -100
- package/.next/standalone/src/lib/markdown/sanitize-schema.ts +0 -106
- package/.next/standalone/src/lib/markdown/to-html.ts +0 -282
- package/.next/standalone/src/lib/markdown/to-markdown.ts +0 -252
- package/.next/standalone/src/lib/proof/activity-shared.ts +0 -31
- package/.next/standalone/src/lib/proof/activity.ts +0 -74
- package/.next/standalone/src/lib/proof/audit.ts +0 -88
- package/.next/standalone/src/lib/proof/auth.ts +0 -143
- package/.next/standalone/src/lib/proof/block-refs.ts +0 -133
- package/.next/standalone/src/lib/proof/blocks.ts +0 -73
- package/.next/standalone/src/lib/proof/client-auth.ts +0 -23
- package/.next/standalone/src/lib/proof/collab-state.ts +0 -81
- package/.next/standalone/src/lib/proof/event-bus.ts +0 -47
- package/.next/standalone/src/lib/proof/file-lock.ts +0 -43
- package/.next/standalone/src/lib/proof/glob.ts +0 -51
- package/.next/standalone/src/lib/proof/idempotency.ts +0 -37
- package/.next/standalone/src/lib/proof/lease.ts +0 -117
- package/.next/standalone/src/lib/proof/mutex.ts +0 -36
- package/.next/standalone/src/lib/proof/ops-applier.ts +0 -727
- package/.next/standalone/src/lib/proof/owner-auth.ts +0 -2
- package/.next/standalone/src/lib/proof/pending.ts +0 -97
- package/.next/standalone/src/lib/proof/proof-span.ts +0 -141
- package/.next/standalone/src/lib/proof/rate-limit.ts +0 -53
- package/.next/standalone/src/lib/proof/raw-fs.ts +0 -197
- package/.next/standalone/src/lib/proof/register-rate-limit.ts +0 -53
- package/.next/standalone/src/lib/proof/registry.ts +0 -195
- package/.next/standalone/src/lib/proof/sidecar.ts +0 -103
- package/.next/standalone/src/lib/proof/suggest-capture.ts +0 -69
- package/.next/standalone/src/lib/proof/types.ts +0 -182
- package/.next/standalone/src/lib/proof-config.ts +0 -14
- package/.next/standalone/src/lib/root-dir.ts +0 -35
- package/.next/standalone/src/lib/search/indexable-exts.ts +0 -25
- package/.next/standalone/src/lib/search/indexer.ts +0 -634
- package/.next/standalone/src/lib/search/sanitize.ts +0 -35
- package/.next/standalone/src/lib/search/search-db.ts +0 -57
- package/.next/standalone/src/lib/search/watcher-pool.ts +0 -79
- package/.next/standalone/src/lib/shared-docs/db.ts +0 -177
- package/.next/standalone/src/lib/toast.ts +0 -9
- package/.next/standalone/src/lib/utils.ts +0 -6
- package/.next/standalone/src/lib/wiki-helpers.ts +0 -36
- package/.next/standalone/src/lib/workspace-client.ts +0 -82
- package/.next/standalone/src/lib/workspace-context.ts +0 -173
- package/.next/standalone/src/lib/workspaces.ts +0 -400
- package/.next/standalone/src/middleware.ts +0 -44
- package/.next/standalone/src/stores/ai-panel-store.ts +0 -79
- package/.next/standalone/src/stores/editor-store.ts +0 -439
- package/.next/standalone/src/stores/pin-store.ts +0 -56
- package/.next/standalone/src/stores/proof-store.ts +0 -124
- package/.next/standalone/src/stores/recent-store.ts +0 -51
- package/.next/standalone/src/stores/search-store.ts +0 -104
- package/.next/standalone/src/stores/show-hidden-store.ts +0 -26
- package/.next/standalone/src/stores/sidebar-width-store.ts +0 -39
- package/.next/standalone/src/stores/skin-store.ts +0 -42
- package/.next/standalone/src/stores/tree-store.ts +0 -52
- package/.next/standalone/src/stores/view-width-store.ts +0 -101
- package/.next/standalone/src/stores/wiki-slugs-store.ts +0 -75
- package/.next/standalone/src/tests/proof/activity-aggregator.test.ts +0 -146
- package/.next/standalone/src/tests/proof/admin-create-user.test.ts +0 -43
- package/.next/standalone/src/tests/proof/agent-fs.test.ts +0 -973
- package/.next/standalone/src/tests/proof/agent-ownership.test.ts +0 -140
- package/.next/standalone/src/tests/proof/agent-workspace-scope.test.ts +0 -134
- package/.next/standalone/src/tests/proof/agents-install.test.ts +0 -110
- package/.next/standalone/src/tests/proof/bench-write.ts +0 -179
- package/.next/standalone/src/tests/proof/better-auth.test.ts +0 -68
- package/.next/standalone/src/tests/proof/block-refs.test.ts +0 -108
- package/.next/standalone/src/tests/proof/blocks.test.ts +0 -92
- package/.next/standalone/src/tests/proof/collab-state.test.ts +0 -501
- package/.next/standalone/src/tests/proof/comments-ops.test.ts +0 -177
- package/.next/standalone/src/tests/proof/editor-roundtrip.test.ts +0 -85
- package/.next/standalone/src/tests/proof/external-edit.test.ts +0 -58
- package/.next/standalone/src/tests/proof/file-lock.test.ts +0 -80
- package/.next/standalone/src/tests/proof/git-branches-checkout.test.ts +0 -136
- package/.next/standalone/src/tests/proof/git-read-features.test.ts +0 -473
- package/.next/standalone/src/tests/proof/git-secrets.test.ts +0 -138
- package/.next/standalone/src/tests/proof/git-sparse.test.ts +0 -257
- package/.next/standalone/src/tests/proof/git-workspace.test.ts +0 -135
- package/.next/standalone/src/tests/proof/git.test.ts +0 -243
- package/.next/standalone/src/tests/proof/glob.test.ts +0 -82
- package/.next/standalone/src/tests/proof/helpers/auth-session.ts +0 -27
- package/.next/standalone/src/tests/proof/markdown-to-html.test.ts +0 -46
- package/.next/standalone/src/tests/proof/ops-applier.test.ts +0 -368
- package/.next/standalone/src/tests/proof/owner-init.test.ts +0 -2
- package/.next/standalone/src/tests/proof/parse-frontmatter.test.ts +0 -60
- package/.next/standalone/src/tests/proof/preload.ts +0 -21
- package/.next/standalone/src/tests/proof/proof-span.test.ts +0 -98
- package/.next/standalone/src/tests/proof/rate-limit.test.ts +0 -54
- package/.next/standalone/src/tests/proof/readonly-enforcement.test.ts +0 -161
- package/.next/standalone/src/tests/proof/reconcile-sidecar.test.ts +0 -195
- package/.next/standalone/src/tests/proof/registration-flow.test.ts +0 -330
- package/.next/standalone/src/tests/proof/registry.test.ts +0 -169
- package/.next/standalone/src/tests/proof/routes.test.ts +0 -516
- package/.next/standalone/src/tests/proof/search-index.test.ts +0 -376
- package/.next/standalone/src/tests/proof/sidecar-lifecycle.test.ts +0 -177
- package/.next/standalone/src/tests/proof/sidecar-trim.test.ts +0 -58
- package/.next/standalone/src/tests/proof/suggestion-ops.test.ts +0 -280
- package/.next/standalone/src/tests/proof/wiki-content-put.test.ts +0 -140
- package/.next/standalone/src/tests/proof/wiki-routes-auth.test.ts +0 -208
- package/.next/standalone/src/tests/proof/workspace-isolation.test.ts +0 -87
- package/.next/standalone/src/tests/proof/workspaces-api.test.ts +0 -227
- package/.next/standalone/src/types/index.ts +0 -47
- package/.next/standalone/tailwind.config.ts +0 -191
- package/.next/standalone/tsconfig.json +0 -41
- package/.next/standalone/tsconfig.tsbuildinfo +0 -1
- /package/.next/standalone/.next/static/{WMOTgfV82apmdovxzgbMx → ecpoWt_SP3WkUTh0NsnuT}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{WMOTgfV82apmdovxzgbMx → ecpoWt_SP3WkUTh0NsnuT}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{WMOTgfV82apmdovxzgbMx → ecpoWt_SP3WkUTh0NsnuT}/_ssgManifest.js +0 -0
|
@@ -1,1327 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { cellAround, isInTable } from "@tiptap/pm/tables";
|
|
4
|
-
import { EditorContent, useEditor } from "@tiptap/react";
|
|
5
|
-
import { AlertCircle, Check, Code2, FilePlus, Loader2, Sparkles } from "lucide-react";
|
|
6
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
-
import { findNodeByPath } from "@/lib/cabinets/tree";
|
|
8
|
-
import { markdownToHtml } from "@/lib/markdown/to-html";
|
|
9
|
-
import { htmlToMarkdown } from "@/lib/markdown/to-markdown";
|
|
10
|
-
import { parseFrontmatter } from "@/lib/markdown/parse-frontmatter";
|
|
11
|
-
import { useAIPanelStore } from "@/stores/ai-panel-store";
|
|
12
|
-
import { useEditorStore } from "@/stores/editor-store";
|
|
13
|
-
import { useTreeStore } from "@/stores/tree-store";
|
|
14
|
-
import {
|
|
15
|
-
useViewWidthStore,
|
|
16
|
-
VIEW_WIDTH_CSS,
|
|
17
|
-
VIEW_ALIGN_ML,
|
|
18
|
-
} from "@/stores/view-width-store";
|
|
19
|
-
import { useWikiSlugsStore } from "@/stores/wiki-slugs-store";
|
|
20
|
-
import type { TreeNode } from "@/types";
|
|
21
|
-
import { useProofStore } from "@/stores/proof-store";
|
|
22
|
-
import { captureSuggestion } from "@/lib/proof/suggest-capture";
|
|
23
|
-
import { wsFetch, withWs } from "@/lib/workspace-client";
|
|
24
|
-
import { showError } from "@/lib/toast";
|
|
25
|
-
import { EditorBubbleMenu } from "./bubble-menu";
|
|
26
|
-
import { EditorToolbar } from "./editor-toolbar";
|
|
27
|
-
import { editorExtensions } from "./extensions";
|
|
28
|
-
import { FolderIndex } from "./folder-index";
|
|
29
|
-
import { CommentPip } from "./comment-pip";
|
|
30
|
-
import { CommentThread } from "./comment-thread";
|
|
31
|
-
import { ProofSpanPopover } from "./proof-span-popover";
|
|
32
|
-
import { SuggestionCard } from "./suggestion-card";
|
|
33
|
-
import { SuggestEditPopover } from "./suggest-edit-popover";
|
|
34
|
-
import { SlashCommands } from "./slash-commands";
|
|
35
|
-
import { DocumentOutline } from "./document-outline";
|
|
36
|
-
import { BacklinksPanel } from "./backlinks-panel";
|
|
37
|
-
import { TableMenu } from "./table-menu";
|
|
38
|
-
import {
|
|
39
|
-
useWikiLinkCreate,
|
|
40
|
-
type WikiCreateResult,
|
|
41
|
-
} from "./wiki-link-create-dialog";
|
|
42
|
-
import { WikiLinkPicker } from "./wiki-link-picker";
|
|
43
|
-
import { FrontmatterHeader } from "@/components/wiki/frontmatter-header";
|
|
44
|
-
import { ViewModeCommentButton } from "./view-mode-comment-button";
|
|
45
|
-
|
|
46
|
-
async function uploadFile(
|
|
47
|
-
pagePath: string,
|
|
48
|
-
file: File,
|
|
49
|
-
): Promise<string | null> {
|
|
50
|
-
const formData = new FormData();
|
|
51
|
-
formData.append("file", file);
|
|
52
|
-
try {
|
|
53
|
-
const res = await wsFetch(`/api/upload/${pagePath}`, {
|
|
54
|
-
method: "POST",
|
|
55
|
-
body: formData,
|
|
56
|
-
});
|
|
57
|
-
if (!res.ok) {
|
|
58
|
-
showError(`Upload failed: ${file.name}`);
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
const data = await res.json();
|
|
62
|
-
return data.url;
|
|
63
|
-
} catch {
|
|
64
|
-
showError(`Upload failed: ${file.name}`);
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function flattenTree(nodes: TreeNode[]): { path: string; name: string }[] {
|
|
70
|
-
const result: { path: string; name: string }[] = [];
|
|
71
|
-
for (const node of nodes) {
|
|
72
|
-
result.push({ path: node.path, name: node.name });
|
|
73
|
-
if (node.children) result.push(...flattenTree(node.children));
|
|
74
|
-
}
|
|
75
|
-
return result;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function findPageBySlug(
|
|
79
|
-
slug: string,
|
|
80
|
-
currentPath: string | null,
|
|
81
|
-
nodes: TreeNode[],
|
|
82
|
-
): string | null {
|
|
83
|
-
const allPages = flattenTree(nodes);
|
|
84
|
-
// The slug matches the last segment of the path
|
|
85
|
-
const matches = allPages.filter(
|
|
86
|
-
(p) => p.name === slug || p.path.endsWith(`/${slug}`),
|
|
87
|
-
);
|
|
88
|
-
if (matches.length === 0) return null;
|
|
89
|
-
if (matches.length === 1) return matches[0].path;
|
|
90
|
-
|
|
91
|
-
// Prefer sibling pages (same parent directory as current page)
|
|
92
|
-
if (currentPath) {
|
|
93
|
-
const parentDir = currentPath.includes("/")
|
|
94
|
-
? currentPath.substring(0, currentPath.lastIndexOf("/"))
|
|
95
|
-
: "";
|
|
96
|
-
const sibling = matches.find(
|
|
97
|
-
(m) => m.path === (parentDir ? `${parentDir}/${slug}` : slug),
|
|
98
|
-
);
|
|
99
|
-
if (sibling) return sibling.path;
|
|
100
|
-
}
|
|
101
|
-
return matches[0].path;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function navigateToPage(
|
|
105
|
-
targetPath: string,
|
|
106
|
-
selectPage: (path: string) => void,
|
|
107
|
-
expandPath: (path: string) => void,
|
|
108
|
-
) {
|
|
109
|
-
const parts = targetPath.split("/");
|
|
110
|
-
for (let i = 1; i < parts.length; i++) {
|
|
111
|
-
expandPath(parts.slice(0, i).join("/"));
|
|
112
|
-
}
|
|
113
|
-
selectPage(targetPath);
|
|
114
|
-
useEditorStore.getState().loadPage(targetPath);
|
|
115
|
-
// Scroll editor container to top
|
|
116
|
-
setTimeout(() => {
|
|
117
|
-
document.querySelector("[data-editor-scroll]")?.scrollTo(0, 0);
|
|
118
|
-
}, 0);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function resolveInternalLink(
|
|
122
|
-
href: string,
|
|
123
|
-
currentPath: string | null,
|
|
124
|
-
nodes: TreeNode[],
|
|
125
|
-
): string | null {
|
|
126
|
-
const allPages = flattenTree(nodes);
|
|
127
|
-
|
|
128
|
-
// Clean up the href: strip .md extension, leading ./ or /
|
|
129
|
-
const linkPath = href
|
|
130
|
-
.replace(/\.md$/, "")
|
|
131
|
-
.replace(/^\.\//, "")
|
|
132
|
-
.replace(/^\//, "");
|
|
133
|
-
|
|
134
|
-
// 1. Try as absolute path (exact match in tree)
|
|
135
|
-
const exactMatch = allPages.find((p) => p.path === linkPath);
|
|
136
|
-
if (exactMatch) return exactMatch.path;
|
|
137
|
-
|
|
138
|
-
// 2. Try relative to current page's directory
|
|
139
|
-
if (currentPath) {
|
|
140
|
-
const parentDir = currentPath.includes("/")
|
|
141
|
-
? currentPath.substring(0, currentPath.lastIndexOf("/"))
|
|
142
|
-
: "";
|
|
143
|
-
const relativePath = parentDir ? `${parentDir}/${linkPath}` : linkPath;
|
|
144
|
-
const relMatch = allPages.find((p) => p.path === relativePath);
|
|
145
|
-
if (relMatch) return relMatch.path;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 3. Try matching by last segment (slug-style lookup)
|
|
149
|
-
const slug = linkPath.includes("/") ? linkPath.split("/").pop()! : linkPath;
|
|
150
|
-
return findPageBySlug(slug, currentPath, nodes);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
type KBEditorMode = "viewing" | "editing" | "suggesting";
|
|
154
|
-
|
|
155
|
-
interface KBEditorProps {
|
|
156
|
-
mode?: KBEditorMode;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function KBEditor({ mode }: KBEditorProps = {}) {
|
|
160
|
-
const {
|
|
161
|
-
currentPath,
|
|
162
|
-
content,
|
|
163
|
-
saveStatus,
|
|
164
|
-
frontmatter,
|
|
165
|
-
isLoading,
|
|
166
|
-
loadStatus,
|
|
167
|
-
createMissingPage,
|
|
168
|
-
editMode,
|
|
169
|
-
setEditMode,
|
|
170
|
-
} = useEditorStore();
|
|
171
|
-
const effectiveMode = mode ?? editMode;
|
|
172
|
-
const isViewing = effectiveMode === "viewing";
|
|
173
|
-
const isSuggesting = effectiveMode === "suggesting";
|
|
174
|
-
const parsedViewingContent = useMemo(
|
|
175
|
-
() => (isViewing ? parseFrontmatter(content) : { data: {}, body: content }),
|
|
176
|
-
[content, isViewing],
|
|
177
|
-
);
|
|
178
|
-
const nodes = useTreeStore((s) => s.nodes);
|
|
179
|
-
const editorMaxW = useViewWidthStore((s) => VIEW_WIDTH_CSS[s.width]);
|
|
180
|
-
const editorMl = useViewWidthStore((s) => VIEW_ALIGN_ML[s.align]);
|
|
181
|
-
const isRtl = isViewing
|
|
182
|
-
? parsedViewingContent.data.dir === "rtl"
|
|
183
|
-
: frontmatter?.dir === "rtl";
|
|
184
|
-
const { open: openAI, clearMessages } = useAIPanelStore();
|
|
185
|
-
const { open: openWikiCreate, Dialog: WikiCreateDialog } =
|
|
186
|
-
useWikiLinkCreate();
|
|
187
|
-
// Keep a stable ref so the click handler closure can call the latest version
|
|
188
|
-
// without being re-created on every render.
|
|
189
|
-
const openWikiCreateRef =
|
|
190
|
-
useRef<(slug: string) => Promise<WikiCreateResult>>(openWikiCreate);
|
|
191
|
-
openWikiCreateRef.current = openWikiCreate;
|
|
192
|
-
|
|
193
|
-
const isLoadingRef = useRef(false);
|
|
194
|
-
const isViewingRef = useRef(isViewing);
|
|
195
|
-
isViewingRef.current = isViewing;
|
|
196
|
-
const [sourceMode, setSourceMode] = useState(false);
|
|
197
|
-
const [sourceText, setSourceText] = useState("");
|
|
198
|
-
// Reset the tab to "page" whenever the path changes — opening a new folder
|
|
199
|
-
// shouldn't skip its index.md if the previous folder was on Files. Has to
|
|
200
|
-
// be an effect (not state-during-render) because Tiptap's EditorContent
|
|
201
|
-
// calls flushSync internally; setState during the parent render explodes
|
|
202
|
-
// when EditorContent renders in the same pass.
|
|
203
|
-
const [folderTab, setFolderTab] = useState<"page" | "files">("page");
|
|
204
|
-
useEffect(() => {
|
|
205
|
-
setFolderTab("page");
|
|
206
|
-
}, []);
|
|
207
|
-
|
|
208
|
-
// Prime the slug index once on mount so wiki-link broken-state and
|
|
209
|
-
// the autocomplete picker both have data immediately.
|
|
210
|
-
useEffect(() => {
|
|
211
|
-
void useWikiSlugsStore.getState().load();
|
|
212
|
-
}, []);
|
|
213
|
-
|
|
214
|
-
// Load sidecar when the current path changes. Debounced: rapid navigation
|
|
215
|
-
// otherwise fires a sidecar fetch per pass-through file, flooding the
|
|
216
|
-
// connection pool. The cleanup clears the timer, so only the settled path loads.
|
|
217
|
-
useEffect(() => {
|
|
218
|
-
if (!currentPath) return;
|
|
219
|
-
const id = setTimeout(() => {
|
|
220
|
-
void useProofStore.getState().loadSidecar(currentPath);
|
|
221
|
-
}, 200);
|
|
222
|
-
return () => clearTimeout(id);
|
|
223
|
-
}, [currentPath]);
|
|
224
|
-
|
|
225
|
-
// Human edit-lease heartbeat: tell the server a human has this markdown doc
|
|
226
|
-
// open so computeCollabState() reports "active" even before the first
|
|
227
|
-
// suggestion/comment. This is what makes raw-fs writes from agents defer to
|
|
228
|
-
// the collaborative (Tier-2) path while a human is editing. Only markdown
|
|
229
|
-
// files participate in the collab-state machine.
|
|
230
|
-
useEffect(() => {
|
|
231
|
-
if (typeof window === "undefined") return;
|
|
232
|
-
if (!currentPath) return;
|
|
233
|
-
if (isViewing) return;
|
|
234
|
-
if (!/\.(md|markdown)$/i.test(currentPath)) return;
|
|
235
|
-
|
|
236
|
-
const path = currentPath;
|
|
237
|
-
const ping = (action: "open" | "heartbeat" | "close") => {
|
|
238
|
-
// keepalive lets the "close" beacon survive page unload.
|
|
239
|
-
void wsFetch("/api/wiki/presence", {
|
|
240
|
-
method: "POST",
|
|
241
|
-
headers: { "Content-Type": "application/json" },
|
|
242
|
-
body: JSON.stringify({ path, action }),
|
|
243
|
-
keepalive: action === "close",
|
|
244
|
-
}).catch(() => {
|
|
245
|
-
/* presence is best-effort; ignore failures */
|
|
246
|
-
});
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
// Debounce the "open" ping: rapid navigation through files otherwise emits
|
|
250
|
-
// an open+close pair per pass-through. Only claim the lease once the user
|
|
251
|
-
// settles on a file for >200ms; if we never opened, we never close.
|
|
252
|
-
let opened = false;
|
|
253
|
-
let id: ReturnType<typeof setInterval> | null = null;
|
|
254
|
-
const onHidden = () => {
|
|
255
|
-
if (opened && document.visibilityState === "hidden") ping("heartbeat");
|
|
256
|
-
};
|
|
257
|
-
const openTimer = setTimeout(() => {
|
|
258
|
-
opened = true;
|
|
259
|
-
ping("open");
|
|
260
|
-
// Refresh well within the 90s server lease TTL.
|
|
261
|
-
id = setInterval(() => ping("heartbeat"), 30_000);
|
|
262
|
-
document.addEventListener("visibilitychange", onHidden);
|
|
263
|
-
}, 200);
|
|
264
|
-
|
|
265
|
-
return () => {
|
|
266
|
-
clearTimeout(openTimer);
|
|
267
|
-
if (id) clearInterval(id);
|
|
268
|
-
document.removeEventListener("visibilitychange", onHidden);
|
|
269
|
-
if (opened) ping("close");
|
|
270
|
-
};
|
|
271
|
-
}, [currentPath, isViewing]);
|
|
272
|
-
|
|
273
|
-
// Subscribe to chokidar SSE: when current file changes on disk, reload sidecar.
|
|
274
|
-
useEffect(() => {
|
|
275
|
-
if (typeof window === "undefined") return;
|
|
276
|
-
const es = new EventSource(withWs("/api/wiki/watch"));
|
|
277
|
-
es.onmessage = (evt: MessageEvent<string>) => {
|
|
278
|
-
try {
|
|
279
|
-
const data = JSON.parse(evt.data) as { type: string; path: string };
|
|
280
|
-
const activePath = useEditorStore.getState().currentPath;
|
|
281
|
-
if (
|
|
282
|
-
(data.type === "change" || data.type === "add") &&
|
|
283
|
-
activePath &&
|
|
284
|
-
data.path === activePath
|
|
285
|
-
) {
|
|
286
|
-
// loadSnapshot first so server-side readSnapshot detects
|
|
287
|
-
// fingerprint mismatch, emits file.externallyEdited, and persists
|
|
288
|
-
// the sidecar. Then loadSidecar to refresh comments/suggestions.
|
|
289
|
-
void useProofStore
|
|
290
|
-
.getState()
|
|
291
|
-
.loadSnapshot(activePath)
|
|
292
|
-
.then(() => useProofStore.getState().loadSidecar(activePath));
|
|
293
|
-
if (isViewingRef.current) {
|
|
294
|
-
void useEditorStore.getState().loadPage(activePath);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
} catch {
|
|
298
|
-
// ignore malformed events
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
return () => {
|
|
302
|
-
es.close();
|
|
303
|
-
};
|
|
304
|
-
}, []);
|
|
305
|
-
|
|
306
|
-
// Proof-span popover state.
|
|
307
|
-
const [proofTarget, setProofTarget] = useState<HTMLElement | null>(null);
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Ref to the editor scroll container. Used to compute block positions
|
|
311
|
-
* relative to the scrollable area for suggestion cards and comment pips.
|
|
312
|
-
*
|
|
313
|
-
* Phase D coordination: comment-pip positioning uses this same ref and the
|
|
314
|
-
* same blockRefPositions map computed below.
|
|
315
|
-
*/
|
|
316
|
-
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
317
|
-
|
|
318
|
-
/** Map of block ref → position relative to scroll container */
|
|
319
|
-
const [blockRefPositions, setBlockRefPositions] = useState<
|
|
320
|
-
Map<string, { top: number; left: number; width: number; bottom: number }>
|
|
321
|
-
>(new Map());
|
|
322
|
-
|
|
323
|
-
// Subscribe to snapshot data for suggestion cards.
|
|
324
|
-
// NOTE: select the RAW stored references here — returning a freshly built
|
|
325
|
-
// array (e.g. `?? []` or `.filter(...)`) on every call makes
|
|
326
|
-
// useSyncExternalStore think the snapshot changed each render, which spins
|
|
327
|
-
// into a "Maximum update depth exceeded" loop. Derive defaults/filters below.
|
|
328
|
-
const snapshotBlocksRaw = useProofStore((s) =>
|
|
329
|
-
currentPath ? s.byPath[currentPath]?.snapshotBlocks : undefined
|
|
330
|
-
);
|
|
331
|
-
const suggestionsRaw = useProofStore((s) =>
|
|
332
|
-
currentPath ? s.byPath[currentPath]?.sidecar?.suggestions : undefined
|
|
333
|
-
);
|
|
334
|
-
const snapshotRevision = useProofStore((s) =>
|
|
335
|
-
currentPath ? (s.byPath[currentPath]?.snapshotRevision ?? 0) : 0
|
|
336
|
-
);
|
|
337
|
-
const commentsRaw = useProofStore((s) =>
|
|
338
|
-
currentPath ? s.byPath[currentPath]?.sidecar?.comments : undefined
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
const snapshotBlocks = useMemo(() => snapshotBlocksRaw ?? [], [snapshotBlocksRaw]);
|
|
342
|
-
const comments = useMemo(() => commentsRaw ?? [], [commentsRaw]);
|
|
343
|
-
const pendingSuggestions = useMemo(
|
|
344
|
-
() => suggestionsRaw?.filter((sg) => sg.status === "pending") ?? [],
|
|
345
|
-
[suggestionsRaw],
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
/** Group comments by block ref for pip rendering. */
|
|
349
|
-
const commentsByRef = useMemo(() => {
|
|
350
|
-
const map: Record<string, typeof comments> = {};
|
|
351
|
-
for (const c of comments) {
|
|
352
|
-
(map[c.ref] ??= []).push(c);
|
|
353
|
-
}
|
|
354
|
-
return map;
|
|
355
|
-
}, [comments]);
|
|
356
|
-
|
|
357
|
-
/** Tracks which block's comment thread is open and its anchor element. */
|
|
358
|
-
const [threadTarget, setThreadTarget] = useState<{ blockRef: string; el: HTMLElement } | null>(null);
|
|
359
|
-
|
|
360
|
-
/** Tracks the open human "suggest edit" popover (block + anchor + content). */
|
|
361
|
-
const [suggestTarget, setSuggestTarget] = useState<
|
|
362
|
-
{ blockRef: string; markdown: string; anchor: { top: number; left: number } } | null
|
|
363
|
-
>(null);
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Resolve the current editor selection to a top-level block.
|
|
367
|
-
*
|
|
368
|
-
* Primary strategy: map the selection to its top-level ProseMirror child
|
|
369
|
-
* INDEX, then look up snapshotBlocks[index] — the same index-based mapping
|
|
370
|
-
* used by the position-tracker effect. This is robust even when the DOM
|
|
371
|
-
* `data-block-ref` annotation has not been applied yet (e.g. snapshot still
|
|
372
|
-
* loading), which previously made the suggest/comment buttons silently
|
|
373
|
-
* no-op. Falls back to walking the DOM for an existing [data-block-ref].
|
|
374
|
-
*/
|
|
375
|
-
const resolveSelectionBlock = useCallback((): {
|
|
376
|
-
blockRef: string;
|
|
377
|
-
blockEl: HTMLElement;
|
|
378
|
-
markdown: string;
|
|
379
|
-
} | null => {
|
|
380
|
-
if (!editorRef.current) return null;
|
|
381
|
-
const view = editorRef.current.view;
|
|
382
|
-
const { from } = view.state.selection;
|
|
383
|
-
const path = useEditorStore.getState().currentPath ?? "";
|
|
384
|
-
const blocks = useProofStore.getState().byPath[path]?.snapshotBlocks ?? [];
|
|
385
|
-
|
|
386
|
-
// Find the top-level child index containing the selection head.
|
|
387
|
-
const $pos = view.state.doc.resolve(from);
|
|
388
|
-
const topIndex = $pos.depth > 0 ? $pos.index(0) : 0;
|
|
389
|
-
|
|
390
|
-
const proseMirror = scrollContainerRef.current?.querySelector(".ProseMirror");
|
|
391
|
-
const children = proseMirror
|
|
392
|
-
? (Array.from(proseMirror.children) as HTMLElement[])
|
|
393
|
-
: [];
|
|
394
|
-
const blockEl = children[topIndex] ?? null;
|
|
395
|
-
|
|
396
|
-
// Prefer the index-aligned snapshot block; fall back to the DOM attr.
|
|
397
|
-
const block = blocks[topIndex];
|
|
398
|
-
let blockRef: string | null =
|
|
399
|
-
block?.ref ?? blockEl?.getAttribute("data-block-ref") ?? null;
|
|
400
|
-
const markdown = block?.markdown ?? "";
|
|
401
|
-
|
|
402
|
-
if (!blockRef && blockEl) {
|
|
403
|
-
// Last-resort: DOM walk from selection anchor.
|
|
404
|
-
const domAt = view.domAtPos(from);
|
|
405
|
-
const node: HTMLElement | null =
|
|
406
|
-
domAt.node.nodeType === Node.ELEMENT_NODE
|
|
407
|
-
? (domAt.node as HTMLElement)
|
|
408
|
-
: domAt.node.parentElement;
|
|
409
|
-
const found = node?.closest<HTMLElement>("[data-block-ref]") ?? null;
|
|
410
|
-
blockRef = found?.getAttribute("data-block-ref") ?? null;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (!blockRef || !blockEl) return null;
|
|
414
|
-
return { blockRef, blockEl, markdown };
|
|
415
|
-
}, []);
|
|
416
|
-
|
|
417
|
-
const openSuggestForSelection = useCallback(() => {
|
|
418
|
-
const resolved = resolveSelectionBlock();
|
|
419
|
-
if (!resolved) return;
|
|
420
|
-
const rect = resolved.blockEl.getBoundingClientRect();
|
|
421
|
-
setSuggestTarget({
|
|
422
|
-
blockRef: resolved.blockRef,
|
|
423
|
-
markdown: resolved.markdown,
|
|
424
|
-
anchor: { top: rect.bottom + 4, left: rect.left },
|
|
425
|
-
});
|
|
426
|
-
}, [resolveSelectionBlock]);
|
|
427
|
-
|
|
428
|
-
const openCommentForSelection = useCallback(() => {
|
|
429
|
-
const resolved = resolveSelectionBlock();
|
|
430
|
-
if (!resolved) return;
|
|
431
|
-
setThreadTarget({ blockRef: resolved.blockRef, el: resolved.blockEl });
|
|
432
|
-
}, [resolveSelectionBlock]);
|
|
433
|
-
|
|
434
|
-
// ── Suggesting mode: capture human block edits as suggestions ──────────────
|
|
435
|
-
//
|
|
436
|
-
// In suggesting mode the editor stays editable but edits never touch the
|
|
437
|
-
// file. On flush (leaving a block or blurring the editor) we diff each
|
|
438
|
-
// top-level block against the snapshot, emit a human `suggestion.add` for
|
|
439
|
-
// every changed/added/removed block, then revert the editor to the snapshot
|
|
440
|
-
// so the pending suggestion cards render over the original content.
|
|
441
|
-
|
|
442
|
-
/** Set true whenever the user edits while in suggesting mode. */
|
|
443
|
-
const suggestDirtyRef = useRef(false);
|
|
444
|
-
/** Guards against re-entrant flushes (capture is async). */
|
|
445
|
-
const flushingRef = useRef(false);
|
|
446
|
-
/** Top-level block index that currently holds the selection. */
|
|
447
|
-
const activeBlockIndexRef = useRef<number | null>(null);
|
|
448
|
-
|
|
449
|
-
const normalizeMd = (s: string): string => s.replace(/\s+$/g, "").trimStart();
|
|
450
|
-
|
|
451
|
-
const flushSuggestions = useCallback(async () => {
|
|
452
|
-
if (flushingRef.current) return;
|
|
453
|
-
if (isViewingRef.current) return;
|
|
454
|
-
if (useEditorStore.getState().editMode !== "suggesting") return;
|
|
455
|
-
if (!suggestDirtyRef.current) return;
|
|
456
|
-
const ed = editorRef.current;
|
|
457
|
-
const path = useEditorStore.getState().currentPath;
|
|
458
|
-
if (!ed || !path) return;
|
|
459
|
-
|
|
460
|
-
const proseMirror = scrollContainerRef.current?.querySelector(".ProseMirror");
|
|
461
|
-
if (!proseMirror) return;
|
|
462
|
-
const children = Array.from(proseMirror.children) as HTMLElement[];
|
|
463
|
-
const snapBlocks =
|
|
464
|
-
useProofStore.getState().byPath[path]?.snapshotBlocks ?? [];
|
|
465
|
-
if (snapBlocks.length === 0) return;
|
|
466
|
-
|
|
467
|
-
flushingRef.current = true;
|
|
468
|
-
suggestDirtyRef.current = false;
|
|
469
|
-
try {
|
|
470
|
-
const getRevision = () =>
|
|
471
|
-
useProofStore.getState().byPath[path]?.snapshotRevision ?? 0;
|
|
472
|
-
const refresh = async () => {
|
|
473
|
-
await useProofStore.getState().loadSnapshot(path);
|
|
474
|
-
await useProofStore.getState().loadSidecar(path);
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
const count = Math.max(children.length, snapBlocks.length);
|
|
478
|
-
let captured = false;
|
|
479
|
-
for (let i = 0; i < count; i++) {
|
|
480
|
-
const el = children[i];
|
|
481
|
-
const snap = snapBlocks[i];
|
|
482
|
-
const curMd = el ? htmlToMarkdown(el.outerHTML).trim() : null;
|
|
483
|
-
|
|
484
|
-
if (snap && curMd !== null) {
|
|
485
|
-
if (normalizeMd(curMd) !== normalizeMd(snap.markdown)) {
|
|
486
|
-
const ok = await captureSuggestion({
|
|
487
|
-
path,
|
|
488
|
-
ref: snap.ref,
|
|
489
|
-
kind: "replace",
|
|
490
|
-
markdown: curMd,
|
|
491
|
-
getRevision,
|
|
492
|
-
refresh,
|
|
493
|
-
});
|
|
494
|
-
captured = captured || ok;
|
|
495
|
-
}
|
|
496
|
-
} else if (snap && curMd === null) {
|
|
497
|
-
const ok = await captureSuggestion({
|
|
498
|
-
path,
|
|
499
|
-
ref: snap.ref,
|
|
500
|
-
kind: "delete",
|
|
501
|
-
getRevision,
|
|
502
|
-
refresh,
|
|
503
|
-
});
|
|
504
|
-
captured = captured || ok;
|
|
505
|
-
} else if (!snap && curMd !== null && curMd.length > 0) {
|
|
506
|
-
// New trailing block: suggest inserting after the last known block.
|
|
507
|
-
const lastRef = snapBlocks[snapBlocks.length - 1]?.ref;
|
|
508
|
-
if (lastRef) {
|
|
509
|
-
const ok = await captureSuggestion({
|
|
510
|
-
path,
|
|
511
|
-
ref: lastRef,
|
|
512
|
-
kind: "insertAfter",
|
|
513
|
-
markdown: curMd,
|
|
514
|
-
getRevision,
|
|
515
|
-
refresh,
|
|
516
|
-
});
|
|
517
|
-
captured = captured || ok;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
if (captured) {
|
|
523
|
-
// Reload sidecar so the new pending suggestion cards appear, then
|
|
524
|
-
// revert the editor to the snapshot (file unchanged).
|
|
525
|
-
await refresh();
|
|
526
|
-
const freshSnap =
|
|
527
|
-
useProofStore.getState().byPath[path]?.snapshotBlocks ?? snapBlocks;
|
|
528
|
-
const snapshotMarkdown = freshSnap.map((b) => b.markdown).join("\n\n");
|
|
529
|
-
isLoadingRef.current = true;
|
|
530
|
-
const html = await markdownToHtml(snapshotMarkdown, path);
|
|
531
|
-
ed.commands.setContent(html);
|
|
532
|
-
setTimeout(() => {
|
|
533
|
-
isLoadingRef.current = false;
|
|
534
|
-
}, 50);
|
|
535
|
-
}
|
|
536
|
-
} finally {
|
|
537
|
-
flushingRef.current = false;
|
|
538
|
-
}
|
|
539
|
-
}, []);
|
|
540
|
-
|
|
541
|
-
// Load snapshot (ordered block list) when path changes so suggestion cards
|
|
542
|
-
// can look up block content by ref.
|
|
543
|
-
useEffect(() => {
|
|
544
|
-
if (!currentPath) return;
|
|
545
|
-
const id = setTimeout(() => {
|
|
546
|
-
void useProofStore.getState().loadSnapshot(currentPath);
|
|
547
|
-
}, 200);
|
|
548
|
-
return () => clearTimeout(id);
|
|
549
|
-
}, [currentPath]);
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* After content renders, walk `.ProseMirror > *` to build ref→position map.
|
|
553
|
-
* Matches by index: the i-th ProseMirror child = snapshotBlocks[i].
|
|
554
|
-
*
|
|
555
|
-
* Phase D coordination: this effect also annotates each child element with
|
|
556
|
-
* data-block-ref for any consumer that needs CSS/query-based lookup.
|
|
557
|
-
*/
|
|
558
|
-
useEffect(() => {
|
|
559
|
-
if (!currentPath || snapshotBlocks.length === 0 || !scrollContainerRef.current) return;
|
|
560
|
-
const container = scrollContainerRef.current;
|
|
561
|
-
const proseMirror = container.querySelector(".ProseMirror");
|
|
562
|
-
if (!proseMirror) return;
|
|
563
|
-
const children = Array.from(proseMirror.children) as HTMLElement[];
|
|
564
|
-
const containerRect = container.getBoundingClientRect();
|
|
565
|
-
const next = new Map<string, { top: number; left: number; width: number; bottom: number }>();
|
|
566
|
-
for (let i = 0; i < Math.min(children.length, snapshotBlocks.length); i++) {
|
|
567
|
-
const el = children[i];
|
|
568
|
-
const block = snapshotBlocks[i];
|
|
569
|
-
// Annotate DOM element — Phase D comment-pip and other consumers read this
|
|
570
|
-
el.setAttribute("data-block-ref", block.ref);
|
|
571
|
-
const rect = el.getBoundingClientRect();
|
|
572
|
-
next.set(block.ref, {
|
|
573
|
-
top: rect.top - containerRect.top + container.scrollTop,
|
|
574
|
-
left: rect.left - containerRect.left,
|
|
575
|
-
width: rect.width,
|
|
576
|
-
bottom: rect.bottom - containerRect.top + container.scrollTop,
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
setBlockRefPositions(next);
|
|
580
|
-
}, [currentPath, snapshotBlocks]);
|
|
581
|
-
|
|
582
|
-
const handleUpdate = useCallback(
|
|
583
|
-
({ editor }: { editor: ReturnType<typeof useEditor> }) => {
|
|
584
|
-
if (isLoadingRef.current || isViewingRef.current || !editor) return;
|
|
585
|
-
// In suggesting mode, mark the edit dirty so the next block-change or
|
|
586
|
-
// blur flushes it into suggestions. Still push content to the store so
|
|
587
|
-
// the store guard (no autosave in suggesting mode) keeps it in sync.
|
|
588
|
-
if (useEditorStore.getState().editMode === "suggesting") {
|
|
589
|
-
suggestDirtyRef.current = true;
|
|
590
|
-
}
|
|
591
|
-
const html = editor.getHTML();
|
|
592
|
-
const md = htmlToMarkdown(html);
|
|
593
|
-
useEditorStore.getState().updateContent(md);
|
|
594
|
-
},
|
|
595
|
-
[],
|
|
596
|
-
);
|
|
597
|
-
|
|
598
|
-
const editor = useEditor({
|
|
599
|
-
extensions: editorExtensions,
|
|
600
|
-
content: "",
|
|
601
|
-
editable: !isViewing,
|
|
602
|
-
onUpdate: handleUpdate,
|
|
603
|
-
onBlur: () => {
|
|
604
|
-
void flushSuggestions();
|
|
605
|
-
},
|
|
606
|
-
onSelectionUpdate: ({ editor: ed }) => {
|
|
607
|
-
if (useEditorStore.getState().editMode !== "suggesting") return;
|
|
608
|
-
const { from } = ed.state.selection;
|
|
609
|
-
const $pos = ed.state.doc.resolve(from);
|
|
610
|
-
const idx = $pos.depth > 0 ? $pos.index(0) : 0;
|
|
611
|
-
const prev = activeBlockIndexRef.current;
|
|
612
|
-
activeBlockIndexRef.current = idx;
|
|
613
|
-
// Moved to a different top-level block — flush edits to the prior one.
|
|
614
|
-
if (prev !== null && prev !== idx && suggestDirtyRef.current) {
|
|
615
|
-
void flushSuggestions();
|
|
616
|
-
}
|
|
617
|
-
},
|
|
618
|
-
editorProps: {
|
|
619
|
-
attributes: {
|
|
620
|
-
class:
|
|
621
|
-
"focus:outline-none min-h-[calc(100vh-12rem)] px-4 sm:px-8 py-6 max-w-[var(--editor-max-w,48rem)] ml-[var(--editor-ml,auto)] mr-auto",
|
|
622
|
-
},
|
|
623
|
-
handleKeyDown: (view, event) => {
|
|
624
|
-
if (
|
|
625
|
-
(event.metaKey || event.ctrlKey) &&
|
|
626
|
-
event.key.toLowerCase() === "a" &&
|
|
627
|
-
isInTable(view.state)
|
|
628
|
-
) {
|
|
629
|
-
const $cell = cellAround(view.state.selection.$from);
|
|
630
|
-
const cell = $cell?.nodeAfter;
|
|
631
|
-
if (!$cell || !cell) return false;
|
|
632
|
-
|
|
633
|
-
const from = $cell.pos + 1;
|
|
634
|
-
const to = $cell.pos + cell.nodeSize - 1;
|
|
635
|
-
if (
|
|
636
|
-
view.state.selection.from === from &&
|
|
637
|
-
view.state.selection.to === to
|
|
638
|
-
) {
|
|
639
|
-
return false;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
event.preventDefault();
|
|
643
|
-
editor?.chain().focus().setTextSelection({ from, to }).run();
|
|
644
|
-
return true;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
return false;
|
|
648
|
-
},
|
|
649
|
-
handleClick: (_view, _pos, event) => {
|
|
650
|
-
const target = event.target as HTMLElement;
|
|
651
|
-
const link = target.closest("a") as HTMLAnchorElement | null;
|
|
652
|
-
if (!link) return false;
|
|
653
|
-
|
|
654
|
-
const href = link.getAttribute("href");
|
|
655
|
-
if (!href) return false;
|
|
656
|
-
|
|
657
|
-
// Wiki-links inserted by the WikiLink mark
|
|
658
|
-
if (link.dataset.wikiLink === "true") {
|
|
659
|
-
event.preventDefault();
|
|
660
|
-
event.stopPropagation();
|
|
661
|
-
const slug = link.dataset.slug ?? "";
|
|
662
|
-
const anchor = link.dataset.anchor ?? null;
|
|
663
|
-
if (!slug) return true;
|
|
664
|
-
|
|
665
|
-
const slugStore = useWikiSlugsStore.getState();
|
|
666
|
-
if (slugStore.has(slug)) {
|
|
667
|
-
const dir = slugStore.getDir(slug);
|
|
668
|
-
const pagePath =
|
|
669
|
-
dir === null || dir === "root"
|
|
670
|
-
? `${slug}.md`
|
|
671
|
-
: `${dir}/${slug}.md`;
|
|
672
|
-
void useEditorStore.getState().loadPage(pagePath);
|
|
673
|
-
if (anchor) {
|
|
674
|
-
setTimeout(() => {
|
|
675
|
-
document
|
|
676
|
-
.querySelector(`[id="${anchor}"]`)
|
|
677
|
-
?.scrollIntoView({ behavior: "smooth" });
|
|
678
|
-
}, 200);
|
|
679
|
-
}
|
|
680
|
-
} else if (isViewingRef.current) {
|
|
681
|
-
return true;
|
|
682
|
-
} else {
|
|
683
|
-
void openWikiCreateRef.current(slug).then((result) => {
|
|
684
|
-
if (result.ok) {
|
|
685
|
-
const dir = result.dir ?? "entities";
|
|
686
|
-
const pagePath = `${dir}/${result.slug}.md`;
|
|
687
|
-
void useEditorStore.getState().loadPage(pagePath);
|
|
688
|
-
}
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
return true;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Wiki-links: #page:slug
|
|
695
|
-
if (href.startsWith("#page:")) {
|
|
696
|
-
event.preventDefault();
|
|
697
|
-
event.stopPropagation();
|
|
698
|
-
const slug = href.replace("#page:", "");
|
|
699
|
-
const { nodes, selectPage, expandPath } = useTreeStore.getState();
|
|
700
|
-
const activePath = useEditorStore.getState().currentPath;
|
|
701
|
-
const targetPath = findPageBySlug(slug, activePath, nodes);
|
|
702
|
-
if (targetPath) {
|
|
703
|
-
navigateToPage(targetPath, selectPage, expandPath);
|
|
704
|
-
}
|
|
705
|
-
return true;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
// Internal links: relative paths to .md files or other KB pages
|
|
709
|
-
// Skip external URLs and API asset links (PDFs, images)
|
|
710
|
-
if (/^https?:\/\//.test(href) || href.startsWith("/api/")) return false;
|
|
711
|
-
if (href.startsWith("mailto:") || href.startsWith("tel:")) return false;
|
|
712
|
-
|
|
713
|
-
event.preventDefault();
|
|
714
|
-
event.stopPropagation();
|
|
715
|
-
|
|
716
|
-
const { nodes, selectPage, expandPath } = useTreeStore.getState();
|
|
717
|
-
const activePath = useEditorStore.getState().currentPath;
|
|
718
|
-
|
|
719
|
-
// Resolve the link target to a KB page path
|
|
720
|
-
const targetPath = resolveInternalLink(href, activePath, nodes);
|
|
721
|
-
if (targetPath) {
|
|
722
|
-
navigateToPage(targetPath, selectPage, expandPath);
|
|
723
|
-
}
|
|
724
|
-
return true;
|
|
725
|
-
},
|
|
726
|
-
handlePaste: (_view, event) => {
|
|
727
|
-
if (isViewingRef.current) return false;
|
|
728
|
-
const files = event.clipboardData?.files;
|
|
729
|
-
const pagePath = useEditorStore.getState().currentPath;
|
|
730
|
-
|
|
731
|
-
// 1. File paste → upload then insert appropriate node
|
|
732
|
-
if (files && files.length > 0 && pagePath) {
|
|
733
|
-
for (const file of Array.from(files)) {
|
|
734
|
-
uploadFile(pagePath, file).then((url) => {
|
|
735
|
-
if (!url || !editor) return;
|
|
736
|
-
if (file.type.startsWith("image/")) {
|
|
737
|
-
editor
|
|
738
|
-
.chain()
|
|
739
|
-
.focus()
|
|
740
|
-
.setImage({ src: url, alt: file.name })
|
|
741
|
-
.run();
|
|
742
|
-
} else {
|
|
743
|
-
editor
|
|
744
|
-
.chain()
|
|
745
|
-
.focus()
|
|
746
|
-
.insertContent(`<a href="${url}">${file.name}</a>`)
|
|
747
|
-
.run();
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
return true;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
return false;
|
|
755
|
-
},
|
|
756
|
-
handleDrop: (_view, event) => {
|
|
757
|
-
if (isViewingRef.current) return false;
|
|
758
|
-
const files = event.dataTransfer?.files;
|
|
759
|
-
if (!files || files.length === 0) return false;
|
|
760
|
-
|
|
761
|
-
const pagePath = useEditorStore.getState().currentPath;
|
|
762
|
-
if (!pagePath) return false;
|
|
763
|
-
|
|
764
|
-
event.preventDefault();
|
|
765
|
-
for (const file of Array.from(files)) {
|
|
766
|
-
uploadFile(pagePath, file).then((url) => {
|
|
767
|
-
if (!url || !editor) return;
|
|
768
|
-
if (file.type.startsWith("image/")) {
|
|
769
|
-
editor
|
|
770
|
-
.chain()
|
|
771
|
-
.focus()
|
|
772
|
-
.setImage({ src: url, alt: file.name })
|
|
773
|
-
.run();
|
|
774
|
-
} else {
|
|
775
|
-
editor
|
|
776
|
-
.chain()
|
|
777
|
-
.focus()
|
|
778
|
-
.insertContent(`<a href="${url}">${file.name}</a>`)
|
|
779
|
-
.run();
|
|
780
|
-
}
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
return true;
|
|
784
|
-
},
|
|
785
|
-
},
|
|
786
|
-
immediatelyRender: false,
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
// Stable ref to the editor so callbacks with empty deps reach the live instance.
|
|
790
|
-
const editorRef = useRef<typeof editor>(editor);
|
|
791
|
-
editorRef.current = editor;
|
|
792
|
-
|
|
793
|
-
useEffect(() => {
|
|
794
|
-
editor?.setEditable(!isViewing);
|
|
795
|
-
if (isViewing) setSourceMode(false);
|
|
796
|
-
}, [editor, isViewing]);
|
|
797
|
-
|
|
798
|
-
// When content updates from store (after loadPage), set it in editor
|
|
799
|
-
const prevPathRef = useRef<string | null>(null);
|
|
800
|
-
const renderedKeyRef = useRef<string | null>(null);
|
|
801
|
-
const [renderedPath, setRenderedPath] = useState<string | null>(null);
|
|
802
|
-
useEffect(() => {
|
|
803
|
-
if (!editor || currentPath === null) return;
|
|
804
|
-
// Skip if content hasn't actually changed (same path, dirty edit)
|
|
805
|
-
if (
|
|
806
|
-
useEditorStore.getState().isDirty &&
|
|
807
|
-
currentPath === prevPathRef.current
|
|
808
|
-
)
|
|
809
|
-
return;
|
|
810
|
-
// During page navigation the store briefly holds content="" while the
|
|
811
|
-
// fetch is in flight. Rendering that empty string into ProseMirror is
|
|
812
|
-
// pure waste — every extension runs a full schema pass twice per
|
|
813
|
-
// navigation. Skip until the real content arrives.
|
|
814
|
-
if (isLoading && content === "") return;
|
|
815
|
-
// Dedupe identical (path, content) renders — e.g. cached paint followed
|
|
816
|
-
// by a fresh fetch that returned the same markdown.
|
|
817
|
-
const renderMarkdown = parsedViewingContent.body;
|
|
818
|
-
const key = `${currentPath} ${renderMarkdown}`;
|
|
819
|
-
if (renderedKeyRef.current === key) {
|
|
820
|
-
if (renderedPath !== currentPath) setRenderedPath(currentPath);
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
prevPathRef.current = currentPath;
|
|
824
|
-
|
|
825
|
-
let cancelled = false;
|
|
826
|
-
const setContent = async () => {
|
|
827
|
-
isLoadingRef.current = true;
|
|
828
|
-
const html = await markdownToHtml(
|
|
829
|
-
renderMarkdown,
|
|
830
|
-
isViewing ? { pagePath: currentPath, sanitize: true } : currentPath,
|
|
831
|
-
);
|
|
832
|
-
// Rapid navigation: a newer page may have superseded this render while
|
|
833
|
-
// markdownToHtml was awaiting. Don't stamp stale HTML into the editor.
|
|
834
|
-
if (cancelled || useEditorStore.getState().currentPath !== currentPath) {
|
|
835
|
-
isLoadingRef.current = false;
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
editor.commands.setContent(html);
|
|
839
|
-
renderedKeyRef.current = key;
|
|
840
|
-
setRenderedPath(currentPath);
|
|
841
|
-
setTimeout(() => {
|
|
842
|
-
isLoadingRef.current = false;
|
|
843
|
-
}, 50);
|
|
844
|
-
};
|
|
845
|
-
|
|
846
|
-
setContent();
|
|
847
|
-
return () => {
|
|
848
|
-
cancelled = true;
|
|
849
|
-
};
|
|
850
|
-
}, [editor, content, currentPath, isLoading, renderedPath, parsedViewingContent.body, isViewing]);
|
|
851
|
-
|
|
852
|
-
useEffect(() => {
|
|
853
|
-
if (!isViewing || !renderedPath) return;
|
|
854
|
-
const container = scrollContainerRef.current;
|
|
855
|
-
if (!container) return;
|
|
856
|
-
container
|
|
857
|
-
.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')
|
|
858
|
-
.forEach((input) => {
|
|
859
|
-
input.disabled = true;
|
|
860
|
-
});
|
|
861
|
-
}, [isViewing, renderedPath, parsedViewingContent.body]);
|
|
862
|
-
|
|
863
|
-
const isLoadingState =
|
|
864
|
-
currentPath !== null && (isLoading || renderedPath !== currentPath);
|
|
865
|
-
// Don't flash a spinner for fast/cached opens: only reveal the overlay if the
|
|
866
|
-
// load is still pending after a grace period. Instant (prefetched/cached) opens
|
|
867
|
-
// resolve well within it and never show a loader — the world-class default.
|
|
868
|
-
const [showLoadingOverlay, setShowLoadingOverlay] = useState(false);
|
|
869
|
-
useEffect(() => {
|
|
870
|
-
if (!isLoadingState) {
|
|
871
|
-
setShowLoadingOverlay(false);
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
const id = setTimeout(() => setShowLoadingOverlay(true), 150);
|
|
875
|
-
return () => clearTimeout(id);
|
|
876
|
-
}, [isLoadingState]);
|
|
877
|
-
|
|
878
|
-
const handleOpenAI = () => {
|
|
879
|
-
clearMessages();
|
|
880
|
-
openAI();
|
|
881
|
-
};
|
|
882
|
-
|
|
883
|
-
if (currentPath === null) {
|
|
884
|
-
return (
|
|
885
|
-
<div className="flex-1 flex items-center justify-center text-muted-foreground">
|
|
886
|
-
<div className="text-center space-y-3">
|
|
887
|
-
<p className="text-lg font-medium tracking-[-0.02em]">
|
|
888
|
-
No page selected
|
|
889
|
-
</p>
|
|
890
|
-
<p className="text-sm text-muted-foreground/70">
|
|
891
|
-
Select a page from the sidebar or create a new one
|
|
892
|
-
</p>
|
|
893
|
-
</div>
|
|
894
|
-
</div>
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// Path resolved to a folder (or otherwise-missing target) without an
|
|
899
|
-
// index.md. Render an explicit placeholder + Create CTA instead of
|
|
900
|
-
// dropping the user into an empty editor that pretends to be the page.
|
|
901
|
-
if (loadStatus === "missing") {
|
|
902
|
-
const slug = currentPath.split("/").pop() || currentPath;
|
|
903
|
-
const inferredTitle = slug
|
|
904
|
-
.replace(/[-_]+/g, " ")
|
|
905
|
-
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
906
|
-
const folderNode = findNodeByPath(nodes, currentPath);
|
|
907
|
-
const folderChildren = folderNode?.children ?? [];
|
|
908
|
-
const hasChildren = folderChildren.length > 0;
|
|
909
|
-
return (
|
|
910
|
-
<div className="flex-1 overflow-y-auto">
|
|
911
|
-
<div className="max-w-3xl mx-auto px-6 py-10 space-y-6">
|
|
912
|
-
<div className="space-y-3">
|
|
913
|
-
<p className="text-lg font-medium tracking-[-0.02em] text-foreground">
|
|
914
|
-
{inferredTitle}
|
|
915
|
-
</p>
|
|
916
|
-
<p className="text-sm text-muted-foreground/80">
|
|
917
|
-
This folder doesn't have an{" "}
|
|
918
|
-
<code className="px-1 py-0.5 rounded bg-muted text-[12px]">
|
|
919
|
-
index.md
|
|
920
|
-
</code>
|
|
921
|
-
{hasChildren ? " yet — its contents are listed below." : " yet."}
|
|
922
|
-
</p>
|
|
923
|
-
<button
|
|
924
|
-
onClick={() => void createMissingPage(inferredTitle)}
|
|
925
|
-
className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[13px] font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
|
926
|
-
>
|
|
927
|
-
<FilePlus className="h-3.5 w-3.5" />
|
|
928
|
-
Create page
|
|
929
|
-
</button>
|
|
930
|
-
</div>
|
|
931
|
-
{hasChildren && (
|
|
932
|
-
<FolderIndex
|
|
933
|
-
key={currentPath}
|
|
934
|
-
folderPath={currentPath}
|
|
935
|
-
entries={folderChildren}
|
|
936
|
-
/>
|
|
937
|
-
)}
|
|
938
|
-
</div>
|
|
939
|
-
</div>
|
|
940
|
-
);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
const toggleSourceMode = async () => {
|
|
944
|
-
if (!sourceMode) {
|
|
945
|
-
// Switching TO source mode — grab current markdown
|
|
946
|
-
setSourceText(useEditorStore.getState().content);
|
|
947
|
-
setSourceMode(true);
|
|
948
|
-
} else {
|
|
949
|
-
// Switching FROM source mode — apply changes
|
|
950
|
-
useEditorStore.getState().updateContent(sourceText);
|
|
951
|
-
if (editor) {
|
|
952
|
-
isLoadingRef.current = true;
|
|
953
|
-
const html = await markdownToHtml(sourceText, currentPath ?? undefined);
|
|
954
|
-
editor.commands.setContent(html);
|
|
955
|
-
setTimeout(() => {
|
|
956
|
-
isLoadingRef.current = false;
|
|
957
|
-
}, 50);
|
|
958
|
-
}
|
|
959
|
-
setSourceMode(false);
|
|
960
|
-
}
|
|
961
|
-
};
|
|
962
|
-
|
|
963
|
-
// Folder pages with both an index.md (loadStatus === "ok") AND children
|
|
964
|
-
// get a Page / Files tab strip so users can switch between the page body
|
|
965
|
-
// and the directory listing without leaving the route.
|
|
966
|
-
const renderedFolderNode = findNodeByPath(nodes, currentPath);
|
|
967
|
-
const renderedFolderChildren =
|
|
968
|
-
renderedFolderNode?.type === "directory" ||
|
|
969
|
-
renderedFolderNode?.type === "cabinet"
|
|
970
|
-
? (renderedFolderNode.children ?? [])
|
|
971
|
-
: [];
|
|
972
|
-
const showFolderTabs = renderedFolderChildren.length > 0;
|
|
973
|
-
const onFilesTab = showFolderTabs && folderTab === "files";
|
|
974
|
-
|
|
975
|
-
return (
|
|
976
|
-
<>
|
|
977
|
-
<div className="flex-1 flex flex-col overflow-hidden">
|
|
978
|
-
{showFolderTabs && (
|
|
979
|
-
<div className="flex items-center gap-1 px-3 pt-2 border-b border-border">
|
|
980
|
-
<button
|
|
981
|
-
onClick={() => setFolderTab("page")}
|
|
982
|
-
className={`px-3 py-1.5 text-[12px] rounded-t-md border-b-2 -mb-px transition-colors ${
|
|
983
|
-
folderTab === "page"
|
|
984
|
-
? "border-primary text-foreground"
|
|
985
|
-
: "border-transparent text-muted-foreground hover:text-foreground"
|
|
986
|
-
}`}
|
|
987
|
-
aria-pressed={folderTab === "page"}
|
|
988
|
-
>
|
|
989
|
-
Page
|
|
990
|
-
</button>
|
|
991
|
-
<button
|
|
992
|
-
onClick={() => setFolderTab("files")}
|
|
993
|
-
className={`px-3 py-1.5 text-[12px] rounded-t-md border-b-2 -mb-px transition-colors ${
|
|
994
|
-
folderTab === "files"
|
|
995
|
-
? "border-primary text-foreground"
|
|
996
|
-
: "border-transparent text-muted-foreground hover:text-foreground"
|
|
997
|
-
}`}
|
|
998
|
-
aria-pressed={folderTab === "files"}
|
|
999
|
-
>
|
|
1000
|
-
Files
|
|
1001
|
-
<span className="ml-1.5 text-muted-foreground/60">
|
|
1002
|
-
{renderedFolderChildren.length}
|
|
1003
|
-
</span>
|
|
1004
|
-
</button>
|
|
1005
|
-
</div>
|
|
1006
|
-
)}
|
|
1007
|
-
{onFilesTab ? (
|
|
1008
|
-
<div className="flex-1 overflow-y-auto">
|
|
1009
|
-
<div className="max-w-3xl mx-auto px-6 py-6">
|
|
1010
|
-
<FolderIndex
|
|
1011
|
-
key={currentPath}
|
|
1012
|
-
folderPath={currentPath}
|
|
1013
|
-
entries={renderedFolderChildren}
|
|
1014
|
-
/>
|
|
1015
|
-
</div>
|
|
1016
|
-
</div>
|
|
1017
|
-
) : (
|
|
1018
|
-
<>
|
|
1019
|
-
{!isViewing && (
|
|
1020
|
-
<div className="flex items-center min-w-0">
|
|
1021
|
-
<div className="flex-1 min-w-0">
|
|
1022
|
-
{!sourceMode && <EditorToolbar editor={editor} />}
|
|
1023
|
-
</div>
|
|
1024
|
-
<button
|
|
1025
|
-
onClick={toggleSourceMode}
|
|
1026
|
-
className={`flex items-center gap-1.5 px-3 py-1 mr-2 text-[11px] rounded-md transition-colors border border-border ${
|
|
1027
|
-
sourceMode
|
|
1028
|
-
? "bg-primary text-primary-foreground"
|
|
1029
|
-
: "text-muted-foreground hover:bg-accent"
|
|
1030
|
-
}`}
|
|
1031
|
-
>
|
|
1032
|
-
<Code2 className="h-3 w-3" />
|
|
1033
|
-
{sourceMode ? "Preview" : "Markdown"}
|
|
1034
|
-
</button>
|
|
1035
|
-
</div>
|
|
1036
|
-
)}
|
|
1037
|
-
|
|
1038
|
-
{sourceMode ? (
|
|
1039
|
-
<div
|
|
1040
|
-
className="flex-1 overflow-y-auto p-4"
|
|
1041
|
-
dir={isRtl ? "rtl" : undefined}
|
|
1042
|
-
>
|
|
1043
|
-
<textarea
|
|
1044
|
-
value={sourceText}
|
|
1045
|
-
onChange={(e) => setSourceText(e.target.value)}
|
|
1046
|
-
className="w-full h-full min-h-[calc(100vh-12rem)] bg-transparent font-mono text-[13px] leading-relaxed resize-none focus:outline-none"
|
|
1047
|
-
spellCheck={false}
|
|
1048
|
-
/>
|
|
1049
|
-
</div>
|
|
1050
|
-
) : (
|
|
1051
|
-
<div className="flex-1 relative" dir={isRtl ? "rtl" : undefined}>
|
|
1052
|
-
{isSuggesting && (
|
|
1053
|
-
<div className="absolute top-0 inset-x-0 z-20 flex items-center justify-center gap-2 px-3 py-1 bg-primary/10 border-b border-primary/20 text-[11px] text-primary pointer-events-none">
|
|
1054
|
-
Suggesting mode · your edits become suggestions for review
|
|
1055
|
-
</div>
|
|
1056
|
-
)}
|
|
1057
|
-
<DocumentOutline editor={editor} scrollContainerRef={scrollContainerRef} />
|
|
1058
|
-
<div
|
|
1059
|
-
ref={scrollContainerRef}
|
|
1060
|
-
className={`absolute inset-0 overflow-y-auto ${
|
|
1061
|
-
isSuggesting ? "pt-7" : ""
|
|
1062
|
-
}`}
|
|
1063
|
-
style={{
|
|
1064
|
-
["--editor-max-w" as string]: editorMaxW,
|
|
1065
|
-
["--editor-ml" as string]: editorMl,
|
|
1066
|
-
}}
|
|
1067
|
-
data-editor-scroll
|
|
1068
|
-
>
|
|
1069
|
-
{/* Absolutely-positioned overlay for comment pips and suggestion cards.
|
|
1070
|
-
height:0 so it doesn't push content; children overflow freely.
|
|
1071
|
-
Positions from blockRefPositions are relative to scroll container top. */}
|
|
1072
|
-
<div
|
|
1073
|
-
aria-hidden="true"
|
|
1074
|
-
className="relative pointer-events-none"
|
|
1075
|
-
style={{ height: 0 }}
|
|
1076
|
-
>
|
|
1077
|
-
{/* Comment pips — one per block with at least one comment */}
|
|
1078
|
-
{Object.entries(commentsByRef).map(([blockRef, blockComments]) => {
|
|
1079
|
-
const pos = blockRefPositions.get(blockRef);
|
|
1080
|
-
if (!pos) return null;
|
|
1081
|
-
return (
|
|
1082
|
-
<div key={`pip-${blockRef}`} style={{ pointerEvents: "auto" }}>
|
|
1083
|
-
<CommentPip
|
|
1084
|
-
blockRef={blockRef}
|
|
1085
|
-
comments={blockComments}
|
|
1086
|
-
top={pos.top + 4}
|
|
1087
|
-
left={Math.max(0, pos.left - 20)}
|
|
1088
|
-
onClick={() => {
|
|
1089
|
-
const el = scrollContainerRef.current?.querySelector(
|
|
1090
|
-
`[data-block-ref="${blockRef}"]`,
|
|
1091
|
-
) as HTMLElement | null;
|
|
1092
|
-
if (el) setThreadTarget({ blockRef, el });
|
|
1093
|
-
}}
|
|
1094
|
-
/>
|
|
1095
|
-
</div>
|
|
1096
|
-
);
|
|
1097
|
-
})}
|
|
1098
|
-
|
|
1099
|
-
{/* Suggestion cards — one per pending suggestion */}
|
|
1100
|
-
{currentPath && pendingSuggestions.map((sg) => {
|
|
1101
|
-
const pos = blockRefPositions.get(sg.ref);
|
|
1102
|
-
const currentBlock = snapshotBlocks.find((b) => b.ref === sg.ref);
|
|
1103
|
-
if (!pos || !currentBlock) return null;
|
|
1104
|
-
const cardTop =
|
|
1105
|
-
sg.kind === "insertAfter" ? pos.bottom + 4 :
|
|
1106
|
-
sg.kind === "insertBefore" ? Math.max(0, pos.top - 80) :
|
|
1107
|
-
pos.top;
|
|
1108
|
-
return (
|
|
1109
|
-
<div key={`sug-${sg.id}`} style={{ pointerEvents: "auto" }}>
|
|
1110
|
-
<SuggestionCard
|
|
1111
|
-
path={currentPath}
|
|
1112
|
-
suggestion={sg}
|
|
1113
|
-
currentMarkdown={currentBlock.markdown}
|
|
1114
|
-
baseRevision={snapshotRevision}
|
|
1115
|
-
getLatestRevision={() =>
|
|
1116
|
-
useProofStore.getState().byPath[currentPath]?.snapshotRevision ?? 0
|
|
1117
|
-
}
|
|
1118
|
-
top={cardTop}
|
|
1119
|
-
left={pos.left}
|
|
1120
|
-
width={pos.width}
|
|
1121
|
-
onSettled={() => {
|
|
1122
|
-
void useProofStore.getState().loadSidecar(currentPath);
|
|
1123
|
-
void useProofStore.getState().loadSnapshot(currentPath);
|
|
1124
|
-
}}
|
|
1125
|
-
readOnly={isViewing}
|
|
1126
|
-
/>
|
|
1127
|
-
</div>
|
|
1128
|
-
);
|
|
1129
|
-
})}
|
|
1130
|
-
</div>
|
|
1131
|
-
|
|
1132
|
-
{/* Comment thread — Portal-rendered, driven by threadTarget */}
|
|
1133
|
-
{threadTarget && currentPath && (
|
|
1134
|
-
<CommentThread
|
|
1135
|
-
path={currentPath}
|
|
1136
|
-
blockRef={threadTarget.blockRef}
|
|
1137
|
-
comments={commentsByRef[threadTarget.blockRef] ?? []}
|
|
1138
|
-
anchorEl={threadTarget.el}
|
|
1139
|
-
onClose={() => setThreadTarget(null)}
|
|
1140
|
-
readOnly={isViewing}
|
|
1141
|
-
/>
|
|
1142
|
-
)}
|
|
1143
|
-
|
|
1144
|
-
{/* Human suggest-edit popover — driven by suggestTarget */}
|
|
1145
|
-
{suggestTarget && currentPath && (
|
|
1146
|
-
<SuggestEditPopover
|
|
1147
|
-
path={currentPath}
|
|
1148
|
-
blockRef={suggestTarget.blockRef}
|
|
1149
|
-
currentMarkdown={suggestTarget.markdown}
|
|
1150
|
-
anchor={suggestTarget.anchor}
|
|
1151
|
-
onClose={() => setSuggestTarget(null)}
|
|
1152
|
-
/>
|
|
1153
|
-
)}
|
|
1154
|
-
|
|
1155
|
-
{isViewing && Object.keys(parsedViewingContent.data).length > 0 && (
|
|
1156
|
-
<div className="max-w-[var(--editor-max-w,48rem)] ml-[var(--editor-ml,auto)] mr-auto px-4 sm:px-8 pt-6">
|
|
1157
|
-
<FrontmatterHeader
|
|
1158
|
-
data={parsedViewingContent.data as Record<string, never>}
|
|
1159
|
-
/>
|
|
1160
|
-
</div>
|
|
1161
|
-
)}
|
|
1162
|
-
<EditorContent editor={editor} />
|
|
1163
|
-
{currentPath && /\.(md|markdown)$/i.test(currentPath) && (
|
|
1164
|
-
<BacklinksPanel currentPath={currentPath} />
|
|
1165
|
-
)}
|
|
1166
|
-
{/* Proof-span hover delegation */}
|
|
1167
|
-
<div
|
|
1168
|
-
aria-hidden="true"
|
|
1169
|
-
className="contents"
|
|
1170
|
-
onMouseOver={(e) => {
|
|
1171
|
-
const span = (e.target as HTMLElement).closest<HTMLElement>(".proof-span");
|
|
1172
|
-
if (span && span !== proofTarget) setProofTarget(span);
|
|
1173
|
-
}}
|
|
1174
|
-
onMouseOut={(e) => {
|
|
1175
|
-
const related = e.relatedTarget as HTMLElement | null;
|
|
1176
|
-
if (!related?.closest(".proof-span")) setProofTarget(null);
|
|
1177
|
-
}}
|
|
1178
|
-
/>
|
|
1179
|
-
{currentPath && (
|
|
1180
|
-
<ProofSpanPopover
|
|
1181
|
-
targetEl={proofTarget}
|
|
1182
|
-
path={currentPath}
|
|
1183
|
-
onClose={() => setProofTarget(null)}
|
|
1184
|
-
onComment={() => {
|
|
1185
|
-
if (!proofTarget) return;
|
|
1186
|
-
const blockEl = proofTarget.closest<HTMLElement>("[data-block-ref]");
|
|
1187
|
-
if (!blockEl) return;
|
|
1188
|
-
const blockRef = blockEl.getAttribute("data-block-ref");
|
|
1189
|
-
if (blockRef) setThreadTarget({ blockRef, el: blockEl });
|
|
1190
|
-
}}
|
|
1191
|
-
/>
|
|
1192
|
-
)}
|
|
1193
|
-
{!isViewing && (
|
|
1194
|
-
<>
|
|
1195
|
-
<EditorBubbleMenu
|
|
1196
|
-
editor={editor}
|
|
1197
|
-
onSuggestEdit={openSuggestForSelection}
|
|
1198
|
-
onComment={openCommentForSelection}
|
|
1199
|
-
/>
|
|
1200
|
-
<TableMenu editor={editor} />
|
|
1201
|
-
<SlashCommands editor={editor} />
|
|
1202
|
-
<WikiLinkPicker
|
|
1203
|
-
editor={editor}
|
|
1204
|
-
onCreateRequest={openWikiCreateRef.current}
|
|
1205
|
-
/>
|
|
1206
|
-
</>
|
|
1207
|
-
)}
|
|
1208
|
-
{isViewing && (
|
|
1209
|
-
<ViewModeCommentButton
|
|
1210
|
-
containerRef={scrollContainerRef}
|
|
1211
|
-
onComment={openCommentForSelection}
|
|
1212
|
-
/>
|
|
1213
|
-
)}
|
|
1214
|
-
{/* AI Edit Prompt + slash hint */}
|
|
1215
|
-
<div className="max-w-[var(--editor-max-w,48rem)] ml-[var(--editor-ml,auto)] mr-auto px-8 pb-8 flex items-center gap-4">
|
|
1216
|
-
<button
|
|
1217
|
-
onClick={handleOpenAI}
|
|
1218
|
-
className="group flex items-center gap-2 text-[13px] text-muted-foreground/50 hover:text-muted-foreground transition-colors cursor-pointer"
|
|
1219
|
-
>
|
|
1220
|
-
<Sparkles className="h-3.5 w-3.5 group-hover:text-primary transition-colors" />
|
|
1221
|
-
<span>Edit with AI</span>
|
|
1222
|
-
</button>
|
|
1223
|
-
{!isViewing && (
|
|
1224
|
-
<span className="text-[11px] text-muted-foreground/30 select-none">
|
|
1225
|
-
<kbd className="rounded px-1 py-0.5 font-mono text-[10px] ring-1 ring-foreground/10">
|
|
1226
|
-
/
|
|
1227
|
-
</kbd>{" "}
|
|
1228
|
-
for commands
|
|
1229
|
-
</span>
|
|
1230
|
-
)}
|
|
1231
|
-
</div>
|
|
1232
|
-
</div>
|
|
1233
|
-
|
|
1234
|
-
{showLoadingOverlay && (
|
|
1235
|
-
<div
|
|
1236
|
-
className="absolute inset-0 flex items-center justify-center bg-background/80 backdrop-blur-md z-20 pointer-events-none"
|
|
1237
|
-
aria-hidden="true"
|
|
1238
|
-
>
|
|
1239
|
-
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground/70" />
|
|
1240
|
-
</div>
|
|
1241
|
-
)}
|
|
1242
|
-
</div>
|
|
1243
|
-
)}
|
|
1244
|
-
|
|
1245
|
-
{!isViewing && (
|
|
1246
|
-
/* Status bar */
|
|
1247
|
-
<div className="flex items-center justify-between px-4 py-1 border-t border-border text-xs text-muted-foreground/60">
|
|
1248
|
-
<span className="text-[10.5px] text-muted-foreground/30 select-none hidden sm:block">
|
|
1249
|
-
<kbd className="rounded px-1 font-mono text-[9.5px] ring-1 ring-foreground/10">
|
|
1250
|
-
⌘S
|
|
1251
|
-
</kbd>{" "}
|
|
1252
|
-
save
|
|
1253
|
-
<span className="mx-1.5 opacity-40">·</span>
|
|
1254
|
-
<kbd className="rounded px-1 font-mono text-[9.5px] ring-1 ring-foreground/10">
|
|
1255
|
-
/
|
|
1256
|
-
</kbd>{" "}
|
|
1257
|
-
commands
|
|
1258
|
-
</span>
|
|
1259
|
-
<div className="flex items-center gap-3">
|
|
1260
|
-
{/* Mode toggle */}
|
|
1261
|
-
<div
|
|
1262
|
-
className="flex items-center rounded-md border border-border overflow-hidden text-[10.5px]"
|
|
1263
|
-
role="radiogroup"
|
|
1264
|
-
aria-label="Edit mode"
|
|
1265
|
-
>
|
|
1266
|
-
<button
|
|
1267
|
-
type="button"
|
|
1268
|
-
role="radio"
|
|
1269
|
-
aria-checked={editMode === "editing"}
|
|
1270
|
-
onClick={() => setEditMode("editing")}
|
|
1271
|
-
className={`px-2 py-0.5 transition-colors ${
|
|
1272
|
-
editMode === "editing"
|
|
1273
|
-
? "bg-primary text-primary-foreground"
|
|
1274
|
-
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1275
|
-
}`}
|
|
1276
|
-
>
|
|
1277
|
-
Editing
|
|
1278
|
-
</button>
|
|
1279
|
-
<button
|
|
1280
|
-
type="button"
|
|
1281
|
-
role="radio"
|
|
1282
|
-
aria-checked={editMode === "suggesting"}
|
|
1283
|
-
onClick={() => setEditMode("suggesting")}
|
|
1284
|
-
className={`px-2 py-0.5 transition-colors ${
|
|
1285
|
-
editMode === "suggesting"
|
|
1286
|
-
? "bg-primary text-primary-foreground"
|
|
1287
|
-
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1288
|
-
}`}
|
|
1289
|
-
>
|
|
1290
|
-
Suggesting
|
|
1291
|
-
</button>
|
|
1292
|
-
</div>
|
|
1293
|
-
<span
|
|
1294
|
-
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10.5px] transition-all duration-300 ${
|
|
1295
|
-
saveStatus === "idle"
|
|
1296
|
-
? "opacity-0 pointer-events-none"
|
|
1297
|
-
: "opacity-100"
|
|
1298
|
-
} ${
|
|
1299
|
-
saveStatus === "saving"
|
|
1300
|
-
? "bg-muted text-muted-foreground"
|
|
1301
|
-
: saveStatus === "saved"
|
|
1302
|
-
? "bg-success/10 text-success"
|
|
1303
|
-
: saveStatus === "error"
|
|
1304
|
-
? "bg-destructive/10 text-destructive"
|
|
1305
|
-
: ""
|
|
1306
|
-
}`}
|
|
1307
|
-
>
|
|
1308
|
-
{saveStatus === "saving" && (
|
|
1309
|
-
<><Loader2 className="h-2.5 w-2.5 animate-spin" />Saving…</>
|
|
1310
|
-
)}
|
|
1311
|
-
{saveStatus === "saved" && (
|
|
1312
|
-
<><Check className="h-2.5 w-2.5" />Saved</>
|
|
1313
|
-
)}
|
|
1314
|
-
{saveStatus === "error" && (
|
|
1315
|
-
<><AlertCircle className="h-2.5 w-2.5" />Save failed</>
|
|
1316
|
-
)}
|
|
1317
|
-
</span>
|
|
1318
|
-
</div>
|
|
1319
|
-
</div>
|
|
1320
|
-
)}
|
|
1321
|
-
</>
|
|
1322
|
-
)}
|
|
1323
|
-
</div>
|
|
1324
|
-
{WikiCreateDialog}
|
|
1325
|
-
</>
|
|
1326
|
-
);
|
|
1327
|
-
}
|