mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 12:32:40 +00:00
Add Gist code search (#194)
This commit is contained in:
parent
4cb7dc2d30
commit
87a6113cc7
25 changed files with 734 additions and 87 deletions
|
@ -17,6 +17,12 @@ opengist-home:
|
|||
# Name of the SQLite database file. Default: opengist.db
|
||||
db-filename: opengist.db
|
||||
|
||||
# Enable or disable the code search index (either `true` or `false`). Default: true
|
||||
index.enabled: true
|
||||
|
||||
# Name of the directory where the code search index is stored. Default: opengist.index
|
||||
index.dirname: opengist.index
|
||||
|
||||
# Default branch name used by Opengist when initializing Git repositories.
|
||||
# If not set, uses the Git default branch name. See https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_new_default_branch
|
||||
git.default-branch:
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
| external-url | OG_EXTERNAL_URL | none | Public URL to access to Opengist. |
|
||||
| opengist-home | OG_OPENGIST_HOME | home directory | Path to the directory where Opengist stores its data. |
|
||||
| db-filename | OG_DB_FILENAME | `opengist.db` | Name of the SQLite database file. |
|
||||
| index.enabled | OG_INDEX_ENABLED | `true` | Enable or disable the code search index (`true` or `false`) |
|
||||
| index.dirname | OG_INDEX_DIRNAME | `opengist.index` | Name of the directory where the code search index is stored. |
|
||||
| git.default-branch | OG_GIT_DEFAULT_BRANCH | none | Default branch name used by Opengist when initializing Git repositories. If not set, uses the Git default branch name. More info [here](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_new_default_branch) |
|
||||
| sqlite.journal-mode | OG_SQLITE_JOURNAL_MODE | `WAL` | Set the journal mode for SQLite. More info [here](https://www.sqlite.org/pragma.html#pragma_journal_mode) |
|
||||
| http.host | OG_HTTP_HOST | `0.0.0.0` | The host on which the HTTP server should bind. |
|
||||
|
|
23
go.mod
23
go.mod
|
@ -5,6 +5,7 @@ go 1.20
|
|||
require (
|
||||
github.com/Kunde21/markdownfmt/v3 v3.1.0
|
||||
github.com/alecthomas/chroma/v2 v2.12.0
|
||||
github.com/blevesearch/bleve/v2 v2.3.10
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/glebarez/go-sqlite v1.21.2
|
||||
github.com/glebarez/sqlite v1.9.0
|
||||
|
@ -27,28 +28,50 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/RoaringBitmap/roaring v1.2.3 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.0.6 // indirect
|
||||
github.com/blevesearch/geo v0.1.18 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.6 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/vellum v1.0.10 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.13 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede // indirect
|
||||
github.com/labstack/gommon v0.4.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/oauth2 v0.12.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
|
|
47
go.sum
47
go.sum
|
@ -36,12 +36,48 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0=
|
||||
github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc=
|
||||
github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY=
|
||||
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/blevesearch/bleve/v2 v2.3.10 h1:z8V0wwGoL4rp7nG/O3qVVLYxUqCbEwskMt4iRJsPLgg=
|
||||
github.com/blevesearch/bleve/v2 v2.3.10/go.mod h1:RJzeoeHC+vNHsoLR54+crS1HmOWpnH87fL70HAUCzIA=
|
||||
github.com/blevesearch/bleve_index_api v1.0.6 h1:gyUUxdsrvmW3jVhhYdCVL6h9dCjNT/geNU7PxGn37p8=
|
||||
github.com/blevesearch/bleve_index_api v1.0.6/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||
github.com/blevesearch/geo v0.1.18 h1:Np8jycHTZ5scFe7VEPLrDoHnnb9C4j636ue/CGrhtDw=
|
||||
github.com/blevesearch/geo v0.1.18/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.6 h1:CdekX/Ob6YCYmeHzD72cKpwzBjvkOGegHOqhAkXp6yA=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.6/go.mod h1:nQQYlp51XvoSVxcciBjtvuHPIVjlWrN1hX4qwK2cqdc=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||
github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
|
||||
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
|
||||
github.com/blevesearch/zapx/v15 v15.3.13 h1:6EkfaZiPlAxqXz0neniq35my6S48QI94W/wyhnpDHHQ=
|
||||
github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9 h1:wMSvdj3BswqfQOXp2R1bJOAE7xIQLt2dlMQDMf836VY=
|
||||
github.com/chromedp/chromedp v0.9.1 h1:CC7cC5p1BeLiiS2gfNNPwp3OaUxtRMBjfiw3E3k6dFA=
|
||||
|
@ -91,6 +127,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
|
|||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -119,6 +157,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
|||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -131,6 +171,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
|
@ -177,6 +218,8 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
|
|||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede h1:YrgBGwxMRK0Vq0WSCWFaZUnTsrA/PZE/xs1QZh+/edg=
|
||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
|
@ -212,6 +255,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
@ -254,6 +299,8 @@ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+
|
|||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||
go.abhg.dev/goldmark/mermaid v0.5.0 h1:mDkykpSPJ+5wCQ8bSXgzJ2KQskjXkI5Ndxz7JYDHW38=
|
||||
go.abhg.dev/goldmark/mermaid v0.5.0/go.mod h1:OCyk2o85TX2drWHH+HRy6bih2yZlUwbbv/R1MMh1YLs=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/thomiceli/opengist/internal/config"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/git"
|
||||
"github.com/thomiceli/opengist/internal/index"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -21,6 +22,7 @@ const (
|
|||
GitGcRepos = iota
|
||||
SyncGistPreviews = iota
|
||||
ResetHooks = iota
|
||||
IndexGists = iota
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -69,6 +71,8 @@ func Run(actionType int) {
|
|||
functionToRun = syncGistPreviews
|
||||
case ResetHooks:
|
||||
functionToRun = resetHooks
|
||||
case IndexGists:
|
||||
functionToRun = indexGists
|
||||
default:
|
||||
panic("unhandled default case")
|
||||
}
|
||||
|
@ -88,7 +92,6 @@ func syncReposFromFS() {
|
|||
if _, err := os.Stat(git.RepositoryPath(gist.User.Username, gist.Uuid)); err != nil && !os.IsExist(err) {
|
||||
if err2 := gist.Delete(); err2 != nil {
|
||||
log.Error().Err(err2).Msgf("Cannot delete gist %d", gist.ID)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +112,6 @@ func syncReposFromDB() {
|
|||
if gist.ID == 0 {
|
||||
if err := git.DeleteRepository(path[len(path)-2], path[len(path)-1]); err != nil {
|
||||
log.Error().Err(err).Msgf("Cannot delete repository %s/%s", path[len(path)-2], path[len(path)-1])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +135,6 @@ func syncGistPreviews() {
|
|||
for _, gist := range gists {
|
||||
if err = gist.UpdatePreviewAndCount(false); err != nil {
|
||||
log.Error().Err(err).Msgf("Cannot update preview and count for gist %d", gist.ID)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +151,27 @@ func resetHooks() {
|
|||
path := strings.Split(e, string(os.PathSeparator))
|
||||
if err := git.CreateDotGitFiles(path[len(path)-2], path[len(path)-1]); err != nil {
|
||||
log.Error().Err(err).Msgf("Cannot reset hooks for repository %s/%s", path[len(path)-2], path[len(path)-1])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func indexGists() {
|
||||
log.Info().Msg("Indexing all Gists...")
|
||||
gists, err := db.GetAllGistsRows()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get gists")
|
||||
return
|
||||
}
|
||||
|
||||
for _, gist := range gists {
|
||||
log.Info().Msgf("Indexing gist %d", gist.ID)
|
||||
indexedGist, err := gist.ToIndexedGist()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Cannot convert gist %d to indexed gist", gist.ID)
|
||||
continue
|
||||
}
|
||||
if err = index.AddInIndex(indexedGist); err != nil {
|
||||
log.Error().Err(err).Msgf("Cannot index gist %d", gist.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ type config struct {
|
|||
ExternalUrl string `yaml:"external-url" env:"OG_EXTERNAL_URL"`
|
||||
OpengistHome string `yaml:"opengist-home" env:"OG_OPENGIST_HOME"`
|
||||
DBFilename string `yaml:"db-filename" env:"OG_DB_FILENAME"`
|
||||
IndexEnabled bool `yaml:"index.enabled" env:"OG_INDEX_ENABLED"`
|
||||
IndexDirname string `yaml:"index.dirname" env:"OG_INDEX_DIRNAME"`
|
||||
|
||||
GitDefaultBranch string `yaml:"git.default-branch" env:"OG_GIT_DEFAULT_BRANCH"`
|
||||
|
||||
|
@ -66,6 +68,8 @@ func configWithDefaults() (*config, error) {
|
|||
c.LogOutput = "stdout,file"
|
||||
c.OpengistHome = ""
|
||||
c.DBFilename = "opengist.db"
|
||||
c.IndexEnabled = true
|
||||
c.IndexDirname = "opengist.index"
|
||||
|
||||
c.SqliteJournalMode = "WAL"
|
||||
|
||||
|
|
|
@ -2,9 +2,14 @@ package db
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/index"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -213,6 +218,25 @@ func GetAllGistsRows() ([]*Gist, error) {
|
|||
return gists, err
|
||||
}
|
||||
|
||||
func GetAllGistsVisibleByUser(userId uint) ([]uint, error) {
|
||||
var gists []uint
|
||||
|
||||
err := db.Table("gists").
|
||||
Where("gists.private = 0 or gists.user_id = ?", userId).
|
||||
Pluck("gists.id", &gists).Error
|
||||
|
||||
return gists, err
|
||||
}
|
||||
|
||||
func GetAllGistsByIds(ids []uint) ([]*Gist, error) {
|
||||
var gists []*Gist
|
||||
err := db.Preload("User").Preload("Forked.User").
|
||||
Where("id in ?", ids).
|
||||
Find(&gists).Error
|
||||
|
||||
return gists, err
|
||||
}
|
||||
|
||||
func (gist *Gist) Create() error {
|
||||
// avoids foreign key constraint error because the default value in the struct is 0
|
||||
return db.Omit("forked_id").Create(&gist).Error
|
||||
|
@ -361,6 +385,10 @@ func (gist *Gist) File(revision string, filename string, truncate bool) (*git.Fi
|
|||
}, err
|
||||
}
|
||||
|
||||
func (gist *Gist) FileNames(revision string) ([]string, error) {
|
||||
return git.GetFilesOfRepository(gist.User.Username, gist.Uuid, revision)
|
||||
}
|
||||
|
||||
func (gist *Gist) Log(skip int) ([]*git.Commit, error) {
|
||||
return git.GetLog(gist.User.Username, gist.Uuid, skip)
|
||||
}
|
||||
|
@ -475,6 +503,30 @@ func (gist *Gist) Identifier() string {
|
|||
return gist.Uuid
|
||||
}
|
||||
|
||||
func (gist *Gist) GetLanguagesFromFiles() ([]string, error) {
|
||||
files, err := gist.Files("HEAD", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
languages := make([]string, 0, len(files))
|
||||
for _, file := range files {
|
||||
var lexer chroma.Lexer
|
||||
if lexer = lexers.Get(file.Filename); lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
|
||||
fileType := lexer.Config().Name
|
||||
if lexer.Config().Name == "fallback" || lexer.Config().Name == "plaintext" {
|
||||
fileType = "Text"
|
||||
}
|
||||
|
||||
languages = append(languages, fileType)
|
||||
}
|
||||
|
||||
return languages, nil
|
||||
}
|
||||
|
||||
// -- DTO -- //
|
||||
|
||||
type GistDTO struct {
|
||||
|
@ -507,3 +559,74 @@ func (dto *GistDTO) ToExistingGist(gist *Gist) *Gist {
|
|||
gist.URL = dto.URL
|
||||
return gist
|
||||
}
|
||||
|
||||
// -- Index -- //
|
||||
|
||||
func (gist *Gist) ToIndexedGist() (*index.Gist, error) {
|
||||
files, err := gist.Files("HEAD", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exts := make([]string, 0, len(files))
|
||||
wholeContent := ""
|
||||
for _, file := range files {
|
||||
wholeContent += file.Content
|
||||
exts = append(exts, filepath.Ext(file.Filename))
|
||||
}
|
||||
|
||||
fileNames, err := gist.FileNames("HEAD")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
langs, err := gist.GetLanguagesFromFiles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexedGist := &index.Gist{
|
||||
GistID: gist.ID,
|
||||
Username: gist.User.Username,
|
||||
Title: gist.Title,
|
||||
Content: wholeContent,
|
||||
Filenames: fileNames,
|
||||
Extensions: exts,
|
||||
Languages: langs,
|
||||
CreatedAt: gist.CreatedAt,
|
||||
UpdatedAt: gist.UpdatedAt,
|
||||
}
|
||||
|
||||
return indexedGist, nil
|
||||
}
|
||||
|
||||
func (gist *Gist) AddInIndex() {
|
||||
if !index.Enabled() {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
indexedGist, err := gist.ToIndexedGist()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Cannot convert gist %d to indexed gist", gist.ID)
|
||||
return
|
||||
}
|
||||
err = index.AddInIndex(indexedGist)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error adding gist %d to index", gist.ID)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (gist *Gist) RemoveFromIndex() {
|
||||
if !index.Enabled() {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := index.RemoveFromIndex(gist.ID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error remove gist %d from index", gist.ID)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -68,6 +68,14 @@ gist.list.files: files
|
|||
gist.list.last-active: Last active
|
||||
gist.list.no-gists: No gists
|
||||
|
||||
gist.search.found: gists found
|
||||
gist.search.no-results: No gists found
|
||||
gist.search.help.user: gists created by user
|
||||
gist.search.help.title: gists with given title
|
||||
gist.search.help.filename: gists having files with given name
|
||||
gist.search.help.extension: gists having files with given extension
|
||||
gist.search.help.language: gists having files with given language
|
||||
|
||||
gist.forks: Forks
|
||||
gist.forks.view: View fork
|
||||
gist.forks.no: No public forks
|
||||
|
@ -164,6 +172,7 @@ admin.actions.sync-db: Synchronize gists from database
|
|||
admin.actions.git-gc: Garbage collect all git repositories
|
||||
admin.actions.sync-previews: Synchronize all gists previews
|
||||
admin.actions.reset-hooks: Reset Git server hooks for all repositories
|
||||
admin.actions.index-gists: Index all gists
|
||||
admin.id: ID
|
||||
admin.user: User
|
||||
admin.delete: Delete
|
||||
|
|
157
internal/index/bleve.go
Normal file
157
internal/index/bleve.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
"github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/camelcase"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
|
||||
"github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
||||
"github.com/blevesearch/bleve/v2/search/query"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var bleveIndex bleve.Index
|
||||
|
||||
func Enabled() bool {
|
||||
return config.C.IndexEnabled
|
||||
}
|
||||
|
||||
func Open(indexFilename string) error {
|
||||
var err error
|
||||
bleveIndex, err = bleve.Open(indexFilename)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !errors.Is(err, bleve.ErrorIndexPathDoesNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
docMapping := bleve.NewDocumentMapping()
|
||||
docMapping.AddFieldMappingsAt("GistID", bleve.NewNumericFieldMapping())
|
||||
docMapping.AddFieldMappingsAt("Content", bleve.NewTextFieldMapping())
|
||||
|
||||
mapping := bleve.NewIndexMapping()
|
||||
|
||||
if err = mapping.AddCustomTokenFilter("unicodeNormalize", map[string]any{
|
||||
"type": unicodenorm.Name,
|
||||
"form": unicodenorm.NFC,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mapping.AddCustomAnalyzer("gistAnalyser", map[string]interface{}{
|
||||
"type": custom.Name,
|
||||
"char_filters": []string{},
|
||||
"tokenizer": unicode.Name,
|
||||
"token_filters": []string{"unicodeNormalize", camelcase.Name, lowercase.Name},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
docMapping.DefaultAnalyzer = "gistAnalyser"
|
||||
|
||||
bleveIndex, err = bleve.New(indexFilename, mapping)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func Close() error {
|
||||
return bleveIndex.Close()
|
||||
}
|
||||
|
||||
func AddInIndex(gist *Gist) error {
|
||||
if !Enabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if gist == nil {
|
||||
return errors.New("failed to add nil gist to index")
|
||||
}
|
||||
return bleveIndex.Index(strconv.Itoa(int(gist.GistID)), gist)
|
||||
}
|
||||
|
||||
func RemoveFromIndex(gistID uint) error {
|
||||
if !Enabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bleveIndex.Delete(strconv.Itoa(int(gistID)))
|
||||
}
|
||||
|
||||
func SearchGists(queryStr string, queryMetadata SearchGistMetadata, gistsIds []uint, page int) ([]uint, uint64, map[string]int, error) {
|
||||
if !Enabled() {
|
||||
return nil, 0, nil, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
var indexerQuery query.Query
|
||||
if queryStr != "" {
|
||||
contentQuery := bleve.NewMatchPhraseQuery(queryStr)
|
||||
contentQuery.FieldVal = "Content"
|
||||
indexerQuery = contentQuery
|
||||
} else {
|
||||
contentQuery := bleve.NewMatchAllQuery()
|
||||
indexerQuery = contentQuery
|
||||
}
|
||||
|
||||
if len(gistsIds) > 0 {
|
||||
repoQueries := make([]query.Query, 0, len(gistsIds))
|
||||
|
||||
truee := true
|
||||
for _, id := range gistsIds {
|
||||
f := float64(id)
|
||||
qq := bleve.NewNumericRangeInclusiveQuery(&f, &f, &truee, &truee)
|
||||
qq.SetField("GistID")
|
||||
repoQueries = append(repoQueries, qq)
|
||||
}
|
||||
|
||||
indexerQuery = bleve.NewConjunctionQuery(bleve.NewDisjunctionQuery(repoQueries...), indexerQuery)
|
||||
}
|
||||
|
||||
addQuery := func(field, value string) {
|
||||
if value != "" && value != "." {
|
||||
q := bleve.NewMatchPhraseQuery(value)
|
||||
q.FieldVal = field
|
||||
indexerQuery = bleve.NewConjunctionQuery(indexerQuery, q)
|
||||
}
|
||||
}
|
||||
|
||||
addQuery("Username", queryMetadata.Username)
|
||||
addQuery("Title", queryMetadata.Title)
|
||||
addQuery("Extensions", "."+queryMetadata.Extension)
|
||||
addQuery("Filenames", queryMetadata.Filename)
|
||||
addQuery("Languages", queryMetadata.Language)
|
||||
|
||||
languageFacet := bleve.NewFacetRequest("Languages", 10)
|
||||
|
||||
perPage := 10
|
||||
offset := (page - 1) * perPage
|
||||
|
||||
s := bleve.NewSearchRequestOptions(indexerQuery, perPage, offset, false)
|
||||
s.AddFacet("languageFacet", languageFacet)
|
||||
s.Fields = []string{"GistID"}
|
||||
s.IncludeLocations = false
|
||||
|
||||
results, err := bleveIndex.Search(s)
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
|
||||
gistIds := make([]uint, 0, len(results.Hits))
|
||||
for _, hit := range results.Hits {
|
||||
gistIds = append(gistIds, uint(hit.Fields["GistID"].(float64)))
|
||||
}
|
||||
|
||||
languageCounts := make(map[string]int)
|
||||
if facets, found := results.Facets["languageFacet"]; found {
|
||||
for _, term := range facets.Terms.Terms() {
|
||||
languageCounts[term.Term] = term.Count
|
||||
}
|
||||
}
|
||||
|
||||
return gistIds, results.Total, languageCounts, nil
|
||||
}
|
21
internal/index/gist.go
Normal file
21
internal/index/gist.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package index
|
||||
|
||||
type Gist struct {
|
||||
GistID uint
|
||||
Username string
|
||||
Title string
|
||||
Content string
|
||||
Filenames []string
|
||||
Extensions []string
|
||||
Languages []string
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
type SearchGistMetadata struct {
|
||||
Username string
|
||||
Title string
|
||||
Filename string
|
||||
Extension string
|
||||
Language string
|
||||
}
|
|
@ -95,6 +95,7 @@ func runGitCommand(ch ssh.Channel, gitCmd string, key string, ip string) error {
|
|||
if verb == "receive-pack" {
|
||||
_ = gist.SetLastActiveNow()
|
||||
_ = gist.UpdatePreviewAndCount(false)
|
||||
gist.AddInIndex()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -46,6 +46,7 @@ func adminIndex(ctx echo.Context) error {
|
|||
setData(ctx, "gitGcRepos", actions.IsRunning(actions.GitGcRepos))
|
||||
setData(ctx, "syncGistPreviews", actions.IsRunning(actions.SyncGistPreviews))
|
||||
setData(ctx, "resetHooks", actions.IsRunning(actions.ResetHooks))
|
||||
setData(ctx, "indexGists", actions.IsRunning(actions.IndexGists))
|
||||
return html(ctx, "admin_index.html")
|
||||
}
|
||||
|
||||
|
@ -116,6 +117,8 @@ func adminGistDelete(ctx echo.Context) error {
|
|||
return errorRes(500, "Cannot delete this gist", err)
|
||||
}
|
||||
|
||||
gist.RemoveFromIndex()
|
||||
|
||||
addFlash(ctx, "Gist has been deleted", "success")
|
||||
return redirect(ctx, "/admin-panel/gists")
|
||||
}
|
||||
|
@ -150,6 +153,12 @@ func adminResetHooks(ctx echo.Context) error {
|
|||
return redirect(ctx, "/admin-panel")
|
||||
}
|
||||
|
||||
func adminIndexGists(ctx echo.Context) error {
|
||||
addFlash(ctx, "Indexing all gists...", "success")
|
||||
go actions.Run(actions.IndexGists)
|
||||
return redirect(ctx, "/admin-panel")
|
||||
}
|
||||
|
||||
func adminConfig(ctx echo.Context) error {
|
||||
setData(ctx, "title", "Configuration")
|
||||
setData(ctx, "htmlTitle", "Configuration - Admin panel")
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/git"
|
||||
"github.com/thomiceli/opengist/internal/index"
|
||||
"github.com/thomiceli/opengist/internal/render"
|
||||
"html/template"
|
||||
"net/url"
|
||||
|
@ -252,20 +253,20 @@ func allGists(ctx echo.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
renderedFiles := make([]*render.RenderedGist, 0, len(gists))
|
||||
renderedGists := make([]*render.RenderedGist, 0, len(gists))
|
||||
for _, gist := range gists {
|
||||
rendered, err := render.HighlightGistPreview(gist)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Error rendering gist preview for " + gist.Identifier() + " - " + gist.PreviewFilename)
|
||||
log.Error().Err(err).Msg("Error rendering gist preview for " + gist.Identifier() + " - " + gist.PreviewFilename)
|
||||
}
|
||||
renderedFiles = append(renderedFiles, &rendered)
|
||||
renderedGists = append(renderedGists, &rendered)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errorRes(500, "Error fetching gists", err)
|
||||
}
|
||||
|
||||
if err = paginate(ctx, renderedFiles, pageInt, 10, "gists", fromUserStr, 2, "&sort="+sort+"&order="+order); err != nil {
|
||||
if err = paginate(ctx, renderedGists, pageInt, 10, "gists", fromUserStr, 2, "&sort="+sort+"&order="+order); err != nil {
|
||||
return errorRes(404, "Page not found", nil)
|
||||
}
|
||||
|
||||
|
@ -273,6 +274,69 @@ func allGists(ctx echo.Context) error {
|
|||
return html(ctx, "all.html")
|
||||
}
|
||||
|
||||
func search(ctx echo.Context) error {
|
||||
var err error
|
||||
|
||||
content, meta := parseSearchQueryStr(ctx.QueryParam("q"))
|
||||
pageInt := getPage(ctx)
|
||||
|
||||
var currentUserId uint
|
||||
userLogged := getUserLogged(ctx)
|
||||
if userLogged != nil {
|
||||
currentUserId = userLogged.ID
|
||||
} else {
|
||||
currentUserId = 0
|
||||
}
|
||||
|
||||
var visibleGistsIds []uint
|
||||
visibleGistsIds, err = db.GetAllGistsVisibleByUser(currentUserId)
|
||||
if err != nil {
|
||||
return errorRes(500, "Error fetching gists", err)
|
||||
}
|
||||
|
||||
gistsIds, nbHits, langs, err := index.SearchGists(content, index.SearchGistMetadata{
|
||||
Username: meta["user"],
|
||||
Title: meta["title"],
|
||||
Filename: meta["filename"],
|
||||
Extension: meta["extension"],
|
||||
Language: meta["language"],
|
||||
}, visibleGistsIds, pageInt)
|
||||
if err != nil {
|
||||
return errorRes(500, "Error searching gists", err)
|
||||
}
|
||||
|
||||
gists, err := db.GetAllGistsByIds(gistsIds)
|
||||
if err != nil {
|
||||
return errorRes(500, "Error fetching gists", err)
|
||||
}
|
||||
|
||||
renderedGists := make([]*render.RenderedGist, 0, len(gists))
|
||||
for _, gist := range gists {
|
||||
rendered, err := render.HighlightGistPreview(gist)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error rendering gist preview for " + gist.Identifier() + " - " + gist.PreviewFilename)
|
||||
}
|
||||
renderedGists = append(renderedGists, &rendered)
|
||||
}
|
||||
|
||||
if pageInt > 1 && len(renderedGists) != 0 {
|
||||
setData(ctx, "prevPage", pageInt-1)
|
||||
}
|
||||
if 10*pageInt < int(nbHits) {
|
||||
setData(ctx, "nextPage", pageInt+1)
|
||||
}
|
||||
setData(ctx, "prevLabel", tr(ctx, "pagination.previous"))
|
||||
setData(ctx, "nextLabel", tr(ctx, "pagination.next"))
|
||||
setData(ctx, "urlPage", "search")
|
||||
setData(ctx, "urlParams", template.URL("&q="+ctx.QueryParam("q")))
|
||||
setData(ctx, "htmlTitle", "Search results")
|
||||
setData(ctx, "nbHits", nbHits)
|
||||
setData(ctx, "gists", renderedGists)
|
||||
setData(ctx, "langs", langs)
|
||||
setData(ctx, "searchQuery", ctx.QueryParam("q"))
|
||||
return html(ctx, "search.html")
|
||||
}
|
||||
|
||||
func gistIndex(ctx echo.Context) error {
|
||||
if getData(ctx, "gistpage") == "js" {
|
||||
return gistJs(ctx)
|
||||
|
@ -545,6 +609,8 @@ func processCreate(ctx echo.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
gist.AddInIndex()
|
||||
|
||||
return redirect(ctx, "/"+user.Username+"/"+gist.Identifier())
|
||||
}
|
||||
|
||||
|
@ -566,6 +632,7 @@ func deleteGist(ctx echo.Context) error {
|
|||
if err := gist.Delete(); err != nil {
|
||||
return errorRes(500, "Error deleting this gist", err)
|
||||
}
|
||||
gist.RemoveFromIndex()
|
||||
|
||||
addFlash(ctx, "Gist has been deleted", "success")
|
||||
return redirect(ctx, "/")
|
||||
|
|
|
@ -218,6 +218,7 @@ func pack(ctx echo.Context, serviceType string) error {
|
|||
|
||||
_ = gist.SetLastActiveNow()
|
||||
_ = gist.UpdatePreviewAndCount(false)
|
||||
gist.AddInIndex()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ package web
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/thomiceli/opengist/internal/index"
|
||||
htmlpkg "html"
|
||||
"html/template"
|
||||
"io"
|
||||
|
@ -115,6 +117,22 @@ var (
|
|||
"safe": func(s string) template.HTML {
|
||||
return template.HTML(s)
|
||||
},
|
||||
"dict": func(values ...interface{}) (map[string]interface{}, error) {
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("invalid dict call")
|
||||
}
|
||||
dict := make(map[string]interface{})
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("dict keys must be strings")
|
||||
}
|
||||
dict[key] = values[i+1]
|
||||
}
|
||||
return dict, nil
|
||||
},
|
||||
"addMetadataToSearchQuery": addMetadataToSearchQuery,
|
||||
"indexEnabled": index.Enabled,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -223,7 +241,6 @@ func NewServer(isDev bool) *Server {
|
|||
g1.DELETE("/settings/ssh-keys/:id", sshKeysDelete, logged)
|
||||
g1.PUT("/settings/password", passwordProcess, logged)
|
||||
g1.PUT("/settings/username", usernameProcess, logged)
|
||||
|
||||
g2 := g1.Group("/admin-panel")
|
||||
{
|
||||
g2.Use(adminPermission)
|
||||
|
@ -237,6 +254,7 @@ func NewServer(isDev bool) *Server {
|
|||
g2.POST("/gc-repos", adminGcRepos)
|
||||
g2.POST("/sync-previews", adminSyncGistPreviews)
|
||||
g2.POST("/reset-hooks", adminResetHooks)
|
||||
g2.POST("/index-gists", adminIndexGists)
|
||||
g2.GET("/configuration", adminConfig)
|
||||
g2.PUT("/set-config", adminSetConfig)
|
||||
}
|
||||
|
@ -246,7 +264,13 @@ func NewServer(isDev bool) *Server {
|
|||
}
|
||||
|
||||
g1.GET("/all", allGists, checkRequireLogin)
|
||||
g1.GET("/search", allGists, checkRequireLogin)
|
||||
|
||||
if index.Enabled() {
|
||||
g1.GET("/search", search, checkRequireLogin)
|
||||
} else {
|
||||
g1.GET("/search", allGists, checkRequireLogin)
|
||||
}
|
||||
|
||||
g1.GET("/:user", allGists, checkRequireLogin)
|
||||
g1.GET("/:user/liked", allGists, checkRequireLogin)
|
||||
g1.GET("/:user/forked", allGists, checkRequireLogin)
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/git"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGists(t *testing.T) {
|
||||
|
|
|
@ -133,14 +133,13 @@ func setup(t *testing.T) {
|
|||
|
||||
git.ReposDirectory = path.Join("tests")
|
||||
|
||||
config.C.IndexEnabled = false
|
||||
config.C.LogLevel = "debug"
|
||||
config.InitLog()
|
||||
|
||||
homePath := config.GetHomeDir()
|
||||
log.Info().Msg("Data directory: " + homePath)
|
||||
|
||||
err = os.MkdirAll(filepath.Join(homePath, "repos"), 0755)
|
||||
require.NoError(t, err, "Could not create repos directory")
|
||||
|
||||
err = os.MkdirAll(filepath.Join(homePath, "tmp", "repos"), 0755)
|
||||
require.NoError(t, err, "Could not create tmp repos directory")
|
||||
|
||||
|
@ -149,6 +148,9 @@ func setup(t *testing.T) {
|
|||
|
||||
err = memdb.Setup()
|
||||
require.NoError(t, err, "Could not initialize in memory database")
|
||||
|
||||
// err = index.Open(filepath.Join(homePath, "testsindex", "opengist.index"))
|
||||
// require.NoError(t, err, "Could not open index")
|
||||
}
|
||||
|
||||
func teardown(t *testing.T, s *testServer) {
|
||||
|
@ -159,4 +161,10 @@ func teardown(t *testing.T, s *testServer) {
|
|||
|
||||
err = os.RemoveAll(path.Join(config.C.OpengistHome, "tests"))
|
||||
require.NoError(t, err, "Could not remove repos directory")
|
||||
|
||||
// err = os.RemoveAll(path.Join(config.C.OpengistHome, "testsindex"))
|
||||
// require.NoError(t, err, "Could not remove repos directory")
|
||||
|
||||
// err = index.Close()
|
||||
// require.NoError(t, err, "Could not close index")
|
||||
}
|
||||
|
|
|
@ -248,6 +248,46 @@ func tr(ctx echo.Context, key string) template.HTML {
|
|||
return l.Tr(key)
|
||||
}
|
||||
|
||||
func parseSearchQueryStr(query string) (string, map[string]string) {
|
||||
words := strings.Fields(query)
|
||||
metadata := make(map[string]string)
|
||||
var contentBuilder strings.Builder
|
||||
|
||||
for _, word := range words {
|
||||
if strings.Contains(word, ":") {
|
||||
keyValue := strings.SplitN(word, ":", 2)
|
||||
if len(keyValue) == 2 {
|
||||
key := keyValue[0]
|
||||
value := keyValue[1]
|
||||
metadata[key] = value
|
||||
}
|
||||
} else {
|
||||
contentBuilder.WriteString(word + " ")
|
||||
}
|
||||
}
|
||||
|
||||
content := strings.TrimSpace(contentBuilder.String())
|
||||
return content, metadata
|
||||
}
|
||||
|
||||
func addMetadataToSearchQuery(input, key, value string) string {
|
||||
content, metadata := parseSearchQueryStr(input)
|
||||
|
||||
metadata[key] = value
|
||||
|
||||
var resultBuilder strings.Builder
|
||||
resultBuilder.WriteString(content)
|
||||
|
||||
for k, v := range metadata {
|
||||
resultBuilder.WriteString(" ")
|
||||
resultBuilder.WriteString(k)
|
||||
resultBuilder.WriteString(":")
|
||||
resultBuilder.WriteString(v)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(resultBuilder.String())
|
||||
}
|
||||
|
||||
type Argon2ID struct {
|
||||
format string
|
||||
version int
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/thomiceli/opengist/internal/config"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/git"
|
||||
"github.com/thomiceli/opengist/internal/index"
|
||||
"github.com/thomiceli/opengist/internal/memdb"
|
||||
"github.com/thomiceli/opengist/internal/ssh"
|
||||
"github.com/thomiceli/opengist/internal/web"
|
||||
|
@ -59,6 +60,13 @@ func initialize() {
|
|||
if err := memdb.Setup(); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to initialize in memory database")
|
||||
}
|
||||
|
||||
if config.C.IndexEnabled {
|
||||
log.Info().Msg("Index directory: " + filepath.Join(homePath, config.C.IndexDirname))
|
||||
if err := index.Open(filepath.Join(homePath, config.C.IndexDirname)); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to open index")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -167,4 +167,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
const searchinput = document.getElementById('search') as HTMLInputElement;
|
||||
searchinput.addEventListener('focusin', () => {
|
||||
document.getElementById('search-help').classList.remove('hidden');
|
||||
})
|
||||
|
||||
searchinput.addEventListener('focusout', (e) => {
|
||||
document.getElementById('search-help').classList.add('hidden');
|
||||
})
|
||||
});
|
||||
|
|
15
templates/base/base_header.html
vendored
15
templates/base/base_header.html
vendored
|
@ -89,9 +89,22 @@
|
|||
</svg>
|
||||
</div>
|
||||
<form action="{{ $.c.ExternalUrl }}/search" method="GET">
|
||||
<input id="search" name="q" class="bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-md pl-10" placeholder="Search" type="search" value="{{ .searchQuery }}">
|
||||
<input id="search" name="q" autocomplete="off" class="bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-md pl-10" placeholder="{{if indexEnabled}}Code search{{else}}Search{{end}}" type="search" value="{{ .searchQuery }}">
|
||||
<input type="submit" hidden="hidden">
|
||||
</form>
|
||||
{{if indexEnabled}}
|
||||
<div id="search-help" class="hidden absolute left-1/2 z-10 mt-5 w-screen max-w-max -translate-x-1/2 px-4">
|
||||
<div class="flex-auto overflow-hidden rounded-md bg-white dark:bg-gray-800 text-sm leading-6 border-1 border-gray-100 dark:border-gray-700 ring-1 ring-gray-900/5">
|
||||
<div class="p-4 text-xs space-y-1">
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">user:thomas</code> {{ .locale.Tr "gist.search.help.user" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">title:mygist</code> {{ .locale.Tr "gist.search.help.title" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">filename:myfile.txt</code> {{ .locale.Tr "gist.search.help.filename" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">extension:yml</code> {{ .locale.Tr "gist.search.help.extension" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">language:go</code> {{ .locale.Tr "gist.search.help.language" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
10
templates/pages/admin_index.html
vendored
10
templates/pages/admin_index.html
vendored
|
@ -74,18 +74,24 @@
|
|||
{{ .locale.Tr "admin.actions.git-gc" }}
|
||||
</button>
|
||||
</form>
|
||||
<form action="/admin-panel/sync-previews" method="POST">
|
||||
<form action="{{ $.c.ExternalUrl }}/admin-panel/sync-previews" method="POST">
|
||||
{{ .csrfHtml }}
|
||||
<button type="submit" {{ if .syncGistPreviews }}disabled="disabled"{{ end }} class="whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncGistPreviews }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
{{ .locale.Tr "admin.actions.sync-previews" }}
|
||||
</button>
|
||||
</form>
|
||||
<form action="/admin-panel/reset-hooks" method="POST">
|
||||
<form action="{{ $.c.ExternalUrl }}/admin-panel/reset-hooks" method="POST">
|
||||
{{ .csrfHtml }}
|
||||
<button type="submit" {{ if .resetHooks }}disabled="disabled"{{ end }} class="whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .resetHooks }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
{{ .locale.Tr "admin.actions.reset-hooks" }}
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ $.c.ExternalUrl }}/admin-panel/index-gists" method="POST">
|
||||
{{ .csrfHtml }}
|
||||
<button type="submit" {{ if .indexGists }}disabled="disabled"{{ end }} class="whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .indexGists }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
{{ .locale.Tr "admin.actions.index-gists" }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
71
templates/pages/all.html
vendored
71
templates/pages/all.html
vendored
|
@ -108,75 +108,8 @@
|
|||
<div>
|
||||
{{ if ne (len .gists) 0 }}
|
||||
{{ range $gist := .gists }}
|
||||
<div class="mb-8">
|
||||
<div class="flex ">
|
||||
<div class="div">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}">
|
||||
<img class="h-10 w-10 rounded-md mr-2 border border-gray-200 dark:border-gray-700 my-1" src="{{ avatarUrl $gist.User $.DisableGravatar }}" alt="{{ $gist.User.Username }}'s Avatar">
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-auto">
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<h4 class="text-md leading-tight break-all py-1 flex-auto">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}">{{ $gist.User.Username }}</a> <span class="text-slate-700 dark:text-slate-300">/</span> <a class="font-bold" href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}/{{ $gist.Identifier }}">{{ $gist.Title }}</a>
|
||||
</h4>
|
||||
<div class="flex space-x-4 lg:flex-row flex py-1 lg:py-0 lg:ml-auto text-slate-500">
|
||||
<div class="flex items-center float-right text-xs">
|
||||
<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 mr-1 inline-flex">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
|
||||
</svg>
|
||||
<span class="whitespace-nowrap">{{ $gist.NbLikes }} {{ $.locale.Tr "gist.list.likes" }}</span>
|
||||
</div>
|
||||
<div class="flex items-center float-right text-xs">
|
||||
<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 mr-1 inline-flex">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" />
|
||||
</svg>
|
||||
<span class="whitespace-nowrap">{{ $gist.NbForks }} {{ $.locale.Tr "gist.list.forks" }}</span>
|
||||
</div>
|
||||
<div class="flex items-center float-right text-xs">
|
||||
<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 mr-1 inline-flex">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z" />
|
||||
</svg>
|
||||
<span class="whitespace-nowrap">{{ $gist.NbFiles }} {{ $.locale.Tr "gist.list.files" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<h5 class="text-sm text-slate-500 pb-1">{{ $.locale.Tr "gist.list.last-active" }} <span class="moment-timestamp">{{ $gist.UpdatedAt }}</span>
|
||||
{{ if $gist.Forked }} • {{ $.locale.Tr "gist.list.forked-from" }} <a href="{{ $.c.ExternalUrl }}/{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Identifier }}">{{ $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"> {{ visibilityStr $gist.Private false }} </span>{{ end }}</h5>
|
||||
<h6 class="text-xs text-slate-700 dark:text-slate-300 py-1">{{ $gist.Description }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}/{{ $gist.Identifier }}" 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="code overflow-auto">
|
||||
{{ if $gist.PreviewFilename }}
|
||||
{{ if isMarkdown $gist.PreviewFilename }}
|
||||
<div class="chroma preview markdown markdown-body p-8">{{ $gist.HTML | safe }}</div>
|
||||
{{ else }}
|
||||
<table class="chroma table-code w-full whitespace-pre" data-filename="{{ $gist.PreviewFilename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
|
||||
<tbody>
|
||||
{{ $ii := "1" }}
|
||||
{{ $i := toInt $ii }}
|
||||
{{ range $line := $gist.Lines }}
|
||||
|
||||
<tr>
|
||||
<td class="select-none line-num px-4">{{$i}}</td>
|
||||
<td class="line-code">{{ $line | safe }}</td>
|
||||
</tr>
|
||||
{{ $i = inc $i }}
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<div class="pl-4 py-0.5 text-xs"><p>{{ $.locale.Tr "gist.no-content" }}</p></div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{{ $nest := dict "gist" $gist "C" $.c "locale" $.locale "DisableGravatar" $.DisableGravatar "searchQuery" $.searchQuery }}
|
||||
{{ template "_gist_preview" $nest }}
|
||||
{{ end }}
|
||||
|
||||
{{ template "pagination" . }}
|
||||
|
|
40
templates/pages/search.html
vendored
Normal file
40
templates/pages/search.html
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
{{ template "header" .}}
|
||||
<div class="py-10">
|
||||
<header class="pb-4 ">
|
||||
<div class="flex">
|
||||
<div class="flex-auto">
|
||||
<h1 class="text-2xl font-bold leading-tight">{{ .nbHits }} {{ .locale.Tr "gist.search.found" }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
{{ if ne (len .gists) 0 }}
|
||||
<div class="md:grid md:grid-cols-12 gap-x-4">
|
||||
<div class="md:col-span-3 pb-4">
|
||||
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 overflow-auto">
|
||||
{{ range $lang, $count := .langs }}
|
||||
<a href="{{ $.c.ExternalUrl }}/search?q={{ addMetadataToSearchQuery $.searchQuery "language" $lang }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100">
|
||||
{{ $lang }} ({{ $count }})
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:col-span-9">
|
||||
{{ range $gist := .gists }}
|
||||
{{ $nest := dict "gist" $gist "C" $.c "locale" $.locale "DisableGravatar" $.DisableGravatar }}
|
||||
{{ template "_gist_preview" $nest }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ template "pagination" . }}
|
||||
{{ else }}
|
||||
<div class="text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-slate-600 dark:text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14 10l-2 1m0 0l-2-1m2 1v2.5M20 7l-2 1m2-1l-2-1m2 1v2.5M14 4l-2-1-2 1M4 7l2-1M4 7l2 1M4 7v2.5M12 21l-2-1m2 1l2-1m-2 1v-2.5M6 18l-2-1v-2.5M18 18l2-1v-2.5" />
|
||||
</svg>
|
||||
<h3 class="mt-2 text-sm font-medium text-slate-700 dark:text-slate-300">{{ .locale.Tr "gist.search.no-results" }}</h3>
|
||||
</div>
|
||||
{{ end }}
|
||||
</main>
|
||||
</div>
|
||||
{{ template "footer" .}}
|
76
templates/partials/_gist_preview.html
vendored
Normal file
76
templates/partials/_gist_preview.html
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
{{ define "_gist_preview" }}
|
||||
|
||||
|
||||
<div class="mb-8">
|
||||
<div class="flex ">
|
||||
<div class="div">
|
||||
<a href="{{ .c.ExternalUrl }}/{{ .gist.User.Username }}">
|
||||
<img class="h-10 w-10 rounded-md mr-2 border border-gray-200 dark:border-gray-700 my-1" src="{{ avatarUrl .gist.User .DisableGravatar }}" alt="{{ .gist.User.Username }}'s Avatar">
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-auto">
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<h4 class="text-md leading-tight break-all py-1 flex-auto">
|
||||
<a href="{{ .c.ExternalUrl }}/{{ .gist.User.Username }}">{{ .gist.User.Username }}</a> <span class="text-slate-700 dark:text-slate-300">/</span> <a class="font-bold" href="{{ .c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Identifier }}">{{ .gist.Title }}</a>
|
||||
</h4>
|
||||
<div class="flex space-x-4 lg:flex-row flex py-1 lg:py-0 lg:ml-auto text-slate-500">
|
||||
<div class="flex items-center float-right text-xs">
|
||||
<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 mr-1 inline-flex">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
|
||||
</svg>
|
||||
<span class="whitespace-nowrap">{{ .gist.NbLikes }} {{ .locale.Tr "gist.list.likes" }}</span>
|
||||
</div>
|
||||
<div class="flex items-center float-right text-xs">
|
||||
<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 mr-1 inline-flex">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" />
|
||||
</svg>
|
||||
<span class="whitespace-nowrap">{{ .gist.NbForks }} {{ .locale.Tr "gist.list.forks" }}</span>
|
||||
</div>
|
||||
<div class="flex items-center float-right text-xs">
|
||||
<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 mr-1 inline-flex">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z" />
|
||||
</svg>
|
||||
<span class="whitespace-nowrap">{{ .gist.NbFiles }} {{ .locale.Tr "gist.list.files" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<h5 class="text-sm text-slate-500 pb-1">{{ .locale.Tr "gist.list.last-active" }} <span class="moment-timestamp">{{ .gist.UpdatedAt }}</span>
|
||||
{{ if .gist.Forked }} • {{ .locale.Tr "gist.list.forked-from" }} <a href="{{ .c.ExternalUrl }}/{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Identifier }}">{{ .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"> {{ visibilityStr .gist.Private false }} </span>{{ end }}</h5>
|
||||
<h6 class="text-xs text-slate-700 dark:text-slate-300 py-1">{{ .gist.Description }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ .c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Identifier }}" 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="code overflow-auto">
|
||||
{{ if .gist.PreviewFilename }}
|
||||
{{ if isMarkdown .gist.PreviewFilename }}
|
||||
<div class="chroma preview markdown markdown-body p-8">{{ .gist.HTML | safe }}</div>
|
||||
{{ else }}
|
||||
<table class="chroma table-code w-full whitespace-pre" data-filename="{{ .gist.PreviewFilename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
|
||||
<tbody>
|
||||
{{ $ii := "1" }}
|
||||
{{ $i := toInt $ii }}
|
||||
{{ range $line := .gist.Lines }}
|
||||
|
||||
<tr>
|
||||
<td class="select-none line-num px-4">{{$i}}</td>
|
||||
<td class="line-code">{{ $line | safe }}</td>
|
||||
</tr>
|
||||
{{ $i = inc $i }}
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<div class="pl-4 py-0.5 text-xs"><p>{{ .locale.Tr "gist.no-content" }}</p></div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
{{ end }}
|
||||
|
Loading…
Reference in a new issue