feat: internal links widget with counts and suggestions

- E10 golem15-seo-links in the SEO tab after the graph preview (types
  with a body): inbound and outbound counts from the last finished crawl,
  the orphan warning, up to 10 pages to link to (Copy address) and 10
  that could link here
- classes/linkgraph.go builds the report from the crawl's pages and
  links; the crawler's orphan suggestions share NormalText
- GET /_seo/api/v1/links and seo_get_link_suggestions; widget action
  seo_links mapped in AdminOperations
- element bundle rebuilt
This commit is contained in:
Jakub Zych
2026-11-04 16:40:16 +01:00
parent ab4e83416d
commit 49cc241f58
20 changed files with 1131 additions and 12 deletions

33
controllers/api/links.go Normal file
View File

@@ -0,0 +1,33 @@
package api
import (
"context"
"net/http"
"git.golem15.com/golem15/sm-seo-plugin/classes"
)
// GetLinksInput is the query of GET /_seo/api/v1/links.
type GetLinksInput struct {
URL string `json:"url" query:"url" jsonschema:"the page: a path such as /blog/post or an absolute URL of the site"`
}
// Links serves GET /_seo/api/v1/links?url= (D-82, UI-SPEC C19): the
// page's internal links from the last finished crawl, {crawledAt,
// inbound, outbound, orphan, out [{phrase, title, url}], in [{phrase,
// title, adminUrl}]}; before the first crawl crawledAt, inbound and
// outbound are null and the lists empty. A url that is not a path or an
// http(s) URL is 422.
func Links(service func() *classes.Service) http.HandlerFunc {
return manage(service, func(ctx context.Context, svc *classes.Service, r *http.Request) (int, any, error) {
db, err := read(ctx, svc)
if err != nil {
return 0, nil, err
}
report, err := svc.LinksOfURL(ctx, db, r.URL.Query().Get("url"))
if err != nil {
return 0, nil, err
}
return http.StatusOK, report, nil
})
}