mirror of
https://github.com/thomiceli/opengist.git
synced 2025-06-22 01:37:58 +02:00
Add embedded gists & JSON gist data/metadata (#179)
This commit is contained in:
@ -340,7 +340,7 @@ func (gist *Gist) File(revision string, filename string, truncate bool) (*git.Fi
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var size int64
|
||||
var size uint64
|
||||
|
||||
size, err = git.GetFileSize(gist.User.Username, gist.Uuid, revision, filename)
|
||||
if err != nil {
|
||||
@ -349,7 +349,8 @@ func (gist *Gist) File(revision string, filename string, truncate bool) (*git.Fi
|
||||
|
||||
return &git.File{
|
||||
Filename: filename,
|
||||
Size: humanize.IBytes(uint64(size)),
|
||||
Size: size,
|
||||
HumanSize: humanize.IBytes(size),
|
||||
Content: content,
|
||||
Truncated: truncated,
|
||||
}, err
|
||||
@ -426,6 +427,19 @@ func (gist *Gist) UpdatePreviewAndCount() error {
|
||||
return gist.Update()
|
||||
}
|
||||
|
||||
func (gist *Gist) VisibilityStr() string {
|
||||
switch gist.Private {
|
||||
case PublicVisibility:
|
||||
return "public"
|
||||
case UnlistedVisibility:
|
||||
return "unlisted"
|
||||
case PrivateVisibility:
|
||||
return "private"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// -- DTO -- //
|
||||
|
||||
type GistDTO struct {
|
||||
|
@ -149,7 +149,7 @@ func GetFileContent(user string, gist string, revision string, filename string,
|
||||
return content, truncated, nil
|
||||
}
|
||||
|
||||
func GetFileSize(user string, gist string, revision string, filename string) (int64, error) {
|
||||
func GetFileSize(user string, gist string, revision string, filename string) (uint64, error) {
|
||||
repositoryPath := RepositoryPath(user, gist)
|
||||
|
||||
cmd := exec.Command(
|
||||
@ -165,7 +165,7 @@ func GetFileSize(user string, gist string, revision string, filename string) (in
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return strconv.ParseInt(strings.TrimSuffix(string(stdout), "\n"), 10, 64)
|
||||
return strconv.ParseUint(strings.TrimSuffix(string(stdout), "\n"), 10, 64)
|
||||
}
|
||||
|
||||
func GetLog(user string, gist string, skip int) ([]*Commit, error) {
|
||||
|
@ -11,13 +11,14 @@ import (
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Filename string
|
||||
Size string
|
||||
OldFilename string
|
||||
Content string
|
||||
Truncated bool
|
||||
IsCreated bool
|
||||
IsDeleted bool
|
||||
Filename string `json:"filename"`
|
||||
Size uint64 `json:"size"`
|
||||
HumanSize string `json:"human_size"`
|
||||
OldFilename string `json:"-"`
|
||||
Content string `json:"content"`
|
||||
Truncated bool `json:"truncated"`
|
||||
IsCreated bool `json:"-"`
|
||||
IsDeleted bool `json:"-"`
|
||||
}
|
||||
|
||||
type CsvFile struct {
|
||||
|
@ -17,8 +17,8 @@ gist.header.clone-http: Klonovat pomocí %s
|
||||
gist.header.clone-http-help: Klonovat s pomocí Git pomocí základní autentizace HTTP.
|
||||
gist.header.clone-ssh: Klonovat pomocí SSH
|
||||
gist.header.clone-ssh-help: Klonovat s pomocí Git pomocí klíče SSH.
|
||||
gist.header.share: Sdílet
|
||||
gist.header.share-help: Zkopírovat sdílitelný odkaz na tento gist.
|
||||
gist.header.embed:
|
||||
gist.header.embed-help:
|
||||
gist.header.download-zip: Stáhnout ZIP
|
||||
|
||||
gist.raw: Raw
|
||||
|
@ -17,8 +17,8 @@ gist.header.clone-http: Clone via %s
|
||||
gist.header.clone-http-help: Clone with Git using HTTP basic authentication.
|
||||
gist.header.clone-ssh: Clone via SSH
|
||||
gist.header.clone-ssh-help: Clone with Git using an SSH key.
|
||||
gist.header.share: Share
|
||||
gist.header.share-help: Copy shareable link for this gist.
|
||||
gist.header.embed: Embed
|
||||
gist.header.embed-help: Embed this gist to your website.
|
||||
gist.header.download-zip: Download ZIP
|
||||
|
||||
gist.raw: Raw
|
||||
|
@ -17,8 +17,8 @@ gist.header.clone-http: Clonar via %s
|
||||
gist.header.clone-http-help: Clonar con Git usando autenticación básica HTTP.
|
||||
gist.header.clone-ssh: Clonar via SSH
|
||||
gist.header.clone-ssh-help: Clonar con Git usando una clave SSH.
|
||||
gist.header.share: Compartir
|
||||
gist.header.share-help: Copiar enlace para compartir este gist.
|
||||
gist.header.embed:
|
||||
gist.header.embed-help:
|
||||
gist.header.download-zip: Descargar ZIP
|
||||
|
||||
gist.raw: Sin formato
|
||||
|
@ -17,8 +17,8 @@ gist.header.clone-http: Cloner via %s
|
||||
gist.header.clone-http-help: Cloner avec Git en utilisant l'authentification HTTP basic.
|
||||
gist.header.clone-ssh: Cloner via SSH
|
||||
gist.header.clone-ssh-help: Cloner avec Git en utilisant une clé SSH.
|
||||
gist.header.share: Partager
|
||||
gist.header.share-help: Copier le lien partageable de ce gist.
|
||||
gist.header.embed:
|
||||
gist.header.embed-help:
|
||||
gist.header.download-zip: Télécharger en ZIP
|
||||
|
||||
gist.raw: Brut
|
||||
|
@ -17,8 +17,8 @@ gist.header.clone-http: "Clone-ozás ezzel: %s"
|
||||
gist.header.clone-http-help: Clone-ozás Git HTTP basic hitelesítéssel.
|
||||
gist.header.clone-ssh: Clone-ozás SSH-n keresztül
|
||||
gist.header.clone-ssh-help: Clone-ozás SSH kulccsal
|
||||
gist.header.share: Megosztás
|
||||
gist.header.share-help: Másold ki ennek a gistnek a megosztható linkjét
|
||||
gist.header.embed:
|
||||
gist.header.embed-help:
|
||||
gist.header.download-zip: ZIP archívum letöltése
|
||||
|
||||
gist.raw: Eredeti
|
||||
|
@ -17,8 +17,8 @@ gist.header.clone-http: Клонировать с помощью %s
|
||||
gist.header.clone-http-help: Клонировать с помощью Git используя аутентификацию HTTP.
|
||||
gist.header.clone-ssh: Клонировать c помощью SSH
|
||||
gist.header.clone-ssh-help: Клонировать c помощью Git используя ключ SSH.
|
||||
gist.header.share: Поделиться
|
||||
gist.header.share-help: Скопировать ссылку на фрагмент.
|
||||
gist.header.embed:
|
||||
gist.header.embed-help:
|
||||
gist.header.download-zip: Скачать ZIP-архив
|
||||
|
||||
gist.raw: Исходник
|
||||
|
@ -17,8 +17,8 @@ gist.header.clone-http: 通过 %s 克隆
|
||||
gist.header.clone-http-help: 使用 Git 通过 HTTP 基础认证克隆。
|
||||
gist.header.clone-ssh: 通过 SSH 克隆
|
||||
gist.header.clone-ssh-help: 使用 Git 通过 SSH 密钥克隆。
|
||||
gist.header.share: 分享
|
||||
gist.header.share-help: 为此 Gist 复制可供分享的链接。
|
||||
gist.header.embed:
|
||||
gist.header.embed-help:
|
||||
gist.header.download-zip: 下载 ZIP
|
||||
|
||||
gist.raw: 原始文件
|
||||
|
@ -17,8 +17,8 @@ gist.header.clone-http: 透過 %s 複製
|
||||
gist.header.clone-http-help: 使用 HTTP 基本認證透過 Git 複製。
|
||||
gist.header.clone-ssh: 透過 SSH 複製
|
||||
gist.header.clone-ssh-help: 使用 SSH 金鑰透過 Git 複製。
|
||||
gist.header.share: 分享
|
||||
gist.header.share-help: 複製這個 Gist 的連結。
|
||||
gist.header.embed:
|
||||
gist.header.embed-help:
|
||||
gist.header.download-zip: 下載 ZIP
|
||||
|
||||
gist.raw: 原始檔案
|
||||
|
@ -8,15 +8,16 @@ import (
|
||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/git"
|
||||
)
|
||||
|
||||
type RenderedFile struct {
|
||||
*git.File
|
||||
Type string
|
||||
Lines []string
|
||||
HTML string
|
||||
Type string `json:"type"`
|
||||
Lines []string `json:"-"`
|
||||
HTML string `json:"-"`
|
||||
}
|
||||
|
||||
type RenderedGist struct {
|
||||
@ -66,6 +67,19 @@ func HighlightFile(file *git.File) (RenderedFile, error) {
|
||||
return rendered, err
|
||||
}
|
||||
|
||||
func HighlightFiles(files []*git.File) ([]RenderedFile, error) {
|
||||
renderedFiles := make([]RenderedFile, 0, len(files))
|
||||
for _, file := range files {
|
||||
rendered, err := HighlightFile(file)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Error rendering gist preview for " + file.Filename)
|
||||
}
|
||||
renderedFiles = append(renderedFiles, rendered)
|
||||
}
|
||||
|
||||
return renderedFiles, nil
|
||||
}
|
||||
|
||||
func HighlightGistPreview(gist *db.Gist) (RenderedGist, error) {
|
||||
rendered := RenderedGist{
|
||||
Gist: gist,
|
||||
|
@ -2,15 +2,19 @@ package web
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/render"
|
||||
"html/template"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
@ -26,7 +30,17 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
userName := ctx.Param("user")
|
||||
gistName := ctx.Param("gistname")
|
||||
|
||||
gistName = strings.TrimSuffix(gistName, ".git")
|
||||
switch filepath.Ext(gistName) {
|
||||
case ".js":
|
||||
setData(ctx, "gistpage", "js")
|
||||
gistName = strings.TrimSuffix(gistName, ".js")
|
||||
case ".json":
|
||||
setData(ctx, "gistpage", "json")
|
||||
gistName = strings.TrimSuffix(gistName, ".json")
|
||||
case ".git":
|
||||
setData(ctx, "gistpage", "git")
|
||||
gistName = strings.TrimSuffix(gistName, ".git")
|
||||
}
|
||||
|
||||
gist, err := db.GetGist(userName, gistName)
|
||||
if err != nil {
|
||||
@ -71,12 +85,15 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
baseHttpUrl = httpProtocol + "://" + ctx.Request().Host
|
||||
}
|
||||
|
||||
setData(ctx, "baseHttpUrl", baseHttpUrl)
|
||||
|
||||
if config.C.HttpGit {
|
||||
setData(ctx, "httpCloneUrl", baseHttpUrl+"/"+userName+"/"+gistName+".git")
|
||||
}
|
||||
|
||||
setData(ctx, "httpCopyUrl", baseHttpUrl+"/"+userName+"/"+gistName)
|
||||
setData(ctx, "currentUrl", template.URL(ctx.Request().URL.Path))
|
||||
setData(ctx, "embedScript", fmt.Sprintf(`<script src="%s"></script>`, baseHttpUrl+"/"+userName+"/"+gistName+".js"))
|
||||
|
||||
nbCommits, err := gist.NbCommits()
|
||||
if err != nil {
|
||||
@ -256,6 +273,12 @@ func allGists(ctx echo.Context) error {
|
||||
}
|
||||
|
||||
func gistIndex(ctx echo.Context) error {
|
||||
if getData(ctx, "gistpage") == "js" {
|
||||
return gistJs(ctx)
|
||||
} else if getData(ctx, "gistpage") == "json" {
|
||||
return gistJson(ctx)
|
||||
}
|
||||
|
||||
gist := getData(ctx, "gist").(*db.Gist)
|
||||
revision := ctx.Param("revision")
|
||||
|
||||
@ -272,13 +295,9 @@ func gistIndex(ctx echo.Context) error {
|
||||
return notFound("Revision not found")
|
||||
}
|
||||
|
||||
renderedFiles := make([]render.RenderedFile, 0, len(files))
|
||||
for _, file := range files {
|
||||
rendered, err := render.HighlightFile(file)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Error rendering gist preview for " + gist.Uuid + " - " + gist.PreviewFilename)
|
||||
}
|
||||
renderedFiles = append(renderedFiles, rendered)
|
||||
renderedFiles, err := render.HighlightFiles(files)
|
||||
if err != nil {
|
||||
return errorRes(500, "Error rendering files", err)
|
||||
}
|
||||
|
||||
setData(ctx, "page", "code")
|
||||
@ -289,6 +308,83 @@ func gistIndex(ctx echo.Context) error {
|
||||
return html(ctx, "gist.html")
|
||||
}
|
||||
|
||||
func gistJson(ctx echo.Context) error {
|
||||
gist := getData(ctx, "gist").(*db.Gist)
|
||||
files, err := gist.Files("HEAD")
|
||||
if err != nil {
|
||||
return errorRes(500, "Error fetching files", err)
|
||||
}
|
||||
|
||||
renderedFiles, err := render.HighlightFiles(files)
|
||||
if err != nil {
|
||||
return errorRes(500, "Error rendering files", err)
|
||||
}
|
||||
|
||||
setData(ctx, "files", renderedFiles)
|
||||
|
||||
htmlbuf := bytes.Buffer{}
|
||||
w := bufio.NewWriter(&htmlbuf)
|
||||
if err = ctx.Echo().Renderer.Render(w, "gist_embed.html", dataMap(ctx), ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
jsUrl, err := url.JoinPath(getData(ctx, "baseHttpUrl").(string), gist.User.Username, gist.Uuid+".js")
|
||||
if err != nil {
|
||||
return errorRes(500, "Error joining url", err)
|
||||
}
|
||||
|
||||
return ctx.JSON(200, map[string]interface{}{
|
||||
"owner": gist.User.Username,
|
||||
"id": gist.Uuid,
|
||||
"title": gist.Title,
|
||||
"description": gist.Description,
|
||||
"created_at": time.Unix(gist.CreatedAt, 0).Format(time.RFC3339),
|
||||
"visibility": gist.VisibilityStr(),
|
||||
"files": renderedFiles,
|
||||
"embed": map[string]string{
|
||||
"html": htmlbuf.String(),
|
||||
"css": getData(ctx, "baseHttpUrl").(string) + asset("embed.css"),
|
||||
"js": jsUrl,
|
||||
"js_dark": jsUrl + "?dark",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func gistJs(ctx echo.Context) error {
|
||||
if _, exists := ctx.QueryParams()["dark"]; exists {
|
||||
setData(ctx, "dark", "dark")
|
||||
}
|
||||
|
||||
gist := getData(ctx, "gist").(*db.Gist)
|
||||
files, err := gist.Files("HEAD")
|
||||
if err != nil {
|
||||
return errorRes(500, "Error fetching files", err)
|
||||
}
|
||||
|
||||
renderedFiles, err := render.HighlightFiles(files)
|
||||
if err != nil {
|
||||
return errorRes(500, "Error rendering files", err)
|
||||
}
|
||||
|
||||
setData(ctx, "files", renderedFiles)
|
||||
|
||||
htmlbuf := bytes.Buffer{}
|
||||
w := bufio.NewWriter(&htmlbuf)
|
||||
if err = ctx.Echo().Renderer.Render(w, "gist_embed.html", dataMap(ctx), ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
js := `document.write('<link rel="stylesheet" href="%s">')
|
||||
document.write('%s')
|
||||
`
|
||||
js = fmt.Sprintf(js, getData(ctx, "baseHttpUrl").(string)+asset("embed.css"),
|
||||
strings.Replace(htmlbuf.String(), "\n", `\n`, -1))
|
||||
ctx.Response().Header().Set("Content-Type", "application/javascript")
|
||||
return plainText(ctx, 200, js)
|
||||
}
|
||||
|
||||
func revisions(ctx echo.Context) error {
|
||||
gist := getData(ctx, "gist").(*db.Gist)
|
||||
userName := gist.User.Username
|
||||
|
@ -86,12 +86,7 @@ var (
|
||||
|
||||
return defaultAvatar()
|
||||
},
|
||||
"asset": func(file string) string {
|
||||
if dev {
|
||||
return "http://localhost:16157/" + file
|
||||
}
|
||||
return config.C.ExternalUrl + "/" + manifestEntries[file].File
|
||||
},
|
||||
"asset": asset,
|
||||
"dev": func() bool {
|
||||
return dev
|
||||
},
|
||||
@ -482,3 +477,10 @@ func defaultAvatar() string {
|
||||
}
|
||||
return config.C.ExternalUrl + "/" + manifestEntries["default.png"].File
|
||||
}
|
||||
|
||||
func asset(file string) string {
|
||||
if dev {
|
||||
return "http://localhost:16157/" + file
|
||||
}
|
||||
return config.C.ExternalUrl + "/" + manifestEntries[file].File
|
||||
}
|
||||
|
@ -36,6 +36,10 @@ func getData(ctx echo.Context, key string) any {
|
||||
return data[key]
|
||||
}
|
||||
|
||||
func dataMap(ctx echo.Context) echo.Map {
|
||||
return ctx.Request().Context().Value(dataKey).(echo.Map)
|
||||
}
|
||||
|
||||
func html(ctx echo.Context, template string) error {
|
||||
return htmlWithCode(ctx, 200, template)
|
||||
}
|
||||
|
Reference in New Issue
Block a user