Skip to content

Commit

Permalink
Add embedded gists & JSON gist data/metadata (thomiceli#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomiceli committed Jan 4, 2024
1 parent 845e28d commit 0753c5c
Show file tree
Hide file tree
Showing 32 changed files with 872 additions and 60 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ install:
build_frontend:
@echo "Building frontend assets..."
npx vite build
EMBED=1 npx postcss 'public/assets/embed-*.css' -c postcss.config.js --replace # until we can .nest { @tailwind } in Sass

build_backend:
@echo "Building Opengist binary..."
Expand Down
18 changes: 16 additions & 2 deletions internal/db/gist.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions internal/git/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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) {
Expand Down
15 changes: 8 additions & 7 deletions internal/git/output_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions internal/i18n/locales/cs-CZ.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/i18n/locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/i18n/locales/es-ES.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/i18n/locales/fr-FR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/i18n/locales/hu-HU.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/i18n/locales/ru-RU.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: Исходник
Expand Down
4 changes: 2 additions & 2 deletions internal/i18n/locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 原始文件
Expand Down
4 changes: 2 additions & 2 deletions internal/i18n/locales/zh-TW.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 原始檔案
Expand Down
20 changes: 17 additions & 3 deletions internal/render/highlight.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
112 changes: 104 additions & 8 deletions internal/web/gist.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down Expand Up @@ -71,12 +85,15 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
baseHttpUrl = httpProtocol + ":https://" + 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 {
Expand Down Expand Up @@ -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")

Expand All @@ -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")
Expand All @@ -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
Expand Down

0 comments on commit 0753c5c

Please sign in to comment.