six-degrees 0.4.7 → 0.4.9
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 +3 -3
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
- 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 +2 -2
- 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 +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- 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 +2 -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 +2 -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 +2 -2
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js +3 -2
- 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 +2 -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 +2 -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/messages/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/social/messages/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/social/messages/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js +9 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/messages/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/social/route.js +4 -2
- 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/favicon.ico/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/favicon.ico.body +0 -0
- 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 +1 -1
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +13 -13
- 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 +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +1 -1
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +13 -13
- 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 +1 -1
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06mi9k2._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08f5awg._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0ea9qwy._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +6 -6
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1mxn_lb._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1xh07bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_messages_route_actions_0skh09r.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_18cr1gi._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_1zc5q0a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00d8snt._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00g61b8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08lf3gy._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ho-u0w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1feicqq._.js → [root-of-the-server]__0qdeuyo._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ze473p._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1cnxxc9._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jeztd-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ll97ry._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m0255u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1w9z0zv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0ezj4e0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1mj739k._.js → _0l8cxmo._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1errbuf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0wxbq78._.js → app_components_19c2d9c._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.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_setup_page_1-282zx.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_1wax83z._.js → node_modules_0h91jdk._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_1nkgb45._.js → node_modules_1f8t_rp._.js} +1 -1
- 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/{2_zu7rmb8flzl.js → 04bbi55urkrd6.js} +1 -1
- package/.next/{static/chunks/3q3pmt5vv-4pi.js → standalone/.next/static/chunks/054zdthcnyd2h.js} +1 -1
- package/.next/{static/chunks/236f0jtp68j_h.js → standalone/.next/static/chunks/1_mohm2643nxp.js} +1 -1
- package/.next/{static/chunks/3zw8bv7-mc4n6.js → standalone/.next/static/chunks/1z_rt4jteewqp.js} +1 -1
- package/.next/standalone/.next/static/chunks/24tvdun1cb3g1.js +1 -0
- package/.next/standalone/.next/static/chunks/2bftn9gubwf0w.js +1 -0
- package/.next/{static/chunks/0g_p4p7oci18u.js → standalone/.next/static/chunks/2kfhc58df2xyo.js} +1 -1
- package/.next/standalone/.next/static/chunks/2pw6_osz1n_te.js +1 -0
- package/.next/standalone/.next/static/chunks/39csdcx7yv227.js +1 -0
- package/.next/standalone/.next/static/chunks/3yzlf70j5t0-o.js +1 -0
- package/.next/standalone/.next/static/chunks/{2_wcobfqry2uj.js → 3z-swu5pet507.js} +1 -1
- package/.next/standalone/.next/static/media/favicon.23vm2o6v3er2z.ico +0 -0
- package/.next/standalone/CHANGELOG.md +72 -0
- package/.next/standalone/CONTRIBUTING.md +5 -0
- package/.next/standalone/README.md +168 -56
- package/.next/standalone/app/api/social/messages/route.js +130 -0
- package/.next/standalone/app/api/social/route.js +89 -47
- package/.next/standalone/app/components/ScanStatusBar.js +40 -3
- package/.next/standalone/app/components/settings/DataSection.js +2 -1
- package/.next/standalone/app/favicon.ico +0 -0
- package/.next/standalone/app/paths/page.js +27 -18
- package/.next/standalone/app/profile/page.js +2 -0
- package/.next/standalone/app/queue/page.js +2 -0
- package/.next/standalone/app/scores/page.js +2 -0
- package/.next/standalone/app/settings/page.js +2 -0
- package/.next/standalone/app/setup/page.js +6 -3
- package/.next/standalone/app/social/Conversations.js +246 -0
- package/.next/standalone/app/social/page.js +90 -9
- package/.next/standalone/lib/galaxy-lab.js +6 -1
- package/.next/standalone/lib/linkedin-export.js +175 -5
- package/.next/standalone/lib/scoring.js +42 -1
- package/.next/standalone/lib/social-store.js +73 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/build-site.mjs +537 -0
- package/.next/standalone/scripts/scrape.py +156 -23
- package/.next/standalone/scripts/site-markdown.mjs +188 -0
- package/.next/standalone/scripts/site-news.mjs +311 -0
- package/.next/static/chunks/{2_zu7rmb8flzl.js → 04bbi55urkrd6.js} +1 -1
- package/.next/{standalone/.next/static/chunks/3q3pmt5vv-4pi.js → static/chunks/054zdthcnyd2h.js} +1 -1
- package/.next/{standalone/.next/static/chunks/236f0jtp68j_h.js → static/chunks/1_mohm2643nxp.js} +1 -1
- package/.next/{standalone/.next/static/chunks/3zw8bv7-mc4n6.js → static/chunks/1z_rt4jteewqp.js} +1 -1
- package/.next/static/chunks/24tvdun1cb3g1.js +1 -0
- package/.next/static/chunks/2bftn9gubwf0w.js +1 -0
- package/.next/{standalone/.next/static/chunks/0g_p4p7oci18u.js → static/chunks/2kfhc58df2xyo.js} +1 -1
- package/.next/static/chunks/2pw6_osz1n_te.js +1 -0
- package/.next/static/chunks/39csdcx7yv227.js +1 -0
- package/.next/static/chunks/3yzlf70j5t0-o.js +1 -0
- package/.next/static/chunks/{2_wcobfqry2uj.js → 3z-swu5pet507.js} +1 -1
- package/.next/static/media/favicon.23vm2o6v3er2z.ico +0 -0
- package/CHANGELOG.md +72 -0
- package/README.md +168 -56
- package/lib/galaxy-lab.js +6 -1
- package/lib/linkedin-export.js +175 -5
- package/lib/scoring.js +42 -1
- package/lib/social-store.js +73 -0
- package/package.json +1 -1
- package/scripts/scrape.py +156 -23
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17kn1mk._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1dmc7-t._.js +0 -8
- package/.next/standalone/.next/server/chunks/ssr/_005q9n1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_217nddo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_0i3fw_m._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +0 -3
- package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/standalone/.next/static/chunks/0y1yms5kez2f3.js +0 -1
- package/.next/standalone/.next/static/chunks/14m1zu-8warte.js +0 -1
- package/.next/standalone/.next/static/chunks/15qbli9udo0vh.js +0 -1
- package/.next/standalone/.next/static/chunks/3p_pwu5gx2i9h.js +0 -1
- package/.next/standalone/.next/static/media/favicon.0zdzw5kurn8m-.ico +0 -0
- package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/static/chunks/0y1yms5kez2f3.js +0 -1
- package/.next/static/chunks/14m1zu-8warte.js +0 -1
- package/.next/static/chunks/15qbli9udo0vh.js +0 -1
- package/.next/static/chunks/3p_pwu5gx2i9h.js +0 -1
- package/.next/static/media/favicon.0zdzw5kurn8m-.ico +0 -0
- /package/.next/standalone/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_ssgManifest.js +0 -0
- /package/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_buildManifest.js +0 -0
- /package/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{YNRcnda8lvMnnrOKL1L5l → yPlsfKhdS8MMtyXF7twSZ}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { rmSync } from 'node:fs';
|
|
2
|
+
import { conversationList } from '../../../../lib/linkedin-export';
|
|
3
|
+
import { socialFiles, readJson, writeJsonAtomic, readMessages, deleteMessages } from '../../../../lib/social-store';
|
|
4
|
+
|
|
5
|
+
// The Social tab's Conversations: the list, one thread at a time, and the
|
|
6
|
+
// messages themselves when Keep my messages is on (lib/social-store.js has the
|
|
7
|
+
// two files and why they're apart).
|
|
8
|
+
//
|
|
9
|
+
// GET the list, no words: who, when, who wrote last,
|
|
10
|
+
// unread, how many, the thread's link
|
|
11
|
+
// GET ?id=<id> | ?with=<key> one thread, a page at a time (before, limit):
|
|
12
|
+
// its messages only if they're kept
|
|
13
|
+
// GET ?q=<words> the conversations whose kept messages say that
|
|
14
|
+
// POST an import's messages, in parts
|
|
15
|
+
// DELETE the messages gone, the list kept
|
|
16
|
+
//
|
|
17
|
+
// Nothing here goes anywhere but this computer's browser: no route of the app
|
|
18
|
+
// sends messages on, and a copy of the network doesn't carry this file.
|
|
19
|
+
|
|
20
|
+
const PAGE = 100;
|
|
21
|
+
const MAX_TEXT = 20000;
|
|
22
|
+
|
|
23
|
+
function state() {
|
|
24
|
+
const files = socialFiles();
|
|
25
|
+
if (!files) return null;
|
|
26
|
+
const social = readJson(files.social, {}) || {};
|
|
27
|
+
const keep = social.keepMessages === true;
|
|
28
|
+
const kept = keep ? readMessages(files.messages) : { threads: {}, previews: {} };
|
|
29
|
+
return { files, social, keep, kept, list: conversationList(social.conversations, social.live) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A conversation's kept messages, oldest first, with the live sync's newest one after them when it's newer. */
|
|
33
|
+
function messagesOf(c, kept) {
|
|
34
|
+
const msgs = kept.threads[c.id]?.messages || [];
|
|
35
|
+
const preview = !c.group && c.people.length === 1 ? kept.previews[c.people[0]] : null;
|
|
36
|
+
if (preview?.text && (!msgs.length || (preview.t || 0) > (msgs[msgs.length - 1].t || 0))) return [...msgs, preview];
|
|
37
|
+
return msgs;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function GET(request) {
|
|
41
|
+
const s = state();
|
|
42
|
+
if (!s) return Response.json({ keepMessages: false, conversations: [] });
|
|
43
|
+
const url = new URL(request.url);
|
|
44
|
+
const id = url.searchParams.get('id');
|
|
45
|
+
const withKey = url.searchParams.get('with');
|
|
46
|
+
|
|
47
|
+
if (id || withKey) {
|
|
48
|
+
const c = id
|
|
49
|
+
? s.list.find((x) => x.id === id)
|
|
50
|
+
: s.list.filter((x) => !x.group && x.people[0] === withKey).sort((a, b) => (b.last || 0) - (a.last || 0))[0];
|
|
51
|
+
if (!c) return Response.json({ error: 'That conversation isn’t here.' }, { status: 404 });
|
|
52
|
+
if (!s.keep) return Response.json({ kept: false, id: c.id, total: c.count });
|
|
53
|
+
const msgs = messagesOf(c, s.kept);
|
|
54
|
+
const total = msgs.length;
|
|
55
|
+
const limit = Math.min(500, Math.max(1, Number(url.searchParams.get('limit')) || PAGE));
|
|
56
|
+
const before = url.searchParams.has('before') ? Math.max(0, Math.min(total, Number(url.searchParams.get('before')) || 0)) : total;
|
|
57
|
+
const start = Math.max(0, before - limit);
|
|
58
|
+
return Response.json({ kept: true, id: c.id, title: s.kept.threads[c.id]?.title || '', total, start, messages: msgs.slice(start, before) });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const q = String(url.searchParams.get('q') || '').trim().toLowerCase();
|
|
62
|
+
if (url.searchParams.has('q')) {
|
|
63
|
+
if (!s.keep || q.length < 2) return Response.json({ ids: [] });
|
|
64
|
+
const ids = s.list.filter((c) => messagesOf(c, s.kept).some((m) => String(m.text || '').toLowerCase().includes(q))).map((c) => c.id);
|
|
65
|
+
return Response.json({ ids });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const conversations = s.list.map((c) => ({
|
|
69
|
+
...c,
|
|
70
|
+
title: s.kept.threads[c.id]?.title || '',
|
|
71
|
+
hasText: s.keep && messagesOf(c, s.kept).length > 0,
|
|
72
|
+
}));
|
|
73
|
+
return Response.json({ keepMessages: s.keep, conversations });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** One message as it's kept, or null when it isn't one. */
|
|
77
|
+
function cleanMessage(m) {
|
|
78
|
+
if (!m || typeof m !== 'object' || !Number.isFinite(Number(m.t))) return null;
|
|
79
|
+
return { t: Number(m.t), fromMe: m.fromMe === true, text: typeof m.text === 'string' ? m.text.slice(0, MAX_TEXT) : '' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// An import's messages: { part, parts, threads: { id: { title, messages } } }.
|
|
83
|
+
// Each part stays under the 10 MB a request can carry here, so a long history
|
|
84
|
+
// comes in several. They gather in the `incoming` file and replace the kept
|
|
85
|
+
// messages only when the last one lands, so a half-sent import never shows as
|
|
86
|
+
// the whole thing. Refused while Keep my messages is off, whatever the page sent.
|
|
87
|
+
export async function POST(request) {
|
|
88
|
+
const s = state();
|
|
89
|
+
if (!s) return Response.json({ error: 'Pick a profile first.' }, { status: 400 });
|
|
90
|
+
if (!s.keep) return Response.json({ error: 'Keep my messages is off, so nothing was saved.' }, { status: 409 });
|
|
91
|
+
let body;
|
|
92
|
+
try { body = await request.json(); } catch { return Response.json({ error: 'That isn’t readable.' }, { status: 400 }); }
|
|
93
|
+
const part = Number(body?.part);
|
|
94
|
+
const parts = Number(body?.parts);
|
|
95
|
+
if (!Number.isInteger(part) || !Number.isInteger(parts) || part < 0 || part >= parts) {
|
|
96
|
+
return Response.json({ error: 'That isn’t a part of an import.' }, { status: 400 });
|
|
97
|
+
}
|
|
98
|
+
let into;
|
|
99
|
+
if (part === 0) {
|
|
100
|
+
// A new import replaces the export's messages; the live sync's previews stay.
|
|
101
|
+
into = { threads: {}, previews: s.kept.previews || {} };
|
|
102
|
+
} else {
|
|
103
|
+
into = readJson(s.files.incoming, null);
|
|
104
|
+
if (!into?.threads) return Response.json({ error: 'The start of this import is missing. Choose the export folder again.' }, { status: 409 });
|
|
105
|
+
}
|
|
106
|
+
const threads = body?.threads && typeof body.threads === 'object' ? body.threads : {};
|
|
107
|
+
for (const [id, t] of Object.entries(threads)) {
|
|
108
|
+
if (!id || id.length > 200 || !t || !Array.isArray(t.messages)) continue;
|
|
109
|
+
const to = into.threads[id] || (into.threads[id] = { title: '', messages: [] });
|
|
110
|
+
if (typeof t.title === 'string' && t.title && !to.title) to.title = t.title.slice(0, 200);
|
|
111
|
+
for (const m of t.messages) {
|
|
112
|
+
const c = cleanMessage(m);
|
|
113
|
+
if (c) to.messages.push(c);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (part === parts - 1) {
|
|
117
|
+
writeJsonAtomic(s.files.messages, into);
|
|
118
|
+
rmSync(s.files.incoming, { force: true });
|
|
119
|
+
} else {
|
|
120
|
+
writeJsonAtomic(s.files.incoming, into);
|
|
121
|
+
}
|
|
122
|
+
return Response.json({ ok: true, part, parts, done: part === parts - 1 });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// The words gone, the list and the numbers kept: what switching Keep my
|
|
126
|
+
// messages off does (the PATCH that turns it off deletes them as well).
|
|
127
|
+
export async function DELETE() {
|
|
128
|
+
deleteMessages(socialFiles());
|
|
129
|
+
return Response.json({ ok: true });
|
|
130
|
+
}
|
|
@@ -1,40 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { dataDir } from '../../../lib/db-client';
|
|
1
|
+
import { rmSync } from 'node:fs';
|
|
2
|
+
import { db } from '../../../lib/db';
|
|
4
3
|
import { resolveProfile } from '../../../lib/profile';
|
|
4
|
+
import { matchLive } from '../../../lib/linkedin-export';
|
|
5
|
+
import { socialFiles, readJson, writeJsonAtomic, readMessages, deleteMessages } from '../../../lib/social-store';
|
|
5
6
|
|
|
6
7
|
// The Social tab's saved findings (lib/linkedin-export.js): numbers and dates
|
|
7
|
-
// from your own LinkedIn export
|
|
8
|
-
//
|
|
9
|
-
//
|
|
8
|
+
// from your own LinkedIn export and the live messages sync, one file per
|
|
9
|
+
// profile in the data folder. No message text is kept in this file: the words,
|
|
10
|
+
// when Keep my messages is on, go to their own file (lib/social-store.js,
|
|
11
|
+
// app/api/social/messages). GET reads it, POST replaces it, PUT takes a live
|
|
12
|
+
// sync, PATCH flips a switch, DELETE forgets it all.
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
// A person's key: their profile link, as lib/separation.js keyFor makes it.
|
|
15
|
+
const KEY_OK = (k) => typeof k === 'string' && k.length > 0 && k.length < 300;
|
|
16
|
+
|
|
17
|
+
/** An import's list of conversations, with nothing but the fields the list shows. */
|
|
18
|
+
function cleanConversations(list) {
|
|
19
|
+
if (!Array.isArray(list)) return [];
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const c of list) {
|
|
22
|
+
if (!c || typeof c.id !== 'string' || !c.id || c.id.length > 200 || !Array.isArray(c.people)) continue;
|
|
23
|
+
const people = c.people.filter(KEY_OK).slice(0, 200);
|
|
24
|
+
if (!people.length) continue;
|
|
25
|
+
out.push({
|
|
26
|
+
id: c.id,
|
|
27
|
+
people,
|
|
28
|
+
group: c.group === true,
|
|
29
|
+
last: Number(c.last) || null,
|
|
30
|
+
lastFromThem: typeof c.lastFromThem === 'boolean' ? c.lastFromThem : null,
|
|
31
|
+
count: Number.isFinite(Number(c.count)) ? Number(c.count) : null,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
export async function GET() {
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
20
|
-
|
|
38
|
+
const files = socialFiles();
|
|
39
|
+
if (!files) return Response.json({ social: null });
|
|
40
|
+
const social = readJson(files.social, null);
|
|
41
|
+
if (!social) return Response.json({ social: null });
|
|
42
|
+
// The list of conversations can run to thousands; the Galaxy and the tab's
|
|
43
|
+
// counts don't need it, and GET /api/social/messages serves it.
|
|
44
|
+
const { conversations, ...rest } = social;
|
|
45
|
+
return Response.json({ social: { ...rest, conversationCount: Array.isArray(conversations) ? conversations.length : 0 } });
|
|
21
46
|
}
|
|
22
47
|
|
|
23
48
|
export async function POST(request) {
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
49
|
+
const files = socialFiles();
|
|
50
|
+
if (!files) return Response.json({ error: 'Pick a profile first.' }, { status: 400 });
|
|
26
51
|
let body;
|
|
27
52
|
try { body = await request.json(); } catch { return Response.json({ error: 'That isn’t readable.' }, { status: 400 }); }
|
|
28
53
|
// Only the known fields, so nothing else rides along into the file.
|
|
29
|
-
|
|
30
|
-
try { before = JSON.parse(readFileSync(file, 'utf8')) || {}; } catch { /* none yet */ }
|
|
54
|
+
const before = readJson(files.social, {}) || {};
|
|
31
55
|
const clean = {
|
|
32
56
|
live: before.live || {},
|
|
33
57
|
liveAt: before.liveAt || null,
|
|
34
58
|
autoSync: before.autoSync === true,
|
|
59
|
+
keepMessages: before.keepMessages === true,
|
|
35
60
|
importedAt: new Date().toISOString(),
|
|
36
61
|
asOf: Number(body?.asOf) || null,
|
|
37
62
|
people: body?.people && typeof body.people === 'object' ? body.people : {},
|
|
63
|
+
conversations: cleanConversations(body?.conversations),
|
|
38
64
|
chapters: Array.isArray(body?.chapters) ? body.chapters : [],
|
|
39
65
|
posts: body?.posts && typeof body.posts === 'object' ? body.posts : null,
|
|
40
66
|
invites: body?.invites && typeof body.invites === 'object' ? body.invites : null,
|
|
@@ -46,51 +72,67 @@ export async function POST(request) {
|
|
|
46
72
|
if (!clean.emails) delete p.email;
|
|
47
73
|
}
|
|
48
74
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
renameSync(tmp, file);
|
|
52
|
-
return Response.json({ ok: true, people: Object.keys(clean.people).length });
|
|
75
|
+
writeJsonAtomic(files.social, clean);
|
|
76
|
+
return Response.json({ ok: true, people: Object.keys(clean.people).length, conversations: clean.conversations.length });
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
// From the scanner's experimental messages sync (scripts/scrape.py sync_messages):
|
|
56
|
-
// per person, when your 1:1 conversation was last active
|
|
57
|
-
//
|
|
80
|
+
// per person, when your 1:1 conversation was last active, unread count, the
|
|
81
|
+
// thread's link, who wrote last and the newest message. Merged in beside what
|
|
82
|
+
// the export gave, kept under `live`. The newest message's words go to the
|
|
83
|
+
// messages file, and only while Keep my messages is on.
|
|
58
84
|
export async function PUT(request) {
|
|
59
|
-
const
|
|
60
|
-
if (!
|
|
85
|
+
const files = socialFiles();
|
|
86
|
+
if (!files) return Response.json({ error: 'Pick a profile first.' }, { status: 400 });
|
|
61
87
|
let body;
|
|
62
88
|
try { body = await request.json(); } catch { return Response.json({ error: 'That isn’t readable.' }, { status: 400 }); }
|
|
63
|
-
|
|
64
|
-
|
|
89
|
+
const saved = readJson(files.social, {}) || {};
|
|
90
|
+
// Matched to your connections here, so the names the sync sends go no further.
|
|
91
|
+
const me = resolveProfile({ create: false });
|
|
92
|
+
let conns = [];
|
|
93
|
+
try {
|
|
94
|
+
const q = db.from('linkedin_connections').select('name, profile_url, id').eq('degree', 1);
|
|
95
|
+
if (me?.id) q.eq('user_id', me.id);
|
|
96
|
+
conns = (await q).data || [];
|
|
97
|
+
} catch { /* no connections yet */ }
|
|
98
|
+
const { live: matched, unmatched } = matchLive(body?.live || {}, conns);
|
|
65
99
|
const live = {};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
live[
|
|
100
|
+
const previews = {};
|
|
101
|
+
for (const [key, { preview, ...rest }] of Object.entries(matched)) {
|
|
102
|
+
live[key] = rest;
|
|
103
|
+
if (saved.keepMessages === true && preview?.text) previews[key] = preview;
|
|
104
|
+
}
|
|
105
|
+
writeJsonAtomic(files.social, { ...saved, live, liveAt: new Date().toISOString() });
|
|
106
|
+
if (Object.keys(previews).length) {
|
|
107
|
+
const kept = readJson(files.messages, {}) || {};
|
|
108
|
+
writeJsonAtomic(files.messages, { ...kept, previews: { ...(kept.previews || {}), ...previews } });
|
|
69
109
|
}
|
|
70
|
-
|
|
71
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
72
|
-
writeFileSync(tmp, JSON.stringify(next));
|
|
73
|
-
renameSync(tmp, file);
|
|
74
|
-
return Response.json({ ok: true, people: Object.keys(live).length });
|
|
110
|
+
return Response.json({ ok: true, people: Object.keys(live).length, unmatched, previews: Object.keys(previews).length });
|
|
75
111
|
}
|
|
76
112
|
|
|
77
|
-
// The Social tab's "once a day"
|
|
113
|
+
// The Social tab's switches: "once a day" for the live messages sync, and
|
|
114
|
+
// Keep my messages. Only the ones sent change. Turning Keep my messages off
|
|
115
|
+
// deletes the kept messages here too, so the words can't outlive the switch
|
|
116
|
+
// whatever the page did first.
|
|
78
117
|
export async function PATCH(request) {
|
|
79
|
-
const
|
|
80
|
-
if (!
|
|
118
|
+
const files = socialFiles();
|
|
119
|
+
if (!files) return Response.json({ error: 'Pick a profile first.' }, { status: 400 });
|
|
81
120
|
let body = {};
|
|
82
121
|
try { body = await request.json(); } catch { /* nothing to change */ }
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return Response.json({ ok: true, autoSync: next.autoSync });
|
|
122
|
+
const next = { ...(readJson(files.social, {}) || {}) };
|
|
123
|
+
if (typeof body?.autoSync === 'boolean') next.autoSync = body.autoSync;
|
|
124
|
+
if (typeof body?.keepMessages === 'boolean') next.keepMessages = body.keepMessages;
|
|
125
|
+
if (next.keepMessages !== true) deleteMessages(files);
|
|
126
|
+
writeJsonAtomic(files.social, next);
|
|
127
|
+
return Response.json({ ok: true, autoSync: next.autoSync === true, keepMessages: next.keepMessages === true });
|
|
90
128
|
}
|
|
91
129
|
|
|
130
|
+
// Forget it: both files, the numbers and the words.
|
|
92
131
|
export async function DELETE() {
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
132
|
+
const files = socialFiles();
|
|
133
|
+
if (files) {
|
|
134
|
+
rmSync(files.social, { force: true });
|
|
135
|
+
deleteMessages(files);
|
|
136
|
+
}
|
|
95
137
|
return Response.json({ ok: true });
|
|
96
138
|
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// pacing and the caps live in the scanner (scripts/scrape.py, lib/linkedin-limits.js).
|
|
9
9
|
|
|
10
10
|
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
11
|
+
import { createPortal } from 'react-dom';
|
|
12
|
+
import { usePathname } from 'next/navigation';
|
|
11
13
|
import Link from 'next/link';
|
|
12
14
|
import { watchScanner, scannerNow, stopScrape } from '../../lib/scraper-client';
|
|
13
15
|
import { watchAllDay, allDayNow } from '../../lib/experimental-client';
|
|
@@ -37,19 +39,54 @@ export default function ScanStatusBar() {
|
|
|
37
39
|
// The Scan page's experimental all-day Auto-Bridge switch: with it on, the bar
|
|
38
40
|
// stays under the tabs even while nothing runs, so you can see the mode is on.
|
|
39
41
|
const allDay = useSyncExternalStore(watchAllDay, allDayNow, () => false);
|
|
42
|
+
// Where it sits: in the page's own slot under its header when the page has one
|
|
43
|
+
// (it then pushes the page down rather than covering it), otherwise fixed just
|
|
44
|
+
// under the header's buttons, measured, since a header can wrap to two rows.
|
|
45
|
+
const pathname = usePathname();
|
|
46
|
+
const [spot, setSpot] = useState({ slot: null, top: null });
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
// The header can load late or be swapped for a new one (a page that shows
|
|
49
|
+
// "Loading…" first), so it's found again whenever the page's elements change,
|
|
50
|
+
// and its size is watched for a wrap to two rows.
|
|
51
|
+
let header = null;
|
|
52
|
+
let ro = null;
|
|
53
|
+
let queued = 0;
|
|
54
|
+
const measure = () => {
|
|
55
|
+
queued = 0;
|
|
56
|
+
const slot = document.getElementById('scan-bar-slot');
|
|
57
|
+
const now = slot ? null : document.querySelector('header');
|
|
58
|
+
if (now !== header) {
|
|
59
|
+
ro?.disconnect();
|
|
60
|
+
ro = null;
|
|
61
|
+
header = now;
|
|
62
|
+
if (header) { ro = new ResizeObserver(later); ro.observe(header); }
|
|
63
|
+
}
|
|
64
|
+
const top = header ? Math.round(header.getBoundingClientRect().bottom) + 6 : null;
|
|
65
|
+
setSpot((was) => (was.slot === slot && was.top === top ? was : { slot, top }));
|
|
66
|
+
};
|
|
67
|
+
const later = () => { if (!queued) queued = requestAnimationFrame(measure); };
|
|
68
|
+
later();
|
|
69
|
+
const mo = new MutationObserver(later);
|
|
70
|
+
mo.observe(document.body, { childList: true, subtree: true });
|
|
71
|
+
window.addEventListener('resize', later);
|
|
72
|
+
return () => { cancelAnimationFrame(queued); ro?.disconnect(); mo.disconnect(); window.removeEventListener('resize', later); };
|
|
73
|
+
}, [pathname]);
|
|
40
74
|
useEffect(() => watchScanner(() => {
|
|
41
75
|
const now = scannerNow();
|
|
42
76
|
setJob(now);
|
|
43
77
|
if (!now.running) setStopping(false);
|
|
44
78
|
}), []);
|
|
45
79
|
const place = {
|
|
46
|
-
|
|
80
|
+
...(spot.slot
|
|
81
|
+
? { position: 'relative', margin: '8px auto 12px', width: 'fit-content' }
|
|
82
|
+
: { position: 'fixed', left: '50%', top: spot.top != null ? spot.top : 'var(--scan-bar-top, 94px)', transform: 'translateX(-50%)', zIndex: 60 }),
|
|
47
83
|
maxWidth: 'calc(100vw - 32px)', display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'nowrap', whiteSpace: 'nowrap',
|
|
48
84
|
padding: '8px 12px', borderRadius: 12, fontSize: 12, color: '#cfd8d8', background: 'rgba(14,16,32,0.94)',
|
|
49
85
|
};
|
|
86
|
+
const put = (bar) => (spot.slot && document.body.contains(spot.slot) ? createPortal(bar, spot.slot) : bar);
|
|
50
87
|
if (!job?.running) {
|
|
51
88
|
if (!allDay) return null;
|
|
52
|
-
return (
|
|
89
|
+
return put(
|
|
53
90
|
<div role="status" style={{ ...place, opacity: 0.55, border: '1px solid rgba(255,255,255,0.1)' }}>
|
|
54
91
|
<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#556', flexShrink: 0 }} />
|
|
55
92
|
<b style={{ color: '#b8c4c4' }}>Auto scan</b>
|
|
@@ -65,7 +102,7 @@ export default function ScanStatusBar() {
|
|
|
65
102
|
const b = job.budget;
|
|
66
103
|
const last = [...(job.log || [])].reverse().find((l) => l && l.trim())?.trim();
|
|
67
104
|
|
|
68
|
-
return (
|
|
105
|
+
return put(
|
|
69
106
|
<div role="status" aria-live="polite" style={{ ...place, border: '1px solid rgba(0,255,136,0.25)' }}>
|
|
70
107
|
<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#00ff88', flexShrink: 0 }} />
|
|
71
108
|
<b style={{ color: '#fff' }}>{WHAT[job.action] || 'Scanning'}</b>
|
|
@@ -334,7 +334,8 @@ export default function DataSection() {
|
|
|
334
334
|
<Body style={small}>
|
|
335
335
|
The copy holds the names, headlines and photos of the people in your network. Keep it private: don’t
|
|
336
336
|
post it or share it, and delete it once it has been imported. A network opened from a LinkedIn CSV lives
|
|
337
|
-
only in its browser tab, so it isn’t in the copy; import the CSV again on the new computer.
|
|
337
|
+
only in its browser tab, so it isn’t in the copy; import the CSV again on the new computer. The Social
|
|
338
|
+
tab’s findings, and any messages you keep there, stay on this computer and aren’t in the copy either.
|
|
338
339
|
</Body>
|
|
339
340
|
|
|
340
341
|
<h3 style={subhead}>Bring in a network from another computer</h3>
|
|
Binary file
|
|
@@ -106,12 +106,22 @@ function PathsInner() {
|
|
|
106
106
|
function enrichPeople(people) {
|
|
107
107
|
const d1Urls = new Set(d1Data.map(c => c.profile_url));
|
|
108
108
|
const knownUrls = new Set([...d1Data, ...d2Data].map(c => c.profile_url));
|
|
109
|
+
// A person's mutuals are everyone here you're connected to but them. Counted
|
|
110
|
+
// once, then less their own, rather than a pass over everyone per person:
|
|
111
|
+
// that was 3.7 million checks at a company of 1,900.
|
|
112
|
+
const connectedById = new Map();
|
|
113
|
+
let connectedHere = 0;
|
|
114
|
+
for (const op of people) {
|
|
115
|
+
if (!d1Urls.has(op.profile_url || op.profileUrl)) continue;
|
|
116
|
+
connectedHere++;
|
|
117
|
+
connectedById.set(op.id, (connectedById.get(op.id) || 0) + 1);
|
|
118
|
+
}
|
|
109
119
|
return people.map(p => ({
|
|
110
120
|
...p,
|
|
111
121
|
connected: d1Urls.has(p.profile_url || p.profileUrl),
|
|
112
122
|
known: knownUrls.has(p.profile_url || p.profileUrl),
|
|
113
123
|
seniority: getSeniority(p.headline),
|
|
114
|
-
mutualCount:
|
|
124
|
+
mutualCount: connectedHere - (connectedById.get(p.id) || 0),
|
|
115
125
|
}));
|
|
116
126
|
}
|
|
117
127
|
|
|
@@ -177,6 +187,9 @@ function PathsInner() {
|
|
|
177
187
|
}
|
|
178
188
|
|
|
179
189
|
// Build hierarchy for selected company
|
|
190
|
+
const d1ById = new Map();
|
|
191
|
+
for (const d of d1Data) if (!d1ById.has(d.id)) d1ById.set(d.id, d);
|
|
192
|
+
const d1Urls = new Set(d1Data.map(c => c.profile_url));
|
|
180
193
|
const hierarchy = {};
|
|
181
194
|
let connectedCount = 0;
|
|
182
195
|
companyPeople.forEach(p => {
|
|
@@ -195,7 +208,7 @@ function PathsInner() {
|
|
|
195
208
|
// Find the bridge path for degree-2 people
|
|
196
209
|
let bridgeName = null;
|
|
197
210
|
if (p.degree === 2 && p.source_connection_id) {
|
|
198
|
-
const bridge =
|
|
211
|
+
const bridge = d1ById.get(p.source_connection_id);
|
|
199
212
|
if (bridge) bridgeName = bridge.name;
|
|
200
213
|
}
|
|
201
214
|
|
|
@@ -289,6 +302,8 @@ function PathsInner() {
|
|
|
289
302
|
)}
|
|
290
303
|
</div>
|
|
291
304
|
</header>
|
|
305
|
+
{/* The scan status bar sits here, under the header's buttons (ScanStatusBar.js). */}
|
|
306
|
+
<div id="scan-bar-slot" />
|
|
292
307
|
|
|
293
308
|
{!selectedCompany && tab !== 'companies' ? (
|
|
294
309
|
<PathsAnalyzer d1={d1Data} d2={d2Data} d3={d3Data} tab={tab}
|
|
@@ -380,7 +395,7 @@ function PathsInner() {
|
|
|
380
395
|
</div>
|
|
381
396
|
|
|
382
397
|
{level.people.map((p, pi) => (
|
|
383
|
-
<PathPersonRow key={p.id || pi} p={p} level={level} companyPeople={companyPeople}
|
|
398
|
+
<PathPersonRow key={p.id || pi} p={p} level={level} companyPeople={companyPeople} d1Urls={d1Urls} selectedCompany={selectedCompany} />
|
|
384
399
|
))}
|
|
385
400
|
</div>
|
|
386
401
|
))}
|
|
@@ -391,24 +406,18 @@ function PathsInner() {
|
|
|
391
406
|
);
|
|
392
407
|
}
|
|
393
408
|
|
|
394
|
-
function PathPersonRow({ p, level, companyPeople,
|
|
409
|
+
function PathPersonRow({ p, level, companyPeople, d1Urls, selectedCompany }) {
|
|
395
410
|
const [expanded, setExpanded] = useState(false);
|
|
396
411
|
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const s = getSeniority(op.headline);
|
|
402
|
-
return s.level > p.seniority.level && !d1Urls.has(op.profile_url || op.profileUrl);
|
|
403
|
-
});
|
|
404
|
-
const sameLevel = companyPeople.filter(op => {
|
|
405
|
-
const s = getSeniority(op.headline);
|
|
406
|
-
return s.level === p.seniority.level && op.id !== p.id;
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
// What happens if you add this person
|
|
412
|
+
// What happens if you add this person. Only worked out for a row that's open,
|
|
413
|
+
// since only an open row shows it: every row comparing itself with everyone
|
|
414
|
+
// at the company, each time the page drew, took minutes at a company of 1,900.
|
|
415
|
+
// Everyone's level is read once already (enrichPeople's seniority).
|
|
410
416
|
const unlockInsights = [];
|
|
411
|
-
if (!p.connected) {
|
|
417
|
+
if (expanded && !p.connected) {
|
|
418
|
+
const aboveMe = companyPeople.filter(op => op.seniority.level > p.seniority.level && !d1Urls.has(op.profile_url || op.profileUrl));
|
|
419
|
+
const sameLevel = companyPeople.filter(op => op.seniority.level === p.seniority.level && op.id !== p.id);
|
|
420
|
+
|
|
412
421
|
// How many people above would gain a mutual
|
|
413
422
|
const aboveCount = aboveMe.length;
|
|
414
423
|
if (aboveCount > 0) {
|
|
@@ -164,6 +164,8 @@ function ProfileInner() {
|
|
|
164
164
|
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
|
165
165
|
}}>Profile</h1>
|
|
166
166
|
</header>
|
|
167
|
+
{/* The scan status bar sits here, under the header's buttons (ScanStatusBar.js). */}
|
|
168
|
+
<div id="scan-bar-slot" />
|
|
167
169
|
|
|
168
170
|
<div style={{ flex: 1, overflow: 'auto', padding: '30px 20px' }}>
|
|
169
171
|
<div style={{ maxWidth: 700, margin: '0 auto' }}>
|
|
@@ -301,6 +301,8 @@ function QueueInner() {
|
|
|
301
301
|
))}
|
|
302
302
|
</div>
|
|
303
303
|
</header>
|
|
304
|
+
{/* The scan status bar sits here, under the header's buttons (ScanStatusBar.js). */}
|
|
305
|
+
<div id="scan-bar-slot" />
|
|
304
306
|
|
|
305
307
|
{/* Batch action bar — recs view only */}
|
|
306
308
|
{view === 'recs' && selected.size > 0 && (
|
|
@@ -56,6 +56,8 @@ function ScoresInner() {
|
|
|
56
56
|
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
|
57
57
|
}}>Scores</h1>
|
|
58
58
|
</header>
|
|
59
|
+
{/* The scan status bar sits here, under the header's buttons (ScanStatusBar.js). */}
|
|
60
|
+
<div id="scan-bar-slot" />
|
|
59
61
|
<main style={{ maxWidth: 980, margin: '0 auto', padding: '8px 24px 64px' }}>
|
|
60
62
|
<Body style={{ marginTop: 16 }}>
|
|
61
63
|
How someone’s power score is worked out, and the three things you can change about it:{' '}
|
|
@@ -73,6 +73,8 @@ function SettingsInner() {
|
|
|
73
73
|
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
|
74
74
|
}}>Settings</h1>
|
|
75
75
|
</header>
|
|
76
|
+
{/* The scan status bar sits here, under the header's buttons (ScanStatusBar.js). */}
|
|
77
|
+
<div id="scan-bar-slot" />
|
|
76
78
|
|
|
77
79
|
<main style={{ maxWidth: 720, margin: '0 auto', padding: '8px 24px 64px' }}>
|
|
78
80
|
{error && <Body style={{ color: '#ff7676', marginTop: 16 }}>{error}</Body>}
|
|
@@ -203,6 +203,8 @@ function SetupInner() {
|
|
|
203
203
|
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
|
204
204
|
}}>Scan your network</h1>
|
|
205
205
|
</header>
|
|
206
|
+
{/* The scan status bar sits here, under the header's buttons (ScanStatusBar.js). */}
|
|
207
|
+
<div id="scan-bar-slot" />
|
|
206
208
|
|
|
207
209
|
<div style={{ maxWidth: 720, margin: '0 auto', padding: '32px 24px 64px' }}>
|
|
208
210
|
|
|
@@ -448,9 +450,10 @@ function SetupInner() {
|
|
|
448
450
|
<input type="checkbox" checked={experimental} disabled={running} style={{ marginTop: 3 }}
|
|
449
451
|
onChange={(e) => { setExperimental(e.target.checked); window.dispatchEvent(new Event('six-degrees:experimental')); }} />
|
|
450
452
|
<span>
|
|
451
|
-
<b style={{ color: '#FFD700' }}>Experimental:</b> all-day pacing. Up to
|
|
452
|
-
then
|
|
453
|
-
|
|
453
|
+
<b style={{ color: '#FFD700' }}>Experimental:</b> all-day pacing. Up to 8 pages in a sitting,
|
|
454
|
+
then an hour’s rest; searches only from 9:00 to 18:00; never more than 40 searches in a day
|
|
455
|
+
or 200 in a week, however high your budget; two days’ rest after any check from LinkedIn; at
|
|
456
|
+
the budget it waits instead of stopping; and it saves every page. It also reads LinkedIn’s own data beside
|
|
454
457
|
the page text, to fill gaps and measure how the two compare. It keeps running while the app is open.
|
|
455
458
|
</span>
|
|
456
459
|
</label>
|