medvednikov

/

gitlyx Public
0 commits 27 issues 3 pull requests 32 contributors Discussions Projects CI

committed 56 years ago · View patch
8 files changed +178 -13
gitly.v +89 -0

@@ -41,6 +41,7 @@ pub struct Context {

4141mut:

4242 user User

4343 current_path string

44+ page_title string

4445 page_gen_time string

4546 page_gen_start i64

4647 is_tree bool

@@ -147,6 +148,7 @@ pub fn (mut app App) init_server() {

147148

148149pub fn (mut app App) before_request(mut ctx Context) bool {

149150 ctx.page_gen_start = time.ticks()

151+ ctx.set_page_title_from_path(ctx.req.url)

150152 $if trace_prealloc ? {

151153 unsafe { prealloc_scope_checkpoint(c'gitly before_request start') }

152154 }

@@ -225,6 +227,93 @@ pub fn (mut ctx Context) redirect_to_repository(username string, repo_name strin

225227 return ctx.redirect('/${username}/${repo_name}')

226228}

227229

230+fn (mut ctx Context) set_page_title(parts []string) {

231+ mut clean_parts := []string{}

232+ for part in parts {

233+ clean := part.replace('\r', ' ').replace('\n', ' ').trim_space()

234+ if clean != '' {

235+ clean_parts << clean

236+ }

237+ }

238+ clean_parts << 'Gitly'

239+ ctx.page_title = clean_parts.join(' - ')

240+}

241+

242+fn (mut ctx Context) set_page_title_from_path(raw_url string) {

243+ path := raw_url.all_before('?').trim('/')

244+ if path == '' {

245+ ctx.set_page_title([]string{})

246+ return

247+ }

248+

249+ segments := path.split('/')

250+ if segments.len >= 2 && segments[0] == 'api' {

251+ ctx.set_page_title([]string{})

252+ return

253+ }

254+ if segments.len >= 4 && segments[2] in ['issue', 'pull'] {

255+ ctx.set_page_title(['${page_title_label(segments[2])} #${segments[3]}',

256+ '${segments[0]}/${segments[1]}'])

257+ return

258+ }

259+ if segments.len >= 3 {

260+ ctx.set_page_title([page_title_label(segments[2]), '${segments[0]}/${segments[1]}'])

261+ return

262+ }

263+ if segments.len == 2 {

264+ if is_user_page_segment(segments[1]) {

265+ ctx.set_page_title([page_title_label(segments[1]), segments[0]])

266+ } else {

267+ ctx.set_page_title(['${segments[0]}/${segments[1]}'])

268+ }

269+ return

270+ }

271+ ctx.set_page_title([page_title_label(segments[0])])

272+}

273+

274+fn is_user_page_segment(slug string) bool {

275+ return slug in ['feed', 'issues', 'pulls', 'repos', 'settings', 'stars']

276+}

277+

278+fn page_title_label(slug string) string {

279+ return match slug {

280+ '2fa' { 'Two-factor authentication' }

281+ 'api-tokens' { 'API tokens' }

282+ 'blob' { 'File' }

283+ 'branches' { 'Branches' }

284+ 'ci' { 'CI' }

285+ 'commit' { 'Commit' }

286+ 'compare' { 'New pull request' }

287+ 'contributors' { 'Contributors' }

288+ 'discussions' { 'Discussions' }

289+ 'edit' { 'Edit file' }

290+ 'feed' { 'Feed' }

291+ 'issue' { 'Issue' }

292+ 'issues' { 'Issues' }

293+ 'login' { 'Login' }

294+ 'milestones' { 'Milestones' }

295+ 'new' { 'New repository' }

296+ 'open-source' { 'Open source' }

297+ 'organizations' { 'Organizations' }

298+ 'pricing' { 'Pricing' }

299+ 'projects' { 'Projects' }

300+ 'pull' { 'Pull request' }

301+ 'pulls' { 'Pull requests' }

302+ 'register' { 'Register' }

303+ 'releases' { 'Releases' }

304+ 'repos' { 'Repositories' }

305+ 'search' { 'Search' }

306+ 'security' { 'Security' }

307+ 'settings' { 'Settings' }

308+ 'ssh-keys' { 'SSH keys' }

309+ 'stars' { 'Stars' }

310+ 'tag' { 'Tag' }

311+ 'tree' { 'Code' }

312+ 'webhooks' { 'Webhooks' }

313+ else { slug.replace('-', ' ').replace('_', ' ') }

314+ }

315+}

316+

228317fn (mut app App) create_tables() ! {

229318 sql app.db {

230319 create table Repo

issue_routes.v +12 -1

@@ -29,11 +29,12 @@ fn (mut app App) handle_issues_count(username string, repo_name string) veb.Resu

2929}

3030

3131@['/:username/:repo_name/issues/new']

32-pub fn (mut app App) new_issue(username string, repo_name string) veb.Result {

32+pub fn (mut app App) new_issue(mut ctx Context, username string, repo_name string) veb.Result {

3333 if !ctx.logged_in {

3434 return ctx.not_found()

3535 }

3636 repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() }

37+ ctx.set_page_title(['New issue', '${repo.user_name}/${repo.name}'])

3738 return $veb.html()

3839}

3940

@@ -116,6 +117,7 @@ pub fn (mut app App) issues(mut ctx Context, username string, repo_name string,

116117 }

117118 page_count := calculate_pages(repo.nr_open_issues, commits_per_page)

118119 prev_page, next_page := generate_prev_next_pages(page_i)

120+ ctx.set_page_title(['Issues', '${repo.user_name}/${repo.name}'])

119121 return $veb.html()

120122}

121123

@@ -124,6 +126,7 @@ pub fn (mut app App) issue(mut ctx Context, username string, repo_name string, i

124126 repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() }

125127 issue := app.find_issue_by_id(id.int()) or { return ctx.not_found() }

126128 issue_author := app.get_user_by_id(issue.author_id) or { return ctx.not_found() }

129+ ctx.set_page_title(['${issue.title} #${issue.id}', '${repo.user_name}/${repo.name}'])

127130 mut comments_with_users := []CommentWithUser{}

128131 mut comment := Comment{}

129132 mut comment_author := User{}

@@ -200,5 +203,13 @@ pub fn (mut app App) user_issues(mut ctx Context, username string, tab string) v

200203 'user-issues-sidebar__item'

201204 }

202205 show_repo_link := true

206+ tab_title := match current_tab {

207+ 'assigned' { 'Assigned issues' }

208+ 'mentioned' { 'Mentioned issues' }

209+ 'activity' { 'Issue activity' }

210+ else { 'Created issues' }

211+ }

212+

213+ ctx.set_page_title([tab_title, user.username])

203214 return $veb.html()

204215}

pr_routes.v +11 -0

@@ -191,6 +191,13 @@ pub fn (mut app App) repo_pulls(mut ctx Context, username string, repo_name stri

191191 tab_open_class := if current_tab == 'open' { 'pr-tab pr-tab--active' } else { 'pr-tab' }

192192 tab_merged_class := if current_tab == 'merged' { 'pr-tab pr-tab--active' } else { 'pr-tab' }

193193 tab_closed_class := if current_tab == 'closed' { 'pr-tab pr-tab--active' } else { 'pr-tab' }

194+ tab_title := match current_tab {

195+ 'closed' { 'Closed pull requests' }

196+ 'merged' { 'Merged pull requests' }

197+ else { 'Open pull requests' }

198+ }

199+

200+ ctx.set_page_title([tab_title, '${repo.user_name}/${repo.name}'])

194201 return $veb.html('templates/pulls.html')

195202}

196203

@@ -226,6 +233,7 @@ pub fn (mut app App) new_pull_request_form(mut ctx Context, username string, rep

226233 }

227234 }

228235 }

236+ ctx.set_page_title(['New pull request', '${repo.user_name}/${repo.name}'])

229237 return $veb.html('templates/new/pull.html')

230238}

231239

@@ -329,6 +337,7 @@ pub fn (mut app App) pull_request(mut ctx Context, username string, repo_name st

329337 can_merge := is_repo_owner && pr.is_open()

330338 can_close := pr.is_open() && (is_repo_owner || pr.author_id == ctx.user.id)

331339 can_reopen := pr.is_closed() && (is_repo_owner || pr.author_id == ctx.user.id)

340+ ctx.set_page_title(['${pr.title} #${pr.id}', '${repo.user_name}/${repo.name}'])

332341 return $veb.html('templates/pull.html')

333342}

334343

@@ -365,6 +374,7 @@ pub fn (mut app App) pull_request_files(mut ctx Context, username string, repo_n

365374 }

366375 can_comment := ctx.logged_in && pr.is_open()

367376 line_comment_placeholder := veb.tr(ctx.lang.str(), 'pr_line_comment_placeholder').trim_space()

377+ ctx.set_page_title(['${pr.title} #${pr.id}', 'Files changed', '${repo.user_name}/${repo.name}'])

368378 return $veb.html('templates/pull_files.html')

369379}

370380

@@ -614,6 +624,7 @@ pub fn (mut app App) handle_get_user_pulls(mut ctx Context, username string) veb

614624 pr.repo_name = r.name

615625 prs_with_repo << pr

616626 }

627+ ctx.set_page_title(['Pull requests', user.username])

617628 return $veb.html('templates/user_pulls.html')

618629}

619630

repo/repo.v +15 -0

@@ -352,6 +352,21 @@ fn (r &Repo) last_updated_str() string {

352352 return time.unix(r.latest_commit_at).relative()

353353}

354354

355+fn (r &Repo) created_str() string {

356+ if r.created_at <= 0 {

357+ return ''

358+ }

359+ return time.unix(r.created_at).relative()

360+}

361+

362+fn (r &Repo) last_activity_str() string {

363+ activity_at := if r.latest_commit_at > 0 { r.latest_commit_at } else { r.created_at }

364+ if activity_at <= 0 {

365+ return ''

366+ }

367+ return time.unix(activity_at).relative()

368+}

369+

355370fn (mut app App) delete_repository(id int, path string, name string) ! {

356371 sql app.db {

357372 update Repo set is_deleted = true where id == id

static/assets/version +1 -1

@@ -1 +1 @@

1-3e6e162

1+63435c2

static/css/gitly.scss +34 -1

@@ -748,10 +748,19 @@ form {

748748}

749749

750750.repo-card__header {

751+ display: flex;

752+ align-items: flex-start;

753+ justify-content: space-between;

754+ gap: 12px;

755+ margin-bottom: 4px;

756+}

757+

758+.repo-card__heading {

751759 display: flex;

752760 align-items: center;

753761 gap: 10px;

754- margin-bottom: 4px;

762+ flex-wrap: wrap;

763+ min-width: 0;

755764}

756765

757766.repo-card__title {

@@ -764,6 +773,18 @@ form {

764773 }

765774}

766775

776+.repo-card__dates {

777+ display: flex;

778+ flex-direction: column;

779+ align-items: flex-end;

780+ gap: 3px;

781+ flex-shrink: 0;

782+ color: $gray-dark;

783+ font-size: 12px;

784+ line-height: 1.35;

785+ text-align: right;

786+}

787+

767788.repo-card__footer {

768789 display: flex;

769790 align-items: center;

@@ -839,6 +860,18 @@ form {

839860 color: $gray-dark;

840861}

841862

863+@media (max-width: 700px) {

864+ .repo-card__header {

865+ flex-direction: column;

866+ align-items: flex-start;

867+ }

868+

869+ .repo-card__dates {

870+ align-items: flex-start;

871+ text-align: left;

872+ }

873+}

874+

842875.repos-empty {

843876 color: $gray-dark;

844877 margin: 30px 0;

templates/layout/head.html +1 -2

@@ -1,5 +1,5 @@

11<meta charset='utf-8'>

2-<title>Gitly</title>

2+<title>@ctx.page_title</title>

33<meta name="description" content="Open-source development platform written in V">

44<meta name="viewport" content="width=device-width, initial-scale=1">

55<link rel='icon' type='image/svg+xml' href='/assets/favicon.svg' sizes='any'>

@@ -9,4 +9,3 @@

99

1010<script src="/js/gitly.js?v=@{app.started_at}"></script>

1111

12-

templates/user/repos.html +15 -8

@@ -19,11 +19,21 @@

1919 @for repo in repos

2020 <div class="repo-card">

2121 <div class="repo-card__header">

22- <a class="repo-card__title" href="/@user.username/@repo.name">@repo.name</a>

23- @if repo.is_public

24- <span class="repo-card__badge">Public</span>

25- @else

26- <span class="repo-card__badge repo-card__badge--private">Private</span>

22+ <div class="repo-card__heading">

23+ <a class="repo-card__title" href="/@user.username/@repo.name">@repo.name</a>

24+ @if repo.is_public

25+ <span class="repo-card__badge">Public</span>

26+ @else

27+ <span class="repo-card__badge repo-card__badge--private">Private</span>

28+ @end

29+ </div>

30+ @if repo.latest_commit_at > 0 || repo.created_at > 0

31+ <div class="repo-card__dates">

32+ @if repo.created_at > 0

33+ <span>Created @{repo.created_str()}</span>

34+ @end

35+ <span>Last activity @{repo.last_activity_str()}</span>

36+ </div>

2737 @end

2838 </div>

2939 @if repo.description.len > 0

@@ -37,9 +47,6 @@

3747 @repo.lang_stats[0].name

3848 </span>

3949 @end

40- @if repo.latest_commit_at > 0

41- <span class="repo-card__updated">Updated @{repo.last_updated_str()}</span>

42- @end

4350 </div>

4451 @if repo.activity_buckets.len > 0

4552 <svg class="repo-card__activity" viewBox="0 0 120 28" preserveAspectRatio="none">