Search gists (#68)

This commit is contained in:
Thomas Miceli 2023-06-21 18:19:17 +02:00 committed by GitHub
parent 98c5cd1794
commit 7b5d035a32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 334 additions and 74 deletions

View file

@ -28,7 +28,15 @@ func Setup(dbPath string) error {
return err return err
} }
if err = db.AutoMigrate(&User{}, &SSHKey{}, &Gist{}, &AdminSetting{}); err != nil { if err = db.SetupJoinTable(&Gist{}, "Likes", &Like{}); err != nil {
return err
}
if err = db.SetupJoinTable(&User{}, "Liked", &Like{}); err != nil {
return err
}
if err = db.AutoMigrate(&User{}, &Gist{}, &SSHKey{}, &AdminSetting{}); err != nil {
return err return err
} }

View file

@ -29,6 +29,12 @@ type Gist struct {
ForkedID uint ForkedID uint
} }
type Like struct {
UserID uint `gorm:"primaryKey"`
GistID uint `gorm:"primaryKey"`
CreatedAt int64
}
func (gist *Gist) BeforeDelete(tx *gorm.DB) error { func (gist *Gist) BeforeDelete(tx *gorm.DB) error {
// Decrement fork counter if the gist was forked // Decrement fork counter if the gist was forked
err := tx.Model(&Gist{}). err := tx.Model(&Gist{}).
@ -80,11 +86,11 @@ func GetAllGists(offset int) ([]*Gist, error) {
return gists, err return gists, err
} }
func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) { func GetAllGistsFromSearch(currentUserId uint, query string, offset int, sort string, order string) ([]*Gist, error) {
var gists []*Gist var gists []*Gist
err := db.Preload("User").Preload("Forked.User"). err := db.Preload("User").Preload("Forked.User").
Where("users.username = ? and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", fromUser, currentUserId). Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
Joins("join users on gists.user_id = users.id"). Where("gists.title like ? or gists.description like ?", "%"+query+"%", "%"+query+"%").
Limit(11). Limit(11).
Offset(offset * 10). Offset(offset * 10).
Order("gists." + sort + "_at " + order). Order("gists." + sort + "_at " + order).
@ -93,6 +99,74 @@ func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort s
return gists, err return gists, err
} }
func gistsFromUserStatement(fromUserId uint, currentUserId uint) *gorm.DB {
return db.Preload("User").Preload("Forked.User").
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
Where("users.id = ?", fromUserId).
Joins("join users on gists.user_id = users.id")
}
func GetAllGistsFromUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
var gists []*Gist
err := gistsFromUserStatement(fromUserId, currentUserId).Limit(11).
Offset(offset * 10).
Order("gists." + sort + "_at " + order).
Find(&gists).Error
return gists, err
}
func CountAllGistsFromUser(fromUserId uint, currentUserId uint) (int64, error) {
var count int64
err := gistsFromUserStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error
return count, err
}
func likedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
return db.Preload("User").Preload("Forked.User").
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
Where("likes.user_id = ?", fromUserId).
Joins("join likes on gists.id = likes.gist_id").
Joins("join users on likes.user_id = users.id")
}
func GetAllGistsLikedByUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
var gists []*Gist
err := likedStatement(fromUserId, currentUserId).Limit(11).
Offset(offset * 10).
Order("gists." + sort + "_at " + order).
Find(&gists).Error
return gists, err
}
func CountAllGistsLikedByUser(fromUserId uint, currentUserId uint) (int64, error) {
var count int64
err := likedStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error
return count, err
}
func forkedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
return db.Preload("User").Preload("Forked.User").
Where("gists.forked_id is not null and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
Where("gists.user_id = ?", fromUserId).
Joins("join users on gists.user_id = users.id")
}
func GetAllGistsForkedByUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
var gists []*Gist
err := forkedStatement(fromUserId, currentUserId).Limit(11).
Offset(offset * 10).
Order("gists." + sort + "_at " + order).
Find(&gists).Error
return gists, err
}
func CountAllGistsForkedByUser(fromUserId uint, currentUserId uint) (int64, error) {
var count int64
err := forkedStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error
return count, err
}
func GetAllGistsRows() ([]*Gist, error) { func GetAllGistsRows() ([]*Gist, error) {
var gists []*Gist var gists []*Gist
err := db.Table("gists"). err := db.Table("gists").

View file

@ -337,7 +337,6 @@ func urlJoin(base string, elem ...string) string {
} }
func getAvatarUrlFromProvider(provider string, identifier string) string { func getAvatarUrlFromProvider(provider string, identifier string) string {
fmt.Println("getAvatarUrlFromProvider", provider, identifier)
switch provider { switch provider {
case "github": case "github":
return "https://avatars.githubusercontent.com/u/" + identifier + "?v=4" return "https://avatars.githubusercontent.com/u/" + identifier + "?v=4"

View file

@ -11,6 +11,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"html/template" "html/template"
"net/url" "net/url"
"regexp"
"strconv" "strconv"
"strings" "strings"
) )
@ -85,10 +86,10 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
func allGists(ctx echo.Context) error { func allGists(ctx echo.Context) error {
var err error var err error
var urlPage string
fromUserStr := ctx.Param("user") fromUserStr := ctx.Param("user")
userLogged := getUserLogged(ctx) userLogged := getUserLogged(ctx)
pageInt := getPage(ctx) pageInt := getPage(ctx)
sort := "created" sort := "created"
@ -114,14 +115,37 @@ func allGists(ctx echo.Context) error {
} else { } else {
currentUserId = 0 currentUserId = 0
} }
if fromUserStr == "" { if fromUserStr == "" {
setData(ctx, "htmlTitle", "All gists") urlctx := ctx.Request().URL.Path
fromUserStr = "all" if strings.HasSuffix(urlctx, "search") {
gists, err = models.GetAllGistsForCurrentUser(currentUserId, pageInt-1, sort, order) setData(ctx, "htmlTitle", "Search results")
setData(ctx, "mode", "search")
setData(ctx, "searchQuery", template.URL("&q="+ctx.QueryParam("q")))
urlPage = "search"
gists, err = models.GetAllGistsFromSearch(currentUserId, ctx.QueryParam("q"), pageInt-1, sort, order)
} else if strings.HasSuffix(urlctx, "all") {
setData(ctx, "htmlTitle", "All gists")
setData(ctx, "mode", "all")
urlPage = "all"
gists, err = models.GetAllGistsForCurrentUser(currentUserId, pageInt-1, sort, order)
}
} else { } else {
setData(ctx, "htmlTitle", "All gists from "+fromUserStr) liked := false
forked := false
liked, err = regexp.MatchString(`/[^/]*/liked`, ctx.Request().URL.Path)
if err != nil {
return errorRes(500, "Error matching regexp", err)
}
forked, err = regexp.MatchString(`/[^/]*/forked`, ctx.Request().URL.Path)
if err != nil {
return errorRes(500, "Error matching regexp", err)
}
var fromUser *models.User var fromUser *models.User
fromUser, err = models.GetUserByUsername(fromUserStr) fromUser, err = models.GetUserByUsername(fromUserStr)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
@ -131,7 +155,40 @@ func allGists(ctx echo.Context) error {
} }
setData(ctx, "fromUser", fromUser) setData(ctx, "fromUser", fromUser)
gists, err = models.GetAllGistsFromUser(fromUserStr, currentUserId, pageInt-1, sort, order) if countFromUser, err := models.CountAllGistsFromUser(fromUser.ID, currentUserId); err != nil {
return errorRes(500, "Error counting gists", err)
} else {
setData(ctx, "countFromUser", countFromUser)
}
if countLiked, err := models.CountAllGistsLikedByUser(fromUser.ID, currentUserId); err != nil {
return errorRes(500, "Error counting liked gists", err)
} else {
setData(ctx, "countLiked", countLiked)
}
if countForked, err := models.CountAllGistsForkedByUser(fromUser.ID, currentUserId); err != nil {
return errorRes(500, "Error counting forked gists", err)
} else {
setData(ctx, "countForked", countForked)
}
if liked {
urlPage = fromUserStr + "/liked"
setData(ctx, "htmlTitle", "All gists liked by "+fromUserStr)
setData(ctx, "mode", "liked")
gists, err = models.GetAllGistsLikedByUser(fromUser.ID, currentUserId, pageInt-1, sort, order)
} else if forked {
urlPage = fromUserStr + "/forked"
setData(ctx, "htmlTitle", "All gists forked by "+fromUserStr)
setData(ctx, "mode", "forked")
gists, err = models.GetAllGistsForkedByUser(fromUser.ID, currentUserId, pageInt-1, sort, order)
} else {
urlPage = fromUserStr
setData(ctx, "htmlTitle", "All gists from "+fromUserStr)
setData(ctx, "mode", "fromUser")
gists, err = models.GetAllGistsFromUser(fromUser.ID, currentUserId, pageInt-1, sort, order)
}
} }
if err != nil { if err != nil {
@ -142,6 +199,7 @@ func allGists(ctx echo.Context) error {
return errorRes(404, "Page not found", nil) return errorRes(404, "Page not found", nil)
} }
setData(ctx, "urlPage", urlPage)
return html(ctx, "all.html") return html(ctx, "all.html")
} }
@ -527,7 +585,7 @@ func likes(ctx echo.Context) error {
return errorRes(404, "Page not found", nil) return errorRes(404, "Page not found", nil)
} }
setData(ctx, "htmlTitle", "Likes for "+gist.Title) setData(ctx, "htmlTitle", "Like for "+gist.Title)
setData(ctx, "revision", "HEAD") setData(ctx, "revision", "HEAD")
return html(ctx, "likes.html") return html(ctx, "likes.html")
} }

View file

@ -198,7 +198,10 @@ func Start() {
} }
g1.GET("/all", allGists, checkRequireLogin) g1.GET("/all", allGists, checkRequireLogin)
g1.GET("/search", allGists, checkRequireLogin)
g1.GET("/:user", allGists, checkRequireLogin) g1.GET("/:user", allGists, checkRequireLogin)
g1.GET("/:user/liked", allGists, checkRequireLogin)
g1.GET("/:user/forked", allGists, checkRequireLogin)
g3 := g1.Group("/:user/:gistname") g3 := g1.Group("/:user/:gistname")
{ {

View file

@ -166,7 +166,7 @@ func validateReservedKeywords(fl validator.FieldLevel) bool {
name := fl.Field().String() name := fl.Field().String()
restrictedNames := map[string]struct{}{} restrictedNames := map[string]struct{}{}
for _, restrictedName := range []string{"assets", "register", "login", "logout", "config", "admin-panel", "all"} { for _, restrictedName := range []string{"assets", "register", "login", "logout", "settings", "admin-panel", "all", "search"} {
restrictedNames[restrictedName] = struct{}{} restrictedNames[restrictedName] = struct{}{}
} }

View file

@ -35,6 +35,10 @@ document.addEventListener('DOMContentLoaded', () => {
themeMenu.classList.toggle('hidden'); themeMenu.classList.toggle('hidden');
} }
document.getElementById('user-btn')?.addEventListener("click" , (e) => {
document.getElementById('user-menu').classList.toggle('hidden');
})
document.querySelectorAll('.moment-timestamp').forEach((e: HTMLElement) => { document.querySelectorAll('.moment-timestamp').forEach((e: HTMLElement) => {
e.title = moment.unix(parseInt(e.innerHTML)).format('LLLL'); e.title = moment.unix(parseInt(e.innerHTML)).format('LLLL');
e.innerHTML = moment.unix(parseInt(e.innerHTML)).fromNow(); e.innerHTML = moment.unix(parseInt(e.innerHTML)).fromNow();

View file

@ -63,27 +63,79 @@
<div class="flex space-x-4"> <div class="flex space-x-4">
<a href="/all" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">All</a> <a href="/all" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">All</a>
<a href="/" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">New</a> <a href="/" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">New</a>
{{ if .userLogged }} <div class="flex flex-1 items-center justify-center px-2 lg:ml-6 lg:justify-end">
<a href="/{{ .userLogged.Username }}" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">My gists</a> <div class="w-full max-w-lg lg:max-w-xs">
{{ end }} <label for="search" class="sr-only">Search</label>
<div class="relative">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd" />
</svg>
</div>
<form action="/search" method="GET">
<input id="search" name="q" class="bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-md pl-10" placeholder="Search" type="search">
<input type="submit" hidden="hidden">
</form>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"> <div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
{{ if .userLogged }} {{ if .userLogged }}
{{ if .userLogged.IsAdmin }} <div id="user-btn" class="hidden sm:flex items-center ml-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md px-3 py-2">
<a href="/admin-panel" class="hidden sm:block text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium" aria-current="page">Admin</a> <div class="inline-flex">
{{ end }} <p class="hidden sm:block text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white rounded-md text-sm font-medium mr-2">{{ .userLogged.Username }}</p>
<a href="/settings" class="hidden sm:block text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium" aria-current="page">Settings</a> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="h-5 w-5 inline-block">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
<a href="/logout" id="logged-button" class="inline-flex text-slate-700 dark:text-slate-300 hover:bg-rose-500 hover:text-white dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">
<p class="mr-1 username">{{ .userLogged.Username }}</p>
<p class="mr-1 logout hidden">Logout</p>
<span class="sr-only">User</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg> </svg>
</a> </div>
<div class="hidden relative sm:inline-block text-left">
<div id="user-menu" class="hidden w-32 font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-600 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none">
<div class="py-1" role="none">
<a href="/{{ .userLogged.Username }}" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
</svg>
My gists
</a>
<a href="/{{ .userLogged.Username }}/liked" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
<path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
</svg>
Liked
</a>
</div>
{{ if .userLogged.IsAdmin }}
<div class="py-1" role="none">
<a href="/admin-panel" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
</svg>
Admin
</a>
</div>
{{ end }}
<div class="py-1" role="none">
<a href="/settings" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Settings
</a>
<a href="/logout" class="dark:text-rose-400 text-rose-500 group flex items-center px-3 py-1.5 text-sm w-full hover:text-rose-600 dark:hover:text-rose-500" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 dark:text-rose-400 text-rose-500 group-hover:text-rose-600 dark:group-hover:text-rose-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
</svg>
Logout
</a>
</div>
</div>
</div>
</div>
{{ else }} {{ else }}
{{ if not .DisableSignup }} {{ if not .DisableSignup }}
<a href="/register" class="inline-flex text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium"> <a href="/register" class="inline-flex text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">
@ -106,7 +158,7 @@
</svg> </svg>
</div> </div>
<div class="hidden relative sm:inline-block text-left"> <div class="hidden relative sm:inline-block text-left">
<div id="theme-menu" class="hidden font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-900 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none"> <div id="theme-menu" class="hidden font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-600 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none">
<div class="py-1" role="none"> <div class="py-1" role="none">
<!-- Active: "bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900", Not Active: "text-gray-300 dark:text-gray-700" --> <!-- Active: "bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900", Not Active: "text-gray-300 dark:text-gray-700" -->
<button id="light-mode" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1"> <button id="light-mode" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
@ -138,6 +190,20 @@
<!-- Mobile menu --> <!-- Mobile menu -->
<div class="sm:hidden hidden" id="mobile-menu"> <div class="sm:hidden hidden" id="mobile-menu">
<div class="mx-2">
<label for="search" class="sr-only">Search</label>
<div class="relative">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd" />
</svg>
</div>
<form action="/search" method="GET">
<input id="search" name="q" class="bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-md pl-10" placeholder="Search" type="search">
<input type="submit" hidden="hidden">
</form>
</div>
</div>
<div class="px-2 pt-2 pb-3 space-y-1"> <div class="px-2 pt-2 pb-3 space-y-1">
<a href="/all" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">All</a> <a href="/all" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">All</a>
<a href="/" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">New</a> <a href="/" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">New</a>

View file

@ -121,7 +121,7 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
</svg> </svg>
Revisions Revisions
<span class="inline-flex items-center ml-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> {{ if .nbCommits }}{{ .nbCommits }}{{else}}0{{ end }} </span> <span class="inline-flex items-center ml-2 px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> {{ if .nbCommits }}{{ .nbCommits }}{{else}}0{{ end }} </span>
</a> </a>
</nav> </nav>
<div class="float-right inline-flex items-center space-x-2"> <div class="float-right inline-flex items-center space-x-2">

View file

@ -1,56 +1,104 @@
{{ template "header" .}} {{ template "header" .}}
<div class="py-10"> <div class="py-10">
<header class="pb-4 flex "> <header class="pb-4 ">
<div class="flex-auto"> <div class="flex">
{{if .fromUser}} <div class="flex-auto">
<div class="flex items-center"> {{if .fromUser}}
<div class="flex-shrink-0"> <div class="flex items-center">
<img class="h-12 w-12 rounded-md mr-2 border border-gray-200 dark:border-gray-700" src="{{ avatarUrl .fromUser .DisableGravatar }}" alt=""> <div class="flex-shrink-0">
</div> <img class="h-12 w-12 rounded-md mr-2 border border-gray-200 dark:border-gray-700" src="{{ avatarUrl .fromUser .DisableGravatar }}" alt="">
<div> </div>
<h1 class="text-2xl font-bold leading-tight">{{.fromUser.Username}}</h1> <div>
<p class="text-sm text-slate-500">Joined <span class="moment-timestamp">{{.fromUser.CreatedAt}}</span></p> <h1 class="text-2xl font-bold leading-tight">{{.fromUser.Username}}</h1>
<p class="text-sm text-slate-500">Joined <span class="moment-timestamp">{{.fromUser.CreatedAt}}</span></p>
</div>
</div> </div>
{{ else }}
<h1 class="text-2xl font-bold leading-tight">All gists</h1>
{{ end }}
</div> </div>
{{ else }} <div class="align-middle inline-flex items-center">
<h1 class="text-2xl font-bold leading-tight">All gists</h1> <div class="relative text-left">
{{ end }} <div>
</div> <button type="button" class="whitespace-nowrap inline-flex text-slate-700 dark:text-slate-300 rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 leading-3" id="sort-gists-button">
<div class="float-right"> <span class="text-gray-700 dark:text-gray-300">Sort : <span class="text-slate-700 dark:text-slate-300">{{.order}} {{.sort}}</span></span>
<div class="relative inline-block text-left"> <svg class="-mr-1 ml-2 h-3 w-3" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<div> <path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
<button type="button" class="whitespace-nowrap inline-flex text-slate-700 dark:text-slate-300 rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 leading-3" id="sort-gists-button"> </svg>
<span class="text-gray-700 dark:text-gray-300">Sort : <span class="text-slate-700 dark:text-slate-300">{{.order}} {{.sort}}</span></span> </button>
<svg class="-mr-1 ml-2 h-3 w-3" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</button>
</div>
<div id="sort-gists-dropdown" class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-300 dark:divide-gray-700 rounded-md rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 shadow-lg ring-1 ring-white dark:ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div class="" role="none">
<a href="/{{if .fromUser}}{{.fromUser.Username}}{{else}}all{{end}}?sort=created&order=desc" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-t-md" role="menuitem">
Recently created
</a>
</div> </div>
<div class="" role="none"> <div id="sort-gists-dropdown" class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-200 dark:divide-gray-700 rounded-md rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 shadow-lg ring-1 ring-white dark:ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<a href="/{{if .fromUser}}{{.fromUser.Username}}{{else}}all{{end}}?sort=created&order=asc" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500" role="menuitem"> <div class="" role="none">
Least recently created <a href="/{{ .urlPage }}?sort=created&order=desc{{.searchQuery}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-t-md" role="menuitem">
</a> Recently created
</div> </a>
<div class="" role="none"> </div>
<a href="/{{if .fromUser}}{{.fromUser.Username}}{{else}}all{{end}}?sort=updated&order=desc" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500" role="menuitem"> <div class="" role="none">
Recently updated <a href="/{{ .urlPage }}?sort=created&order=asc{{.searchQuery}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500" role="menuitem">
</a> Least recently created
</div> </a>
<div class="" role="none"> </div>
<a href="/{{if .fromUser}}{{.fromUser.Username}}{{else}}all{{end}}?sort=updated&order=asc" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-b-md" role="menuitem"> <div class="" role="none">
Least recently updated <a href="/{{ .urlPage }}?sort=updated&order=desc{{.searchQuery}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500" role="menuitem">
</a> Recently updated
</a>
</div>
<div class="" role="none">
<a href="/{{ .urlPage }}?sort=updated&order=asc{{.searchQuery}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-b-md" role="menuitem">
Least recently updated
</a>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{{ if and (ne .mode "all") (ne .mode "search") }}
<div class="mt-4">
<div class="sm:hidden">
<label for="tabs" class="sr-only">Select a tab</label>
<select id="gist-tabs" name="tabs" class="block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-primary-500 focus:outline-none focus:ring-primary-500 sm:text-sm dark:bg-gray-800 dark:border-gray-700">
<option {{if eq .mode "fromUser"}}selected {{end}}data-url="/{{ .fromUser.Username }}">All gists ({{ .countFromUser }})</option>
{{ if ne .countLiked 0 }}<option {{if eq .mode "liked"}}selected {{end}}data-url="/{{ .fromUser.Username }}/liked">Liked ({{ .countLiked }})</option>{{end}}
{{ if ne .countForked 0 }}<option {{if eq .mode "forked"}}selected {{end}}data-url="/{{ .fromUser.Username }}/forked">Forked ({{ .countForked }})</option>{{end}}
</select>
</div>
<div class="hidden sm:block">
<div class="border-b border-gray-200 dark:border-gray-700 flex">
<div class="flex-auto">
<nav class="-mb-px flex space-x-6" aria-label="Tabs">
<a href="/{{ .fromUser.Username }}" class="{{if eq .mode "fromUser"}}border-primary-500 font-bold {{else}}border-transparent hover:border-gray-200 hover:text-gray-700{{end}} text-slate-700 dark:text-slate-300 inline-flex items-center whitespace-nowrap border-b-2 py-2 px-1 text-sm">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-1">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z" />
</svg>
All gists
<span class="bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-slate-300 ml-2 hidden rounded-full py-0.5 px-2.5 text-xs font-medium md:inline-block">{{ .countFromUser }}</span>
</a>
{{ if ne .countLiked 0 }}
<a href="/{{ .fromUser.Username }}/liked" class="{{if eq .mode "liked"}}border-primary-500 font-bold {{else}}border-transparent hover:border-gray-200 hover:text-gray-700{{end}} text-slate-700 dark:text-slate-300 inline-flex items-center whitespace-nowrap border-b-2 py-2 px-1 text-sm">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6 mr-1">
<path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
</svg>
Liked
<span class="bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-slate-300 ml-2 hidden rounded-full py-0.5 px-2.5 text-xs font-medium md:inline-block">{{ .countLiked }}</span>
</a>
{{ end }}
{{ if ne .countForked 0 }}
<a href="/{{ .fromUser.Username }}/forked" class="{{if eq .mode "forked"}}border-primary-500 font-bold {{else}}border-transparent hover:border-gray-200 hover:text-gray-700{{end}} text-slate-700 dark:text-slate-300 inline-flex items-center whitespace-nowrap border-b-2 py-2 px-1 text-sm" aria-current="page">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-1">
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" />
</svg>
Forked
<span class="bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-slate-300 ml-2 hidden rounded-full py-0.5 px-2.5 text-xs font-medium md:inline-block">{{ .countForked }}</span>
</a>
{{ end }}
</nav>
</div>
</div>
</div>
</div>
{{ end }}
</header> </header>
<main> <main>
<div> <div>