host: logged-in "add related" hint + filterable infinite-scroll relate picker
Make relating discoverable and pleasant: a hint on posts with no relations, and a real candidate picker on the edit page. - post page: when a post has no relations AND the viewer is logged in, show a subtle "No related posts yet — add some" hint linking to the edit page; anonymous viewers still see nothing. - GET /<slug>/relate-options?q=&offset= — SX endpoint returning one page of candidate rows (HTML <li> fragment): every post except itself and ones already related, narrowed by q (case-insensitive title/slug substring), title-sorted, paginated by host/blog--picker-limit. Public read; the relate POST stays guarded. - GET /relate-picker.js — small vanilla glue (debounced live filter + scroll-to-load-more) served from a route. The host serves static HTML (no SX island hydration), so the interactive layer is a cached script, not an island; data-slug on the input carries the post to it. - edit page: the plain "slug to relate" box becomes a filter input + scrollable results list (#relate-filter/#relate-results) populated by the script; each row is a one-click relate form. 8 tests: endpoint lists/excludes-self/filters-by-q/excludes-already-related, JS route content-type + glue, hint shown logged-in / hidden anonymous. 238/238. Verified live: hint (logged-in only), candidate rows, q=filter, JS route (node --check OK), edit picker UI with data-slug. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -249,6 +249,34 @@
|
||||
(contains? (host/blog-related "my-first-post") "another-one"))
|
||||
false)
|
||||
|
||||
;; -- relate picker (filterable candidate endpoint + glue + hint) --
|
||||
(host/blog-put! "alpha-post" "Alpha Post" "(p \"a\")" "published")
|
||||
(host/blog-put! "beta-post" "Beta Post" "(p \"b\")" "published")
|
||||
(host/blog-put! "gamma-post" "Gamma Post" "(p \"g\")" "published")
|
||||
(host-bl-test "relate-options lists other posts"
|
||||
(contains? (dream-resp-body (host-bl-app (host-bl-req "/alpha-post/relate-options"))) "Beta Post") true)
|
||||
(host-bl-test "relate-options excludes the post itself"
|
||||
(contains? (dream-resp-body (host-bl-app (host-bl-req "/alpha-post/relate-options"))) ">Alpha Post<") false)
|
||||
(host-bl-test "relate-options filters by q (title substring)"
|
||||
(let ((body (dream-resp-body (host-bl-app (host-bl-req "/alpha-post/relate-options?q=beta")))))
|
||||
(list (contains? body "Beta Post") (contains? body "Gamma Post")))
|
||||
(list true false))
|
||||
(host-bl-test "relate-options excludes already-related candidates"
|
||||
(begin
|
||||
(host/blog-relate! "alpha-post" "beta-post")
|
||||
(contains? (dream-resp-body (host-bl-app (host-bl-req "/alpha-post/relate-options"))) "Beta Post"))
|
||||
false)
|
||||
(host/blog-unrelate! "alpha-post" "beta-post")
|
||||
(host-bl-test "relate-picker.js served as javascript"
|
||||
(dream-resp-header (host-bl-app (host-bl-req "/relate-picker.js")) "content-type")
|
||||
"application/javascript; charset=utf-8")
|
||||
(host-bl-test "relate-picker.js carries the fetch glue"
|
||||
(contains? (dream-resp-body (host-bl-app (host-bl-req "/relate-picker.js"))) "relate-options") true)
|
||||
(host-bl-test "related block: hint when logged-in + no relations"
|
||||
(contains? (str (host/blog--related-block "gamma-post" true)) "add some") true)
|
||||
(host-bl-test "related block: empty when anonymous + no relations"
|
||||
(= (host/blog--related-block "gamma-post" false) "") true)
|
||||
|
||||
;; -- experimental unguarded create-only route (POST /new, no auth) --
|
||||
(define host-bl-oapp (host/make-app (list host/blog-open-create-routes host/blog-routes)))
|
||||
(host/blog-use-store! (persist/open))
|
||||
|
||||
Reference in New Issue
Block a user