fedipod 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/architecture.svg +89 -0
- package/bin/fedipod.mjs +2303 -0
- package/cli.md +243 -0
- package/groups.md +85 -0
- package/gui.md +99 -0
- package/lib/account.mjs +87 -0
- package/lib/admin.mjs +1606 -0
- package/lib/atproto.mjs +310 -0
- package/lib/bskyfeed.mjs +166 -0
- package/lib/bskygroup.mjs +162 -0
- package/lib/c2s.mjs +305 -0
- package/lib/certs.mjs +265 -0
- package/lib/deliver.mjs +249 -0
- package/lib/directory.mjs +90 -0
- package/lib/embed.mjs +214 -0
- package/lib/export-collections.mjs +161 -0
- package/lib/front-core.mjs +322 -0
- package/lib/gateway-core.mjs +115 -0
- package/lib/guard.mjs +213 -0
- package/lib/home.mjs +161 -0
- package/lib/httpsig.mjs +109 -0
- package/lib/import.mjs +357 -0
- package/lib/intake.mjs +1710 -0
- package/lib/keys.mjs +139 -0
- package/lib/lease.mjs +190 -0
- package/lib/localapi.mjs +87 -0
- package/lib/mastoapi.mjs +1588 -0
- package/lib/migrate.mjs +127 -0
- package/lib/oidc-auth.mjs +53 -0
- package/lib/podrdf.mjs +132 -0
- package/lib/ports.mjs +27 -0
- package/lib/publisher.mjs +1003 -0
- package/lib/remote.mjs +303 -0
- package/lib/safefetch.mjs +235 -0
- package/lib/setup.mjs +262 -0
- package/lib/social.mjs +505 -0
- package/lib/storage.mjs +170 -0
- package/lib/store.mjs +470 -0
- package/lib/streaming.mjs +154 -0
- package/lib/tagfeed.mjs +158 -0
- package/lib/ua.mjs +29 -0
- package/lib/update.mjs +91 -0
- package/lib/webpush.mjs +98 -0
- package/lib/wire.mjs +762 -0
- package/package.json +60 -0
- package/phanpy/dist/404.html +40 -0
- package/phanpy/dist/_headers +2 -0
- package/phanpy/dist/apple-touch-icon.png +0 -0
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js +2 -0
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +1 -0
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +183 -0
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
- package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +2 -0
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +1 -0
- package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
- package/phanpy/dist/assets/chunk-aKtaBQYM.js +1 -0
- package/phanpy/dist/assets/compose-CFEx7wb4.js +2 -0
- package/phanpy/dist/assets/compose-CFEx7wb4.js.map +1 -0
- package/phanpy/dist/assets/compose-ZJN5xSb4.js +35 -0
- package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +1 -0
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js +52 -0
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +1 -0
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js +2 -0
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +1 -0
- package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +1 -0
- package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +1 -0
- package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
- package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +1 -0
- package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
- package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
- package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
- package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
- package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
- package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
- package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
- package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
- package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +1 -0
- package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +1 -0
- package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
- package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +1 -0
- package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
- package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +1 -0
- package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
- package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
- package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +1 -0
- package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
- package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +1 -0
- package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
- package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
- package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
- package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
- package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
- package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +1 -0
- package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +1 -0
- package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +1 -0
- package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +1 -0
- package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +1 -0
- package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
- package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +1 -0
- package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
- package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +1 -0
- package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +1 -0
- package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +1 -0
- package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
- package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
- package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +1 -0
- package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +1 -0
- package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
- package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
- package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +1 -0
- package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +1 -0
- package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +1 -0
- package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
- package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +1 -0
- package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +1 -0
- package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +1 -0
- package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
- package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
- package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
- package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +1 -0
- package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +1 -0
- package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +1 -0
- package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +1 -0
- package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +1 -0
- package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
- package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
- package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +1 -0
- package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +1 -0
- package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +1 -0
- package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +1 -0
- package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +1 -0
- package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +1 -0
- package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +1 -0
- package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +1 -0
- package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
- package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +1 -0
- package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +1 -0
- package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +1 -0
- package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
- package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +1 -0
- package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +1 -0
- package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
- package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
- package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
- package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +1 -0
- package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +1 -0
- package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
- package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +1 -0
- package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +1 -0
- package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +1 -0
- package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +1 -0
- package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +1 -0
- package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +1 -0
- package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +1 -0
- package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +1 -0
- package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +1 -0
- package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
- package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +1 -0
- package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
- package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
- package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
- package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +1 -0
- package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
- package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +1 -0
- package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +1 -0
- package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
- package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +1 -0
- package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
- package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +1 -0
- package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
- package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +1 -0
- package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +1 -0
- package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
- package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +1 -0
- package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +1 -0
- package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
- package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +1 -0
- package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +1 -0
- package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +1 -0
- package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
- package/phanpy/dist/assets/instances-BNPptnn-.json +333 -0
- package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +1 -0
- package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
- package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +1 -0
- package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +1 -0
- package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +1 -0
- package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +1 -0
- package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +1 -0
- package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +1 -0
- package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +1 -0
- package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +1 -0
- package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +1 -0
- package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +1 -0
- package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +1 -0
- package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +1 -0
- package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
- package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
- package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +1 -0
- package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
- package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +1 -0
- package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +1 -0
- package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
- package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/tok-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +1 -0
- package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +1 -0
- package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/main-BdqNbG-a.js +26 -0
- package/phanpy/dist/assets/main-BdqNbG-a.js.map +1 -0
- package/phanpy/dist/assets/mock-home-DGhR__He.js +2 -0
- package/phanpy/dist/assets/mock-home-DGhR__He.js.map +1 -0
- package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
- package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +2 -0
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +1 -0
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +20 -0
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +1 -0
- package/phanpy/dist/assets/style-B1iLPTPR.css +2 -0
- package/phanpy/dist/assets/temml-DuyIQMei.js +97 -0
- package/phanpy/dist/assets/temml-DuyIQMei.js.map +1 -0
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +2 -0
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
- package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
- package/phanpy/dist/assets/year-in-posts-DJv_star.js +3 -0
- package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +1 -0
- package/phanpy/dist/compose/index.html +20 -0
- package/phanpy/dist/favicon.ico +0 -0
- package/phanpy/dist/index.html +73 -0
- package/phanpy/dist/logo-192.png +0 -0
- package/phanpy/dist/logo-512.png +0 -0
- package/phanpy/dist/logo-badge-72.png +0 -0
- package/phanpy/dist/logo-maskable-512.png +0 -0
- package/phanpy/dist/logo-monochrome-512.png +0 -0
- package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
- package/phanpy/dist/manifest.webmanifest +1 -0
- package/phanpy/dist/og-image-2.jpg +0 -0
- package/phanpy/dist/og-image.png +0 -0
- package/phanpy/dist/robots.txt +2 -0
- package/phanpy/dist/sw.js +25 -0
- package/phanpy/dist/version.json +1 -0
- package/run-agent.mjs +805 -0
- package/scripts/build-dist.mjs +37 -0
- package/scripts/verify-ui.mjs +60 -0
- package/ui/README.md +23 -0
- package/vendor/gate.cjs +132 -0
- package/vendor/idp-grant.cjs +394 -0
- package/vendor/jfetch.cjs +16 -0
- package/web/admin/admin.js +958 -0
- package/web/admin/bar.css +64 -0
- package/web/admin/bar.js +61 -0
- package/web/admin/client/client.js +66 -0
- package/web/admin/client/index.html +42 -0
- package/web/admin/index.html +493 -0
- package/web/admin/setup/index.html +172 -0
- package/web/admin/setup/setup.js +268 -0
- package/web/admin/window.css +55 -0
- package/web/admin/window.js +136 -0
- package/web/front/install.sh +69 -0
- package/web/front/new-account.html +424 -0
- package/web/front/run.html +138 -0
- package/web/front/solid-client-authn.bundle.js +2 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* The bar every page of ours carries — the record, the setup form and the
|
|
2
|
+
client view. One file so the three cannot drift apart. */
|
|
3
|
+
/* One column, 960px, centred — the bar included, so the title sits over the
|
|
4
|
+
page's left edge and the buttons over its right rather than out at the window
|
|
5
|
+
borders. `width: 100%` with `box-sizing: border-box` keeps the padding inside
|
|
6
|
+
that 960 instead of adding to it, which is what makes the two margins equal. */
|
|
7
|
+
/* Phanpy's own colours, read off the running client. --surface is the one you
|
|
8
|
+
see behind Phanpy — its --bg-faded-color — and that is the PAGE. --raised is
|
|
9
|
+
Phanpy's --bg-color, and that is the bar sitting on top of it. Declared here
|
|
10
|
+
because bar.css is on all three pages, so they cannot drift. */
|
|
11
|
+
:root { --surface: #f0f2f5; --raised: #fff;
|
|
12
|
+
--ink: #1b1c1e; --field-bg: #ffffff; --field-bg-hover: #f7f4ee; --field-edge: #767676;
|
|
13
|
+
--chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23444444' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
|
|
14
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
:root { --surface: #18191a; --raised: #242526;
|
|
16
|
+
--ink: #e8eaed; --field-bg: #383d44; --field-bg-hover: #40464e; --field-edge: #9aa0a6;
|
|
17
|
+
--chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23dddddd' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
|
|
18
|
+
}
|
|
19
|
+
body { background: var(--surface); }
|
|
20
|
+
/* The BAND spans the window; its CONTENTS stay in the 960 column. Constraining
|
|
21
|
+
the element itself left the colour as a stripe with bare page either side.
|
|
22
|
+
The inline padding is the column's own margin plus the 1rem the page uses
|
|
23
|
+
inside it, so the title lands exactly on the page's left edge — and `max`
|
|
24
|
+
keeps it at 1rem once the window is narrower than the column.
|
|
25
|
+
No rule underneath: the tone change is the boundary, and a line as well
|
|
26
|
+
would be drawing it twice. */
|
|
27
|
+
/* Wraps, because it does not fit: at 320px the title plus three destinations
|
|
28
|
+
ran 67px past the viewport, and the bar is on every page — so the whole
|
|
29
|
+
document scrolled sideways whatever was under it. */
|
|
30
|
+
#bar { flex: 0 0 auto; display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
|
|
31
|
+
width: 100%; max-width: none; margin: 0; box-sizing: border-box;
|
|
32
|
+
/* The column width is the page's to declare (--bar-col); 960 stands where a
|
|
33
|
+
page says nothing. */
|
|
34
|
+
padding: .5rem max(1rem, calc((100% - var(--bar-col, 960px)) / 2 + 1rem));
|
|
35
|
+
background: var(--raised); }
|
|
36
|
+
/* The actor's fediverse handle, in the row between the title and the
|
|
37
|
+
destinations. */
|
|
38
|
+
#bar-handle { color: var(--heading); font-size: .95rem; margin-right: .6rem; }
|
|
39
|
+
#bar-handle:empty { display: none; }
|
|
40
|
+
/* The actors dropdown, where a page's bar carries one instead of the static
|
|
41
|
+
handle. Styled here so every page's bar shows the same control: the same
|
|
42
|
+
light well and drawn caret every setting on the record page uses. */
|
|
43
|
+
#bar select { font: inherit; font-size: .95rem; max-width: min(24rem, 55vw);
|
|
44
|
+
margin-right: .6rem; appearance: none; min-width: 0;
|
|
45
|
+
padding: .15rem 1.7rem .15rem .55rem;
|
|
46
|
+
border: 1px solid var(--field-edge); border-radius: .3rem;
|
|
47
|
+
color: var(--ink); cursor: pointer;
|
|
48
|
+
background-color: var(--field-bg); background-image: var(--chevron);
|
|
49
|
+
background-repeat: no-repeat; background-position: right .55rem center;
|
|
50
|
+
background-size: .6rem auto; }
|
|
51
|
+
#bar select:hover { background-color: var(--field-bg-hover); }
|
|
52
|
+
/* The app title, in px rather than rem: it is chrome, the same on every page,
|
|
53
|
+
not something that should move when a page changes its base size. */
|
|
54
|
+
#bar .name { flex: 1 1 auto; font-weight: 600; font-size: 24px; color: var(--heading);
|
|
55
|
+
margin: 0; line-height: 1.1; }
|
|
56
|
+
/* Plain text, not chrome: three destinations in a row read better without a
|
|
57
|
+
box round each. */
|
|
58
|
+
#bar button, #bar a { font: inherit; font-size: .95rem; padding: .15rem .2rem;
|
|
59
|
+
border: 0; background: none; color: var(--link); cursor: pointer;
|
|
60
|
+
text-decoration: none; }
|
|
61
|
+
/* The page you are already on stays in the row, so the set never moves — it is
|
|
62
|
+
underlined rather than removed. */
|
|
63
|
+
#bar button[aria-current="page"], #bar a[aria-current="page"] {
|
|
64
|
+
text-decoration: underline; text-underline-offset: .3rem; }
|
package/web/admin/bar.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// bar.js — the three destinations, shared by the record, the setup form and the
|
|
2
|
+
// client view, so they cannot drift apart. Nothing here touches the client in
|
|
3
|
+
// the frame: it is this agent's own root, and framing it is the whole of the
|
|
4
|
+
// relationship.
|
|
5
|
+
|
|
6
|
+
(() => {
|
|
7
|
+
// The three destinations are <a href> now: they are navigations, so they
|
|
8
|
+
// belong in a screen reader's links list, and middle-click and open-in-new-tab
|
|
9
|
+
// work the way they do everywhere else. Nothing here has to drive them.
|
|
10
|
+
//
|
|
11
|
+
// One exception, and it keeps its href as the fallback: on the record page
|
|
12
|
+
// `add new account` opens the form IN PLACE rather than reloading. admin.js
|
|
13
|
+
// binds that and calls preventDefault; without the form it is a plain link to
|
|
14
|
+
// ?new=1, which opens it on arrival.
|
|
15
|
+
|
|
16
|
+
// The bar says the product; the TAB says which actor, which is what tells two
|
|
17
|
+
// open windows apart without putting the handle in the page twice.
|
|
18
|
+
// /status, not /config: an agent that is not set up yet answers this one and
|
|
19
|
+
// 409s the other, which logged a failed request on every setup page.
|
|
20
|
+
fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status').then(r => (r.ok ? r.json() : null)).then((s) => {
|
|
21
|
+
if (s?.handle) document.title = `FediPod — ${s.handle}`;
|
|
22
|
+
// The full fediverse handle, centred in the bar on every page.
|
|
23
|
+
const el = document.getElementById('bar-handle');
|
|
24
|
+
if (el && s?.handle && s?.actor) {
|
|
25
|
+
try { el.textContent = `@${s.handle}@${new URL(s.actor).host}`; } catch { /* odd actor url */ }
|
|
26
|
+
}
|
|
27
|
+
}).catch(() => { /* not up yet; the bar still works */ });
|
|
28
|
+
|
|
29
|
+
// The actors dropdown, on a page whose bar carries one. The record page
|
|
30
|
+
// wires its own richer version (it can start a stopped actor and open the
|
|
31
|
+
// new-account form in place), so this one stands down there.
|
|
32
|
+
const pick = document.getElementById('actor-pick');
|
|
33
|
+
if (pick && !document.getElementById('new-actor-form')) {
|
|
34
|
+
const base = location.pathname.replace(/\/admin\/.*$/u, '');
|
|
35
|
+
fetch(base + '/profiles').then(r => (r.ok ? r.json() : null)).then((j) => {
|
|
36
|
+
const actors = j?.identities || [];
|
|
37
|
+
pick.textContent = '';
|
|
38
|
+
const label = (r) => (r.address || r.handle || r.name)
|
|
39
|
+
+ (r.mode && r.mode !== 'active' ? ` (${r.mode})` : '') + (r.mode ? '' : ' (stopped)');
|
|
40
|
+
for (const [i, r] of actors.entries()) {
|
|
41
|
+
const o = document.createElement('option');
|
|
42
|
+
o.value = String(i);
|
|
43
|
+
o.selected = !!r.current;
|
|
44
|
+
o.textContent = label(r);
|
|
45
|
+
pick.appendChild(o);
|
|
46
|
+
}
|
|
47
|
+
const add = document.createElement('option');
|
|
48
|
+
add.value = '__add';
|
|
49
|
+
add.textContent = '+ add a new account…';
|
|
50
|
+
pick.appendChild(add);
|
|
51
|
+
pick.addEventListener('change', () => {
|
|
52
|
+
if (pick.value === '__add') { location.href = base + '/admin/?new=1'; return; }
|
|
53
|
+
const r2 = actors[Number(pick.value)];
|
|
54
|
+
if (!r2 || r2.current) return;
|
|
55
|
+
// A running actor's client; a stopped one is started from its record.
|
|
56
|
+
if (r2.mode && r2.admin) location.href = r2.admin + 'client/';
|
|
57
|
+
else if (r2.admin) location.href = r2.admin;
|
|
58
|
+
});
|
|
59
|
+
}).catch(() => { pick.hidden = true; });
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// client.js — make the framed client be THIS agent's actor.
|
|
2
|
+
//
|
|
3
|
+
// The client is a general fediverse client: it keeps a list of accounts in
|
|
4
|
+
// localStorage and one of them is current. Nothing makes that one agree with
|
|
5
|
+
// the agent serving the page. A leftover account from another identity wins,
|
|
6
|
+
// and you get somebody else's timeline on this actor's page, with the profile
|
|
7
|
+
// editor pointed at a host that cannot answer.
|
|
8
|
+
//
|
|
9
|
+
// Per-origin storage is what keeps identities apart, and it does work — each
|
|
10
|
+
// actor answers at <handle>.localhost:<port> and each origin has its own
|
|
11
|
+
// accounts list. What was missing is the other half: telling the client which
|
|
12
|
+
// account it is meant to be showing.
|
|
13
|
+
//
|
|
14
|
+
// The frame's markup declares what it loads (`src="/"`), and that does not
|
|
15
|
+
// change. This navigates the loaded app to its own login route, which is app
|
|
16
|
+
// navigation, not an include: `?instance=` prefills and `?submit=` runs it, so
|
|
17
|
+
// the round trip needs no typing. Hash-routed, so the query lives after the
|
|
18
|
+
// hash; `submit` must be non-empty, because the empty string is falsy and the
|
|
19
|
+
// auto-submit never fires.
|
|
20
|
+
|
|
21
|
+
(async () => {
|
|
22
|
+
const frame = document.getElementById('client');
|
|
23
|
+
if (!frame) return;
|
|
24
|
+
|
|
25
|
+
// Who this agent is, in the form the client stores: `info.uri` is the actor
|
|
26
|
+
// URI on the pod. Matching on that rather than on the origin is deliberate —
|
|
27
|
+
// "which account logged in here" is a different question, and it is the one
|
|
28
|
+
// that goes wrong when the two origins get mixed.
|
|
29
|
+
const base = location.pathname.replace(/\/admin\/.*$/u, '');
|
|
30
|
+
const status = await fetch(base + '/status').then(r => (r.ok ? r.json() : null)).catch(() => null);
|
|
31
|
+
if (!status?.actor) return; // not set up yet; setup owns that
|
|
32
|
+
|
|
33
|
+
// A deep link into the client — the record links an actor's own page this way
|
|
34
|
+
// — arrives as a hash on THIS page, and the frame never sees it. Forward it,
|
|
35
|
+
// so the link lands where it says while keeping the bar above it.
|
|
36
|
+
const wanted = location.hash && location.hash !== '#' ? location.hash : null;
|
|
37
|
+
const sendTo = (hash) => {
|
|
38
|
+
try { frame.contentWindow.location.hash = hash; } catch { /* not ready yet */ }
|
|
39
|
+
};
|
|
40
|
+
const whenReady = (fn) => {
|
|
41
|
+
if (frame.contentWindow?.location?.href && frame.contentWindow.location.href !== 'about:blank') fn();
|
|
42
|
+
else frame.addEventListener('load', fn, { once: true });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
let accounts = [];
|
|
46
|
+
try { accounts = JSON.parse(localStorage.getItem('accounts') || '[]'); } catch { /* unreadable */ }
|
|
47
|
+
const ours = accounts.find(a => a?.info?.uri === status.actor);
|
|
48
|
+
if (ours) {
|
|
49
|
+
// A stored login can be dead — its token revoked or minted by a run that
|
|
50
|
+
// never finished. Trusting it shows an empty client with no way out, so
|
|
51
|
+
// ask the facade first; a dead one falls through to the same one-trip
|
|
52
|
+
// login a missing account gets.
|
|
53
|
+
const alive = ours.accessToken && await fetch('/api/v1/accounts/verify_credentials', {
|
|
54
|
+
headers: { authorization: `Bearer ${ours.accessToken}` },
|
|
55
|
+
}).then(r => r.ok).catch(() => false);
|
|
56
|
+
if (alive) {
|
|
57
|
+
if (wanted) whenReady(() => sendTo(wanted));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Same origin, so the frame's own host is the instance to log into. A deep
|
|
63
|
+
// link is dropped here on purpose: logging in navigates the frame anyway, and
|
|
64
|
+
// arriving somewhere unexpected afterwards is worse than arriving home.
|
|
65
|
+
whenReady(() => sendTo(`#/login?instance=${encodeURIComponent(location.host)}&submit=1`));
|
|
66
|
+
})();
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>FediPod — client</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: light dark; font-size: 112.5%; --link: #1a4f8a; --heading: #5b3a8e; }
|
|
9
|
+
/* The bar is fixed height, the client takes the rest — the page never scrolls,
|
|
10
|
+
the frame does. */
|
|
11
|
+
html { height: 100%; }
|
|
12
|
+
body { font: 1rem/1.5 system-ui, sans-serif; margin: 0; height: 100dvh;
|
|
13
|
+
display: flex; flex-direction: column; overflow: hidden; }
|
|
14
|
+
main { flex: 1 1 auto; min-height: 0; display: flex; }
|
|
15
|
+
#client { flex: 1 1 auto; min-height: 0; width: 100%; border: 0; }
|
|
16
|
+
@media (prefers-color-scheme: dark) {
|
|
17
|
+
:root { --link: #7fb3e8; --heading: #b9a3e3; }
|
|
18
|
+
}
|
|
19
|
+
</style>
|
|
20
|
+
<link rel="stylesheet" href="../bar.css">
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
|
|
24
|
+
<!-- The same bar the record carries, so the two surfaces are one thing with two
|
|
25
|
+
views. The client below is this agent's own, at its own root: same origin,
|
|
26
|
+
which is why framing it needs nothing opened beyond frame-ancestors 'self'. -->
|
|
27
|
+
<nav id="bar" aria-label="Site">
|
|
28
|
+
<h1 class="name">FediPod</h1>
|
|
29
|
+
<select id="actor-pick" aria-label="Local actors"
|
|
30
|
+
title=" Every actor on this machine; choosing one opens its client"></select>
|
|
31
|
+
<a id="bar-fediverse" href="./" title=" Open the client and read your timeline" aria-current="page">visit account</a>
|
|
32
|
+
<a id="bar-manage" href="../" title=" The record: what this actor is, and what you can do to it">manage account</a>
|
|
33
|
+
</nav>
|
|
34
|
+
|
|
35
|
+
<main>
|
|
36
|
+
<iframe id="client" src="../../" title="fediverse client"></iframe>
|
|
37
|
+
</main>
|
|
38
|
+
|
|
39
|
+
<script src="../bar.js"></script>
|
|
40
|
+
<script src="client.js"></script>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|