Add embedded gists & JSON gist data/metadata (#179)

This commit is contained in:
Thomas Miceli 2023-12-21 20:00:04 +01:00
parent 845e28dd59
commit 0753c5cb54
32 changed files with 872 additions and 60 deletions

View file

@ -16,6 +16,7 @@ install:
build_frontend: build_frontend:
@echo "Building frontend assets..." @echo "Building frontend assets..."
npx vite build 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: build_backend:
@echo "Building Opengist binary..." @echo "Building Opengist binary..."

View file

@ -340,7 +340,7 @@ func (gist *Gist) File(revision string, filename string, truncate bool) (*git.Fi
return nil, nil return nil, nil
} }
var size int64 var size uint64
size, err = git.GetFileSize(gist.User.Username, gist.Uuid, revision, filename) size, err = git.GetFileSize(gist.User.Username, gist.Uuid, revision, filename)
if err != nil { if err != nil {
@ -349,7 +349,8 @@ func (gist *Gist) File(revision string, filename string, truncate bool) (*git.Fi
return &git.File{ return &git.File{
Filename: filename, Filename: filename,
Size: humanize.IBytes(uint64(size)), Size: size,
HumanSize: humanize.IBytes(size),
Content: content, Content: content,
Truncated: truncated, Truncated: truncated,
}, err }, err
@ -426,6 +427,19 @@ func (gist *Gist) UpdatePreviewAndCount() error {
return gist.Update() 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 -- // // -- DTO -- //
type GistDTO struct { type GistDTO struct {

View file

@ -149,7 +149,7 @@ func GetFileContent(user string, gist string, revision string, filename string,
return content, truncated, nil 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) repositoryPath := RepositoryPath(user, gist)
cmd := exec.Command( cmd := exec.Command(
@ -165,7 +165,7 @@ func GetFileSize(user string, gist string, revision string, filename string) (in
return 0, err 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) { func GetLog(user string, gist string, skip int) ([]*Commit, error) {

View file

@ -11,13 +11,14 @@ import (
) )
type File struct { type File struct {
Filename string Filename string `json:"filename"`
Size string Size uint64 `json:"size"`
OldFilename string HumanSize string `json:"human_size"`
Content string OldFilename string `json:"-"`
Truncated bool Content string `json:"content"`
IsCreated bool Truncated bool `json:"truncated"`
IsDeleted bool IsCreated bool `json:"-"`
IsDeleted bool `json:"-"`
} }
type CsvFile struct { type CsvFile struct {

View file

@ -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-http-help: Klonovat s pomocí Git pomocí základní autentizace HTTP.
gist.header.clone-ssh: Klonovat pomocí SSH gist.header.clone-ssh: Klonovat pomocí SSH
gist.header.clone-ssh-help: Klonovat s pomocí Git pomocí klíče SSH. gist.header.clone-ssh-help: Klonovat s pomocí Git pomocí klíče SSH.
gist.header.share: Sdílet gist.header.embed:
gist.header.share-help: Zkopírovat sdílitelný odkaz na tento gist. gist.header.embed-help:
gist.header.download-zip: Stáhnout ZIP gist.header.download-zip: Stáhnout ZIP
gist.raw: Raw gist.raw: Raw

View file

@ -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-http-help: Clone with Git using HTTP basic authentication.
gist.header.clone-ssh: Clone via SSH gist.header.clone-ssh: Clone via SSH
gist.header.clone-ssh-help: Clone with Git using an SSH key. gist.header.clone-ssh-help: Clone with Git using an SSH key.
gist.header.share: Share gist.header.embed: Embed
gist.header.share-help: Copy shareable link for this gist. gist.header.embed-help: Embed this gist to your website.
gist.header.download-zip: Download ZIP gist.header.download-zip: Download ZIP
gist.raw: Raw gist.raw: Raw

View file

@ -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-http-help: Clonar con Git usando autenticación básica HTTP.
gist.header.clone-ssh: Clonar via SSH gist.header.clone-ssh: Clonar via SSH
gist.header.clone-ssh-help: Clonar con Git usando una clave SSH. gist.header.clone-ssh-help: Clonar con Git usando una clave SSH.
gist.header.share: Compartir gist.header.embed:
gist.header.share-help: Copiar enlace para compartir este gist. gist.header.embed-help:
gist.header.download-zip: Descargar ZIP gist.header.download-zip: Descargar ZIP
gist.raw: Sin formato gist.raw: Sin formato

View file

@ -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-http-help: Cloner avec Git en utilisant l'authentification HTTP basic.
gist.header.clone-ssh: Cloner via SSH gist.header.clone-ssh: Cloner via SSH
gist.header.clone-ssh-help: Cloner avec Git en utilisant une clé SSH. gist.header.clone-ssh-help: Cloner avec Git en utilisant une clé SSH.
gist.header.share: Partager gist.header.embed:
gist.header.share-help: Copier le lien partageable de ce gist. gist.header.embed-help:
gist.header.download-zip: Télécharger en ZIP gist.header.download-zip: Télécharger en ZIP
gist.raw: Brut gist.raw: Brut

View file

@ -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-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: Clone-ozás SSH-n keresztül
gist.header.clone-ssh-help: Clone-ozás SSH kulccsal gist.header.clone-ssh-help: Clone-ozás SSH kulccsal
gist.header.share: Megosztás gist.header.embed:
gist.header.share-help: Másold ki ennek a gistnek a megosztható linkjét gist.header.embed-help:
gist.header.download-zip: ZIP archívum letöltése gist.header.download-zip: ZIP archívum letöltése
gist.raw: Eredeti gist.raw: Eredeti

View file

@ -17,8 +17,8 @@ gist.header.clone-http: Клонировать с помощью %s
gist.header.clone-http-help: Клонировать с помощью Git используя аутентификацию HTTP. gist.header.clone-http-help: Клонировать с помощью Git используя аутентификацию HTTP.
gist.header.clone-ssh: Клонировать c помощью SSH gist.header.clone-ssh: Клонировать c помощью SSH
gist.header.clone-ssh-help: Клонировать c помощью Git используя ключ SSH. gist.header.clone-ssh-help: Клонировать c помощью Git используя ключ SSH.
gist.header.share: Поделиться gist.header.embed:
gist.header.share-help: Скопировать ссылку на фрагмент. gist.header.embed-help:
gist.header.download-zip: Скачать ZIP-архив gist.header.download-zip: Скачать ZIP-архив
gist.raw: Исходник gist.raw: Исходник

View file

@ -17,8 +17,8 @@ gist.header.clone-http: 通过 %s 克隆
gist.header.clone-http-help: 使用 Git 通过 HTTP 基础认证克隆。 gist.header.clone-http-help: 使用 Git 通过 HTTP 基础认证克隆。
gist.header.clone-ssh: 通过 SSH 克隆 gist.header.clone-ssh: 通过 SSH 克隆
gist.header.clone-ssh-help: 使用 Git 通过 SSH 密钥克隆。 gist.header.clone-ssh-help: 使用 Git 通过 SSH 密钥克隆。
gist.header.share: 分享 gist.header.embed:
gist.header.share-help: 为此 Gist 复制可供分享的链接。 gist.header.embed-help:
gist.header.download-zip: 下载 ZIP gist.header.download-zip: 下载 ZIP
gist.raw: 原始文件 gist.raw: 原始文件

View file

@ -17,8 +17,8 @@ gist.header.clone-http: 透過 %s 複製
gist.header.clone-http-help: 使用 HTTP 基本認證透過 Git 複製。 gist.header.clone-http-help: 使用 HTTP 基本認證透過 Git 複製。
gist.header.clone-ssh: 透過 SSH 複製 gist.header.clone-ssh: 透過 SSH 複製
gist.header.clone-ssh-help: 使用 SSH 金鑰透過 Git 複製。 gist.header.clone-ssh-help: 使用 SSH 金鑰透過 Git 複製。
gist.header.share: 分享 gist.header.embed:
gist.header.share-help: 複製這個 Gist 的連結。 gist.header.embed-help:
gist.header.download-zip: 下載 ZIP gist.header.download-zip: 下載 ZIP
gist.raw: 原始檔案 gist.raw: 原始檔案

View file

@ -8,15 +8,16 @@ import (
"github.com/alecthomas/chroma/v2/formatters/html" "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/lexers" "github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles" "github.com/alecthomas/chroma/v2/styles"
"github.com/rs/zerolog/log"
"github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/db"
"github.com/thomiceli/opengist/internal/git" "github.com/thomiceli/opengist/internal/git"
) )
type RenderedFile struct { type RenderedFile struct {
*git.File *git.File
Type string Type string `json:"type"`
Lines []string Lines []string `json:"-"`
HTML string HTML string `json:"-"`
} }
type RenderedGist struct { type RenderedGist struct {
@ -66,6 +67,19 @@ func HighlightFile(file *git.File) (RenderedFile, error) {
return rendered, err 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) { func HighlightGistPreview(gist *db.Gist) (RenderedGist, error) {
rendered := RenderedGist{ rendered := RenderedGist{
Gist: gist, Gist: gist,

View file

@ -2,15 +2,19 @@ package web
import ( import (
"archive/zip" "archive/zip"
"bufio"
"bytes" "bytes"
"errors" "errors"
"fmt"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/thomiceli/opengist/internal/render" "github.com/thomiceli/opengist/internal/render"
"html/template" "html/template"
"net/url" "net/url"
"path/filepath"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -26,7 +30,17 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
userName := ctx.Param("user") userName := ctx.Param("user")
gistName := ctx.Param("gistname") gistName := ctx.Param("gistname")
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") gistName = strings.TrimSuffix(gistName, ".git")
}
gist, err := db.GetGist(userName, gistName) gist, err := db.GetGist(userName, gistName)
if err != nil { if err != nil {
@ -71,12 +85,15 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
baseHttpUrl = httpProtocol + "://" + ctx.Request().Host baseHttpUrl = httpProtocol + "://" + ctx.Request().Host
} }
setData(ctx, "baseHttpUrl", baseHttpUrl)
if config.C.HttpGit { if config.C.HttpGit {
setData(ctx, "httpCloneUrl", baseHttpUrl+"/"+userName+"/"+gistName+".git") setData(ctx, "httpCloneUrl", baseHttpUrl+"/"+userName+"/"+gistName+".git")
} }
setData(ctx, "httpCopyUrl", baseHttpUrl+"/"+userName+"/"+gistName) setData(ctx, "httpCopyUrl", baseHttpUrl+"/"+userName+"/"+gistName)
setData(ctx, "currentUrl", template.URL(ctx.Request().URL.Path)) 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() nbCommits, err := gist.NbCommits()
if err != nil { if err != nil {
@ -256,6 +273,12 @@ func allGists(ctx echo.Context) error {
} }
func gistIndex(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) gist := getData(ctx, "gist").(*db.Gist)
revision := ctx.Param("revision") revision := ctx.Param("revision")
@ -272,13 +295,9 @@ func gistIndex(ctx echo.Context) error {
return notFound("Revision not found") return notFound("Revision not found")
} }
renderedFiles := make([]render.RenderedFile, 0, len(files)) renderedFiles, err := render.HighlightFiles(files)
for _, file := range files {
rendered, err := render.HighlightFile(file)
if err != nil { if err != nil {
log.Warn().Err(err).Msg("Error rendering gist preview for " + gist.Uuid + " - " + gist.PreviewFilename) return errorRes(500, "Error rendering files", err)
}
renderedFiles = append(renderedFiles, rendered)
} }
setData(ctx, "page", "code") setData(ctx, "page", "code")
@ -289,6 +308,83 @@ func gistIndex(ctx echo.Context) error {
return html(ctx, "gist.html") 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 { func revisions(ctx echo.Context) error {
gist := getData(ctx, "gist").(*db.Gist) gist := getData(ctx, "gist").(*db.Gist)
userName := gist.User.Username userName := gist.User.Username

View file

@ -86,12 +86,7 @@ var (
return defaultAvatar() return defaultAvatar()
}, },
"asset": func(file string) string { "asset": asset,
if dev {
return "http://localhost:16157/" + file
}
return config.C.ExternalUrl + "/" + manifestEntries[file].File
},
"dev": func() bool { "dev": func() bool {
return dev return dev
}, },
@ -482,3 +477,10 @@ func defaultAvatar() string {
} }
return config.C.ExternalUrl + "/" + manifestEntries["default.png"].File 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
}

View file

@ -36,6 +36,10 @@ func getData(ctx echo.Context, key string) any {
return data[key] 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 { func html(ctx echo.Context, template string) error {
return htmlWithCode(ctx, 200, template) return htmlWithCode(ctx, 200, template)
} }

458
package-lock.json generated
View file

@ -23,9 +23,11 @@
"github-markdown-css": "^5.5.0", "github-markdown-css": "^5.5.0",
"nodemon": "^2.0.22", "nodemon": "^2.0.22",
"postcss": "^8.4.13", "postcss": "^8.4.13",
"postcss-cli": "^11.0.0",
"postcss-cssnext": "^3.1.1", "postcss-cssnext": "^3.1.1",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-loader": "^7.1.0", "postcss-loader": "^7.1.0",
"postcss-selector-namespace": "^3.0.1",
"sass": "^1.62.1", "sass": "^1.62.1",
"sugarss": "^4.0.1", "sugarss": "^4.0.1",
"tailwindcss": "^3.2.7", "tailwindcss": "^3.2.7",
@ -665,6 +667,18 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@sindresorhus/merge-streams": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz",
"integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@tailwindcss/forms": { "node_modules/@tailwindcss/forms": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
@ -978,6 +992,15 @@
"ajv": "^6.9.1" "ajv": "^6.9.1"
} }
}, },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": { "node_modules/ansi-styles": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@ -1251,6 +1274,20 @@
"node": ">=6.0" "node": ">=6.0"
} }
}, },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/clone": { "node_modules/clone": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
@ -1563,6 +1600,15 @@
"ms": "^2.1.1" "ms": "^2.1.1"
} }
}, },
"node_modules/dependency-graph": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",
"integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==",
"dev": true,
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@ -1636,6 +1682,12 @@
"integrity": "sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ==", "integrity": "sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ==",
"dev": true "dev": true
}, },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.15.0", "version": "5.15.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
@ -1858,6 +1910,20 @@
"url": "https://github.com/sponsors/rawify" "url": "https://github.com/sponsors/rawify"
} }
}, },
"node_modules/fs-extra": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -1887,6 +1953,27 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-stdin": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/github-markdown-css": { "node_modules/github-markdown-css": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.5.0.tgz", "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.5.0.tgz",
@ -1938,12 +2025,43 @@
"dev": true, "dev": true,
"peer": true "peer": true
}, },
"node_modules/globby": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz",
"integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==",
"dev": true,
"dependencies": {
"@sindresorhus/merge-streams": "^1.0.0",
"fast-glob": "^3.3.2",
"ignore": "^5.2.4",
"path-type": "^5.0.0",
"slash": "^5.1.0",
"unicorn-magic": "^0.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/globby/node_modules/path-type": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
"integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "3.0.0", "version": "3.0.0",
@ -1966,6 +2084,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/ignore": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/ignore-by-default": { "node_modules/ignore-by-default": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
@ -2055,6 +2182,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": { "node_modules/is-glob": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@ -2166,6 +2302,18 @@
"dev": true, "dev": true,
"peer": true "peer": true
}, },
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@ -2782,6 +2930,88 @@
"postcss": "^8.2.2" "postcss": "^8.2.2"
} }
}, },
"node_modules/postcss-cli": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.0.tgz",
"integrity": "sha512-xMITAI7M0u1yolVcXJ9XTZiO9aO49mcoKQy6pCDFdMh9kGqhzLVpWxeD/32M/QBmkhcGypZFFOLNLmIW4Pg4RA==",
"dev": true,
"dependencies": {
"chokidar": "^3.3.0",
"dependency-graph": "^0.11.0",
"fs-extra": "^11.0.0",
"get-stdin": "^9.0.0",
"globby": "^14.0.0",
"picocolors": "^1.0.0",
"postcss-load-config": "^5.0.0",
"postcss-reporter": "^7.0.0",
"pretty-hrtime": "^1.0.3",
"read-cache": "^1.0.0",
"slash": "^5.0.0",
"yargs": "^17.0.0"
},
"bin": {
"postcss": "index.js"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss": "^8.0.0"
}
},
"node_modules/postcss-cli/node_modules/lilconfig": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
"integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/postcss-cli/node_modules/postcss-load-config": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.0.2.tgz",
"integrity": "sha512-Q8QR3FYbqOKa0bnC1UQ2bFq9/ulHX5Bi34muzitMr8aDtUelO5xKeJEYC/5smE0jNE9zdB/NBnOwXKexELbRlw==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"lilconfig": "^3.0.0",
"yaml": "^2.3.4"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"jiti": ">=1.21.0",
"postcss": ">=8.0.9"
},
"peerDependenciesMeta": {
"jiti": {
"optional": true
},
"postcss": {
"optional": true
}
}
},
"node_modules/postcss-cli/node_modules/yaml": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
"dev": true,
"engines": {
"node": ">= 14"
}
},
"node_modules/postcss-color-function": { "node_modules/postcss-color-function": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-color-function/-/postcss-color-function-4.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-color-function/-/postcss-color-function-4.1.0.tgz",
@ -4064,6 +4294,26 @@
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/postcss-reporter": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.0.5.tgz",
"integrity": "sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==",
"dev": true,
"dependencies": {
"picocolors": "^1.0.0",
"thenby": "^1.3.4"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/postcss-selector-matches": { "node_modules/postcss-selector-matches": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz",
@ -4094,6 +4344,41 @@
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/postcss-selector-namespace": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/postcss-selector-namespace/-/postcss-selector-namespace-3.0.1.tgz",
"integrity": "sha512-o7poXaazoGCBOK2JIRVsMQV6G0UV5qCJvYJ1t6vqRzOIpJRpsIXHZNc6VdPj9nuQ9D4Ov5LY3SupAP0xu4T/kQ==",
"dev": true,
"dependencies": {
"postcss": "^7.0.0"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/postcss-selector-namespace/node_modules/picocolors": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==",
"dev": true
},
"node_modules/postcss-selector-namespace/node_modules/postcss": {
"version": "7.0.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
"integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
"dev": true,
"dependencies": {
"picocolors": "^0.2.1",
"source-map": "^0.6.1"
},
"engines": {
"node": ">=6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/postcss-selector-not": { "node_modules/postcss-selector-not": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz",
@ -4188,6 +4473,15 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/pretty-hrtime": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
"integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/pstree.remy": { "node_modules/pstree.remy": {
"version": "1.1.8", "version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
@ -4287,6 +4581,15 @@
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
"dev": true "dev": true
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.8", "version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@ -4492,6 +4795,18 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/slash": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
"dev": true,
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -4528,6 +4843,32 @@
"deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility",
"dev": true "dev": true
}, },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/style-mod": { "node_modules/style-mod": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
@ -4775,6 +5116,12 @@
"dev": true, "dev": true,
"peer": true "peer": true
}, },
"node_modules/thenby": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz",
"integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==",
"dev": true
},
"node_modules/thenify": { "node_modules/thenify": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@ -4839,6 +5186,18 @@
"dev": true, "dev": true,
"peer": true "peer": true
}, },
"node_modules/unicorn-magic": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
"integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/uniq": { "node_modules/uniq": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
@ -4855,6 +5214,15 @@
"viewport-dimensions": "^0.2.0" "viewport-dimensions": "^0.2.0"
} }
}, },
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.13", "version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@ -5040,12 +5408,71 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/wrap-ansi/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true "dev": true
}, },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@ -5060,6 +5487,33 @@
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": {
"node": ">=12"
}
} }
} }
} }

View file

@ -23,9 +23,11 @@
"github-markdown-css": "^5.5.0", "github-markdown-css": "^5.5.0",
"nodemon": "^2.0.22", "nodemon": "^2.0.22",
"postcss": "^8.4.13", "postcss": "^8.4.13",
"postcss-cli": "^11.0.0",
"postcss-cssnext": "^3.1.1", "postcss-cssnext": "^3.1.1",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-loader": "^7.1.0", "postcss-loader": "^7.1.0",
"postcss-selector-namespace": "^3.0.1",
"sass": "^1.62.1", "sass": "^1.62.1",
"sugarss": "^4.0.1", "sugarss": "^4.0.1",
"tailwindcss": "^3.2.7", "tailwindcss": "^3.2.7",

3
postcss.config.js vendored
View file

@ -1,7 +1,10 @@
module.exports = { module.exports = {
plugins: { plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
'postcss-selector-namespace': {namespace() {return (process.env.EMBED) ? '.opengist-embed' : '';}},
cssnano: {}, cssnano: {},
}, },
} }

View file

@ -1,3 +1,4 @@
.chroma:not(.markdown) { color: #4c4f69 }
/* Error */ .chroma .err { color: #d20f39 } /* Error */ .chroma .err { color: #d20f39 }
/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit } /* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }

View file

@ -1,3 +1,4 @@
.chroma:not(.markdown) { color: #cad3f5 }
/* Error */ .chroma .err { color: #f38ba8 } /* Error */ .chroma .err { color: #f38ba8 }
/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit } /* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }

112
public/embed.scss vendored Normal file
View file

@ -0,0 +1,112 @@
@import "github-markdown-css/github-markdown-light";
@import './catppuccin-latte';
.dark {
@import "github-markdown-css/github-markdown-dark";
@import './catppuccin-macchiato';
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@config "../tailwind-embed.config.js";
.html {
-webkit-text-size-adjust: 100%;
font-feature-settings: normal;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
font-variation-settings: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4
}
ol, ul {
list-style: revert;
}
.code {
font-family: Menlo, Consolas, Liberation Mono, monospace;
}
.code .line-num {
width: 4%;
text-align: right;
}
.code td {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.code tbody {
line-height: 18.2px;
}
.line-code {
@apply pl-2;
background: none !important;
}
.line-num {
@apply cursor-pointer text-slate-600 dark:text-slate-400 hover:text-black dark:hover:text-white;
}
table.csv-table {
@apply w-full whitespace-pre text-xs text-slate-300;
}
table.csv-table thead {
text-align: left;
}
table.csv-table thead tr {
@apply bg-slate-100 dark:bg-slate-800;
}
table.csv-table tbody tr {
@apply bg-gray-500 dark:bg-gray-900;
}
table.csv-table thead tr th {
@apply border py-2 px-1 border-slate-300 dark:border-slate-700;
}
table.csv-table tbody td {
@apply border py-1.5 px-1 border-slate-200 dark:border-slate-800;
}
dl.dl-config {
@apply grid grid-cols-3 text-sm;
}
dl.dl-config dt {
@apply col-span-1 text-gray-700 dark:text-slate-300 font-bold;
}
dl.dl-config dd {
@apply ml-1 col-span-2 break-words;
}
.markdown-body {
@apply dark:bg-gray-900;
}
.markdown-body pre {
@apply flex relative items-start p-0;
}
.markdown-body .code-div {
@apply p-4 max-w-full overflow-x-auto;
}
.markdown-body code {
@apply overflow-auto whitespace-pre;
}
.chroma.preview.markdown code {
@apply p-4;
}

1
public/embed.ts Normal file
View file

@ -0,0 +1 @@
import "./embed.scss"

View file

@ -1,4 +1,3 @@
import './style.css';
import './style.scss'; import './style.scss';
import './favicon-32.png'; import './favicon-32.png';
import './opengist.svg'; import './opengist.svg';

6
public/style.css vendored
View file

@ -2,6 +2,8 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@config "../tailwind.config.js";
@layer base { @layer base {
ul, ol { ul, ol {
list-style: revert; list-style: revert;
@ -98,10 +100,6 @@ pre {
max-height: 337px; max-height: 337px;
} }
.hljs {
color: #c9d1d9;
}
.line-code.selected { .line-code.selected {
background-color: rgb(255, 247, 190) !important; background-color: rgb(255, 247, 190) !important;
box-shadow: inset 4px 0 0 rgb(255, 213, 65) !important; box-shadow: inset 4px 0 0 rgb(255, 213, 65) !important;

2
public/style.scss vendored
View file

@ -7,3 +7,5 @@
@import "github-markdown-css/github-markdown-dark"; @import "github-markdown-css/github-markdown-dark";
@import './catppuccin-macchiato'; @import './catppuccin-macchiato';
} }
@import "style.css";

47
tailwind-embed.config.js vendored Normal file
View file

@ -0,0 +1,47 @@
const colors = require('tailwindcss/colors')
module.exports = {
content: [
"./templates/pages/gist_embed.html",
],
theme: {
colors: {
white: colors.white,
black: colors.black,
gray: {
50: "#EEEFF1",
100: "#DEDFE3",
200: "#BABCC5",
300: "#999CA8",
400: "#75798A",
500: "#585B68",
600: "#464853",
700: "#363840",
800: "#232429",
900: "#131316"
},
primary: {
50: '#d6e1ff',
100: '#d1dfff',
200: '#b9d2fe',
300: '#84b1fb',
400: '#74a4f6',
500: '#588fee',
600: '#3c79e2',
700: '#356fc0',
800: '#2d6195',
900: '#2a5574',
950: '#173040',
},
slate: colors.slate
},
extend: {
borderWidth: {
'1': '1px',
}
},
},
plugins: [],
darkMode: 'class',
}

View file

@ -30,7 +30,7 @@
{{ if dev }} {{ if dev }}
<script type="module" src="{{ asset "@vite/client" }}"></script> <script type="module" src="{{ asset "@vite/client" }}"></script>
<link rel="stylesheet" href="{{ asset "style.css" }}" /> <link rel="stylesheet" href="{{ asset "style.scss" }}" />
<script type="module" src="{{ asset "main.ts" }}"></script> <script type="module" src="{{ asset "main.ts" }}"></script>
{{ else }} {{ else }}
<link rel="stylesheet" href="{{ asset "main.css" }}" /> <link rel="stylesheet" href="{{ asset "main.css" }}" />

View file

@ -128,13 +128,16 @@
<div class="flex rounded-md shadow-sm"> <div class="flex rounded-md shadow-sm">
<div class="relative"> <div class="relative">
<button type="button" id="gist-menu-toggle" class="relative text-xs inline-flex items-center space-x-2 rounded-l-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-sm 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 focus-within:z-10 -mr-px"> <button type="button" id="gist-menu-toggle" class="relative text-xs inline-flex items-center space-x-2 rounded-l-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-sm 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 focus-within:z-10 -mr-px">
<span id="gist-menu-title" class="whitespace-nowrap">{{if .httpCloneUrl}}{{ .locale.Tr "gist.header.clone-http" .httpProtocol }}{{ else if .sshCloneUrl }}{{ .locale.Tr "gist.header.clone-ssh" }}{{else}}{{ .locale.Tr "gist.header.share" }}{{end}}</span> <span id="gist-menu-title" class="whitespace-nowrap">{{ .locale.Tr "gist.header.embed" }}</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" /> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg> </svg>
</button> </button>
<div class="absolute left-0 z-10 mt-2 w-56 origin-top-left 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="absolute left-0 z-10 mt-2 w-56 origin-top-left 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="py-1 cursor-pointer border-1 rounded-md border-gray-200 dark:border-gray-700 hidden" id="gist-menu-copy" role="none"> <div class="py-1 cursor-pointer border-1 rounded-md border-gray-200 dark:border-gray-700 hidden" id="gist-menu-copy" role="none">
<div class="text-slate-700 dark:text-slate-300 block px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 gist-menu-item" role="menuitem" id="gist-menu-share" data-link="{{ .embedScript }}"><p>{{ .locale.Tr "gist.header.embed" }}</p>
<p class="text-xs font-normal text-gray-600 dark:text-gray-400">{{ .locale.Tr "gist.header.embed-help" }}</p>
</div>
{{ if .httpCloneUrl }} {{ if .httpCloneUrl }}
<div class="text-slate-700 dark:text-slate-300 block px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 gist-menu-item" role="menuitem" id="gist-menu-http" data-link="{{ .httpCloneUrl }}"><p>{{ .locale.Tr "gist.header.clone-http" .httpProtocol }}</p> <div class="text-slate-700 dark:text-slate-300 block px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 gist-menu-item" role="menuitem" id="gist-menu-http" data-link="{{ .httpCloneUrl }}"><p>{{ .locale.Tr "gist.header.clone-http" .httpProtocol }}</p>
<p class="text-xs font-normal text-gray-600 dark:text-gray-400">{{ .locale.Tr "gist.header.clone-http-help" }}</p> <p class="text-xs font-normal text-gray-600 dark:text-gray-400">{{ .locale.Tr "gist.header.clone-http-help" }}</p>
@ -145,14 +148,12 @@
<p class="text-xs font-normal text-gray-600 dark:text-gray-400">{{ .locale.Tr "gist.header.clone-ssh-help" }}</p> <p class="text-xs font-normal text-gray-600 dark:text-gray-400">{{ .locale.Tr "gist.header.clone-ssh-help" }}</p>
</div> </div>
{{ end }} {{ end }}
<div class="text-slate-700 dark:text-slate-300 block px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 gist-menu-item" role="menuitem" id="gist-menu-share" data-link="{{ .httpCopyUrl }}"><p>{{ .locale.Tr "gist.header.share" }}</p>
<p class="text-xs font-normal text-gray-600 dark:text-gray-400">{{ .locale.Tr "gist.header.share-help" }}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="relative flex flex-grow items-stretch focus-within:z-10"> <div class="relative flex flex-grow items-stretch focus-within:z-10">
<input id="gist-menu-input" value="{{if .httpCloneUrl}}{{.httpCloneUrl}}{{ else if .sshCloneUrl }}{{.sshCloneUrl}}{{else}}{{.httpCopyUrl}}{{end}}" class="block code bg-white dark:bg-gray-900 w-full rounded-none border border-gray-200 dark:border-gray-600 focus:border-primary-500 focus:ring-primary-500 focus:outline-none focus:ring-1 text-xs px-2 py-1"> <input readonly id="gist-menu-input" value="{{.embedScript}}" class="block code bg-white dark:bg-gray-900 w-full rounded-none border border-gray-200 dark:border-gray-600 focus:border-primary-500 focus:ring-primary-500 focus:outline-none focus:ring-1 text-xs px-2 py-1">
</div> </div>
<button id="gist-menu-button-copy" type="button" class="relative text-xs -ml-px inline-flex items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1 text-sm 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 id="gist-menu-button-copy" type="button" class="relative text-xs -ml-px inline-flex items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1 text-sm 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">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">

View file

@ -12,9 +12,9 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-slate-700 dark:text-slate-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-slate-700 dark:text-slate-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /> <path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg> </svg>
<a href="{{ $.c.ExternalUrl }}#file-{{ slug $file.Filename }}" class="hover:text-primary-600 ml-2 mr-1">{{ $file.Filename }}</a> <a href="#file-{{ slug $file.Filename }}" class="hover:text-primary-600 ml-2 mr-1">{{ $file.Filename }}</a>
<span class="hidden sm:block"> <span class="hidden sm:block">
<span class="text-gray-400"> · {{ $file.Size }} · {{ $file.Type }}</span> <span class="text-gray-400"> · {{ $file.HumanSize }} · {{ $file.Type }}</span>
</span> </span>
</span> </span>

53
templates/pages/gist_embed.html vendored Normal file
View file

@ -0,0 +1,53 @@
<div class="opengist-embed" id="{{ .gist.Uuid }}">
<div class="html {{.dark}}">
{{ range $file := .files }}
<div class="rounded-md border-1 border-gray-100 dark:border-gray-800 overflow-auto mb-4">
<div class="border-b-1 border-gray-100 dark:border-gray-700 text-xs p-2 pl-4 bg-gray-50 dark:bg-gray-800 text-gray-400">
<a target="_blank" href="{{ $.baseHttpUrl }}/{{ $.gist.User.Username }}/{{ $.gist.Uuid }}#file-{{ slug $file.Filename }}"><span class="font-bold text-gray-700 dark:text-gray-200">{{ $file.Filename }}</span> · {{ $file.HumanSize }} · {{ $file.Type }}</a>
<span class="float-right"><a target="_blank" href="{{ $.baseHttpUrl }}">Hosted via Opengist</a> · <span class="text-gray-700 dark:text-gray-200 font-bold"><a target="_blank" href="{{ $.baseHttpUrl }}/{{ $.gist.User.Username }}/{{ $.gist.Uuid }}/raw/HEAD/{{$file.Filename}}">view raw</a></span></span>
</div>
{{ if $file.Truncated }}
<div class="text-xs px-4 bg-gray-50 py-1.5 border-b-1 border-gray-100 dark:border-gray-700">
{{ $.locale.Tr "gist.file-truncated" }} <a target="_blank" class="text-primary-600" href="{{ $.baseHttpUrl }}/{{ $.gist.User.Username }}/{{ $.gist.Uuid }}/raw/HEAD/{{$file.Filename}}">{{ $.locale.Tr "gist.watch-full-file" }}.</a>
</div>
{{ end }}
{{ $csv := csvFile $file.File }}
{{ if $csv }}
<table class="csv-table">
<thead>
<tr>
{{ range $csv.Header }}
<th>{{ . }}</th>
{{ end }}
</tr>
</thead>
<tbody>
{{ range $csv.Rows }}
<tr>
{{ range . }}
<td>{{ . }}</td>
{{ end }}
</tr>
{{ end }}
</table>
{{ else if isMarkdown $file.Filename }}
<div class="chroma markdown markdown-body p-8">{{ $file.HTML | safe }}</div>
{{ else }}
<div class="code dark:bg-gray-900">
{{ $fileslug := slug $file.Filename }}
{{ if ne $file.Content "" }}
<table class="chroma table-code w-full whitespace-pre" data-filename-slug="{{ $fileslug }}" data-filename="{{ $file.Filename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
<tbody>
{{ $ii := "1" }}
{{ $i := toInt $ii }}
{{ range $line := $file.Lines }}<tr><td id="file-{{ $fileslug }}-{{$i}}" class="select-none line-num px-4">{{$i}}</td><td class="line-code">{{ $line | safe }}</td></tr>{{ $i = inc $i }}{{ end }}
</tbody>
</table>
{{ end }}
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>

8
vite.config.js vendored
View file

@ -9,7 +9,13 @@ export default defineConfig({
assetsDir: 'assets', assetsDir: 'assets',
manifest: true, manifest: true,
rollupOptions: { rollupOptions: {
input: ['./public/main.ts', './public/editor.ts', './public/admin.ts', './public/gist.ts'] input: [
'./public/main.ts',
'./public/editor.ts',
'./public/admin.ts',
'./public/gist.ts',
'./public/embed.ts'
]
}, },
assetsInlineLimit: 0, assetsInlineLimit: 0,
} }