mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 20:42:40 +00:00
Added private visibility
* Changed gist type and added HTML button on creation * Adapted label and edit button * Changed rules for git HTTP and SSH * Adapt Readme features
This commit is contained in:
parent
4f623881ac
commit
25316d7bf2
11 changed files with 114 additions and 33 deletions
|
@ -22,7 +22,7 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Create public or unlisted snippets
|
* Create public, unlisted or private snippets
|
||||||
* Clone / Pull / Push snippets **via Git** over HTTP or SSH
|
* Clone / Pull / Push snippets **via Git** over HTTP or SSH
|
||||||
* Revisions history
|
* Revisions history
|
||||||
* Syntax highlighting ; markdown & CSV support
|
* Syntax highlighting ; markdown & CSV support
|
||||||
|
|
|
@ -15,7 +15,7 @@ type Gist struct {
|
||||||
Preview string
|
Preview string
|
||||||
PreviewFilename string
|
PreviewFilename string
|
||||||
Description string
|
Description string
|
||||||
Private bool
|
Private int // 0: public, 1: unlisted, 2: private
|
||||||
UserID uint
|
UserID uint
|
||||||
User User
|
User User
|
||||||
NbFiles int
|
NbFiles int
|
||||||
|
@ -89,7 +89,7 @@ func GetAllGists(offset int) ([]*Gist, error) {
|
||||||
func GetAllGistsFromSearch(currentUserId uint, query string, 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("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
|
Where("((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
|
||||||
Where("gists.title like ? or gists.description like ?", "%"+query+"%", "%"+query+"%").
|
Where("gists.title like ? or gists.description like ?", "%"+query+"%", "%"+query+"%").
|
||||||
Limit(11).
|
Limit(11).
|
||||||
Offset(offset * 10).
|
Offset(offset * 10).
|
||||||
|
@ -101,7 +101,7 @@ func GetAllGistsFromSearch(currentUserId uint, query string, offset int, sort st
|
||||||
|
|
||||||
func gistsFromUserStatement(fromUserId uint, currentUserId uint) *gorm.DB {
|
func gistsFromUserStatement(fromUserId uint, currentUserId uint) *gorm.DB {
|
||||||
return db.Preload("User").Preload("Forked.User").
|
return db.Preload("User").Preload("Forked.User").
|
||||||
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
|
Where("((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
|
||||||
Where("users.id = ?", fromUserId).
|
Where("users.id = ?", fromUserId).
|
||||||
Joins("join users on gists.user_id = users.id")
|
Joins("join users on gists.user_id = users.id")
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ func CountAllGistsFromUser(fromUserId uint, currentUserId uint) (int64, error) {
|
||||||
|
|
||||||
func likedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
|
func likedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
|
||||||
return db.Preload("User").Preload("Forked.User").
|
return db.Preload("User").Preload("Forked.User").
|
||||||
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
|
Where("((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
|
||||||
Where("likes.user_id = ?", fromUserId).
|
Where("likes.user_id = ?", fromUserId).
|
||||||
Joins("join likes on gists.id = likes.gist_id").
|
Joins("join likes on gists.id = likes.gist_id").
|
||||||
Joins("join users on likes.user_id = users.id")
|
Joins("join users on likes.user_id = users.id")
|
||||||
|
@ -147,7 +147,7 @@ func CountAllGistsLikedByUser(fromUserId uint, currentUserId uint) (int64, error
|
||||||
|
|
||||||
func forkedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
|
func forkedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
|
||||||
return db.Preload("User").Preload("Forked.User").
|
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.forked_id is not null and ((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
|
||||||
Where("gists.user_id = ?", fromUserId).
|
Where("gists.user_id = ?", fromUserId).
|
||||||
Joins("join users on gists.user_id = users.id")
|
Joins("join users on gists.user_id = users.id")
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ func (gist *Gist) GetForks(currentUserId uint, offset int) ([]*Gist, error) {
|
||||||
var gists []*Gist
|
var gists []*Gist
|
||||||
err := db.Model(&gist).Preload("User").
|
err := db.Model(&gist).Preload("User").
|
||||||
Where("forked_id = ?", gist.ID).
|
Where("forked_id = ?", gist.ID).
|
||||||
Where("(gists.private = 0) or (gists.private = 1 and gists.user_id = ?)", currentUserId).
|
Where("(gists.private = 0) or (gists.private > 0 and gists.user_id = ?)", currentUserId).
|
||||||
Limit(11).
|
Limit(11).
|
||||||
Offset(offset * 10).
|
Offset(offset * 10).
|
||||||
Order("updated_at desc").
|
Order("updated_at desc").
|
||||||
|
@ -379,7 +379,7 @@ func (gist *Gist) UpdatePreviewAndCount() error {
|
||||||
type GistDTO struct {
|
type GistDTO struct {
|
||||||
Title string `validate:"max=50" form:"title"`
|
Title string `validate:"max=50" form:"title"`
|
||||||
Description string `validate:"max=150" form:"description"`
|
Description string `validate:"max=150" form:"description"`
|
||||||
Private bool `form:"private"`
|
Private int `validate:"number,min=0,max=2" form:"private"`
|
||||||
Files []FileDTO `validate:"min=1,dive"`
|
Files []FileDTO `validate:"min=1,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,12 +42,21 @@ func runGitCommand(ch ssh.Channel, gitCmd string, key string, ip string) error {
|
||||||
return errors.New("internal server error")
|
return errors.New("internal server error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if verb == "receive-pack" || requireLogin == "1" {
|
// Check for the key if :
|
||||||
|
// - user wants to push the gist
|
||||||
|
// - user wants to clone a private gist
|
||||||
|
// - gist is not found (obfuscation)
|
||||||
|
// - admin setting to require login is set to true
|
||||||
|
if verb == "receive-pack" ||
|
||||||
|
gist.Private == 2 ||
|
||||||
|
gist.ID == 0 ||
|
||||||
|
requireLogin == "1" {
|
||||||
|
|
||||||
pubKey, err := models.SSHKeyExistsForUser(key, gist.UserID)
|
pubKey, err := models.SSHKeyExistsForUser(key, gist.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
log.Warn().Msg("Invalid SSH authentication attempt from " + ip)
|
log.Warn().Msg("Invalid SSH authentication attempt from " + ip)
|
||||||
return errors.New("unauthorized")
|
return errors.New("gist not found")
|
||||||
}
|
}
|
||||||
errorSsh("Failed to get user by SSH key id", err)
|
errorSsh("Failed to get user by SSH key id", err)
|
||||||
return errors.New("internal server error")
|
return errors.New("internal server error")
|
||||||
|
|
|
@ -80,7 +80,7 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
setData(ctx, "hasLiked", hasLiked)
|
setData(ctx, "hasLiked", hasLiked)
|
||||||
}
|
}
|
||||||
|
|
||||||
if gist.Private {
|
if gist.Private > 0 {
|
||||||
setData(ctx, "NoIndex", true)
|
setData(ctx, "NoIndex", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,22 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gistSoftInit try to load a gist (same as gistInit) but does not return a 404 if the gist is not found
|
||||||
|
// useful for git clients using HTTP to obfuscate the existence of a private gist
|
||||||
|
func gistSoftInit(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
userName := ctx.Param("user")
|
||||||
|
gistName := ctx.Param("gistname")
|
||||||
|
|
||||||
|
gistName = strings.TrimSuffix(gistName, ".git")
|
||||||
|
|
||||||
|
gist, _ := models.GetGist(userName, gistName)
|
||||||
|
setData(ctx, "gist", gist)
|
||||||
|
|
||||||
|
return next(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func allGists(ctx echo.Context) error {
|
func allGists(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
var urlPage string
|
var urlPage string
|
||||||
|
@ -400,7 +416,7 @@ func processCreate(ctx echo.Context) error {
|
||||||
func toggleVisibility(ctx echo.Context) error {
|
func toggleVisibility(ctx echo.Context) error {
|
||||||
var gist = getData(ctx, "gist").(*models.Gist)
|
var gist = getData(ctx, "gist").(*models.Gist)
|
||||||
|
|
||||||
gist.Private = !gist.Private
|
gist.Private = (gist.Private + 1) % 3
|
||||||
if err := gist.Update(); err != nil {
|
if err := gist.Update(); err != nil {
|
||||||
return errorRes(500, "Error updating this gist", err)
|
return errorRes(500, "Error updating this gist", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,16 +47,23 @@ func gitHttp(ctx echo.Context) error {
|
||||||
|
|
||||||
gist := getData(ctx, "gist").(*models.Gist)
|
gist := getData(ctx, "gist").(*models.Gist)
|
||||||
|
|
||||||
|
// Shows basic auth if :
|
||||||
|
// - user wants to push the gist
|
||||||
|
// - user wants to clone a private gist
|
||||||
|
// - gist is not found (obfuscation)
|
||||||
|
// - admin setting to require login is set to true
|
||||||
noAuth := (ctx.QueryParam("service") == "git-upload-pack" ||
|
noAuth := (ctx.QueryParam("service") == "git-upload-pack" ||
|
||||||
strings.HasSuffix(ctx.Request().URL.Path, "git-upload-pack") ||
|
strings.HasSuffix(ctx.Request().URL.Path, "git-upload-pack") ||
|
||||||
ctx.Request().Method == "GET") &&
|
ctx.Request().Method == "GET") &&
|
||||||
|
gist.Private != 2 &&
|
||||||
|
gist.ID != 0 &&
|
||||||
!getData(ctx, "RequireLogin").(bool)
|
!getData(ctx, "RequireLogin").(bool)
|
||||||
|
|
||||||
repositoryPath := git.RepositoryPath(gist.User.Username, gist.Uuid)
|
repositoryPath := git.RepositoryPath(gist.User.Username, gist.Uuid)
|
||||||
|
|
||||||
if _, err := os.Stat(repositoryPath); os.IsNotExist(err) {
|
if _, err := os.Stat(repositoryPath); os.IsNotExist(err) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorRes(500, "Repository does not exist", err)
|
return errorRes(404, "Repository directory does not exist", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +89,16 @@ func gitHttp(ctx echo.Context) error {
|
||||||
return basicAuth(ctx)
|
return basicAuth(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if gist.ID == 0 {
|
||||||
|
return errorRes(404, "Not found", nil)
|
||||||
|
}
|
||||||
|
|
||||||
if ok, err := argon2id.verify(authPassword, gist.User.Password); !ok || gist.User.Username != authUsername {
|
if ok, err := argon2id.verify(authPassword, gist.User.Password); !ok || gist.User.Username != authUsername {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorRes(500, "Cannot verify password", err)
|
return errorRes(500, "Cannot verify password", err)
|
||||||
}
|
}
|
||||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||||
return errorRes(403, "Unauthorized", nil)
|
return errorRes(404, "Not found", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return route.handler(ctx)
|
return route.handler(ctx)
|
||||||
|
|
|
@ -30,11 +30,11 @@ var re = regexp.MustCompile("[^a-z0-9]+")
|
||||||
var fm = template.FuncMap{
|
var fm = template.FuncMap{
|
||||||
"split": strings.Split,
|
"split": strings.Split,
|
||||||
"indexByte": strings.IndexByte,
|
"indexByte": strings.IndexByte,
|
||||||
"toInt": func(i string) int64 {
|
"toInt": func(i string) int {
|
||||||
val, _ := strconv.ParseInt(i, 10, 64)
|
val, _ := strconv.Atoi(i)
|
||||||
return val
|
return val
|
||||||
},
|
},
|
||||||
"inc": func(i int64) int64 {
|
"inc": func(i int) int {
|
||||||
return i + 1
|
return i + 1
|
||||||
},
|
},
|
||||||
"splitGit": func(i string) []string {
|
"splitGit": func(i string) []string {
|
||||||
|
@ -88,6 +88,20 @@ var fm = template.FuncMap{
|
||||||
return config.C.ExternalUrl + "/" + manifestEntries[jsfile].File
|
return config.C.ExternalUrl + "/" + manifestEntries[jsfile].File
|
||||||
},
|
},
|
||||||
"defaultAvatar": defaultAvatar,
|
"defaultAvatar": defaultAvatar,
|
||||||
|
"visibilityStr": func(visibility int, lowercase bool) string {
|
||||||
|
s := "Public"
|
||||||
|
switch visibility {
|
||||||
|
case 1:
|
||||||
|
s = "Unlisted"
|
||||||
|
case 2:
|
||||||
|
s = "Private"
|
||||||
|
}
|
||||||
|
|
||||||
|
if lowercase {
|
||||||
|
return strings.ToLower(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var EmbedFS fs.FS
|
var EmbedFS fs.FS
|
||||||
|
@ -226,7 +240,7 @@ func Start() {
|
||||||
debugStr := ""
|
debugStr := ""
|
||||||
// Git HTTP routes
|
// Git HTTP routes
|
||||||
if config.C.HttpGit {
|
if config.C.HttpGit {
|
||||||
e.Any("/:user/:gistname/*", gitHttp, gistInit)
|
e.Any("/:user/:gistname/*", gitHttp, gistSoftInit)
|
||||||
debugStr = " (with Git over HTTP)"
|
debugStr = " (with Git over HTTP)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -183,4 +183,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const gistmenuvisibility = document.getElementById('gist-menu-visibility');
|
||||||
|
if (gistmenuvisibility) {
|
||||||
|
let submitgistbutton = (document.getElementById('submit-gist') as HTMLInputElement);
|
||||||
|
document.getElementById('gist-visibility-menu-button')!.onclick = () => {
|
||||||
|
console.log("z");
|
||||||
|
gistmenuvisibility!.classList.toggle('hidden');
|
||||||
|
}
|
||||||
|
Array.from(document.querySelectorAll('.gist-visibility-option')).forEach((el) => {
|
||||||
|
(el as HTMLElement).onclick = () => {
|
||||||
|
submitgistbutton.textContent = "Create " + el.textContent.toLowerCase() + " gist";
|
||||||
|
submitgistbutton!.value = (el as HTMLElement).dataset.visibility || '0';
|
||||||
|
gistmenuvisibility!.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
3
templates/base/gist_header.html
vendored
3
templates/base/gist_header.html
vendored
|
@ -92,8 +92,7 @@
|
||||||
<p class="mt-1 max-w-2xl text-sm text-slate-500">Forked from <a href="{{ $.c.ExternalUrl }}/{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Uuid }}">{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Title }}</a></p>
|
<p class="mt-1 max-w-2xl text-sm text-slate-500">Forked from <a href="{{ $.c.ExternalUrl }}/{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Uuid }}">{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Title }}</a></p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<p class="mt-1 max-w-2xl text-sm text-slate-500">Last active <span class="moment-timestamp"> {{ .gist.UpdatedAt }} </span>
|
<p class="mt-1 max-w-2xl text-sm text-slate-500">Last active <span class="moment-timestamp"> {{ .gist.UpdatedAt }} </span>
|
||||||
{{ if .gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> Unlisted </span>{{ end }}
|
{{ if .gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> {{ visibilityStr .gist.Private false }} </span>{{ end }}
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-3 max-w-2xl text-slate-700 dark:text-slate-300">{{ .gist.Description }}</p>
|
<p class="mt-3 max-w-2xl text-slate-700 dark:text-slate-300">{{ .gist.Description }}</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
2
templates/pages/all.html
vendored
2
templates/pages/all.html
vendored
|
@ -137,7 +137,7 @@
|
||||||
</div>
|
</div>
|
||||||
<h5 class="text-sm text-slate-500 pb-1">Last active <span class="moment-timestamp">{{ $gist.UpdatedAt }}</span>
|
<h5 class="text-sm text-slate-500 pb-1">Last active <span class="moment-timestamp">{{ $gist.UpdatedAt }}</span>
|
||||||
{{ if $gist.Forked }} • Forked from <a href="{{ $.c.ExternalUrl }}/{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Uuid }}">{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Title }}</a> {{ end }}
|
{{ if $gist.Forked }} • Forked from <a href="{{ $.c.ExternalUrl }}/{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Uuid }}">{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Title }}</a> {{ end }}
|
||||||
{{ if $gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> Unlisted </span>{{ end }}</h5>
|
{{ if $gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> {{ visibilityStr $gist.Private false }} </span>{{ end }}</h5>
|
||||||
<h5 class="text-xs text-slate-700 dark:text-slate-300 py-1">{{ $gist.Description }}</h5>
|
<h5 class="text-xs text-slate-700 dark:text-slate-300 py-1">{{ $gist.Description }}</h5>
|
||||||
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}/{{ $gist.Uuid }}" class="text-slate-700 dark:text-slate-300">
|
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}/{{ $gist.Uuid }}" class="text-slate-700 dark:text-slate-300">
|
||||||
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 overflow-auto hover:border-primary-600">
|
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 overflow-auto hover:border-primary-600">
|
||||||
|
|
21
templates/pages/create.html
vendored
21
templates/pages/create.html
vendored
|
@ -56,8 +56,25 @@
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button type="button" id="add-file" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-gray-700 dark:text-white bg-gray-100 dark:bg-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">Add file</button>
|
<button type="button" id="add-file" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-gray-700 dark:text-white bg-gray-100 dark:bg-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">Add file</button>
|
||||||
<button type="submit" name="private" value="1" class="ml-auto inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-gray-700 dark:text-white bg-gray-100 dark:bg-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">Create unlisted gist</button>
|
|
||||||
<button type="submit" name="private" value="0" class="ml-2 inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">Create public gist</button>
|
<div class="ml-auto inline-flex ">
|
||||||
|
<button id="submit-gist" type="submit" name="private" value="0" class="ml-2 items-center px-4 py-2 border border-transparent border-primary-200 dark:border-primary-700 text-sm font-medium rounded-l-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 z-20">Create public gist</button>
|
||||||
|
<div class="relative -ml-px block">
|
||||||
|
<button type="button" class="relative inline-flex items-center rounded-r-md bg-primary-500 hover:bg-primary-600 px-2 py-2 text-gray-400 border border-transparent border-primary-200 dark:border-primary-700 focus:z-10" id="gist-visibility-menu-button">
|
||||||
|
<span class="sr-only">Open options</span>
|
||||||
|
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="white" 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 id="gist-menu-visibility" class="hidden absolute right-0 z-10 mt-2 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="gist-visibility-menu-button">
|
||||||
|
<div class="rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none" role="none">
|
||||||
|
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-visibility="0" role="menuitem">Public</span>
|
||||||
|
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-visibility="1" role="menuitem">Unlisted</span>
|
||||||
|
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-visibility="2" role="menuitem">Private</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ .csrfHtml }}
|
{{ .csrfHtml }}
|
||||||
</form>
|
</form>
|
||||||
|
|
19
templates/pages/edit.html
vendored
19
templates/pages/edit.html
vendored
|
@ -11,18 +11,17 @@
|
||||||
<form id="visibility" class="flex items-center whitespace-nowrap" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/visibility">
|
<form id="visibility" class="flex items-center whitespace-nowrap" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/visibility">
|
||||||
{{ .csrfHtml }}
|
{{ .csrfHtml }}
|
||||||
<button type="submit" class="ml-auto relative inline-flex items-center space-x-2 rounded-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
<button type="submit" class="ml-auto relative inline-flex items-center space-x-2 rounded-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||||
{{ if .gist.Private }}
|
{{ if eq .gist.Private 2 }}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||||
</svg>
|
</svg>
|
||||||
Make public
|
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
||||||
</svg>
|
</svg>
|
||||||
Make unlisted
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
Make {{ visibilityStr (inc .gist.Private) true }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form id="delete" onsubmit="return confirm('Are you sure you want to delete this gist ?')" class="ml-2 flex items-center" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/delete">
|
<form id="delete" onsubmit="return confirm('Are you sure you want to delete this gist ?')" class="ml-2 flex items-center" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/delete">
|
||||||
|
|
Loading…
Reference in a new issue