six-degrees 0.4.10 → 0.4.11
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/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/routes-manifest.json +6 -0
- 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/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +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 +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/social/crm/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/social/crm/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js +10 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/crm/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js +4 -3
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js +4 -3
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +15 -15
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +14 -14
- 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/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__06mi9k2._.js → [root-of-the-server]__05_3-22._.js} +7 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +8 -0
- package/.next/standalone/.next/server/chunks/_0ea9qwy._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +4 -4
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1oqfuh8._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1xh07bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_crm_route_actions_0d1aflv.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +7 -7
- package/.next/standalone/.next/server/chunks/lib_linkedin-export_12imqxg.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_05gt5ya.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0-85h6g._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08cfj2b._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +2 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/15sjx07xhpn6p.js +1 -0
- package/.next/standalone/.next/static/chunks/1a74dky22dxd6.js +1 -0
- package/.next/standalone/.next/static/chunks/{1xptlvtcz6654.css → 1x_ysly42-teq.css} +1 -1
- package/.next/standalone/.next/static/chunks/297e1z34yi--z.js +1 -0
- package/.next/standalone/.next/static/chunks/2_ak6qjxb6oa5.js +1 -0
- package/.next/standalone/.next/static/chunks/3ui4d-uqkqj5d.js +2 -0
- package/.next/standalone/CHANGELOG.md +48 -0
- package/.next/standalone/app/api/data/export/route.js +2 -1
- package/.next/standalone/app/api/scraper/route.js +5 -1
- package/.next/standalone/app/api/social/crm/route.js +37 -0
- package/.next/standalone/app/api/social/messages/route.js +44 -8
- package/.next/standalone/app/api/social/route.js +80 -22
- package/.next/standalone/app/components/ScanStatusBar.js +1 -0
- package/.next/standalone/app/components/settings/DataSection.js +12 -3
- package/.next/standalone/app/globals.css +11 -0
- package/.next/standalone/app/social/Crm.js +683 -0
- package/.next/standalone/app/social/Thread.js +90 -0
- package/.next/standalone/app/social/page.js +71 -60
- package/.next/standalone/lib/data-export.js +10 -4
- package/.next/standalone/lib/data-folder.js +9 -0
- package/.next/standalone/lib/data-import.js +13 -3
- package/.next/standalone/lib/linkedin-export.js +267 -46
- package/.next/standalone/lib/social-crm.js +415 -0
- package/.next/standalone/lib/social-store.js +13 -6
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +126 -22
- package/.next/static/chunks/15sjx07xhpn6p.js +1 -0
- package/.next/static/chunks/1a74dky22dxd6.js +1 -0
- package/.next/static/chunks/{1xptlvtcz6654.css → 1x_ysly42-teq.css} +1 -1
- package/.next/static/chunks/297e1z34yi--z.js +1 -0
- package/.next/static/chunks/2_ak6qjxb6oa5.js +1 -0
- package/.next/static/chunks/3ui4d-uqkqj5d.js +2 -0
- package/CHANGELOG.md +48 -0
- package/lib/data-export.js +10 -4
- package/lib/data-folder.js +9 -0
- package/lib/data-import.js +13 -3
- package/lib/linkedin-export.js +267 -46
- package/lib/social-crm.js +415 -0
- package/lib/social-store.js +13 -6
- package/package.json +1 -1
- package/scripts/scrape.py +126 -22
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08f5awg._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_18cr1gi._.js +0 -8
- package/.next/standalone/.next/server/chunks/ssr/_0ezj4e0._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1errbuf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +0 -3
- package/.next/standalone/.next/static/chunks/07dyt47mp4s9-.js +0 -1
- package/.next/standalone/.next/static/chunks/206rm6j42gl_a.js +0 -1
- package/.next/standalone/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
- package/.next/standalone/.next/static/chunks/2pw6_osz1n_te.js +0 -1
- package/.next/standalone/app/social/Conversations.js +0 -246
- package/.next/static/chunks/07dyt47mp4s9-.js +0 -1
- package/.next/static/chunks/206rm6j42gl_a.js +0 -1
- package/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
- package/.next/static/chunks/2pw6_osz1n_te.js +0 -1
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → jqE2D3zWqSGz5su948Azf}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → jqE2D3zWqSGz5su948Azf}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → jqE2D3zWqSGz5su948Azf}/_ssgManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → jqE2D3zWqSGz5su948Azf}/_buildManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → jqE2D3zWqSGz5su948Azf}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → jqE2D3zWqSGz5su948Azf}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,60451,e=>{"use strict";var t=e.i(43476),n=e.i(71645),o=e.i(22016),s=e.i(2776),r=e.i(15812),a=e.i(19821),i=e.i(84278),l=e.i(39160),c=e.i(7214),d=e.i(40996),p=e.i(8819),u=e.i(64745);let h=Object.freeze([{id:"new",label:"New"},{id:"contacted",label:"Contacted"},{id:"replied",label:"Replied"},{id:"meeting",label:"Meeting"},{id:"won",label:"Won/Partner"},{id:"not-now",label:"Not now"}]);h.map(e=>e.id);let g=e=>h.find(t=>t.id===e)?.label||"",f=e=>"string"==typeof e&&e.length>0&&e.length<300;function m(){return{version:1,settings:{awaitDays:7},people:{}}}function y(e=new Date){let t=e=>String(e).padStart(2,"0");return`${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}`}let x=/^https:\/\/(www\.)?linkedin\.com\/in\/[^/?#\s]+$/,w={S:0,A:1,B:2,C:3,D:4},b=(e,t)=>(e.degree??9)<(t.degree??9)||(e.degree??9)===(t.degree??9)&&(w[e.tier]??9)<(w[t.tier]??9),v=e=>"sponsored"===e.kind;function j(e,t,n=7){let o=[];for(let s of e){if(v(s)||!1!==s.lastFromThem||!s.last)continue;let e=Math.floor((t-s.last)/864e5);e>=n&&o.push({contact:s,waited:e})}return o.sort((e,t)=>e.waited-t.waited)}function k(e,t,n=null){let o=String(t||"").trim().toLowerCase();return!o||[e.name,e.row?.company,e.row?.role,e.row?.headline,...e.crm?.tags||[],e.crm?.notes].filter(Boolean).join("\n").toLowerCase().includes(o)||!!n&&e.conversations.some(e=>n.has(e.id))}function S(e){let t=null==e?"":String(e);return/^[=+\-@\t\r]/.test(t)&&(t=`'${t}`),/[",\r\n]/.test(t)?`"${t.replace(/"/g,'""')}"`:t}let T=e=>e?new Date(e).toISOString().slice(0,10):"",C=["Name","Company","Profile","Connection","Stage","Tags","Last contact","Who wrote last","Next follow-up","Notes"];function z({id:e,keep:o,group:s,token:r}){let[a,i]=(0,n.useState)(null),[l,c]=(0,n.useState)(null),d=(0,n.useRef)(null),u=(0,n.useRef)(null);async function h(){let t=d.current,n=await fetch(`/api/social/messages?id=${encodeURIComponent(e)}&before=${a.start}&limit=200`).then(e=>e.json()).catch(()=>null);n&&!n.error&&(u.current=t?t.scrollHeight-t.scrollTop:null,i({...a,start:n.start,messages:[...n.messages,...a.messages]}))}(0,n.useEffect)(()=>{if(!o)return;let t=!0;return fetch(`/api/social/messages?id=${encodeURIComponent(e)}&limit=100`).then(e=>e.json()).then(e=>{t&&(e.error?c(e.error):(u.current="bottom",i(e)))}).catch(()=>{t&&c("It couldn’t be read.")}),()=>{t=!1}},[e,o,r]),(0,n.useLayoutEffect)(()=>{let e=d.current;e&&u.current&&("bottom"===u.current?e.scrollTop=e.scrollHeight:e.scrollTop=e.scrollHeight-u.current,u.current=null)},[a]);let g={padding:"10px 14px 14px",fontSize:12.5,color:"#8b9a9a"};return o?l?(0,t.jsx)("div",{style:{...g,color:"#ff9b9b"},children:l}):a?a.total?(0,t.jsxs)("div",{ref:d,style:{maxHeight:440,overflowY:"auto",padding:"8px 14px 12px",background:"rgba(0,0,0,0.2)"},children:[a.start>0&&(0,t.jsx)("div",{style:{textAlign:"center",margin:"4px 0 10px"},children:(0,t.jsxs)("button",{onClick:h,style:{background:"rgba(255,255,255,0.06)",border:p.LINE,borderRadius:6,color:"#cfe6f7",cursor:"pointer",fontSize:12,padding:"4px 12px"},children:["Show earlier (",a.start.toLocaleString()," more)"]})}),a.messages.map((e,n)=>{let o;return(0,t.jsx)("div",{style:{display:"flex",justifyContent:e.fromMe?"flex-end":"flex-start",margin:"6px 0"},children:(0,t.jsxs)("div",{style:{maxWidth:"72%"},children:[(0,t.jsx)("div",{style:{padding:"7px 11px",borderRadius:12,fontSize:13,lineHeight:1.45,whiteSpace:"pre-wrap",overflowWrap:"anywhere",background:e.fromMe?"rgba(52,152,219,0.35)":"rgba(255,255,255,0.08)",color:"#eef",borderBottomRightRadius:e.fromMe?4:12,borderBottomLeftRadius:e.fromMe?12:4},children:e.text||(0,t.jsx)("i",{style:{color:"#8b9a9a"},children:"(no words: an attachment or a reaction)"})}),(0,t.jsxs)("div",{style:{fontSize:10.5,color:"#778",marginTop:2,textAlign:e.fromMe?"right":"left"},children:[e.fromMe?"You":s?"Someone in the group":"Them"," · ",(o=e.t)?new Date(o).toLocaleString(void 0,{day:"numeric",month:"short",year:"numeric",hour:"numeric",minute:"2-digit"}):""]})]})},a.start+n)})]}):(0,t.jsx)("div",{style:g,children:"No messages are kept for this conversation yet. Choose your export folder again, or sync your messages, to bring them in."}):(0,t.jsx)("div",{style:g,children:"Opening…"}):(0,t.jsxs)("div",{style:g,children:["Turn on ",(0,t.jsx)("b",{children:"Keep my messages"})," to read them here."]})}let I={S:"#FFD700",A:"#9B59B6",B:"#3498DB",C:"#95A5A6",D:"#BDC3C7"},D={new:"#8b9a9a",contacted:"#3498DB",replied:"#00d4aa",meeting:"#ff9f43",won:"#FFD700","not-now":"#667"},$=e=>e?new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"}):"",F=e=>e?new Date(`${e}T12:00:00`).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"}):"",L=e=>/^https:\/\/(www\.)?linkedin\.com\//.test(String(e||""))?e:null,N=(e,t,n=`${t}s`)=>`${e.toLocaleString()} ${1===e?t:n}`,O=[["inbox","Inbox"],["awaiting","Awaiting reply"],["due","Follow-ups due"],["pipeline","Pipeline"],["sent","Sent"],["received","Received"],["all","All"]],R=(e,t="#dfe8e8")=>({fontSize:10.5,padding:"1px 6px",borderRadius:4,background:e,color:t,whiteSpace:"nowrap"}),E={background:"rgba(255,255,255,0.06)",border:p.LINE,borderRadius:6,color:"#cfe6f7",cursor:"pointer",fontSize:12,padding:"4px 10px"},M={padding:"6px 10px",borderRadius:6,border:p.LINE,background:"rgba(0,0,0,0.3)",color:"#fff",fontSize:13};function B({net:e,asOf:o,keep:s,onKeep:r,switching:a,token:l,crmToken:d}){let[u,w]=(0,n.useState)(null),[z,I]=(0,n.useState)(null),[D,L]=(0,n.useState)("inbox"),[R,U]=(0,n.useState)(""),[A,Y]=(0,n.useState)(null),[_,K]=(0,n.useState)(null),[G,V]=(0,n.useState)(100),[Q,X]=(0,n.useState)(null),Z=(0,n.useRef)(null);(0,n.useEffect)(()=>{let e=!0;return fetch("/api/social/messages").then(e=>e.json()).then(t=>{e&&w(t)}).catch(()=>{e&&w({conversations:[],invitations:[],names:{},inviteNotes:{}})}),()=>{e=!1}},[l,s]),(0,n.useEffect)(()=>{let e=!0;return fetch("/api/social/crm").then(e=>e.json()).then(t=>{e&&I(t.crm||m())}).catch(()=>{e&&I(m())}),()=>{e=!1}},[d]),(0,n.useEffect)(()=>{let e=R.trim();if(!s||e.length<2)return;let t=!0,n=setTimeout(()=>{fetch(`/api/social/messages?q=${encodeURIComponent(e)}`).then(e=>e.json()).then(n=>{t&&Y({q:e,ids:new Set(n.ids||[])})}).catch(()=>{})},250);return()=>{t=!1,clearTimeout(n)}},[R,s,l]);let ee=_?.type==="person"?_.key:null,{contacts:et,groups:en}=(0,n.useMemo)(()=>(function({conversations:e=[],invitations:t=[],network:n={},crm:o=m(),names:s={},also:r=[]}={}){let a=n.degree1||[],l=n.degree2||[],d=[...a,...l,...n.degree3||[]],p=new Map,u=new Map;for(let e of d){let t=(0,c.keyFor)(e),n=p.get(t);(!n||b(e,n))&&p.set(t,e)}for(let[e,t]of p){let n=(0,i.nameKey)(t.name);n&&u.set(n,u.has(n)&&u.get(n)!==e?null:e)}let h=new Set(a.map(c.keyFor)),g=(0,c.routeIndex)(l),y=new Map(a.map(e=>[e.id,e])),w=e=>e.startsWith("name:")&&u.get(e.slice(5))||e,v=new Map,j=e=>{var t,n,r;let a=w(e),i=v.get(a);if(i)return i;let l=p.get(a)||null;return i={key:a,name:(t=a,n=l,r=s,n?.name?n.name:r[t]?r[t]:t.startsWith("name:")?t.slice(5).replace(/\b\w/g,e=>e.toUpperCase()):String(t||"").replace(/\/+$/,"").split("/").pop()),row:l,connection:h.has(a),degree:l?.degree??null,profileUrl:x.test(a)?a:l?.profile_url&&x.test((0,c.keyFor)(l))?(0,c.keyFor)(l):null,introducers:l&&2===l.degree?[...new Set((0,c.routesFor)(l,g,y).map(e=>e.bridge?.name).filter(Boolean))]:[],conversations:[],groups:[],last:null,lastFromThem:null,unread:null,threadUrl:null,count:0,theyWrote:!1,kind:null,invitesIn:[],invitesOut:[],request:null,crm:o?.people?.[a]||null,lastContact:null},v.set(a,i),i},k=[];for(let t of e){if(!t||!Array.isArray(t.people))continue;if(t.group){k.push({...t,people:t.people.map(w)});continue}if(!t.people.length){k.push({...t,unknown:!0});continue}let e=j(t.people[0]);e.conversations.push(t),e.count+=t.count||0,(!0===t.lastFromThem||null!=t.count&&null!=t.mine&&t.count>t.mine)&&(e.theyWrote=!0),t.last&&(!e.last||t.last>=e.last)?(e.last=t.last,e.lastFromThem=t.lastFromThem,e.unread=t.unread,e.threadUrl=t.threadUrl||e.threadUrl):e.last||t.last||(e.threadUrl=t.threadUrl||e.threadUrl)}for(let e of v.values()){e.conversations.sort((e,t)=>(t.last||0)-(e.last||0));let t=new Set(e.conversations.map(e=>e.kind||null));e.kind=1===t.size?[...t][0]:null}for(let e of k)for(let t of e.people)v.get(t)?.groups.push(e.id);for(let e of t){if(!e?.key)continue;let t=j(e.key);!e.name||t.row||s[t.key]||(t.name=e.name),("in"===e.dir?t.invitesIn:t.invitesOut).push(e)}for(let e of d){if("sent"!==e.outreach_status&&"accepted"!==e.outreach_status&&"pending"!==e.unlock_status)continue;let t=j((0,c.keyFor)(e)),n=t.connection||"accepted"===e.outreach_status?"accepted":"pending";t.request&&"accepted"!==n||(t.request={status:n})}for(let e of Object.keys(o?.people||{}))j(e);for(let e of r)f(e)&&j(e);for(let e of v.values()){let t=[e.last,...e.invitesIn.map(e=>e.t),...e.invitesOut.map(e=>e.t)].filter(Boolean);e.lastContact=t.length?Math.max(...t):null}return{contacts:v,groups:k}})({conversations:u?.conversations||[],invitations:u?.invitations||[],network:e||{},crm:z||m(),names:u?.names||{},also:ee?[ee]:[]}),[u,e,z,ee]),eo=(0,n.useMemo)(()=>{let t=new Map;for(let n of[...e?.degree1||[],...e?.degree2||[]])t.has((0,c.keyFor)(n))||t.set((0,c.keyFor)(n),n);let n=u?.names||{};return e=>et.get(e)?.name||t.get(e)?.name||n[e]||null},[et,e,u]),es=z?.settings?.awaitDays||7,[er]=(0,n.useState)(()=>Date.now()),ea=o||er,ei=y(new Date(er)),el=(0,n.useMemo)(()=>[...et.values()],[et]),ec=(0,n.useMemo)(()=>{let e=(e,t)=>({type:"person",key:e.key,c:e,sub:t,t:e.lastContact||0}),t=e=>null==e.lastFromThem?"":e.lastFromThem?`They wrote ${$(e.last)}`:`You wrote ${$(e.last)}`,n=el.filter(e=>e.lastContact||e.crm||e.request).map(n=>e(n,t(n)||(n.invitesOut[0]?`You asked to connect ${$(n.invitesOut[0].t)}`:"")||(n.invitesIn[0]?`Asked to connect ${$(n.invitesIn[0].t)}`:"")||(n.request?`Tracked request \xb7 ${n.request.status}`:"")||(n.crm?.stage?g(n.crm.stage):"")));for(let e of en)n.push({type:"group",id:e.id,g:e,sub:e.last?`${e.lastFromThem?"Someone wrote":"You wrote"} ${$(e.last)}`:"",t:e.last||0});return n.sort((e,t)=>t.t-e.t),{inbox:[...el].filter(e=>!v(e)&&(!0===e.lastFromThem||e.unread>0)).sort((e,t)=>(t.unread>0)-(e.unread>0)||(t.last||0)-(e.last||0)).map(n=>e(n,n.unread>0?`${n.unread} unread \xb7 ${t(n)}`:t(n))),awaiting:j(el,ea,es).map(t=>e(t.contact,`You wrote ${$(t.contact.last)}, no reply for ${N(t.waited,"day")}`)),due:(function(e,t=y()){return[...e].filter(e=>e.crm?.followUp&&e.crm.followUp<=t).sort((e,t)=>e.crm.followUp.localeCompare(t.crm.followUp))})(el,ei).map(t=>e(t,t.crm.followUp===ei?"Follow up today":`Follow up was due ${F(t.crm.followUp)}`)),pipeline:function(e){let t=Object.fromEntries(h.map(e=>[e.id,[]]));for(let n of e)n.crm?.stage&&t[n.crm.stage].push(n);for(let e of Object.values(t))e.sort((e,t)=>(t.lastContact||0)-(e.lastContact||0));return t}(el),sent:(function(e,t,n=7){let o=[...e],s=new Map(j(o,t,n).map(e=>[e.contact.key,e.waited])),r=[];for(let e of o){let t=[];for(let n of e.invitesOut)t.push({type:"invite",id:n.id,t:n.t,status:e.connection?"accepted":"pending"});e.request&&t.push({type:"request",status:e.request.status}),s.has(e.key)&&t.push({type:"awaiting",t:e.last,waited:s.get(e.key)}),t.length&&r.push({contact:e,items:t,t:Math.max(0,...t.map(e=>e.t||0))})}return r.sort((e,t)=>t.t-e.t)})(el,ea,es).map(t=>e(t.contact,t.items.map(e=>"invite"===e.type?`Request ${$(e.t)} \xb7 ${e.status}`:"request"===e.type?`Tracked request \xb7 ${e.status}`:`No reply for ${N(e.waited,"day")}`).join(" · "))),received:(function(e){let t=[];for(let n of e)!n.connection&&n.invitesIn.length&&t.push({contact:n,t:Math.max(0,...n.invitesIn.map(e=>e.t||0))});return t.sort((e,t)=>t.t-e.t)})(el).map(t=>e(t.contact,`Asked to connect ${$(t.t)}`)),all:n}},[el,en,ea,es,ei]),ed=s&&A?.q===R.trim()?A.ids:null,ep=R.trim().toLowerCase(),eu=(0,n.useMemo)(()=>{let t="pipeline"===D?[]:ec[D];if(!ep)return t;let n=t.filter(e=>"person"===e.type?k(e.c,ep,ed):String(e.g.title||"").toLowerCase().includes(ep)||e.g.people.some(e=>String(eo(e)||"").toLowerCase().includes(ep))||ed?.has(e.g.id));if("all"===D&&ep.length>=2&&e){let t=new Set(n.map(e=>e.key)),o=new Set;for(let s of[...e.degree1||[],...e.degree2||[]]){let e=(0,c.keyFor)(s);if(!(t.has(e)||o.has(e))&&[s.name,s.company,s.role,s.headline].some(e=>String(e||"").toLowerCase().includes(ep))&&(o.add(e),n.push({type:"person",key:e,c:null,row:s,sub:"Nothing on file yet",t:0}),o.size>=50))break}}return n},[ec,D,ep,ed,e,eo]),eh=(0,n.useMemo)(()=>{if("pipeline"!==D)return null;let e={};for(let[t,n]of Object.entries(ec.pipeline))e[t]=ep?n.filter(e=>k(e,ep,ed)):n;return e},[ec,D,ep,ed]),eg={inbox:ec.inbox.length,awaiting:ec.awaiting.length,due:ec.due.length,pipeline:Object.values(ec.pipeline).reduce((e,t)=>e+t.length,0),sent:ec.sent.length,received:ec.received.length,all:ec.all.length};function ef(e){K(e),window.matchMedia?.("(max-width: 760px)").matches&&setTimeout(()=>Z.current?.scrollIntoView({behavior:"smooth",block:"start"}),50)}async function em(e,t){let n=await fetch("/api/social/crm",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:e,set:t})}),o=await n.json().catch(()=>({}));if(!n.ok)throw Error(o.error||"That couldn’t be saved.");I(t=>{let n={...t||m(),people:{...t?.people||{}}};return o.entry?n.people[e]=o.entry:delete n.people[e],n})}async function ey(e){let t=await fetch("/api/social/crm",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({settings:{awaitDays:e}})}),n=await t.json().catch(()=>({}));t.ok&&I(e=>({...e||m(),settings:n.settings}))}async function ex(){window.confirm("Delete your CRM: every stage, tag, note and follow-up date you added? This can’t be undone.")&&(await fetch("/api/social/crm",{method:"DELETE"}),I(m()),X("Your CRM notes are deleted."))}let ew=!u||!z||!e,eb={padding:"12px 14px",borderRadius:8,border:p.LINE,background:"rgba(255,255,255,0.03)",marginTop:10},ev=_?.type==="group"?en.find(e=>e.id===_.id):null,ej=ee?et.get(ee):null;return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("div",{style:eb,children:[(0,t.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"center",fontSize:13.5,color:"#fff",cursor:a?"default":"pointer"},children:[(0,t.jsx)("input",{type:"checkbox",checked:s,disabled:a,onChange:e=>r(e.target.checked)}),(0,t.jsx)("b",{children:"Keep my messages on this computer"}),(0,t.jsx)("span",{style:{fontSize:11.5,color:s?"#00ff88":"#8b9a9a"},children:s?"on":"off"})]}),(0,t.jsxs)(p.Body,{style:{margin:"6px 0 0",fontSize:12.5},children:["Off unless you turn it on. When it’s on, the messages themselves, including what other people wrote to you, and the notes sent with connection requests, are saved only in the app’s data folder on this computer. They’re never sent anywhere, and they aren’t in “Save a copy of my network”. ",(0,t.jsx)("i",{children:"Forget it"}),", or switching this off, deletes them. With it off, the CRM still shows who, when and who wrote last. Your own stages, tags, notes and follow-ups are always kept, on this computer only."]})]}),(0,t.jsxs)("div",{className:"crm-grid",style:{marginTop:10},children:[(0,t.jsxs)("div",{className:"pipeline"===D?"crm-wide":void 0,style:{...eb,marginTop:0,padding:0,minWidth:0},children:[(0,t.jsx)("div",{role:"tablist","aria-label":"CRM views",style:{display:"flex",flexWrap:"wrap",gap:4,padding:"10px 10px 6px",borderBottom:p.LINE},children:O.map(([e,n])=>(0,t.jsxs)("button",{role:"tab","aria-selected":D===e,onClick:()=>{L(e),V(100)},style:{...E,padding:"4px 9px",fontSize:12,background:D===e?"rgba(52,152,219,0.28)":"rgba(255,255,255,0.04)",color:D===e?"#fff":"#b8c4c4"},children:[n," ",(0,t.jsx)("span",{style:{color:D===e?"#cfe6f7":"#778"},children:ew?"":eg[e].toLocaleString()})]},e))}),(0,t.jsxs)("div",{style:{padding:"8px 10px",borderBottom:p.LINE,display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:[(0,t.jsx)("input",{type:"search",value:R,onChange:e=>{U(e.target.value),V(100)},placeholder:s?"Search names, companies, tags, notes, what was said":"Search names, companies, tags, notes","aria-label":"Search the CRM",style:{...M,flex:"1 1 180px",minWidth:0}}),(0,t.jsx)("button",{onClick:function(){var e;let t,n,o=el.filter(e=>"sponsored"!==e.kind).sort((e,t)=>(t.lastContact||0)-(e.lastContact||0)),s=`Six Degrees CRM ${y()}.csv`;e=function(e){let t=[C.map(S).join(",")];for(let n of e)t.push([n.name,n.row?.company||"",n.profileUrl||"",n.connection?"Yes":"No",g(n.crm?.stage),(n.crm?.tags||[]).join("; "),T(n.lastContact),null==n.lastFromThem?"":n.lastFromThem?"Them":"You",n.crm?.followUp||"",n.crm?.notes||""].map(S).join(","));return`\uFEFF${t.join("\r\n")}\r
|
|
2
|
+
`}(o),t=URL.createObjectURL(new Blob([e],{type:"text/csv;charset=utf-8"})),(n=document.createElement("a")).href=t,n.download=s,n.style.position="fixed",n.style.left="-9999px",document.body.appendChild(n),n.click(),n.remove(),setTimeout(()=>URL.revokeObjectURL(t),6e4),X(`Made “${s}” (${N(o.length,"person","people")}). Look for it in your Downloads folder, or wherever you chose to save it. It holds your notes: keep it private.`)},disabled:ew,style:E,title:"A spreadsheet of everyone here, with your stages, tags, follow-ups and notes",children:"Export CSV"})]}),"awaiting"===D&&(0,t.jsxs)("div",{style:{padding:"8px 12px",borderBottom:p.LINE,fontSize:12.5,color:"#b8c4c4",display:"flex",gap:6,alignItems:"center",flexWrap:"wrap"},children:["You wrote last and nothing came back for",(0,t.jsx)("input",{type:"number",min:1,max:365,value:es,"aria-label":"Days with no reply",onChange:e=>{let t=Number(e.target.value);t>=1&&t<=365&&ey(t)},style:{...M,width:64,padding:"3px 6px"}}),"days or more",o?`, as of ${$(o)}`:"","."]}),Q&&(0,t.jsx)("div",{style:{padding:"8px 12px",fontSize:12.5,color:"#00ff88",borderBottom:p.LINE},children:Q}),ew&&(0,t.jsx)("div",{style:{padding:12,fontSize:13,color:"#8b9a9a"},children:"Loading…"}),!ew&&"pipeline"===D&&(0,t.jsx)(P,{pipeline:eh,picked:ee,onPick:e=>ef({type:"person",key:e})}),!ew&&"pipeline"!==D&&0===eu.length&&(0,t.jsx)("div",{style:{padding:12,fontSize:13,color:"#8b9a9a"},children:ep?"Nothing matches that.":W[D]}),!ew&&"pipeline"!==D&&(0,t.jsxs)("div",{children:[eu.slice(0,G).map(e=>(0,t.jsx)(q,{r:e,nameOf:eo,active:"person"===e.type?ee===e.key:_?.id===e.id,onPick:()=>ef("person"===e.type?{type:"person",key:e.key}:{type:"group",id:e.id})},"person"===e.type?`p:${e.key}`:`g:${e.id}`)),eu.length>G&&(0,t.jsxs)("button",{onClick:()=>V(G+100),style:{width:"100%",padding:10,background:"none",border:"none",color:"#3498DB",cursor:"pointer",fontSize:13},children:["Show ",Math.min(100,eu.length-G).toLocaleString()," more of ",eu.length.toLocaleString()]})]})]}),(0,t.jsxs)("div",{ref:Z,className:"pipeline"===D?"crm-wide":void 0,style:{...eb,marginTop:0,minWidth:0,scrollMarginTop:80},children:[!_&&(0,t.jsx)("div",{style:{fontSize:13,color:"#8b9a9a"},children:"Pick someone to see everything with them, and to add a stage, tags, notes or a follow-up."}),ej&&(0,t.jsx)(H,{c:ej,nameOf:eo,groups:en,keep:s,token:l,inviteNotes:u?.inviteNotes||{},onChange:em,onPickGroup:e=>ef({type:"group",id:e})},ej.key),ev&&(0,t.jsx)(J,{g:ev,contacts:et,nameOf:eo,keep:s,token:l,onPick:e=>ef({type:"person",key:e})})]})]}),(0,t.jsxs)("div",{style:{fontSize:11.5,color:"#778",marginTop:6},children:["Everyone from your export and the live sync, connections or not; groups are listed and marked. Sponsored messages are marked and kept out of the Inbox. Awaiting reply and warmth are judged as of the newest thing your data knows.",Object.keys(z?.people||{}).length>0&&(0,t.jsxs)(t.Fragment,{children:[" ","Your notes are in the app’s data folder, not in “Save a copy of my network”."," ",(0,t.jsx)("button",{onClick:ex,style:{background:"none",border:"none",color:"#ff9b9b",cursor:"pointer",fontSize:11.5,padding:0},children:"Delete my CRM notes"})]})]})]})}let W={inbox:"No one is waiting on a reply.",awaiting:"Nobody you wrote to is overdue a reply.",due:"No follow-ups are due. Set one on anyone, and it shows here on the day.",sent:"Nothing sent yet: choose your export folder above (its Invitations.csv), or mark requests sent in the app.",received:"No requests waiting on you, as far as your export shows.",all:"Nobody here yet. Choose your export folder above, or sync your messages."};function U({tier:e}){return(0,t.jsx)("span",{style:{width:8,height:8,borderRadius:"50%",background:I[e]||"#556",flexShrink:0,display:"inline-block"}})}function A({c:e}){return(0,t.jsxs)(t.Fragment,{children:[e&&!e.connection&&(0,t.jsx)("span",{style:R("rgba(255,159,67,0.18)","#ffc58a"),children:2===e.degree?"2nd degree":"Not a connection"}),e?.kind==="sponsored"&&(0,t.jsx)("span",{style:R("rgba(255,255,255,0.08)"),children:"Sponsored"}),e?.kind==="inmail"&&(0,t.jsx)("span",{style:R("rgba(155,89,182,0.25)"),children:"InMail"}),e?.crm?.stage&&(0,t.jsx)("span",{style:R("rgba(255,255,255,0.06)",D[e.crm.stage]),children:g(e.crm.stage)})]})}function q({r:e,nameOf:n,active:o,onPick:s}){let r,a=null,i=null;if("person"===e.type){let n=e.c;r=n?.name||e.row?.name||e.key,a=n?.row?.tier||e.row?.tier,i=(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(A,{c:n||{connection:e.row?.degree===1,degree:e.row?.degree}}),n?.unread>0&&(0,t.jsxs)("span",{style:R("#3498DB","#fff"),children:[n.unread," unread"]})]})}else r=Y(e.g,n),i=(0,t.jsx)("span",{style:R("rgba(255,255,255,0.08)"),children:e.g.unknown?"To someone unnamed":"Group"});let l="person"===e.type?e.c?.row?.company||e.row?.company:null;return(0,t.jsxs)("div",{role:"button",tabIndex:0,"aria-pressed":o,onClick:s,onKeyDown:e=>{("Enter"===e.key||" "===e.key)&&(e.preventDefault(),s())},style:{padding:"8px 12px",borderBottom:"1px solid rgba(255,255,255,0.04)",cursor:"pointer",background:o?"rgba(52,152,219,0.14)":"none"},children:[(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:6,fontSize:13,minWidth:0,flexWrap:"wrap"},children:[(0,t.jsx)(U,{tier:a}),(0,t.jsx)("b",{style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},children:r}),i]}),(0,t.jsx)("div",{style:{fontSize:11.5,color:"#8b9a9a",marginTop:2,paddingLeft:14,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:[l,e.sub].filter(Boolean).join(" · ")})]})}function Y(e,t){if(e.title)return e.title;if(e.unknown)return"A conversation only you wrote in";let n=e.people.map(t).filter(Boolean);return n.length?`${n.slice(0,3).join(", ")}${e.people.length>3?` +${e.people.length-3}`:""}`:`${e.people.length} people`}function P({pipeline:e,picked:n,onPick:o}){return(0,t.jsx)("div",{className:"crm-board",children:h.map(s=>(0,t.jsxs)("div",{style:{minWidth:0},children:[(0,t.jsxs)("div",{style:{fontSize:12,fontWeight:650,color:D[s.id],margin:"0 0 6px",display:"flex",justifyContent:"space-between"},children:[(0,t.jsx)("span",{children:s.label}),(0,t.jsx)("span",{style:{color:"#778"},children:e[s.id].length.toLocaleString()})]}),0===e[s.id].length&&(0,t.jsx)("div",{style:{fontSize:11.5,color:"#667",padding:"6px 0"},children:"No one"}),e[s.id].slice(0,200).map(e=>(0,t.jsxs)("div",{role:"button",tabIndex:0,onClick:()=>o(e.key),onKeyDown:t=>{("Enter"===t.key||" "===t.key)&&(t.preventDefault(),o(e.key))},style:{padding:"7px 9px",marginBottom:6,borderRadius:6,border:p.LINE,cursor:"pointer",fontSize:12.5,background:n===e.key?"rgba(52,152,219,0.18)":"rgba(255,255,255,0.03)"},children:[(0,t.jsxs)("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[(0,t.jsx)(U,{tier:e.row?.tier}),(0,t.jsx)("b",{style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.name})]}),(0,t.jsx)("div",{style:{color:"#8b9a9a",fontSize:11,marginTop:2},children:[e.row?.company,e.crm.followUp?`Follow up ${F(e.crm.followUp)}`:null].filter(Boolean).join(" · ")})]},e.key)),e[s.id].length>200&&(0,t.jsxs)("div",{style:{fontSize:11,color:"#778"},children:["and ",(e[s.id].length-200).toLocaleString()," more (search to find them)"]})]},s.id))})}let _={fontSize:12,fontWeight:650,color:"#b8c4c4",margin:"16px 0 6px",textTransform:"uppercase",letterSpacing:.4};function H({c:e,nameOf:o,groups:s,keep:r,token:a,inviteNotes:i,onChange:l,onPickGroup:c}){let d=e.row,[u,h]=(0,n.useState)(e.conversations[0]?.id||null),g=L(e.profileUrl),f=L(e.threadUrl),m=e.connection?`A connection${d?.connected_date?` since ${$(Date.parse(d.connected_date))}`:""}.`:2===e.degree?"2nd degree: not a connection yet.":"Not a connection.";return(0,t.jsxs)("div",{style:{fontSize:13},children:[(0,t.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:[(0,t.jsx)(U,{tier:d?.tier}),(0,t.jsx)("h3",{style:{margin:0,fontSize:17},children:e.name}),d?.tier&&(0,t.jsxs)("span",{style:R("rgba(255,255,255,0.06)",I[d.tier]),children:[d.tier," tier"]}),(0,t.jsx)(A,{c:e})]}),(d?.headline||d?.company)&&(0,t.jsx)("div",{style:{color:"#b8c4c4",marginTop:4},children:d.headline||[d.role,d.company].filter(Boolean).join(" at ")}),(0,t.jsxs)("div",{style:{color:"#8b9a9a",marginTop:4,fontSize:12.5},children:[m,e.introducers.length>0&&(0,t.jsxs)(t.Fragment,{children:[" Who can introduce you: ",(0,t.jsx)("b",{style:{color:"#dfe8e8"},children:e.introducers.slice(0,6).join(", ")}),e.introducers.length>6?` and ${e.introducers.length-6} more`:"","."]}),d?.unlocked_from_name&&e.connection&&(0,t.jsxs)(t.Fragment,{children:[" You met through ",(0,t.jsx)("b",{style:{color:"#dfe8e8"},children:d.unlocked_from_name}),"."]})]}),(0,t.jsxs)("div",{style:{display:"flex",gap:12,marginTop:6,flexWrap:"wrap",fontSize:12.5},children:[g&&(0,t.jsx)("a",{href:g,target:"_blank",rel:"noreferrer",style:{color:"#3498DB",textDecoration:"none"},children:"Profile on LinkedIn ↗"}),f&&(0,t.jsx)("a",{href:f,target:"_blank",rel:"noreferrer",style:{color:"#3498DB",textDecoration:"none"},children:"Conversation on LinkedIn ↗"})]}),(0,t.jsx)(K,{c:e,onChange:l}),(0,t.jsx)("div",{style:_,children:"Conversations"}),0===e.conversations.length&&(0,t.jsx)("div",{style:{color:"#8b9a9a",fontSize:12.5},children:"None one to one."}),e.conversations.map(e=>(0,t.jsxs)("div",{style:{border:p.LINE,borderRadius:6,marginBottom:6,overflow:"hidden"},children:[(0,t.jsxs)("div",{role:"button",tabIndex:0,"aria-expanded":u===e.id,onClick:()=>h(u===e.id?null:e.id),onKeyDown:t=>{("Enter"===t.key||" "===t.key)&&(t.preventDefault(),h(u===e.id?null:e.id))},style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap",padding:"7px 10px",cursor:"pointer",fontSize:12.5,background:u===e.id?"rgba(255,255,255,0.04)":"none"},children:[(0,t.jsx)("span",{style:{color:"#dfe8e8"},children:$(e.last)||"No date"}),null!=e.lastFromThem&&(0,t.jsx)("span",{style:{color:e.lastFromThem?"#ff9f43":"#8b9a9a"},children:e.lastFromThem?"They wrote last":"You wrote last"}),null!=e.count&&(0,t.jsx)("span",{style:{color:"#8b9a9a"},children:N(e.count,"message")}),null!=e.mine&&null!=e.count&&e.mine===e.count&&(0,t.jsx)("span",{style:R("rgba(255,255,255,0.06)"),children:"Only you wrote"}),e.folder&&"inbox"!==e.folder&&(0,t.jsx)("span",{style:R("rgba(255,255,255,0.06)"),children:e.folder}),"inmail"===e.kind&&(0,t.jsx)("span",{style:R("rgba(155,89,182,0.25)"),children:"InMail"}),"sponsored"===e.kind&&(0,t.jsx)("span",{style:R("rgba(255,255,255,0.08)"),children:"Sponsored"}),e.unread>0&&(0,t.jsxs)("span",{style:R("#3498DB","#fff"),children:[e.unread," unread"]})]}),u===e.id&&(0,t.jsx)(z,{id:e.id,keep:r,group:!1,token:a},`${e.id}:${a}:${r}`)]},e.id)),e.groups.length>0&&(0,t.jsxs)("div",{style:{fontSize:12.5,color:"#8b9a9a",marginTop:4},children:["Also in ",N(e.groups.length,"group"),":"," ",e.groups.slice(0,8).map((e,n)=>{let r=s.find(t=>t.id===e);return(0,t.jsxs)("span",{children:[n>0?", ":"",(0,t.jsx)("button",{onClick:()=>c(e),style:{background:"none",border:"none",color:"#3498DB",cursor:"pointer",padding:0,fontSize:12.5},children:r?Y(r,o):"a group"})]},e)})]}),(0,t.jsx)("div",{style:_,children:"Requests"}),e.invitesOut.length+e.invitesIn.length===0&&!e.request&&(0,t.jsx)("div",{style:{color:"#8b9a9a",fontSize:12.5},children:"None on file."}),[...e.invitesOut,...e.invitesIn].sort((e,t)=>(t.t||0)-(e.t||0)).map(n=>(0,t.jsxs)("div",{style:{fontSize:12.5,marginBottom:6},children:[(0,t.jsx)("span",{style:{color:"#dfe8e8"},children:"out"===n.dir?"You asked to connect":"They asked to connect"}),(0,t.jsxs)("span",{style:{color:"#8b9a9a"},children:[" · ",$(n.t)||"no date"," · ",e.connection?"accepted":"out"===n.dir?"pending":"not accepted yet"]}),r&&i[n.id]&&(0,t.jsx)("div",{style:{marginTop:3,padding:"6px 9px",borderRadius:8,background:"rgba(255,255,255,0.06)",whiteSpace:"pre-wrap",overflowWrap:"anywhere"},children:i[n.id]})]},n.id)),e.request&&(0,t.jsxs)("div",{style:{fontSize:12.5},children:[(0,t.jsx)("span",{style:{color:"#dfe8e8"},children:"Request tracked in the app"}),(0,t.jsxs)("span",{style:{color:"#8b9a9a"},children:[" · ",e.request.status]})]})]})}function K({c:e,onChange:o}){let s=e.crm||{},[r,a]=(0,n.useState)(s.notes||""),[i,l]=(0,n.useState)(""),[c,d]=(0,n.useState)(null),p=(0,n.useRef)(null),u=e.theyWrote?"replied":e.conversations.length||e.invitesOut.length||e.request?"contacted":"new";async function f(t){d("Saving…");try{await o(e.key,t),d("Saved")}catch(e){d(e.message)}}(0,n.useEffect)(()=>()=>clearTimeout(p.current),[]);let m=e=>y(new Date(Date.now()+864e5*e)),x=s.tags||[];function w(){let e=i.trim();e&&(l(""),f({tags:[...x,...e.split(",")]}))}return(0,t.jsxs)("div",{style:{marginTop:12,padding:"10px 12px",borderRadius:8,background:"rgba(52,152,219,0.06)",border:"1px solid rgba(52,152,219,0.18)"},children:[(0,t.jsxs)("div",{style:{display:"flex",gap:12,flexWrap:"wrap",alignItems:"flex-end"},children:[(0,t.jsxs)("label",{style:{fontSize:11.5,color:"#8b9a9a",display:"flex",flexDirection:"column",gap:3},children:["Stage",(0,t.jsxs)("select",{value:s.stage||"",onChange:e=>f({stage:e.target.value||null}),style:{...M,padding:"5px 8px"},children:[(0,t.jsxs)("option",{value:"",children:["Not set (looks like ",g(u),")"]}),h.map(e=>(0,t.jsx)("option",{value:e.id,children:e.label},e.id))]})]}),(0,t.jsxs)("label",{style:{fontSize:11.5,color:"#8b9a9a",display:"flex",flexDirection:"column",gap:3},children:["Next follow-up",(0,t.jsx)("input",{type:"date",value:s.followUp||"",onChange:e=>f({followUp:e.target.value||null}),style:{...M,padding:"4px 8px",colorScheme:"dark"}})]}),(0,t.jsxs)("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:[(0,t.jsx)("button",{onClick:()=>f({followUp:m(3)}),style:E,children:"In 3 days"}),(0,t.jsx)("button",{onClick:()=>f({followUp:m(7)}),style:E,children:"In a week"}),s.followUp&&(0,t.jsx)("button",{onClick:()=>f({followUp:null}),style:E,children:"Clear"})]})]}),null==s.stage&&"new"!==u&&(0,t.jsxs)("button",{onClick:()=>f({stage:u}),style:{...E,marginTop:8},children:["Set to ",g(u)]}),(0,t.jsxs)("div",{style:{marginTop:10,display:"flex",gap:6,flexWrap:"wrap",alignItems:"center"},children:[x.map(e=>(0,t.jsxs)("span",{style:{...R("rgba(255,255,255,0.08)"),fontSize:11.5,display:"inline-flex",gap:4,alignItems:"center"},children:[e,(0,t.jsx)("button",{"aria-label":`Remove ${e}`,onClick:()=>f({tags:x.filter(t=>t!==e)}),style:{background:"none",border:"none",color:"#8b9a9a",cursor:"pointer",padding:0,fontSize:12},children:"×"})]},e)),(0,t.jsx)("input",{value:i,onChange:e=>l(e.target.value),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),w())},onBlur:w,placeholder:"Add a tag","aria-label":"Add a tag",style:{...M,padding:"3px 8px",fontSize:12,width:120}})]}),(0,t.jsx)("textarea",{value:r,onChange:e=>{var t;a(t=e.target.value),clearTimeout(p.current),p.current=setTimeout(()=>f({notes:t}),800)},onBlur:function(){clearTimeout(p.current),r!==(s.notes||"")&&f({notes:r})},placeholder:"Your notes on them: kept on this computer only","aria-label":"Notes",rows:4,style:{...M,width:"100%",marginTop:10,resize:"vertical",fontFamily:"inherit",lineHeight:1.45,boxSizing:"border-box"}}),(0,t.jsx)("div",{style:{fontSize:11,color:c&&"Saved"!==c&&"Saving…"!==c?"#ff9b9b":"#778",marginTop:4,minHeight:14},children:c||""})]})}function J({g:e,contacts:n,nameOf:o,keep:s,token:r,onPick:a}){let i=L(e.threadUrl);return(0,t.jsxs)("div",{style:{fontSize:13},children:[(0,t.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:[(0,t.jsx)("h3",{style:{margin:0,fontSize:17},children:Y(e,o)}),(0,t.jsx)("span",{style:R("rgba(255,255,255,0.08)"),children:e.unknown?"To someone unnamed":"Group"}),e.unread>0&&(0,t.jsxs)("span",{style:R("#3498DB","#fff"),children:[e.unread," unread"]})]}),(0,t.jsx)("div",{style:{color:"#8b9a9a",marginTop:4,fontSize:12.5},children:[e.last?`Last active ${$(e.last)}`:null,null!=e.count?N(e.count,"message"):null].filter(Boolean).join(" · ")}),i&&(0,t.jsx)("a",{href:i,target:"_blank",rel:"noreferrer",style:{color:"#3498DB",textDecoration:"none",fontSize:12.5,display:"inline-block",marginTop:6},children:"Conversation on LinkedIn ↗"}),e.people.length>0&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("div",{style:_,children:"Who’s in it"}),(0,t.jsx)("div",{style:{display:"flex",flexWrap:"wrap",gap:6},children:e.people.map(e=>{let s=n.get(e);return(0,t.jsxs)("button",{onClick:()=>a(e),style:{...E,display:"inline-flex",gap:6,alignItems:"center"},children:[(0,t.jsx)(U,{tier:s?.row?.tier}),o(e)||e.split("/").pop(),s&&!s.connection&&(0,t.jsx)("span",{style:{color:"#ffc58a",fontSize:10.5},children:"not a connection"})]},e)})})]}),(0,t.jsx)("div",{style:_,children:"Messages"}),(0,t.jsx)("div",{style:{border:p.LINE,borderRadius:6,overflow:"hidden"},children:(0,t.jsx)(z,{id:e.id,keep:s,group:!0,token:r},`${e.id}:${r}:${s}`)})]})}let G={warm:["Warm","#ff9f43"],cool:["Cool","#3498DB"],dormant:["Dormant","#8899aa"],never:["Never messaged","#556"]},V={"connections.csv":"first name","messages.csv":"conversation id","invitations.csv":"direction","positions.csv":"company name","shares.csv":"date"},Q=e=>e?new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"}):"";async function X(e){let t=[],n={},o=0;for(let s of e)for(let e of s.messages){let r=JSON.stringify(e).length+2;o+r>25e5&&o>0&&(t.push(n),n={},o=0),n[s.id]||(n[s.id]={title:s.title||"",messages:[]},o+=s.id.length+(s.title||"").length+40),n[s.id].messages.push(e),o+=r}t.push(n);for(let e=0;e<t.length;e++){let n=await fetch("/api/social/messages",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({part:e,parts:t.length,threads:t[e]})});if(!n.ok)throw Error((await n.json().catch(()=>({}))).error||"The messages couldn’t be saved.")}}async function Z(e){let t={};for(let n of e)n.note&&(t[n.id]=n.note);let n=await fetch("/api/social/messages",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({invites:t})});if(!n.ok)throw Error((await n.json().catch(()=>({}))).error||"The requests’ notes couldn’t be saved.")}function ee(){let{userId:e}=(0,r.useUser)(),[s,h]=(0,n.useState)(void 0),[g,f]=(0,n.useState)(null),[m,y]=(0,n.useState)(!1),[x,w]=(0,n.useState)(null),[b,v]=(0,n.useState)(!1),[j,k]=(0,n.useState)(()=>(0,d.scannerNow)()),[S,T]=(0,n.useState)(!1),[C,z]=(0,n.useState)(0),[I,D]=(0,n.useState)(0),$=(0,n.useRef)(null),F=(0,n.useRef)(null),L=s?.keepMessages===!0,N=()=>fetch("/api/social").then(e=>e.json()).then(e=>h(e.social||null)).catch(()=>h(null)).finally(()=>z(e=>e+1));async function O(e){v(!0),w(null);try{let t={};for(let n of Array.from(e||[])){let e=n.name.toLowerCase();V[e]&&!t[e]&&(t[e]=(0,i.readTable)(await n.text(),V[e]))}if(!Object.keys(t).length)throw Error("That folder doesn’t have LinkedIn’s export files in it (Connections.csv, messages.csv and so on).");let n=t["connections.csv"]||[],o=t["messages.csv"]?(0,i.messageStats)(t["messages.csv"]):{asOf:null,people:new Map},s=t["messages.csv"]?(0,i.buildConversations)(t["messages.csv"],{keepText:L}):null,r=t["invitations.csv"]?(0,i.buildInvitations)(t["invitations.csv"],{keepText:L}):null;$.current=t["messages.csv"]||null,F.current=t["invitations.csv"]||null;let a={};for(let[e,t]of o.people)a[e]={last:t.last,lastFromThem:t.lastFromThem,total:t.total,recent:t.recent};if(m)for(let e of n){let t=e["email address"];if(!t)continue;let n=(0,c.keyFor)({profile_url:e.url});a[n]={...a[n]||{},email:t}}let l=o.asOf||Date.now(),d={asOf:l,people:a,conversations:s?s.conversations.map(i.conversationSummary):[],chapters:(0,i.careerChapters)(t["positions.csv"]||[],n,l),posts:t["shares.csv"]?(0,i.postingEffect)(t["shares.csv"],n):null,invites:t["invitations.csv"]?(0,i.invitationSplit)(t["invitations.csv"]):null,invitations:(r||[]).map(({note:e,...t})=>t),names:s?s.names:{},emails:m},p=await fetch("/api/social",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d)});if(!p.ok)throw Error((await p.json().catch(()=>({}))).error||"It couldn’t be saved.");s&&L&&await X(s.conversations),r&&L&&await Z(r);let u=s?[s.drafts?`${s.drafts.toLocaleString()} unsent ${1===s.drafts?"draft":"drafts"} left out`:"",s.skipped?`${s.skipped.toLocaleString()} ${1===s.skipped?"row":"rows"} with no conversation or date left out`:""].filter(Boolean):[];w({good:!0,text:`Read ${Object.keys(t).length} of LinkedIn’s files: ${o.people.size.toLocaleString()} people you’ve messaged one to one`+(s?`, ${s.conversations.length.toLocaleString()} conversations${L?", their messages kept on this computer":""}`:"")+(r?`, ${r.length.toLocaleString()} connection requests`:"")+`.${u.length?` (${u.join("; ")}.)`:""}`}),await N()}catch(e){w({good:!1,text:e.message})}finally{v(!1)}}async function R(e=!1){w(null);try{let t=await (0,d.runScrape)(e?"messages-full":"messages");w(t?.exitCode===0?{good:!0,text:e?"Your whole messages history is read.":"Your messages list is read."}:{good:!1,text:"The sync stopped before it finished. The Scan page has the log."}),await N()}catch(e){w({good:!1,text:e.message})}}async function E(e){await fetch("/api/social",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({autoSync:e})}),N()}async function M(e){if(e||window.confirm("Delete the messages kept on this computer? The list of conversations stays; the messages themselves are deleted.")){T(!0),w(null);try{if(e||await fetch("/api/social/messages",{method:"DELETE"}),!(await fetch("/api/social",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({keepMessages:e})})).ok)throw Error("The switch couldn’t be saved.");e&&($.current||F.current)?($.current&&await X((0,i.buildConversations)($.current,{keepText:!0}).conversations),F.current&&await Z((0,i.buildInvitations)(F.current,{keepText:!0})),w({good:!0,text:"Your messages are kept on this computer now."})):e?w({good:!0,text:"On. Choose your export folder again, or sync your messages, to bring the messages in."}):w({good:!0,text:"Off. The kept messages are deleted."})}catch(e){w({good:!1,text:e.message})}finally{T(!1),N()}}}async function W(){await fetch("/api/social",{method:"DELETE"}),$.current=null,F.current=null;let e="Forgotten. Nothing from your export or messages is kept here now, the messages themselves included.";window.confirm("Also delete your CRM: the stages, tags, notes and follow-up dates you added? This can’t be undone. Cancel keeps them.")?(await fetch("/api/social/crm",{method:"DELETE"}),D(e=>e+1),e+=" Your CRM notes are deleted too."):e+=" Your CRM notes are kept.",w({good:!0,text:e}),N()}(0,n.useEffect)(()=>{N()},[]),(0,n.useEffect)(()=>(0,d.watchScanner)(()=>k((0,d.scannerNow)())),[]),(0,n.useEffect)(()=>{if(!e)return;let t=!0;return(0,a.loadNetwork)(e).then(e=>{t&&f(e)}).catch(()=>{}),()=>{t=!1}},[e]);let{merged:U,asOf:A}=(0,n.useMemo)(()=>(0,l.mergeSocial)(s),[s]),q=(0,n.useMemo)(()=>{let e={warm:0,cool:0,dormant:0,never:0};for(let t of g?.degree1||[])e[(0,i.warmthOf)(U.get((0,c.keyFor)(t)),A)]+=1;return e},[g,U,A]);if(u.IS_DEMO)return(0,t.jsx)("div",{style:{minHeight:"100vh",background:"#0a0a1a",color:"#aaa",padding:40,fontFamily:p.FONT},children:"Not part of the demo."});let Y=j?.running,P={padding:"12px 14px",borderRadius:8,border:p.LINE,background:"rgba(255,255,255,0.03)",marginTop:10},_={fontSize:16,margin:"24px 0 6px"};return(0,t.jsxs)("div",{style:{minHeight:"100vh",background:"#0a0a1a",color:"#fff",fontFamily:p.FONT},children:[(0,t.jsxs)("header",{style:{padding:"16px 24px",borderBottom:p.LINE,display:"flex",alignItems:"center",gap:16},children:[(0,t.jsx)(o.default,{href:"/",style:{color:"#888",textDecoration:"none",fontSize:13,fontWeight:600,padding:"6px 14px",borderRadius:6,background:"rgba(255,255,255,0.06)",border:p.LINE},children:"← Back to Map"}),(0,t.jsxs)("h1",{style:{fontSize:22,fontWeight:700,margin:0,color:"#FFD700"},children:["Social ",(0,t.jsx)("span",{style:{fontSize:12,color:"#8b9a9a",fontWeight:600},children:"experimental"})]})]}),(0,t.jsxs)("main",{style:{maxWidth:1180,margin:"0 auto",padding:"8px 16px 64px"},children:[(0,t.jsxs)(p.Body,{style:{marginTop:16},children:["Everyone you’ve been in touch with on LinkedIn, connections or not: who’s waiting on you, who hasn’t answered, what you’ve sent and received, and your own notes and follow-ups on each, from your own LinkedIn data. Only dates, who wrote last, counts and names are kept on this computer, and what messages say only if you turn on ",(0,t.jsx)("i",{children:"Keep my messages"})," below."]}),(0,t.jsx)("h2",{style:_,children:"Your LinkedIn export"}),(0,t.jsxs)("div",{style:P,children:[(0,t.jsxs)(p.Body,{style:{margin:0},children:["On LinkedIn: ",(0,t.jsx)("i",{children:"Settings → Data privacy → Get a copy of your data"}),", pick the larger archive, and unzip the file it emails you. Then choose that folder here. It’s read in this window; nothing goes to LinkedIn."]}),(0,t.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"flex-start",fontSize:12.5,color:"#b8c4c4",marginTop:10},children:[(0,t.jsx)("input",{type:"checkbox",checked:m,onChange:e=>y(e.target.checked),style:{marginTop:3}}),(0,t.jsx)("span",{children:"Also keep email addresses from it, on this computer only, for one-click email."})]}),(0,t.jsxs)("div",{style:{marginTop:10,display:"flex",gap:10,alignItems:"center",flexWrap:"wrap"},children:[(0,t.jsxs)("label",{style:{padding:"7px 14px",borderRadius:8,background:"rgba(52,152,219,0.18)",color:"#cfe6f7",cursor:b?"default":"pointer",fontSize:13},children:[b?"Reading…":s?"Choose a newer export":"Choose the export folder",(0,t.jsx)("input",{type:"file",webkitdirectory:"",directory:"",multiple:!0,disabled:b,style:{display:"none"},onChange:e=>O(e.target.files)})]}),s&&(0,t.jsxs)("span",{style:{fontSize:12,color:"#8b9a9a"},children:["From an export up to ",Q(s.asOf),s.liveAt?` \xb7 messages synced ${Q(Date.parse(s.liveAt))}`:""]}),s&&(0,t.jsx)("button",{onClick:W,style:{background:"none",border:"none",color:"#ff9b9b",cursor:"pointer",fontSize:12},children:"Forget it"})]})]}),(0,t.jsxs)("h2",{style:_,children:["Live messages sync ",(0,t.jsx)("span",{style:{fontSize:11,color:"#FFD700"},children:"experimental"})]}),(0,t.jsxs)("div",{style:P,children:[(0,t.jsxs)(p.Body,{style:{margin:0},children:["Reads your messages list once, in your own Chrome: who each conversation is with, connections or not, when it was last active, whether it’s unread and who wrote last. It uses no search budget and opens no conversation. The newest message’s words are kept only if ",(0,t.jsx)("i",{children:"Keep my messages"})," is on. The daily sync reads the top of the list; ",(0,t.jsx)("i",{children:"Read my whole history"})," keeps scrolling until no more conversations load (at most 60 scrolls, about 1,000 conversations), at the same slow, fixed pace, so it takes a few minutes."]}),(0,t.jsxs)("div",{style:{marginTop:10,display:"flex",gap:12,alignItems:"center",flexWrap:"wrap"},children:[(0,t.jsx)("button",{onClick:()=>R(!1),disabled:Y,style:{padding:"7px 14px",borderRadius:8,border:"none",background:Y?"#333":"rgba(0,255,136,0.15)",color:Y?"#777":"#00ff88",cursor:Y?"default":"pointer",fontSize:13},children:Y?"The scanner is busy":"Sync messages now"}),(0,t.jsx)("button",{onClick:()=>R(!0),disabled:Y,style:{padding:"7px 14px",borderRadius:8,border:p.LINE,background:"none",color:Y?"#777":"#cfe6f7",cursor:Y?"default":"pointer",fontSize:13},children:"Read my whole history"}),(0,t.jsxs)("label",{style:{display:"flex",gap:6,alignItems:"center",fontSize:12.5,color:"#b8c4c4"},children:[(0,t.jsx)("input",{type:"checkbox",checked:s?.autoSync===!0,onChange:e=>E(e.target.checked)}),"Once a day while the app is open (daytime only)"]})]})]}),x&&(0,t.jsx)("div",{style:{marginTop:10,fontSize:13,color:x.good?"#00ff88":"#ff9b9b"},children:x.text}),(0,t.jsx)("h2",{style:_,children:"People"}),(0,t.jsx)(B,{net:g,asOf:s?A:null,keep:L,onKeep:M,switching:S||b,token:C,crmToken:I}),s&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("h2",{style:_,children:"Warmth"}),(0,t.jsx)("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(160px, 1fr))",gap:10},children:Object.entries(G).map(([e,[n,o]])=>(0,t.jsxs)("div",{style:{...P,marginTop:0},children:[(0,t.jsx)("div",{style:{fontSize:22,fontWeight:700,color:o},children:(q[e]||0).toLocaleString()}),(0,t.jsx)("div",{style:{fontSize:12.5,color:"#b8c4c4"},children:n})]},e))}),(0,t.jsx)("div",{style:{fontSize:11.5,color:"#778",marginTop:6},children:"Of your connections. Warm: messaged in the last 30 days, or 3+ messages in 90. Cool: within a year. Dormant: longer ago."}),s.chapters?.length>0&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("h2",{style:_,children:"Career chapters"}),(0,t.jsx)("div",{style:{...P,padding:0},children:s.chapters.map(e=>{let n=(e.people||[]).filter(e=>["warm","cool"].includes((0,i.warmthOf)(U.get((0,c.keyFor)({profile_url:e})),A))).length;return(0,t.jsxs)("div",{style:{display:"flex",gap:10,padding:"8px 14px",borderBottom:"1px solid rgba(255,255,255,0.04)",fontSize:13,alignItems:"baseline"},children:[(0,t.jsxs)("span",{style:{flex:1},children:[(0,t.jsx)("b",{children:e.company})," ",(0,t.jsxs)("span",{style:{color:"#8b9a9a"},children:[e.title," · ",new Date(e.from).getFullYear(),"–",e.to?new Date(e.to).getFullYear():"now"]})]}),(0,t.jsxs)("span",{style:{color:"#b8c4c4"},children:[e.made.toLocaleString()," connections made"]}),(0,t.jsx)("span",{style:{color:"#ff9f43",minWidth:90,textAlign:"right"},children:e.made?`${Math.round(100*n/e.made)}% still in touch`:""})]},`${e.company}-${e.from}`)})})]}),s.posts?.posts?.length>0&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("h2",{style:_,children:"Does posting work?"}),(0,t.jsxs)("div",{style:P,children:[(0,t.jsxs)("div",{style:{fontSize:13},children:["In the week after a post you made ",(0,t.jsx)("b",{children:s.posts.afterPosts.toFixed(1)})," new connections on average; a usual week, ",(0,t.jsx)("b",{children:s.posts.usualWeek.toFixed(1)}),"."]}),(0,t.jsx)("div",{style:{fontSize:11.5,color:"#778",marginTop:4},children:"What came after a post, not proof it came from it."}),[...s.posts.posts].sort((e,t)=>t.week-e.week).slice(0,3).map(e=>(0,t.jsxs)("div",{style:{fontSize:12.5,color:"#b8c4c4",marginTop:6},children:[Q(e.t),": ",e.week," new connections that week ",e.link?(0,t.jsx)("a",{href:e.link,target:"_blank",rel:"noreferrer",style:{color:"#3498DB",textDecoration:"none"},children:"Post ↗"}):null]},e.t))]})]}),s.invites?.total>0&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("h2",{style:_,children:"Who reaches out"}),(0,t.jsx)("div",{style:P,children:(0,t.jsxs)("span",{style:{fontSize:13},children:[s.invites.incoming.toLocaleString()," requests came to you and you sent ",s.invites.outgoing.toLocaleString()," ","(",Math.round(100*s.invites.incoming/s.invites.total),"% came to you), as far as your export shows."]})})]})]})]})]})}e.s(["default",0,function(){return(0,t.jsx)(s.default,{children:(0,t.jsx)(ee,{})})}],60451)}]);
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.11] - 2026-09-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **The Social tab is a personal CRM.** Everyone you've been in touch with on LinkedIn is one contact,
|
|
13
|
+
connection or not: a list on the left with views and a search box, the person on the right (stacked
|
|
14
|
+
on a phone). The views:
|
|
15
|
+
- **Inbox:** they wrote last, or there's something unread. Unread first.
|
|
16
|
+
- **Awaiting reply:** you wrote last and nothing came back for 7 days or more (you can change the 7).
|
|
17
|
+
- **Follow-ups due:** a follow-up date of today or before.
|
|
18
|
+
- **Pipeline:** a board of the people you've given a stage.
|
|
19
|
+
- **Sent:** connection requests you sent (from your export's Invitations.csv, with the date, and the
|
|
20
|
+
note you wrote if Keep my messages is on), requests the app tracked (*Mark sent*), each accepted
|
|
21
|
+
once they're a connection and pending until then, and messages awaiting a reply.
|
|
22
|
+
- **Received:** requests to you from people who aren't connections yet.
|
|
23
|
+
- **All:** everyone and every group conversation, newest first.
|
|
24
|
+
|
|
25
|
+
Each person's panel shows their tier, company and how you're connected (and who can introduce you,
|
|
26
|
+
for 2nd degree), their conversations (the messages themselves if you keep them), requests both ways,
|
|
27
|
+
and **your own stage** (New, Contacted, Replied, Meeting, Won/Partner, Not now; it suggests one),
|
|
28
|
+
**tags**, **notes** and **next follow-up date**. Those are yours, so they're always kept, in a file of
|
|
29
|
+
their own per profile in the app's data folder, and never sent anywhere. Search covers names,
|
|
30
|
+
companies, tags and notes, and what was said while the messages are kept; in All it finds anyone in
|
|
31
|
+
your network, so you can add a note before you've written to them. **Export CSV** saves the table
|
|
32
|
+
(name, company, profile, connection, stage, tags, last contact, who wrote last, next follow-up,
|
|
33
|
+
notes) as a spreadsheet file. The list shows 100 at a time, so thousands of conversations stay quick.
|
|
34
|
+
- **Every conversation in your export, not just your connections'.** People who aren't connections
|
|
35
|
+
are listed by the name the export gives them, marked *Not a connection*, with their profile link when
|
|
36
|
+
it has one. Past (archived), current and ones only you wrote in are all there, from every folder;
|
|
37
|
+
group conversations are listed and marked; Sponsored Messages and InMails are marked, and adverts are
|
|
38
|
+
kept out of the Inbox. Unsent drafts are left out and said so.
|
|
39
|
+
- **Read my whole history**, beside *Sync messages now*: the live messages sync keeps scrolling your
|
|
40
|
+
messages list until no new conversations load (at most 60 scrolls, about 1,000 conversations), at the
|
|
41
|
+
same slow, fixed pace, with no random waits. The daily sync still reads just the top. The sync now
|
|
42
|
+
also brings in people who aren't your connections (with their name) and group conversations (without
|
|
43
|
+
any words), and each sync adds to what earlier ones found instead of replacing it.
|
|
44
|
+
- **Delete my CRM notes**, and *Forget it* asks, separately, whether to delete them too, so notes
|
|
45
|
+
aren't lost by accident.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- **"Save a copy of my network" carries the Social tab too.** A new box, ticked by default,
|
|
49
|
+
adds its findings, your CRM stages, tags, notes and follow-ups, and any messages you keep,
|
|
50
|
+
so moving to a new computer brings everything. Importing the copy puts them in place and
|
|
51
|
+
keeps this computer's beside the old network. Untick it to leave them behind.
|
|
52
|
+
- **Waiting on you has become the CRM's Inbox**, and Conversations is part of the CRM.
|
|
53
|
+
- **The Social tab keeps the names of people who aren't your connections**, from your export and the
|
|
54
|
+
sync, since nothing else in the app says who they are. A connection's name still isn't copied: the
|
|
55
|
+
app has it already.
|
|
56
|
+
|
|
9
57
|
## [0.4.10] - 2026-09-29
|
|
10
58
|
|
|
11
59
|
### Changed
|
package/lib/data-export.js
CHANGED
|
@@ -14,7 +14,7 @@ import { DatabaseSync } from 'node:sqlite';
|
|
|
14
14
|
import { readFileSync, lstatSync, statSync } from 'node:fs';
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
16
|
import path from 'node:path';
|
|
17
|
-
import { AVATAR_FILE, TRAVELLING_FILES, namesInFolder } from './data-folder.js';
|
|
17
|
+
import { AVATAR_FILE, TRAVELLING_FILES, SOCIAL_FILE, namesInFolder } from './data-folder.js';
|
|
18
18
|
|
|
19
19
|
/** Bump when the file's layout changes in a way an older import can't read. */
|
|
20
20
|
export const EXPORT_FORMAT = 1;
|
|
@@ -72,7 +72,7 @@ export function referencedPhotos(db) {
|
|
|
72
72
|
* plain files are taken, never a link, and avatars/ is read only if it is a
|
|
73
73
|
* real folder, so nothing outside the data folder can be pulled in through one.
|
|
74
74
|
*/
|
|
75
|
-
export function travellingFiles(dir, { includePhotos = true, photos = null } = {}) {
|
|
75
|
+
export function travellingFiles(dir, { includePhotos = true, photos = null, includeSocial = true } = {}) {
|
|
76
76
|
const plain = (p) => {
|
|
77
77
|
try { return lstatSync(p).isFile(); } catch { return false; }
|
|
78
78
|
};
|
|
@@ -81,6 +81,12 @@ export function travellingFiles(dir, { includePhotos = true, photos = null } = {
|
|
|
81
81
|
const abs = path.join(dir, name);
|
|
82
82
|
if (plain(abs)) out.push({ rel: name, abs });
|
|
83
83
|
}
|
|
84
|
+
if (includeSocial) {
|
|
85
|
+
for (const name of namesInFolder(dir).sort()) {
|
|
86
|
+
const abs = path.join(dir, name);
|
|
87
|
+
if (SOCIAL_FILE.test(name) && plain(abs)) out.push({ rel: name, abs });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
84
90
|
if (includePhotos) {
|
|
85
91
|
for (const name of namesInFolder(path.join(dir, 'avatars')).sort()) {
|
|
86
92
|
if (!AVATAR_FILE.test(name) || (photos && !photos.has(name))) continue;
|
|
@@ -96,7 +102,7 @@ export function travellingFiles(dir, { includePhotos = true, photos = null } = {
|
|
|
96
102
|
* `outFile`, which must not exist yet.
|
|
97
103
|
* @returns {{ bytes, people, photos, files, counts }}
|
|
98
104
|
*/
|
|
99
|
-
export function buildExport(db, { dir, outFile, includePhotos = true, appVersion, schemaSql, now = new Date() }) {
|
|
105
|
+
export function buildExport(db, { dir, outFile, includePhotos = true, includeSocial = true, appVersion, schemaSql, now = new Date() }) {
|
|
100
106
|
db.exec(`VACUUM INTO ${sqlString(outFile)}`);
|
|
101
107
|
|
|
102
108
|
const out = new DatabaseSync(outFile);
|
|
@@ -127,7 +133,7 @@ export function buildExport(db, { dir, outFile, includePhotos = true, appVersion
|
|
|
127
133
|
// photo of someone whose circle was deleted since; the copy has no row
|
|
128
134
|
// for them, so their face has no reason to travel.
|
|
129
135
|
const inNetwork = includePhotos ? referencedPhotos(out) : null;
|
|
130
|
-
for (const f of travellingFiles(dir, { includePhotos, photos: inNetwork })) {
|
|
136
|
+
for (const f of travellingFiles(dir, { includePhotos, photos: inNetwork, includeSocial })) {
|
|
131
137
|
let bytes;
|
|
132
138
|
try { bytes = readFileSync(f.abs); } catch { continue; } // replaced since the listing
|
|
133
139
|
put.run(f.rel, bytes, sha256(bytes));
|
package/lib/data-folder.js
CHANGED
|
@@ -44,6 +44,15 @@ export const BUDGET_FILES = [
|
|
|
44
44
|
'linkedin-cooldown.json',
|
|
45
45
|
];
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The Social tab's files, one set per profile: its findings, the messages kept
|
|
49
|
+
* with "Keep my messages on this computer", and the CRM's stages, tags and
|
|
50
|
+
* notes (lib/social-store.js). A copy carries them when asked (Blake,
|
|
51
|
+
* 2026-09-29: everything goes with the network), and an import puts them back
|
|
52
|
+
* in place of this computer's, which are kept beside the old network.
|
|
53
|
+
*/
|
|
54
|
+
export const SOCIAL_FILE = /^(?:social|social-messages|crm)-[\w-]+\.json$/;
|
|
55
|
+
|
|
47
56
|
/** Everything of the scanner's that an export carries. */
|
|
48
57
|
export const TRAVELLING_FILES = [...NETWORK_FILES, ...BUDGET_FILES];
|
|
49
58
|
|
package/lib/data-import.js
CHANGED
|
@@ -31,7 +31,7 @@ import { runningNow } from './scan-state.js';
|
|
|
31
31
|
import { durable } from './durable.js';
|
|
32
32
|
import { budgetFileProblem, mergeBudgetFiles } from './linkedin-limits.js';
|
|
33
33
|
import {
|
|
34
|
-
AVATAR_FILE, TRAVELLING_FILES, NETWORK_FILES, BUDGET_FILES, PENDING_DIR, IMPORT_BACKUP_PREFIX,
|
|
34
|
+
AVATAR_FILE, TRAVELLING_FILES, NETWORK_FILES, BUDGET_FILES, SOCIAL_FILE, PENDING_DIR, IMPORT_BACKUP_PREFIX,
|
|
35
35
|
sweepLeftovers, namesInFolder,
|
|
36
36
|
} from './data-folder.js';
|
|
37
37
|
import {
|
|
@@ -158,10 +158,11 @@ export async function receiveUpload(body, file, { declared }) {
|
|
|
158
158
|
return received;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/** 'photo', 'file' (one of the scanner's travelling files), or null for anything an export never carries. */
|
|
161
|
+
/** 'photo', 'file' (one of the scanner's travelling files), 'social', or null for anything an export never carries. */
|
|
162
162
|
export function exportedFileKind(rel) {
|
|
163
163
|
if (typeof rel !== 'string') return null;
|
|
164
164
|
if (TRAVELLING_FILES.includes(rel)) return 'file';
|
|
165
|
+
if (SOCIAL_FILE.test(rel)) return 'social';
|
|
165
166
|
// The same pattern the /avatars route serves: no folders, no dots but the
|
|
166
167
|
// extension, so no path can climb out of avatars/.
|
|
167
168
|
if (rel.startsWith('avatars/') && AVATAR_FILE.test(rel.slice('avatars/'.length))) return 'photo';
|
|
@@ -421,7 +422,7 @@ export function stageImport(upload, {
|
|
|
421
422
|
throw new ImportError(`“${shortName(rel)}” inside this file is damaged, so nothing was imported.`);
|
|
422
423
|
}
|
|
423
424
|
writeFileSync(path.join(filesDir, rel), row.bytes, { flag: 'wx' });
|
|
424
|
-
if (kind === 'file') scannerFiles.push(path.join(filesDir, rel));
|
|
425
|
+
if (kind === 'file' || kind === 'social') scannerFiles.push(path.join(filesDir, rel));
|
|
425
426
|
}
|
|
426
427
|
} finally {
|
|
427
428
|
db.close();
|
|
@@ -731,6 +732,14 @@ export function applyPendingImport({ dir, dbFile, now = new Date(), copyDatabase
|
|
|
731
732
|
keptFolder();
|
|
732
733
|
move(from, path.join(keptFiles, name));
|
|
733
734
|
}
|
|
735
|
+
// This computer's Social files step aside only when the copy brings its
|
|
736
|
+
// own; a copy made without them leaves them where they are.
|
|
737
|
+
if (namesInFolder(path.join(pending, 'files')).some((n) => SOCIAL_FILE.test(n))) {
|
|
738
|
+
for (const name of namesInFolder(dir).filter((n) => SOCIAL_FILE.test(n))) {
|
|
739
|
+
keptFolder();
|
|
740
|
+
move(path.join(dir, name), path.join(keptFiles, name));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
734
743
|
for (const name of BUDGET_FILES) {
|
|
735
744
|
const from = path.join(dir, name);
|
|
736
745
|
if (!exists(from) || !lstatSync(from).isFile()) continue;
|
|
@@ -786,6 +795,7 @@ export function applyPendingImport({ dir, dbFile, now = new Date(), copyDatabase
|
|
|
786
795
|
const from = path.join(files, name);
|
|
787
796
|
if (exists(from)) move(from, path.join(dir, name));
|
|
788
797
|
}
|
|
798
|
+
for (const name of namesInFolder(files).filter((n) => SOCIAL_FILE.test(n))) move(path.join(files, name), path.join(dir, name));
|
|
789
799
|
mergeBudgetFiles({ dir, from: files });
|
|
790
800
|
durable.syncFolder(dir);
|
|
791
801
|
step('done');
|