mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 20:42:40 +00:00
Search gists (#68)
This commit is contained in:
parent
98c5cd1794
commit
7b5d035a32
10 changed files with 334 additions and 74 deletions
|
@ -28,7 +28,15 @@ func Setup(dbPath string) error {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,12 @@ type Gist struct {
|
|||
ForkedID uint
|
||||
}
|
||||
|
||||
type Like struct {
|
||||
UserID uint `gorm:"primaryKey"`
|
||||
GistID uint `gorm:"primaryKey"`
|
||||
CreatedAt int64
|
||||
}
|
||||
|
||||
func (gist *Gist) BeforeDelete(tx *gorm.DB) error {
|
||||
// Decrement fork counter if the gist was forked
|
||||
err := tx.Model(&Gist{}).
|
||||
|
@ -80,11 +86,11 @@ func GetAllGists(offset int) ([]*Gist, error) {
|
|||
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
|
||||
err := db.Preload("User").Preload("Forked.User").
|
||||
Where("users.username = ? and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", fromUser, currentUserId).
|
||||
Joins("join users on gists.user_id = users.id").
|
||||
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
|
||||
Where("gists.title like ? or gists.description like ?", "%"+query+"%", "%"+query+"%").
|
||||
Limit(11).
|
||||
Offset(offset * 10).
|
||||
Order("gists." + sort + "_at " + order).
|
||||
|
@ -93,6 +99,74 @@ func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort s
|
|||
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) {
|
||||
var gists []*Gist
|
||||
err := db.Table("gists").
|
||||
|
|
|
@ -337,7 +337,6 @@ func urlJoin(base string, elem ...string) string {
|
|||
}
|
||||
|
||||
func getAvatarUrlFromProvider(provider string, identifier string) string {
|
||||
fmt.Println("getAvatarUrlFromProvider", provider, identifier)
|
||||
switch provider {
|
||||
case "github":
|
||||
return "https://avatars.githubusercontent.com/u/" + identifier + "?v=4"
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"gorm.io/gorm"
|
||||
"html/template"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
@ -85,10 +86,10 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
|
|||
|
||||
func allGists(ctx echo.Context) error {
|
||||
var err error
|
||||
var urlPage string
|
||||
|
||||
fromUserStr := ctx.Param("user")
|
||||
|
||||
userLogged := getUserLogged(ctx)
|
||||
|
||||
pageInt := getPage(ctx)
|
||||
|
||||
sort := "created"
|
||||
|
@ -114,14 +115,37 @@ func allGists(ctx echo.Context) error {
|
|||
} else {
|
||||
currentUserId = 0
|
||||
}
|
||||
|
||||
if fromUserStr == "" {
|
||||
setData(ctx, "htmlTitle", "All gists")
|
||||
fromUserStr = "all"
|
||||
gists, err = models.GetAllGistsForCurrentUser(currentUserId, pageInt-1, sort, order)
|
||||
urlctx := ctx.Request().URL.Path
|
||||
if strings.HasSuffix(urlctx, "search") {
|
||||
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 {
|
||||
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
|
||||
|
||||
fromUser, err = models.GetUserByUsername(fromUserStr)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
@ -131,7 +155,40 @@ func allGists(ctx echo.Context) error {
|
|||
}
|
||||
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 {
|
||||
|
@ -142,6 +199,7 @@ func allGists(ctx echo.Context) error {
|
|||
return errorRes(404, "Page not found", nil)
|
||||
}
|
||||
|
||||
setData(ctx, "urlPage", urlPage)
|
||||
return html(ctx, "all.html")
|
||||
}
|
||||
|
||||
|
@ -527,7 +585,7 @@ func likes(ctx echo.Context) error {
|
|||
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")
|
||||
return html(ctx, "likes.html")
|
||||
}
|
||||
|
|
|
@ -198,7 +198,10 @@ func Start() {
|
|||
}
|
||||
|
||||
g1.GET("/all", allGists, checkRequireLogin)
|
||||
g1.GET("/search", allGists, checkRequireLogin)
|
||||
g1.GET("/:user", allGists, checkRequireLogin)
|
||||
g1.GET("/:user/liked", allGists, checkRequireLogin)
|
||||
g1.GET("/:user/forked", allGists, checkRequireLogin)
|
||||
|
||||
g3 := g1.Group("/:user/:gistname")
|
||||
{
|
||||
|
|
|
@ -166,7 +166,7 @@ func validateReservedKeywords(fl validator.FieldLevel) bool {
|
|||
name := fl.Field().String()
|
||||
|
||||
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{}{}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
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) => {
|
||||
e.title = moment.unix(parseInt(e.innerHTML)).format('LLLL');
|
||||
e.innerHTML = moment.unix(parseInt(e.innerHTML)).fromNow();
|
||||
|
|
98
templates/base/base_header.html
vendored
98
templates/base/base_header.html
vendored
|
@ -63,27 +63,79 @@
|
|||
<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="/" 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 }}
|
||||
<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>
|
||||
{{ end }}
|
||||
<div class="flex flex-1 items-center justify-center px-2 lg:ml-6 lg:justify-end">
|
||||
<div class="w-full max-w-lg lg:max-w-xs">
|
||||
<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 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.IsAdmin }}
|
||||
<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>
|
||||
{{ end }}
|
||||
<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>
|
||||
|
||||
<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" />
|
||||
<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">
|
||||
<div class="inline-flex">
|
||||
<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>
|
||||
<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" />
|
||||
</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 }}
|
||||
{{ 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">
|
||||
|
@ -106,7 +158,7 @@
|
|||
</svg>
|
||||
</div>
|
||||
<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">
|
||||
<!-- 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">
|
||||
|
@ -138,6 +190,20 @@
|
|||
|
||||
<!-- 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">
|
||||
<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>
|
||||
|
|
2
templates/base/gist_header.html
vendored
2
templates/base/gist_header.html
vendored
|
@ -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" />
|
||||
</svg>
|
||||
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>
|
||||
</nav>
|
||||
<div class="float-right inline-flex items-center space-x-2">
|
||||
|
|
134
templates/pages/all.html
vendored
134
templates/pages/all.html
vendored
|
@ -1,56 +1,104 @@
|
|||
{{ template "header" .}}
|
||||
<div class="py-10">
|
||||
<header class="pb-4 flex ">
|
||||
<div class="flex-auto">
|
||||
{{if .fromUser}}
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<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>
|
||||
<p class="text-sm text-slate-500">Joined <span class="moment-timestamp">{{.fromUser.CreatedAt}}</span></p>
|
||||
<header class="pb-4 ">
|
||||
<div class="flex">
|
||||
<div class="flex-auto">
|
||||
{{if .fromUser}}
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<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>
|
||||
<p class="text-sm text-slate-500">Joined <span class="moment-timestamp">{{.fromUser.CreatedAt}}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
<h1 class="text-2xl font-bold leading-tight">All gists</h1>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<h1 class="text-2xl font-bold leading-tight">All gists</h1>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="float-right">
|
||||
<div class="relative inline-block text-left">
|
||||
<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">
|
||||
<span class="text-gray-700 dark:text-gray-300">Sort : <span class="text-slate-700 dark:text-slate-300">{{.order}} {{.sort}}</span></span>
|
||||
<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 class="align-middle inline-flex items-center">
|
||||
<div class="relative text-left">
|
||||
<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">
|
||||
<span class="text-gray-700 dark:text-gray-300">Sort : <span class="text-slate-700 dark:text-slate-300">{{.order}} {{.sort}}</span></span>
|
||||
<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 class="" role="none">
|
||||
<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">
|
||||
Least recently created
|
||||
</a>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<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">
|
||||
Recently updated
|
||||
</a>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<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">
|
||||
Least recently updated
|
||||
</a>
|
||||
<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">
|
||||
<div class="" role="none">
|
||||
<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">
|
||||
Recently created
|
||||
</a>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<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">
|
||||
Least recently created
|
||||
</a>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<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">
|
||||
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>
|
||||
{{ 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>
|
||||
<main>
|
||||
<div>
|
||||
|
|
Loading…
Reference in a new issue