Signed-off-by: Anirudh Oppiliappan anirudh@tangled.org
+46
appview/pages/repoinfo/repoinfo.go
+46
appview/pages/repoinfo/repoinfo.go
···
1
1
package repoinfo
2
2
3
3
import (
4
+
"encoding/json"
4
5
"fmt"
5
6
"path"
6
7
"slices"
···
117
118
func (r RolesInRepo) IsPushAllowed() bool {
118
119
return slices.Contains(r.Roles, "repo:push")
119
120
}
121
+
122
+
// PrimaryLanguage returns the first (most used) language from a list, or empty string if none
123
+
func PrimaryLanguage(languages []interface{}) string {
124
+
if len(languages) == 0 {
125
+
return ""
126
+
}
127
+
128
+
// Languages are already sorted by percentage in descending order
129
+
// Just get the first one
130
+
if firstLang, ok := languages[0].(map[string]interface{}); ok {
131
+
if name, ok := firstLang["Name"].(string); ok {
132
+
return name
133
+
}
134
+
}
135
+
136
+
return ""
137
+
}
138
+
139
+
// StructuredData generates Schema.org JSON-LD structured data for the repository
140
+
func (r RepoInfo) StructuredData(primaryLanguage string) string {
141
+
data := map[string]interface{}{
142
+
"@context": "https://schema.org",
143
+
"@type": "SoftwareSourceCode",
144
+
"name": r.Name,
145
+
"description": r.Description,
146
+
"codeRepository": "https://tangled.org/" + r.FullName(),
147
+
"url": "https://tangled.org/" + r.FullName(),
148
+
"author": map[string]interface{}{
149
+
"@type": "Person",
150
+
"name": r.owner(),
151
+
"url": "https://tangled.org/" + r.owner(),
152
+
},
153
+
}
154
+
155
+
// Add programming language if available
156
+
if primaryLanguage != "" {
157
+
data["programmingLanguage"] = primaryLanguage
158
+
}
159
+
160
+
jsonBytes, err := json.Marshal(data)
161
+
if err != nil {
162
+
return "{}"
163
+
}
164
+
return string(jsonBytes)
165
+
}
+27
-2
appview/pages/templates/goodfirstissues/index.html
+27
-2
appview/pages/templates/goodfirstissues/index.html
···
1
1
{{ define "title" }}good first issues{{ end }}
2
2
3
3
{{ define "extrameta" }}
4
+
<meta name="description" content="Discover beginner-friendly good first issues across open source projects on Tangled. Perfect for new contributors looking to get started with open source development." />
5
+
<meta name="keywords" content="good first issues, beginner issues, open source contribution, first time contributor, beginner friendly, open source projects" />
6
+
4
7
<meta property="og:title" content="good first issues 路 tangled" />
5
-
<meta property="og:type" content="object" />
8
+
<meta property="og:type" content="website" />
6
9
<meta property="og:url" content="https://tangled.org/goodfirstissues" />
7
-
<meta property="og:description" content="Find good first issues to contribute to open source projects" />
10
+
<meta property="og:description" content="Find beginner-friendly issues across all repositories to get started with open source contributions on Tangled." />
11
+
12
+
<meta name="twitter:card" content="summary" />
13
+
<meta name="twitter:title" content="good first issues 路 tangled" />
14
+
<meta name="twitter:description" content="Find beginner-friendly issues to get started with open source contributions." />
15
+
16
+
<!-- structured data for good first issues page -->
17
+
<script type="application/ld+json">
18
+
{
19
+
"@context": "https://schema.org",
20
+
"@type": "CollectionPage",
21
+
"name": "Good First Issues",
22
+
"description": "A curated collection of beginner-friendly issues across open source projects",
23
+
"url": "https://tangled.org/goodfirstissues",
24
+
"isPartOf": {
25
+
"@type": "WebSite",
26
+
"name": "Tangled",
27
+
"url": "https://tangled.org"
28
+
}
29
+
}
30
+
</script>
8
31
{{ end }}
9
32
33
+
{{ define "canonical" }}https://tangled.org/goodfirstissues{{ end }}
34
+
10
35
{{ define "content" }}
11
36
<div class="grid grid-cols-10">
12
37
<header class="col-span-full md:col-span-10 px-6 py-2 text-center flex flex-col items-center justify-center py-8">
+26
-2
appview/pages/templates/layouts/base.html
+26
-2
appview/pages/templates/layouts/base.html
···
4
4
<head>
5
5
<meta charset="UTF-8" />
6
6
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
7
-
<meta name="description" content="Social coding, but for real this time!"/>
7
+
<meta name="description" content="tightly-knit social coding"/>
8
+
<meta name="keywords" content="git hosting, social coding, version control, pull requests, CI/CD, code collaboration, open source, decentralized"/>
8
9
<meta name="htmx-config" content='{"includeIndicatorStyles": false}'>
10
+
<meta name="author" content="Tangled"/>
11
+
12
+
<!-- Canonical URL -->
13
+
<link rel="canonical" href="{{ block "canonical" . }}https://tangled.org{{ .Request.URL.Path }}{{ end }}" />
9
14
10
15
<script defer src="/static/htmx.min.js"></script>
11
16
<script defer src="/static/htmx-ext-ws.min.js"></script>
···
19
24
<link rel="preconnect" href="https://avatar.tangled.sh" />
20
25
<link rel="preconnect" href="https://camo.tangled.sh" />
21
26
27
+
<!-- RSS Feed Discovery -->
28
+
{{ block "rss" . }}{{ end }}
29
+
22
30
<!-- pwa manifest -->
23
31
<link rel="manifest" href="/pwa-manifest.json" />
24
32
···
26
34
<link rel="preload" href="/static/fonts/InterVariable.woff2" as="font" type="font/woff2" crossorigin />
27
35
28
36
<link rel="stylesheet" href="/static/tw.css?{{ cssContentHash }}" type="text/css" />
29
-
<title>{{ block "title" . }}{{ end }} 路 tangled</title>
37
+
<title>{{ block "title" . }}{{ end }}</title>
38
+
39
+
<!-- Structured Data -->
40
+
{{ block "structuredData" . }}
41
+
<script type="application/ld+json">
42
+
{
43
+
"@context": "https://schema.org",
44
+
"@type": "Organization",
45
+
"name": "Tangled",
46
+
"url": "https://tangled.org",
47
+
"logo": "https://tangled.org/favicon.svg",
48
+
"description": "tightly-knit social coding",
49
+
"sameAs": []
50
+
}
51
+
</script>
52
+
{{ end }}
53
+
30
54
{{ block "extrameta" . }}{{ end }}
31
55
</head>
32
56
<body class="min-h-screen flex flex-col gap-4 bg-slate-100 dark:bg-gray-900 dark:text-white transition-colors duration-200">
+20
-1
appview/pages/templates/layouts/profilebase.html
+20
-1
appview/pages/templates/layouts/profilebase.html
···
10
10
<meta property="og:image" content="{{ $avatarUrl }}" />
11
11
<meta property="og:image:width" content="512" />
12
12
<meta property="og:image:height" content="512" />
13
-
13
+
14
14
<meta name="twitter:card" content="summary" />
15
15
<meta name="twitter:title" content="{{ $handle }}" />
16
16
<meta name="twitter:description" content="{{ or .Card.Profile.Description $handle }}" />
17
17
<meta name="twitter:image" content="{{ $avatarUrl }}" />
18
+
19
+
<!-- structured data for user profile -->
20
+
<script type="application/ld+json">
21
+
{
22
+
"@context": "https://schema.org",
23
+
"@type": "Person",
24
+
"name": "{{ or .Card.Profile.DisplayName .Card.UserHandle .Card.UserDid }}",
25
+
"url": "https://tangled.org/{{ or .Card.UserHandle .Card.UserDid }}",
26
+
"image": "{{ $avatarUrl }}",
27
+
"description": "{{ .Card.Profile.Description }}"{{ if .Card.UserHandle }},
28
+
"identifier": "{{ .Card.UserHandle }}"{{ end }}
29
+
}
30
+
</script>
31
+
{{ end }}
32
+
33
+
{{ define "canonical" }}https://tangled.org/{{ or .Card.UserHandle .Card.UserDid }}{{ end }}
34
+
35
+
{{ define "rss" }}
36
+
<link rel="alternate" type="application/atom+xml" title="{{ or .Card.UserHandle .Card.UserDid }} Activity Feed" href="https://tangled.org/{{ or .Card.UserHandle .Card.UserDid }}/feed.atom" />
18
37
{{ end }}
19
38
20
39
{{ define "content" }}
+36
appview/pages/templates/repo/index.html
+36
appview/pages/templates/repo/index.html
···
5
5
{{ template "repo/fragments/meta" . }}
6
6
7
7
{{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo) }}
8
+
9
+
<!-- Structured Data for Repository -->
10
+
<script type="application/ld+json">
11
+
{
12
+
"@context": "https://schema.org",
13
+
"@type": "SoftwareSourceCode",
14
+
"name": "{{ .RepoInfo.Name }}",
15
+
"description": "{{ .RepoInfo.Description }}",
16
+
"codeRepository": "https://tangled.org/{{ .RepoInfo.FullName }}",
17
+
"programmingLanguage": {{ if .Languages }}{{ range $idx, $lang := .Languages }}{{ if eq $idx 0 }}"{{ $lang.Name }}"{{ end }}{{ end }}{{ else }}"Unknown"{{ end }},
18
+
"url": "https://tangled.org/{{ .RepoInfo.FullName }}",
19
+
"author": {
20
+
"@type": "Person",
21
+
"name": "{{ .RepoInfo.OwnerWithAt }}",
22
+
"url": "https://tangled.org/{{ .RepoInfo.OwnerWithAt }}"
23
+
}{{ if .RepoInfo.Source }},
24
+
"isBasedOn": {
25
+
"@type": "SoftwareSourceCode",
26
+
"name": "{{ .RepoInfo.Source.Name }}",
27
+
"url": "https://tangled.org/{{ didOrHandle .RepoInfo.Source.Did .RepoInfo.SourceHandle }}/{{ .RepoInfo.Source.Name }}"
28
+
}{{ end }}
29
+
}
30
+
</script>
31
+
32
+
<!-- Breadcrumb Navigation -->
33
+
{{ template "fragments/breadcrumb" (list
34
+
(list "Home" "https://tangled.org")
35
+
(list .RepoInfo.OwnerWithAt (printf "https://tangled.org/%s" .RepoInfo.OwnerWithAt))
36
+
(list .RepoInfo.Name (printf "https://tangled.org/%s" .RepoInfo.FullName))
37
+
) }}
38
+
{{ end }}
39
+
40
+
{{ define "canonical" }}https://tangled.org/{{ .RepoInfo.FullName }}{{ end }}
41
+
42
+
{{ define "rss" }}
43
+
<link rel="alternate" type="application/atom+xml" title="{{ .RepoInfo.FullName }} Activity Feed" href="https://tangled.org/{{ .RepoInfo.FullName }}/feed.atom" />
8
44
{{ end }}
9
45
10
46
{{ define "repoContent" }}
-19
appview/pages/templates/repo/issues/fragments/og.html
-19
appview/pages/templates/repo/issues/fragments/og.html
···
1
-
{{ define "repo/issues/fragments/og" }}
2
-
{{ $title := printf "%s #%d" .Issue.Title .Issue.IssueId }}
3
-
{{ $description := or .Issue.Body .RepoInfo.Description }}
4
-
{{ $url := printf "https://tangled.org/%s/issues/%d" .RepoInfo.FullName .Issue.IssueId }}
5
-
{{ $imageUrl := printf "https://tangled.org/%s/issues/%d/opengraph" .RepoInfo.FullName .Issue.IssueId }}
6
-
7
-
<meta property="og:title" content="{{ unescapeHtml $title }}" />
8
-
<meta property="og:type" content="object" />
9
-
<meta property="og:url" content="{{ $url }}" />
10
-
<meta property="og:description" content="{{ $description }}" />
11
-
<meta property="og:image" content="{{ $imageUrl }}" />
12
-
<meta property="og:image:width" content="1200" />
13
-
<meta property="og:image:height" content="600" />
14
-
15
-
<meta name="twitter:card" content="summary_large_image" />
16
-
<meta name="twitter:title" content="{{ unescapeHtml $title }}" />
17
-
<meta name="twitter:description" content="{{ $description }}" />
18
-
<meta name="twitter:image" content="{{ $imageUrl }}" />
19
-
{{ end }}
+16
-16
appview/pages/templates/repo/pulls/fragments/og.html
+16
-16
appview/pages/templates/repo/pulls/fragments/og.html
···
1
-
{{ define "repo/pulls/fragments/og" }}
2
-
{{ $title := printf "%s #%d" .Pull.Title .Pull.PullId }}
3
-
{{ $description := or .Pull.Body .RepoInfo.Description }}
4
-
{{ $url := printf "https://tangled.org/%s/pulls/%d" .RepoInfo.FullName .Pull.PullId }}
5
-
{{ $imageUrl := printf "https://tangled.org/%s/pulls/%d/opengraph" .RepoInfo.FullName .Pull.PullId }}
1
+
{{ define "pulls/fragments/og" }}
2
+
{{ $title := printf "%s #%d" .Pull.Title .Pull.PullId }}
3
+
{{ $description := or .Pull.Body .RepoInfo.Description }}
4
+
{{ $url := printf "https://tangled.org/%s/pulls/%d" .RepoInfo.FullName .Pull.PullId }}
5
+
{{ $imageUrl := printf "https://tangled.org/%s/pulls/%d/opengraph" .RepoInfo.FullName .Pull.PullId }}
6
6
7
-
<meta property="og:title" content="{{ unescapeHtml $title }}" />
8
-
<meta property="og:type" content="object" />
9
-
<meta property="og:url" content="{{ $url }}" />
10
-
<meta property="og:description" content="{{ $description }}" />
11
-
<meta property="og:image" content="{{ $imageUrl }}" />
12
-
<meta property="og:image:width" content="1200" />
13
-
<meta property="og:image:height" content="600" />
7
+
<meta property="og:title" content="{{ unescapeHtml $title }}" />
8
+
<meta property="og:type" content="object" />
9
+
<meta property="og:url" content="{{ $url }}" />
10
+
<meta property="og:description" content="{{ $description }}" />
11
+
<meta property="og:image" content="{{ $imageUrl }}" />
12
+
<meta property="og:image:width" content="1200" />
13
+
<meta property="og:image:height" content="600" />
14
14
15
-
<meta name="twitter:card" content="summary_large_image" />
16
-
<meta name="twitter:title" content="{{ unescapeHtml $title }}" />
17
-
<meta name="twitter:description" content="{{ $description }}" />
18
-
<meta name="twitter:image" content="{{ $imageUrl }}" />
15
+
<meta name="twitter:card" content="summary_large_image" />
16
+
<meta name="twitter:title" content="{{ unescapeHtml $title }}" />
17
+
<meta name="twitter:description" content="{{ $description }}" />
18
+
<meta name="twitter:image" content="{{ $imageUrl }}" />
19
19
{{ end }}
+49
-3
appview/pages/templates/timeline/home.html
+49
-3
appview/pages/templates/timeline/home.html
···
1
1
{{ define "title" }}tangled · tightly-knit social coding{{ end }}
2
2
3
3
{{ define "extrameta" }}
4
-
<meta property="og:title" content="timeline 路 tangled" />
5
-
<meta property="og:type" content="object" />
4
+
{{ $desc := "Collaborate on code with decentralized git hosting, modern contribution and review workflows, and lightweight CI/CD pipelines." }}
5
+
{{ $title = "tangled 路 tightly-knit social coding" }}
6
+
7
+
<meta name="description" content="{{ $desc }}" />
8
+
<meta property="og:title" content="{{ $title }}" />
9
+
<meta property="og:type" content="website" />
6
10
<meta property="og:url" content="https://tangled.org" />
7
-
<meta property="og:description" content="tightly-knit social coding" />
11
+
<meta property="og:description" content="Decentralized git hosting with improved pull requests and lightweight CI/CD. Host repositories on your own infrastructure." />
12
+
<meta property="og:image" content="https://assets.tangled.network/tangled_og.png" />
13
+
<meta property="og:image:width" content="1200" />
14
+
<meta property="og:image:height" content="630" />
15
+
16
+
<meta name="twitter:card" content="summary_large_image" />
17
+
<meta name="twitter:title" content="{{ $title }}" />
18
+
<meta name="twitter:description" content="{{ $desc }}" />
19
+
<meta name="twitter:image" content="https://assets.tangled.network/tangled_og.png" />
20
+
21
+
<!-- Enhanced Structured Data for Homepage -->
22
+
<script type="application/ld+json">
23
+
{
24
+
"@context": "https://schema.org",
25
+
"@type": "WebSite",
26
+
"name": "Tangled",
27
+
"alternateName": "Tangled",
28
+
"url": "https://tangled.org",
29
+
"description": "{{ $desc }}",
30
+
"potentialAction": {
31
+
"@type": "SearchAction",
32
+
"target": "https://tangled.org/?q={search_term_string}",
33
+
"query-input": "required name=search_term_string"
34
+
}
35
+
}
36
+
</script>
37
+
<script type="application/ld+json">
38
+
{
39
+
"@context": "https://schema.org",
40
+
"@type": "SoftwareApplication",
41
+
"name": "Tangled",
42
+
"applicationCategory": "DeveloperTool",
43
+
"offers": {
44
+
"@type": "Offer",
45
+
"price": "0",
46
+
"priceCurrency": "USD"
47
+
},
48
+
"operatingSystem": "Web",
49
+
"description": "{{ $desc }}"
50
+
}
51
+
</script>
8
52
{{ end }}
9
53
54
+
{{ define "canonical" }}https://tangled.org{{ end }}
55
+
10
56
11
57
{{ define "content" }}
12
58
<div class="flex flex-col gap-4">
+1
appview/state/router.go
+1
appview/state/router.go
+76
appview/state/state.go
+76
appview/state/state.go
···
208
208
209
209
robotsTxt := `User-agent: *
210
210
Allow: /
211
+
Disallow: /settings
212
+
Disallow: /notifications
213
+
Disallow: /login
214
+
Disallow: /logout
215
+
Disallow: /signup
216
+
Disallow: /oauth
217
+
Disallow: */settings$
218
+
Disallow: */settings/*
219
+
220
+
Crawl-delay: 1
221
+
222
+
Sitemap: https://tangled.org/sitemap.xml
211
223
`
212
224
w.Write([]byte(robotsTxt))
213
225
}
214
226
227
+
func (s *State) Sitemap(w http.ResponseWriter, r *http.Request) {
228
+
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
229
+
w.Header().Set("Cache-Control", "public, max-age=3600")
230
+
231
+
// basic sitemap with static pages
232
+
sitemap := `<?xml version="1.0" encoding="UTF-8"?>
233
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
234
+
<url>
235
+
<loc>https://tangled.org</loc>
236
+
<changefreq>daily</changefreq>
237
+
<priority>1.0</priority>
238
+
</url>
239
+
<url>
240
+
<loc>https://tangled.org/timeline</loc>
241
+
<changefreq>hourly</changefreq>
242
+
<priority>0.9</priority>
243
+
</url>
244
+
<url>
245
+
<loc>https://tangled.org/goodfirstissues</loc>
246
+
<changefreq>daily</changefreq>
247
+
<priority>0.8</priority>
248
+
</url>
249
+
<url>
250
+
<loc>https://tangled.org/terms</loc>
251
+
<changefreq>monthly</changefreq>
252
+
<priority>0.3</priority>
253
+
</url>
254
+
<url>
255
+
<loc>https://tangled.org/privacy</loc>
256
+
<changefreq>monthly</changefreq>
257
+
<priority>0.3</priority>
258
+
</url>
259
+
<url>
260
+
<loc>https://tangled.org/brand</loc>
261
+
<changefreq>monthly</changefreq>
262
+
<priority>0.5</priority>
263
+
</url>
264
+
</urlset>`
265
+
w.Write([]byte(sitemap))
266
+
}
267
+
268
+
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest
269
+
const manifestJson = `{
270
+
"name": "tangled",
271
+
"description": "tightly-knit social coding.",
272
+
"icons": [
273
+
{
274
+
"src": "/favicon.svg",
275
+
"sizes": "144x144"
276
+
}
277
+
],
278
+
"start_url": "/",
279
+
"id": "org.tangled",
280
+
281
+
"display": "standalone",
282
+
"background_color": "#111827",
283
+
"theme_color": "#111827"
284
+
}`
285
+
286
+
func (p *State) PWAManifest(w http.ResponseWriter, r *http.Request) {
287
+
w.Header().Set("Content-Type", "application/json")
288
+
w.Write([]byte(manifestJson))
289
+
}
290
+
215
291
func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) {
216
292
user := s.oauth.GetMultiAccountUser(r)
217
293
s.pages.TermsOfService(w, pages.TermsOfServiceParams{
History
5 rounds
2 comments
anirudh.fi
submitted
#4
1 commit
expand
collapse
appview/pages: seo improvements, sitemap, schemae and more
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
expand 0 comments
pull request successfully merged
anirudh.fi
submitted
#3
1 commit
expand
collapse
appview/pages: seo improvements, sitemap, schemae and more
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
expand 0 comments
anirudh.fi
submitted
#2
1 commit
expand
collapse
appview/pages: seo improvements, sitemap, schemae and more
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
expand 0 comments
anirudh.fi
submitted
#1
1 commit
expand
collapse
appview/pages: seo improvements, sitemap, schemae and more
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
expand 0 comments
anirudh.fi
submitted
#0
1 commit
expand
collapse
appview/pages: seo improvements, sitemap, schemae and more
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
this changeset looks good otherwise. i will look at the ancestor PRs shortly.