ocean-brain 0.7.5 → 0.8.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.
Files changed (89) hide show
  1. package/dist/index.js +3 -3
  2. package/dist/mcp.js +180 -736
  3. package/package.json +4 -1
  4. package/server/client/dist/assets/ArrowRight.es-C4zlc54Z.js +1 -0
  5. package/server/client/dist/assets/{Calendar-Bc2zJueR.js → Calendar-COrN6Hjv.js} +2 -2
  6. package/server/client/dist/assets/Callout-BK0K9E74.js +1 -0
  7. package/server/client/dist/assets/Copy.es-zm8f_XXX.js +1 -0
  8. package/server/client/dist/assets/Graph-BH1fzhue.js +1 -0
  9. package/server/client/dist/assets/{Image-DxWhlIDG.js → Image-KXuHOG4b.js} +1 -1
  10. package/server/client/dist/assets/Image.es-BKahRbIm.js +1 -0
  11. package/server/client/dist/assets/Note-C143cKKw.js +12 -0
  12. package/server/client/dist/assets/Plus.es-BqeC8T8h.js +1 -0
  13. package/server/client/dist/assets/Reminders-BYG5rz1M.js +1 -0
  14. package/server/client/dist/assets/Search-DBWsIMDP.js +1 -0
  15. package/server/client/dist/assets/{SurfaceCard-CkAVMLPs.js → SurfaceCard-CNSQfYfM.js} +1 -1
  16. package/server/client/dist/assets/Tag-5aCfSO8Y.js +1 -0
  17. package/server/client/dist/assets/TagNotes-BPZjN-6l.js +1 -0
  18. package/server/client/dist/assets/Trash.es-CRQkTnXZ.js +1 -0
  19. package/server/client/dist/assets/ViewNotes-DGLNF96x.js +1 -0
  20. package/server/client/dist/assets/ViewSectionDialog-CzskgmMF.js +102 -0
  21. package/server/client/dist/assets/Views-DR5jT23G.js +1 -0
  22. package/server/client/dist/assets/{graph-vendor-CUxe67Lr.js → graph-vendor-UTvF2NS_.js} +2 -2
  23. package/server/client/dist/assets/image.api-8dlMwzMd.js +17 -0
  24. package/server/client/dist/assets/{index-C5BGzDgt.css → index-BEUqDuAZ.css} +1 -1
  25. package/server/client/dist/assets/{index-D3BfroIT.js → index-DGIAh3ZY.js} +1 -1
  26. package/server/client/dist/assets/index-VHp17GQX.js +342 -0
  27. package/server/client/dist/assets/manage-image-RqtWKas2.js +1 -0
  28. package/server/client/dist/assets/{manage-image-detail-C8Q7K7I3.js → manage-image-detail-BTSIbLlQ.js} +1 -1
  29. package/server/client/dist/assets/mcp-EDurbggR.js +4 -0
  30. package/server/client/dist/assets/mcp-admin-adapter-DmGUo5G8.js +1 -0
  31. package/server/client/dist/assets/{note-core-Z6kqulGB.js → note-core-BklwyZHk.js} +1 -1
  32. package/server/client/dist/assets/{note-vendor-i1M2FSI2.js → note-vendor-DkYKUmLo.js} +1 -1
  33. package/server/client/dist/assets/{placeholder-Cr_nI8UI.js → placeholder-DeKgSBnC.js} +4 -4
  34. package/server/client/dist/assets/{properties-CvYWJWUM.js → properties-BjrZvOYn.js} +1 -1
  35. package/server/client/dist/assets/tag.api-Dl8DQd0m.js +21 -0
  36. package/server/client/dist/assets/trash-C5ntddLu.js +1 -0
  37. package/server/client/dist/assets/{useReminderMutate-CDmIEwsw.js → useReminderMutate-CoXXqJY2.js} +1 -1
  38. package/server/client/dist/index.html +5 -5
  39. package/server/dist/features/auth/http/login-page.js +1 -1
  40. package/server/dist/features/image/http/upload.js +34 -23
  41. package/server/dist/features/image/http/upload.js.map +1 -1
  42. package/server/dist/features/image/services/validation.js +111 -0
  43. package/server/dist/features/image/services/validation.js.map +1 -0
  44. package/server/dist/features/mcp-admin/http/handlers.js +11 -3
  45. package/server/dist/features/mcp-admin/http/handlers.js.map +1 -1
  46. package/server/dist/features/note/http/mcp.js +8 -43
  47. package/server/dist/features/note/http/mcp.js.map +1 -1
  48. package/server/dist/features/note/services/cleanup.js +2 -2
  49. package/server/dist/features/note/services/cleanup.js.map +1 -1
  50. package/server/dist/features/note/services/markdown-intent-write.js +11 -43
  51. package/server/dist/features/note/services/markdown-intent-write.js.map +1 -1
  52. package/server/dist/features/note/services/markdown-patch.js +4 -123
  53. package/server/dist/features/note/services/markdown-patch.js.map +1 -1
  54. package/server/dist/features/note/services/write.js +13 -13
  55. package/server/dist/features/note/services/write.js.map +1 -1
  56. package/server/dist/features/tag/graphql/tag.query.resolver.js +16 -1
  57. package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -1
  58. package/server/dist/features/tag/graphql/tag.type-defs.js +2 -0
  59. package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -1
  60. package/server/dist/modules/app-version.js +105 -0
  61. package/server/dist/modules/app-version.js.map +1 -0
  62. package/server/dist/modules/mcp-auth.js +62 -1
  63. package/server/dist/modules/mcp-auth.js.map +1 -1
  64. package/server/dist/paths.js +1 -0
  65. package/server/dist/paths.js.map +1 -1
  66. package/server/dist/routes/api.js +2 -8
  67. package/server/dist/routes/api.js.map +1 -1
  68. package/server/dist/routes/client.js +51 -1
  69. package/server/dist/routes/client.js.map +1 -1
  70. package/server/client/dist/assets/Callout-B4kA_Cvr.js +0 -1
  71. package/server/client/dist/assets/Graph-uq5jfJgt.js +0 -1
  72. package/server/client/dist/assets/Image.es-BpAOJjJT.js +0 -1
  73. package/server/client/dist/assets/Note-Bo8ZYRP-.js +0 -22
  74. package/server/client/dist/assets/Plus.es-Dc_js8AC.js +0 -1
  75. package/server/client/dist/assets/Reminders-lj7Equld.js +0 -1
  76. package/server/client/dist/assets/Search-1wcRO5--.js +0 -1
  77. package/server/client/dist/assets/Tag-CeygwFfv.js +0 -1
  78. package/server/client/dist/assets/TagNotes-B8DqItxw.js +0 -1
  79. package/server/client/dist/assets/Trash.es-OuQVb_SX.js +0 -1
  80. package/server/client/dist/assets/ViewNotes-BH7PbdOD.js +0 -1
  81. package/server/client/dist/assets/ViewSectionTableRenderer-BJwhd5be.js +0 -102
  82. package/server/client/dist/assets/Views-eYFtsdFj.js +0 -1
  83. package/server/client/dist/assets/image.api-C0eYGCeH.js +0 -17
  84. package/server/client/dist/assets/index-jIfkRdY_.js +0 -361
  85. package/server/client/dist/assets/manage-image-C0YwkLIP.js +0 -1
  86. package/server/client/dist/assets/mcp-DHHhTLC8.js +0 -1
  87. package/server/client/dist/assets/trash-DA27aW7L.js +0 -1
  88. package/server/dist/features/image/services/remote-fetch.js +0 -176
  89. package/server/dist/features/image/services/remote-fetch.js.map +0 -1
@@ -1 +0,0 @@
1
- import{j as e}from"./note-vendor-i1M2FSI2.js";import{a6 as L,q as T,y as G,k as S,ah as C,a0 as F,P as N,J as k,L as q,aY as B,T as O,B as D,a1 as z,i as h,j as K,aO as Q}from"./index-jIfkRdY_.js";import{f as U,d as W}from"./image.api-C0eYGCeH.js";import{a as x}from"./graph-vendor-CUxe67Lr.js";import{I as H}from"./Image-DxWhlIDG.js";import{S as M}from"./SurfaceCard-CkAVMLPs.js";import{c as $,n as J}from"./Trash.es-OuQVb_SX.js";import"./note-core-Z6kqulGB.js";const V=n=>{const{data:a}=L({queryKey:T.images.list(n.searchParams),async queryFn(){const s=await U({offset:n.searchParams.offset,limit:n.searchParams.limit});if(s.type==="error")throw s;return s.allImages}});return n.render(a)},b=16;function X(n,a,s){return Math.max(Math.floor((Math.max(n,0)+s)/(a+s)),1)}function _(n,a,s){return n*a+(n-1)*s}function Y({minItemWidth:n,gap:a,rows:s,fallback:l,override:g}){const[i,w]=x.useState(null),[v,p]=x.useState(l??s),r=x.useRef(null),o=x.useCallback(t=>{w(t)},[]),d=g==null,y=d?v:g;return x.useLayoutEffect(()=>{if(!d){r.current=null;return}if(!i){r.current=null,p(c=>c===(l??s)?c:l??s);return}let t=null;const I=()=>{const c=i.offsetWidth,E=X(c,n,a),u=r.current;let j=E;if(u!==null&&u!==E)if(E>u){const f=_(u+1,n,a);c<f+b&&(j=u)}else{const f=_(u,n,a);c>f-b&&(j=u)}r.current=j;const A=Math.max(j*s,s);p(f=>f===A?f:A)},m=()=>{t===null&&(t=window.requestAnimationFrame(()=>{t=null,I()}))};if(m(),typeof ResizeObserver<"u"){const c=new ResizeObserver(()=>{m()});return c.observe(i),()=>{c.disconnect(),t!==null&&window.cancelAnimationFrame(t)}}return window.addEventListener("resize",m),window.addEventListener("orientationchange",m),()=>{window.removeEventListener("resize",m),window.removeEventListener("orientationchange",m),t!==null&&window.cancelAnimationFrame(t)}},[i,l,a,d,n,s]),{containerRef:o,limit:y,isAutoLimit:d}}const Z=240,ee=20,te=4,R=192,P=K(Q),de=()=>{const n=G(),a=S(),s=P.useNavigate(),{page:l}=P.useSearch(),{containerRef:g,limit:i}=Y({minItemWidth:Z,gap:ee,rows:te}),w=C({mutationFn:W,onSuccess:r=>{if(r.type==="error")throw r;a.invalidateQueries({queryKey:T.images.listAll(),exact:!1})}}),v=async r=>{await n("Delete this image? It is not referenced by any notes.")&&w.mutate(r)},p=r=>r===1?"1 reference":`${r} references`;return e.jsx("div",{ref:g,children:e.jsx(F,{fallback:e.jsx(N,{title:"Images",variant:"default",heading:e.jsx(h,{width:136,height:24,className:"rounded-full"}),description:e.jsx(h,{width:232,height:16,className:"rounded-full"}),children:e.jsx("div",{className:"flex flex-col gap-4",children:e.jsx("div",{className:"grid-auto-cards grid gap-4",children:Array.from({length:3},(r,o)=>e.jsxs(M,{flush:!0,children:[e.jsx("div",{className:"flex items-center justify-center bg-muted/25 p-3",style:{height:R},children:e.jsx(h,{width:"100%",height:R-24,className:"rounded-[12px]"})}),e.jsxs("div",{className:"flex items-center justify-between gap-3 border-t border-border-subtle px-3 py-2.5",children:[e.jsx(h,{width:92,height:14,className:"rounded-full"}),e.jsx(h,{width:32,height:32,className:"rounded-[12px]"})]})]},o))})})}),errorTitle:"Failed to load images",errorDescription:"Retry loading uploaded image metadata",resetKeys:[l,i],children:e.jsx(V,{searchParams:{offset:(l-1)*i,limit:i},render:({images:r,totalCount:o})=>{const d=o>0?`Images (${o})`:void 0,y="Review and manage the images you uploaded inside notes";return r.length===0?e.jsx(N,{title:"Images",variant:"default",heading:d,description:y,children:e.jsx(k,{title:"There are no images",description:"Upload an image in any note and it will appear here"})}):e.jsx(N,{title:"Images",variant:"default",heading:d,description:y,children:e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx("div",{className:"grid-auto-cards grid gap-4",children:r.map(t=>e.jsxs(M,{flush:!0,children:[e.jsx(q,{to:B,params:{id:t.id},className:"focus-ring-soft block overflow-hidden rounded-t-[18px] outline-none",children:e.jsx("div",{className:"flex items-center justify-center bg-muted/25 p-3",style:{height:R},children:e.jsx(H,{className:"h-full w-full rounded-[12px] object-contain transition-transform duration-200 hover:scale-[1.02]",src:t.url,alt:t.id})})}),e.jsxs("div",{className:"flex items-center justify-between gap-3 border-t border-border-subtle px-3 py-2.5",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-2 text-fg-secondary",children:[e.jsx($,{className:"h-3.5 w-3.5 shrink-0 text-fg-tertiary"}),e.jsx(O,{as:"span",variant:"meta",weight:"medium",tone:"secondary",className:"truncate",children:p(t.referenceCount)})]}),e.jsx(D,{variant:"ghost",size:"icon-sm",disabled:t.referenceCount>0,onClick:()=>v(t.id),children:e.jsx(J,{className:"h-4 w-4"})})]})]},t.id))}),o&&i<o&&e.jsx(z,{page:l,last:Math.ceil(o/i),onChange:t=>{s({search:I=>({...I,page:t})})}})]})})}})})})};export{de as default};
@@ -1 +0,0 @@
1
- import{j as e}from"./note-vendor-i1M2FSI2.js";import{aR as r,k as Q,u as B,ah as p,P as E,T as t,aS as G,B as M,n as w,w as J,I as K,o as O,r as T,m as U}from"./index-jIfkRdY_.js";import{a as h}from"./graph-vendor-CUxe67Lr.js";import{S as x}from"./SurfaceCard-CkAVMLPs.js";import"./note-core-Z6kqulGB.js";const D=async()=>{const{data:s}=await r.get("/api/mcp-admin/status");return s},z=async s=>{const{data:n}=await r.post("/api/mcp-admin/enabled",{enabled:s});return n},V=async()=>{const{data:s}=await r.post("/api/mcp-admin/token/rotate");return s},H=async()=>{const{data:s}=await r.post("/api/mcp-admin/token/revoke");return s},o=["mcp-admin","status"],W=s=>JSON.stringify({mcpServers:{"ocean-brain":{command:"npx",args:["-y","ocean-brain","mcp","--server",s,"--token-file","/path/to/token.txt"]}}},null,2),X=s=>JSON.stringify({mcpServers:{"ocean-brain":{command:"npx",args:["-y","ocean-brain","mcp","--server",s,"--token","your-token-here"]}}},null,2),se=()=>{const s=U(),n=Q(),[c,S]=h.useState(()=>window.location.origin),[v,g]=h.useState(""),[k,P]=h.useState("token-file"),{data:y,isLoading:A}=B({queryKey:o,queryFn:D}),b=p({mutationFn:z,onSuccess:a=>{n.setQueryData(o,a),s(a.enabled?"MCP access enabled.":"MCP access disabled.")}}),j=p({mutationFn:V,onSuccess:async({token:a})=>{g(a),s("A new MCP token has been issued."),await n.invalidateQueries({queryKey:o})}}),f=p({mutationFn:H,onSuccess:a=>{g(""),n.setQueryData(o,a),s("The active MCP token has been revoked.")}}),l=y?.enabled??!1,d=y?.hasActiveToken??!1,I=!A&&!b.isPending,i="space-y-1",N="space-y-4.5",F="inline-flex items-center gap-3 rounded-[14px] border border-border-subtle bg-muted px-3 py-2",L="rounded-full border border-border-subtle bg-hover-subtle px-2.5 py-1",R="space-y-3 rounded-[16px] border border-border-subtle bg-surface px-4 py-3",q="overflow-x-auto rounded-[14px] border border-border-subtle bg-surface px-4 py-3 text-xs text-fg-secondary",u={variant:"subheading",weight:"medium",tracking:"tight"},C="font-medium text-fg-tertiary",m=k==="token-file"?{title:"Token file",description:"Recommended. Keeps the token out of config.",snippet:W(c)}:{title:"Inline token",description:"Useful for quick local testing.",snippet:X(c)};return e.jsx(E,{title:"MCP",variant:"default",description:"Manage MCP access, tokens, and connection details",children:e.jsxs("div",{className:"grid grid-cols-1 gap-4",children:[e.jsx(x,{tone:"elevated",children:e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-4",children:[e.jsxs("div",{className:i,children:[e.jsx(t,{as:"h2",...u,children:"MCP Access"}),e.jsx(t,{as:"p",variant:"meta",tone:"secondary",children:"Allow or block MCP requests at the server level."})]}),e.jsxs("div",{className:F,children:[e.jsx(t,{as:"span",variant:"meta",weight:"medium",tone:"secondary",children:l?"Enabled":"Disabled"}),e.jsx(G,{"aria-label":"Allow MCP access",checked:l,disabled:!I,onCheckedChange:()=>{b.mutate(!l)}})]})]})}),e.jsx(x,{tone:"elevated",children:e.jsxs("div",{className:N,children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:i,children:[e.jsx(t,{as:"h2",...u,children:"Token Management"}),e.jsx(t,{as:"p",variant:"meta",tone:"secondary",className:"max-w-[64ch]",children:"Ocean Brain supports one active MCP token at a time. Rotating immediately invalidates the previous one."})]}),e.jsx(t,{as:"span",variant:"meta",weight:"medium",tone:d?"secondary":"tertiary",className:L,children:d?"1 active token":"No active token"})]}),e.jsxs("div",{className:"flex flex-wrap gap-2.5",children:[e.jsx(M,{onClick:()=>j.mutate(void 0),isLoading:j.isPending,children:"Rotate token"}),e.jsx(M,{variant:"soft-danger",onClick:()=>f.mutate(),isLoading:f.isPending,disabled:!d,children:"Revoke token"})]}),v&&e.jsxs("div",{className:"space-y-2.5",children:[e.jsx(w,{htmlFor:"issued-mcp-token",className:C,children:"Issued token"}),e.jsx(J,{id:"issued-mcp-token",rows:3,readOnly:!0,value:v})]})]})}),e.jsx(x,{tone:"elevated",children:e.jsxs("div",{className:N,children:[e.jsxs("div",{className:i,children:[e.jsx(t,{as:"h2",...u,children:"Connection Guide"}),e.jsx(t,{as:"p",variant:"meta",tone:"secondary",children:"Connect your MCP client with either a token file or an inline token."})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(w,{htmlFor:"mcp-server-url",className:C,children:"Server URL"}),e.jsx(K,{id:"mcp-server-url",value:c,onChange:a=>S(a.target.value)})]}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs(O,{type:"single",variant:"pills",size:"sm",value:k,onValueChange:a=>{(a==="token-file"||a==="inline-token")&&P(a)},children:[e.jsx(T,{value:"token-file",children:"Token file"}),e.jsx(T,{value:"inline-token",children:"Inline token"})]}),e.jsxs("div",{className:R,children:[e.jsxs("div",{className:i,children:[e.jsx(t,{as:"p",variant:"meta",weight:"semibold",children:m.title}),e.jsx(t,{as:"p",variant:"meta",tone:"secondary",children:m.description})]}),e.jsx("pre",{className:q,children:m.snippet})]})]})]})})]})})};export{se as default};
@@ -1 +0,0 @@
1
- import{j as e}from"./note-vendor-i1M2FSI2.js";import{a0 as M,j as Q,aw as F,k as L,y as _,a6 as I,aT as K,q as i,ah as E,aU as B,N as O,aV as W,P as N,J as H,T as d,ax as U,B as j,a1 as V,aW as Y,i as l,m as $,d as z,al as G,u as J,aX as X,M as y}from"./index-jIfkRdY_.js";import{a as k}from"./graph-vendor-CUxe67Lr.js";import{S as C}from"./SurfaceCard-CkAVMLPs.js";import"./note-core-Z6kqulGB.js";const m=25,v=Q(F),S=30,R=`Deleted notes stay here for ${S} days before permanent removal`,g={title:"Permanently delete note?",description:"This note is referenced by the notes below. If you permanently delete it, those links will stay broken. Other notes will not be edited automatically.",confirmLabel:"Delete now",confirmVariant:"danger"},P=a=>z(a).format("YYYY-MM-DD HH:mm"),Z=({note:a,onClose:o})=>{const s=a?.id??"",c=a?.title||"Untitled note",r=J({queryKey:i.notes.trashDetail(s),enabled:!!a,queryFn:async()=>{const h=await X(s);if(h.type==="error")throw h;if(!h.trashedNote)throw new Error("Trashed note not found");return h.trashedNote}}),f=r.data?.contentAsMarkdown.trim()??"";return e.jsxs(y,{isOpen:!!a,onClose:o,variant:"inspect",children:[e.jsx(y.Header,{title:c,onClose:o}),e.jsx(y.Body,{children:e.jsxs("div",{className:"flex flex-col gap-3",children:[a&&e.jsxs(d,{as:"p",variant:"meta",tone:"secondary",children:["Deleted ",P(a.deletedAt)]}),r.isLoading&&e.jsx(d,{as:"p",variant:"meta",tone:"secondary",children:"Loading deleted note content..."}),r.isError&&e.jsx(d,{as:"p",variant:"meta",tone:"error",children:"Failed to load deleted note content."}),!r.isLoading&&!r.isError&&f&&e.jsx("pre",{className:"max-h-[60vh] overflow-auto whitespace-pre-wrap break-words rounded-[14px] border border-border-subtle bg-hover-subtle/50 px-4 py-3 text-sm leading-6 text-fg-secondary",children:f}),!r.isLoading&&!r.isError&&!f&&e.jsx(d,{as:"p",variant:"meta",tone:"secondary",className:"rounded-[14px] border border-border-subtle bg-hover-subtle/50 px-4 py-3",children:"No readable content was found for this deleted note."})]})}),e.jsx(y.Footer,{children:e.jsx(j,{variant:"ghost",size:"sm",onClick:o,children:"Close"})})]})},ee=()=>e.jsx(N,{title:"Trash",variant:"default",heading:e.jsx(l,{width:118,height:24,className:"rounded-full"}),description:e.jsx(l,{width:352,height:16,className:"rounded-full"}),children:e.jsx("div",{className:"grid gap-3",children:Array.from({length:3},(a,o)=>e.jsx(C,{padding:"compact",children:e.jsxs("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between",children:[e.jsxs("div",{className:"min-w-0 flex-1 space-y-2",children:[e.jsx(l,{width:"40%",height:18,className:"rounded-full"}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx(l,{width:168,height:14,className:"rounded-full"}),e.jsx(l,{width:176,height:14,className:"rounded-full"})]}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx(l,{width:56,height:24,className:"rounded-full"}),e.jsx(l,{width:72,height:24,className:"rounded-full"})]})]}),e.jsx(l,{width:92,height:32,className:"rounded-[12px]"})]})},o))})}),te=()=>{const{page:a}=v.useSearch(),o=v.useNavigate(),s=L(),c=$(),r=_(),[f,h]=k.useState(null),[x,b]=k.useState(null),{data:u}=I({queryKey:i.notes.trash({limit:m,offset:(a-1)*m}),queryFn:async()=>{const t=await K({limit:m,offset:(a-1)*m});if(t.type==="error")throw t;return t.trashedNotes}}),w=E({mutationFn:B,onSuccess:async t=>{if(t.type==="error")throw t;await Promise.all([s.invalidateQueries({queryKey:i.notes.all(),exact:!1}),s.invalidateQueries({queryKey:i.tags.all(),exact:!1}),s.invalidateQueries({queryKey:i.reminders.all(),exact:!1}),s.invalidateQueries({queryKey:i.images.all(),exact:!1}),s.invalidateQueries({queryKey:i.calendar.all(),exact:!1})]),c("The note has been restored."),o({to:O,params:{id:t.restoreTrashedNote.id}})},onError:()=>{c("Failed to restore the note.")}}),p=E({mutationFn:W,onSuccess:async t=>{if(t.type==="error")throw t;await s.invalidateQueries({queryKey:i.notes.trashAll(),exact:!1}),c("The note has been permanently deleted.")},onError:()=>{c("Failed to permanently delete the note.")}}),D=()=>{if(!x)return;const{id:t}=x;b(null),p.mutate(t)},q=async t=>{const n=await G(t);if(n.type==="error"){c("Failed to check linked notes before permanently deleting this note.");return}if(s.setQueryData(i.notes.backReferences(t),n.backReferences),n.backReferences.length>0){b({id:t,backReferences:n.backReferences});return}await r("Permanently delete this note? This cannot be undone.")&&p.mutate(t)},T=u.totalCount>0?`Trash (${u.totalCount})`:void 0,A=u.notes.find(t=>t.id===f)??null;return u.notes.length===0?e.jsx(N,{title:"Trash",variant:"default",heading:T,description:R,children:e.jsx(H,{title:"Trash is empty",description:`Delete a note and you can restore it here for ${S} days before permanent removal`})}):e.jsxs(N,{title:"Trash",variant:"default",heading:T,description:R,children:[e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx("div",{className:"grid gap-3",children:u.notes.map(t=>e.jsx(C,{padding:"compact",children:e.jsxs("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex min-w-0 items-start gap-1.5",children:[e.jsx(d,{as:"h2",variant:"body",weight:"medium",className:"min-w-0 flex-1 break-words leading-[1.25]",children:t.title||"Untitled note"}),t.pinned&&e.jsx("span",{title:"Pinned",className:"inline-flex h-5 w-5 shrink-0 items-center justify-center text-fg-tertiary",children:e.jsx(U,{className:"h-3.5 w-3.5"})})]}),e.jsx("div",{className:"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1",children:e.jsxs(d,{as:"div",variant:"meta",weight:"medium",tone:"secondary",children:["Deleted ",P(t.deletedAt)]})}),t.tagNames.length>0&&e.jsx("div",{className:"mt-2 flex flex-wrap gap-1.5",children:t.tagNames.map(n=>e.jsx(d,{as:"span",variant:"label",weight:"medium",tone:"tertiary",className:"rounded-full border border-border-subtle bg-hover-subtle px-2 py-0.5",children:n},`${t.id}-${n}`))}),t.contentPreview&&e.jsx("div",{className:"mt-3 rounded-[14px] border border-border-subtle bg-hover-subtle/50 px-3 py-2",children:e.jsx(d,{as:"p",variant:"meta",tone:"secondary",className:"line-clamp-3 whitespace-pre-wrap break-words",children:t.contentPreview})})]}),e.jsxs("div",{className:"flex shrink-0 flex-wrap gap-2 sm:justify-end",children:[e.jsx(j,{variant:"subtle",size:"sm",onClick:()=>h(t.id),children:"View content"}),e.jsx(j,{variant:"subtle",size:"sm",isLoading:w.isPending&&w.variables===t.id,onClick:()=>w.mutate(t.id),children:"Restore"}),e.jsx(j,{variant:"soft-danger",size:"sm",isLoading:p.isPending&&p.variables===t.id,onClick:()=>q(t.id),children:"Delete now"})]})]})},t.id))}),u.totalCount>m&&e.jsx(V,{page:a,last:Math.ceil(u.totalCount/m),onChange:t=>{o({search:n=>({...n,page:t})})}})]}),e.jsx(Z,{note:A,onClose:()=>h(null)}),e.jsx(Y,{isOpen:!!x,title:g.title,description:g.description,references:x?.backReferences??[],confirmLabel:g.confirmLabel,confirmVariant:g.confirmVariant,onClose:()=>b(null),onConfirm:D})]})},oe=()=>{const{page:a}=v.useSearch();return e.jsx(M,{fallback:e.jsx(ee,{}),errorTitle:"Failed to load trash",errorDescription:"Retry loading deleted notes",resetKeys:[a],children:e.jsx(te,{})})};export{oe as default};
@@ -1,176 +0,0 @@
1
- import { lookup as dnsLookup } from "node:dns/promises";
2
- import { isIP } from "node:net";
3
- class RemoteImageFetchError extends Error {
4
- code;
5
- status;
6
- constructor(code, status, message) {
7
- super(message);
8
- this.name = "RemoteImageFetchError";
9
- this.code = code;
10
- this.status = status;
11
- }
12
- }
13
- const DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
14
- const DEFAULT_TIMEOUT_MS = 5e3;
15
- const SUPPORTED_IMAGE_TYPES = /* @__PURE__ */ new Map([
16
- ["image/png", "png"],
17
- ["image/jpeg", "jpg"],
18
- ["image/gif", "gif"],
19
- ["image/webp", "webp"],
20
- ["image/bmp", "bmp"],
21
- ["image/avif", "avif"]
22
- ]);
23
- const createRemoteImageFetchError = (code, status, message) => {
24
- return new RemoteImageFetchError(code, status, message);
25
- };
26
- const normalizeIp = (address) => {
27
- return address.toLowerCase().replace(/^::ffff:/, "");
28
- };
29
- const isBlockedIpv4 = (address) => {
30
- const octets = address.split(".").map((segment) => Number(segment));
31
- if (octets.length !== 4 || octets.some((segment) => Number.isNaN(segment))) {
32
- return false;
33
- }
34
- if (octets[0] === 127 || octets[0] === 10) {
35
- return true;
36
- }
37
- if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
38
- return true;
39
- }
40
- if (octets[0] === 192 && octets[1] === 168) {
41
- return true;
42
- }
43
- return octets[0] === 169 && octets[1] === 254;
44
- };
45
- const isBlockedIpv6 = (address) => {
46
- if (address === "::1") {
47
- return true;
48
- }
49
- const firstSegment = address.split(":")[0];
50
- if (!firstSegment) {
51
- return false;
52
- }
53
- const firstValue = Number.parseInt(firstSegment, 16);
54
- if (Number.isNaN(firstValue)) {
55
- return false;
56
- }
57
- return (firstValue & 65024) === 64512 || (firstValue & 65472) === 65152;
58
- };
59
- const isBlockedAddress = (address) => {
60
- const normalizedAddress = normalizeIp(address);
61
- const family = isIP(normalizedAddress);
62
- if (family === 4) {
63
- return isBlockedIpv4(normalizedAddress);
64
- }
65
- if (family === 6) {
66
- return isBlockedIpv6(normalizedAddress);
67
- }
68
- return false;
69
- };
70
- const assertSafeRemoteUrl = async (src, lookupHostname) => {
71
- let remoteUrl;
72
- try {
73
- remoteUrl = new URL(src);
74
- } catch {
75
- throw createRemoteImageFetchError("INVALID_REMOTE_URL", 400, "Remote image URL is invalid.");
76
- }
77
- if (!["http:", "https:"].includes(remoteUrl.protocol)) {
78
- throw createRemoteImageFetchError("INVALID_REMOTE_URL", 400, "Remote image URL must use http or https.");
79
- }
80
- if (!remoteUrl.hostname || remoteUrl.username || remoteUrl.password) {
81
- throw createRemoteImageFetchError("INVALID_REMOTE_URL", 400, "Remote image URL is invalid.");
82
- }
83
- if (remoteUrl.port && !(remoteUrl.protocol === "http:" && remoteUrl.port === "80" || remoteUrl.protocol === "https:" && remoteUrl.port === "443")) {
84
- throw createRemoteImageFetchError("REMOTE_URL_BLOCKED", 403, "Remote image host is not allowed.");
85
- }
86
- if (remoteUrl.hostname.toLowerCase() === "localhost") {
87
- throw createRemoteImageFetchError("REMOTE_URL_BLOCKED", 403, "Remote image host is not allowed.");
88
- }
89
- if (isIP(remoteUrl.hostname)) {
90
- if (isBlockedAddress(remoteUrl.hostname)) {
91
- throw createRemoteImageFetchError("REMOTE_URL_BLOCKED", 403, "Remote image host is not allowed.");
92
- }
93
- return remoteUrl;
94
- }
95
- let addresses;
96
- try {
97
- addresses = await lookupHostname(remoteUrl.hostname);
98
- } catch {
99
- throw createRemoteImageFetchError("REMOTE_FETCH_FAILED", 502, "Remote image could not be fetched.");
100
- }
101
- if (addresses.length === 0 || addresses.some(({ address }) => isBlockedAddress(address))) {
102
- throw createRemoteImageFetchError("REMOTE_URL_BLOCKED", 403, "Remote image host is not allowed.");
103
- }
104
- return remoteUrl;
105
- };
106
- const readImageBody = async (response, maxBytes) => {
107
- if (!response.body) {
108
- throw createRemoteImageFetchError("REMOTE_FETCH_FAILED", 502, "Remote image response body was empty.");
109
- }
110
- const reader = response.body.getReader();
111
- const chunks = [];
112
- let totalBytes = 0;
113
- while (true) {
114
- const { done, value } = await reader.read();
115
- if (done) {
116
- break;
117
- }
118
- totalBytes += value.byteLength;
119
- if (totalBytes > maxBytes) {
120
- await reader.cancel();
121
- throw createRemoteImageFetchError("REMOTE_IMAGE_TOO_LARGE", 413, "Remote image exceeded the size limit.");
122
- }
123
- chunks.push(Buffer.from(value));
124
- }
125
- return Buffer.concat(chunks);
126
- };
127
- const fetchRemoteImage = async (src, options = {}) => {
128
- const fetchImpl = options.fetchImpl ?? fetch;
129
- const lookupHostname = options.lookupHostname ?? ((hostname) => {
130
- return dnsLookup(hostname, {
131
- all: true,
132
- verbatim: true
133
- });
134
- });
135
- const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
136
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
137
- const remoteUrl = await assertSafeRemoteUrl(src, lookupHostname);
138
- let response;
139
- try {
140
- response = await fetchImpl(remoteUrl, {
141
- method: "GET",
142
- redirect: "error",
143
- signal: AbortSignal.timeout(timeoutMs)
144
- });
145
- } catch (error) {
146
- if (error instanceof RemoteImageFetchError) {
147
- throw error;
148
- }
149
- if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError")) {
150
- throw createRemoteImageFetchError("REMOTE_FETCH_TIMEOUT", 504, "Remote image fetch timed out.");
151
- }
152
- throw createRemoteImageFetchError("REMOTE_FETCH_FAILED", 502, "Remote image could not be fetched.");
153
- }
154
- if (!response.ok) {
155
- throw createRemoteImageFetchError("REMOTE_FETCH_FAILED", 502, "Remote image could not be fetched.");
156
- }
157
- const contentTypeHeader = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() ?? "";
158
- const extension = SUPPORTED_IMAGE_TYPES.get(contentTypeHeader);
159
- if (!extension) {
160
- throw createRemoteImageFetchError(
161
- "REMOTE_IMAGE_UNSUPPORTED_CONTENT_TYPE",
162
- 415,
163
- "Remote image content type is not supported."
164
- );
165
- }
166
- return {
167
- buffer: await readImageBody(response, maxBytes),
168
- contentType: contentTypeHeader,
169
- extension
170
- };
171
- };
172
- export {
173
- RemoteImageFetchError,
174
- fetchRemoteImage
175
- };
176
- //# sourceMappingURL=remote-fetch.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/services/remote-fetch.ts"],"sourcesContent":["import { lookup as dnsLookup } from 'node:dns/promises';\nimport { isIP } from 'node:net';\n\nexport interface RemoteImage {\n buffer: Buffer;\n contentType: string;\n extension: string;\n}\n\nexport interface RemoteImageFetchOptions {\n fetchImpl?: typeof fetch;\n lookupHostname?: (hostname: string) => Promise<Array<{ address: string }>>;\n maxBytes?: number;\n timeoutMs?: number;\n}\n\nexport class RemoteImageFetchError extends Error {\n public readonly code: string;\n public readonly status: number;\n\n constructor(code: string, status: number, message: string) {\n super(message);\n this.name = 'RemoteImageFetchError';\n this.code = code;\n this.status = status;\n }\n}\n\nconst DEFAULT_MAX_BYTES = 10 * 1024 * 1024;\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst SUPPORTED_IMAGE_TYPES = new Map([\n ['image/png', 'png'],\n ['image/jpeg', 'jpg'],\n ['image/gif', 'gif'],\n ['image/webp', 'webp'],\n ['image/bmp', 'bmp'],\n ['image/avif', 'avif'],\n]);\n\nconst createRemoteImageFetchError = (code: string, status: number, message: string) => {\n return new RemoteImageFetchError(code, status, message);\n};\n\nconst normalizeIp = (address: string) => {\n return address.toLowerCase().replace(/^::ffff:/, '');\n};\n\nconst isBlockedIpv4 = (address: string) => {\n const octets = address.split('.').map((segment) => Number(segment));\n\n if (octets.length !== 4 || octets.some((segment) => Number.isNaN(segment))) {\n return false;\n }\n\n if (octets[0] === 127 || octets[0] === 10) {\n return true;\n }\n\n if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {\n return true;\n }\n\n if (octets[0] === 192 && octets[1] === 168) {\n return true;\n }\n\n return octets[0] === 169 && octets[1] === 254;\n};\n\nconst isBlockedIpv6 = (address: string) => {\n if (address === '::1') {\n return true;\n }\n\n const firstSegment = address.split(':')[0];\n\n if (!firstSegment) {\n return false;\n }\n\n const firstValue = Number.parseInt(firstSegment, 16);\n\n if (Number.isNaN(firstValue)) {\n return false;\n }\n\n return (firstValue & 0xfe00) === 0xfc00 || (firstValue & 0xffc0) === 0xfe80;\n};\n\nconst isBlockedAddress = (address: string) => {\n const normalizedAddress = normalizeIp(address);\n const family = isIP(normalizedAddress);\n\n if (family === 4) {\n return isBlockedIpv4(normalizedAddress);\n }\n\n if (family === 6) {\n return isBlockedIpv6(normalizedAddress);\n }\n\n return false;\n};\n\nconst assertSafeRemoteUrl = async (\n src: string,\n lookupHostname: NonNullable<RemoteImageFetchOptions['lookupHostname']>,\n) => {\n let remoteUrl: URL;\n\n try {\n remoteUrl = new URL(src);\n } catch {\n throw createRemoteImageFetchError('INVALID_REMOTE_URL', 400, 'Remote image URL is invalid.');\n }\n\n if (!['http:', 'https:'].includes(remoteUrl.protocol)) {\n throw createRemoteImageFetchError('INVALID_REMOTE_URL', 400, 'Remote image URL must use http or https.');\n }\n\n if (!remoteUrl.hostname || remoteUrl.username || remoteUrl.password) {\n throw createRemoteImageFetchError('INVALID_REMOTE_URL', 400, 'Remote image URL is invalid.');\n }\n\n if (\n remoteUrl.port &&\n !(\n (remoteUrl.protocol === 'http:' && remoteUrl.port === '80') ||\n (remoteUrl.protocol === 'https:' && remoteUrl.port === '443')\n )\n ) {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n if (remoteUrl.hostname.toLowerCase() === 'localhost') {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n if (isIP(remoteUrl.hostname)) {\n if (isBlockedAddress(remoteUrl.hostname)) {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n return remoteUrl;\n }\n\n let addresses: Array<{ address: string }>;\n\n try {\n addresses = await lookupHostname(remoteUrl.hostname);\n } catch {\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image could not be fetched.');\n }\n\n if (addresses.length === 0 || addresses.some(({ address }) => isBlockedAddress(address))) {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n return remoteUrl;\n};\n\nconst readImageBody = async (response: Response, maxBytes: number) => {\n if (!response.body) {\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image response body was empty.');\n }\n\n const reader = response.body.getReader();\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n totalBytes += value.byteLength;\n\n if (totalBytes > maxBytes) {\n await reader.cancel();\n throw createRemoteImageFetchError('REMOTE_IMAGE_TOO_LARGE', 413, 'Remote image exceeded the size limit.');\n }\n\n chunks.push(Buffer.from(value));\n }\n\n return Buffer.concat(chunks);\n};\n\nexport const fetchRemoteImage = async (src: string, options: RemoteImageFetchOptions = {}): Promise<RemoteImage> => {\n const fetchImpl = options.fetchImpl ?? fetch;\n const lookupHostname =\n options.lookupHostname ??\n ((hostname: string) => {\n return dnsLookup(hostname, {\n all: true,\n verbatim: true,\n });\n });\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const remoteUrl = await assertSafeRemoteUrl(src, lookupHostname);\n\n let response: Response;\n\n try {\n response = await fetchImpl(remoteUrl, {\n method: 'GET',\n redirect: 'error',\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (error) {\n if (error instanceof RemoteImageFetchError) {\n throw error;\n }\n\n if (error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError')) {\n throw createRemoteImageFetchError('REMOTE_FETCH_TIMEOUT', 504, 'Remote image fetch timed out.');\n }\n\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image could not be fetched.');\n }\n\n if (!response.ok) {\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image could not be fetched.');\n }\n\n const contentTypeHeader = response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() ?? '';\n const extension = SUPPORTED_IMAGE_TYPES.get(contentTypeHeader);\n\n if (!extension) {\n throw createRemoteImageFetchError(\n 'REMOTE_IMAGE_UNSUPPORTED_CONTENT_TYPE',\n 415,\n 'Remote image content type is not supported.',\n );\n }\n\n return {\n buffer: await readImageBody(response, maxBytes),\n contentType: contentTypeHeader,\n extension,\n };\n};\n"],"mappings":"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","names":[]}