diff --git a/appview/pages/repoinfo/repoinfo.go b/appview/pages/repoinfo/repoinfo.go
index f5b505bb..20121d65 100644
--- a/appview/pages/repoinfo/repoinfo.go
+++ b/appview/pages/repoinfo/repoinfo.go
@@ -1,6 +1,7 @@
package repoinfo
import (
+ "encoding/json"
"fmt"
"path"
"slices"
@@ -117,3 +118,48 @@ func (r RolesInRepo) IsCollaborator() bool {
func (r RolesInRepo) IsPushAllowed() bool {
return slices.Contains(r.Roles, "repo:push")
}
+
+// PrimaryLanguage returns the first (most used) language from a list, or empty string if none
+func PrimaryLanguage(languages []interface{}) string {
+ if len(languages) == 0 {
+ return ""
+ }
+
+ // Languages are already sorted by percentage in descending order
+ // Just get the first one
+ if firstLang, ok := languages[0].(map[string]interface{}); ok {
+ if name, ok := firstLang["Name"].(string); ok {
+ return name
+ }
+ }
+
+ return ""
+}
+
+// StructuredData generates Schema.org JSON-LD structured data for the repository
+func (r RepoInfo) StructuredData(primaryLanguage string) string {
+ data := map[string]interface{}{
+ "@context": "https://schema.org",
+ "@type": "SoftwareSourceCode",
+ "name": r.Name,
+ "description": r.Description,
+ "codeRepository": "https://tangled.org/" + r.FullName(),
+ "url": "https://tangled.org/" + r.FullName(),
+ "author": map[string]interface{}{
+ "@type": "Person",
+ "name": r.owner(),
+ "url": "https://tangled.org/" + r.owner(),
+ },
+ }
+
+ // Add programming language if available
+ if primaryLanguage != "" {
+ data["programmingLanguage"] = primaryLanguage
+ }
+
+ jsonBytes, err := json.Marshal(data)
+ if err != nil {
+ return "{}"
+ }
+ return string(jsonBytes)
+}
diff --git a/appview/pages/templates/fragments/breadcrumb.html b/appview/pages/templates/fragments/breadcrumb.html
new file mode 100644
index 00000000..68297379
--- /dev/null
+++ b/appview/pages/templates/fragments/breadcrumb.html
@@ -0,0 +1,22 @@
+{{ define "fragments/breadcrumb" }}
+ {{ $items := . }}
+ {{ if gt (len $items) 0 }}
+
+ {{ end }}
+{{ end }}
\ No newline at end of file
diff --git a/appview/pages/templates/fragments/dolly/logo.svg b/appview/pages/templates/fragments/dolly/logo.svg
new file mode 100644
index 00000000..a3f1891d
--- /dev/null
+++ b/appview/pages/templates/fragments/dolly/logo.svg
@@ -0,0 +1,44 @@
+
diff --git a/appview/pages/templates/goodfirstissues/index.html b/appview/pages/templates/goodfirstissues/index.html
index b4fa8dd1..255b6b84 100644
--- a/appview/pages/templates/goodfirstissues/index.html
+++ b/appview/pages/templates/goodfirstissues/index.html
@@ -1,12 +1,37 @@
{{ define "title" }}good first issues{{ end }}
{{ define "extrameta" }}
+
+
+
-
+
-
+
+
+
+
+
+
+
+
{{ end }}
+{{ define "canonical" }}https://tangled.org/goodfirstissues{{ end }}
+
{{ define "content" }}
diff --git a/appview/pages/templates/layouts/base.html b/appview/pages/templates/layouts/base.html
index f357201d..be098908 100644
--- a/appview/pages/templates/layouts/base.html
+++ b/appview/pages/templates/layouts/base.html
@@ -4,8 +4,13 @@
-
+
+
+
+
+
+
@@ -15,6 +20,9 @@
+
+ {{ block "rss" . }}{{ end }}
+
@@ -22,7 +30,23 @@
- {{ block "title" . }}{{ end }} · tangled
+ {{ block "title" . }}{{ end }}
+
+
+ {{ block "structuredData" . }}
+
+ {{ end }}
+
{{ block "extrameta" . }}{{ end }}
diff --git a/appview/pages/templates/layouts/profilebase.html b/appview/pages/templates/layouts/profilebase.html
index 0747a39f..3a646a7d 100644
--- a/appview/pages/templates/layouts/profilebase.html
+++ b/appview/pages/templates/layouts/profilebase.html
@@ -10,11 +10,30 @@
-
+
+
+
+
+{{ end }}
+
+{{ define "canonical" }}https://tangled.org/{{ or .Card.UserHandle .Card.UserDid }}{{ end }}
+
+{{ define "rss" }}
+
{{ end }}
{{ define "content" }}
diff --git a/appview/pages/templates/repo/index.html b/appview/pages/templates/repo/index.html
index 1144e8d1..605bfa7e 100644
--- a/appview/pages/templates/repo/index.html
+++ b/appview/pages/templates/repo/index.html
@@ -5,6 +5,42 @@
{{ template "repo/fragments/meta" . }}
{{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo) }}
+
+
+
+
+
+ {{ template "fragments/breadcrumb" (list
+ (list "Home" "https://tangled.org")
+ (list .RepoInfo.OwnerWithAt (printf "https://tangled.org/%s" .RepoInfo.OwnerWithAt))
+ (list .RepoInfo.Name (printf "https://tangled.org/%s" .RepoInfo.FullName))
+ ) }}
+{{ end }}
+
+{{ define "canonical" }}https://tangled.org/{{ .RepoInfo.FullName }}{{ end }}
+
+{{ define "rss" }}
+
{{ end }}
{{ define "repoContent" }}
diff --git a/appview/pages/templates/repo/issues/fragments/og.html b/appview/pages/templates/repo/issues/fragments/og.html
deleted file mode 100644
index 3e1794a7..00000000
--- a/appview/pages/templates/repo/issues/fragments/og.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{{ define "repo/issues/fragments/og" }}
- {{ $title := printf "%s #%d" .Issue.Title .Issue.IssueId }}
- {{ $description := or .Issue.Body .RepoInfo.Description }}
- {{ $url := printf "https://tangled.org/%s/issues/%d" .RepoInfo.FullName .Issue.IssueId }}
- {{ $imageUrl := printf "https://tangled.org/%s/issues/%d/opengraph" .RepoInfo.FullName .Issue.IssueId }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{ end }}
diff --git a/appview/pages/templates/repo/pulls/fragments/og.html b/appview/pages/templates/repo/pulls/fragments/og.html
index 58f25035..ad8a00e8 100644
--- a/appview/pages/templates/repo/pulls/fragments/og.html
+++ b/appview/pages/templates/repo/pulls/fragments/og.html
@@ -1,19 +1,19 @@
-{{ define "repo/pulls/fragments/og" }}
- {{ $title := printf "%s #%d" .Pull.Title .Pull.PullId }}
- {{ $description := or .Pull.Body .RepoInfo.Description }}
- {{ $url := printf "https://tangled.org/%s/pulls/%d" .RepoInfo.FullName .Pull.PullId }}
- {{ $imageUrl := printf "https://tangled.org/%s/pulls/%d/opengraph" .RepoInfo.FullName .Pull.PullId }}
+ {{ define "pulls/fragments/og" }}
+ {{ $title := printf "%s #%d" .Pull.Title .Pull.PullId }}
+ {{ $description := or .Pull.Body .RepoInfo.Description }}
+ {{ $url := printf "https://tangled.org/%s/pulls/%d" .RepoInfo.FullName .Pull.PullId }}
+ {{ $imageUrl := printf "https://tangled.org/%s/pulls/%d/opengraph" .RepoInfo.FullName .Pull.PullId }}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
{{ end }}
diff --git a/appview/pages/templates/timeline/home.html b/appview/pages/templates/timeline/home.html
index febf520e..6e098d82 100644
--- a/appview/pages/templates/timeline/home.html
+++ b/appview/pages/templates/timeline/home.html
@@ -1,12 +1,58 @@
{{ define "title" }}tangled · tightly-knit social coding{{ end }}
{{ define "extrameta" }}
-
-
+ {{ $desc := "Collaborate on code with decentralized git hosting, modern contribution and review workflows, and lightweight CI/CD pipelines." }}
+ {{ $title = "tangled · tightly-knit social coding" }}
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ end }}
+{{ define "canonical" }}https://tangled.org{{ end }}
+
{{ define "content" }}
diff --git a/appview/state/router.go b/appview/state/router.go
index 968ed403..9b792e11 100644
--- a/appview/state/router.go
+++ b/appview/state/router.go
@@ -36,6 +36,7 @@ func (s *State) Router() http.Handler {
router.Get("/favicon.ico", s.Favicon)
router.Get("/pwa-manifest.json", s.PWAManifest)
router.Get("/robots.txt", s.RobotsTxt)
+ router.Get("/sitemap.xml", s.Sitemap)
userRouter := s.UserRouter(&middleware)
standardRouter := s.StandardRouter(&middleware)
diff --git a/appview/state/state.go b/appview/state/state.go
index aeef9f78..adc673d9 100644
--- a/appview/state/state.go
+++ b/appview/state/state.go
@@ -220,10 +220,63 @@ func (s *State) RobotsTxt(w http.ResponseWriter, r *http.Request) {
robotsTxt := `User-agent: *
Allow: /
+Disallow: /settings
+Disallow: /notifications
+Disallow: /login
+Disallow: /logout
+Disallow: /signup
+Disallow: /oauth
+Disallow: */settings$
+Disallow: */settings/*
+
+Crawl-delay: 1
+
+Sitemap: https://tangled.org/sitemap.xml
`
w.Write([]byte(robotsTxt))
}
+func (s *State) Sitemap(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/xml; charset=utf-8")
+ w.Header().Set("Cache-Control", "public, max-age=3600")
+
+ // basic sitemap with static pages
+ sitemap := `
+
+
+ https://tangled.org
+ daily
+ 1.0
+
+
+ https://tangled.org/timeline
+ hourly
+ 0.9
+
+
+ https://tangled.org/goodfirstissues
+ daily
+ 0.8
+
+
+ https://tangled.org/terms
+ monthly
+ 0.3
+
+
+ https://tangled.org/privacy
+ monthly
+ 0.3
+
+
+ https://tangled.org/brand
+ monthly
+ 0.5
+
+`
+ w.Write([]byte(sitemap))
+}
+
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest
const manifestJson = `{
"name": "tangled",