Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24fc6dd8e4 | ||
|
|
cc6110bb4e | ||
|
|
2890c60124 | ||
|
|
5bb5886770 | ||
|
|
038d81df2d | ||
|
|
7515e82d34 | ||
|
|
add0299442 | ||
|
|
06b752f567 | ||
|
|
a35b64455a | ||
|
|
9470622125 | ||
|
|
7b5d035a32 | ||
|
|
98c5cd1794 | ||
|
|
0936cbc455 | ||
|
|
fc421a68b5 | ||
|
|
62711ff491 | ||
|
|
da19e486f2 | ||
|
|
98c85de3d6 | ||
|
|
3366cde385 | ||
|
|
b2a56fe5a0 | ||
|
|
24e3de8fc1 | ||
|
|
c517c2d9c9 | ||
|
|
8880e00f48 | ||
|
|
da970d7272 | ||
|
|
62f91c5ed2 |
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
docker-build-release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
checks:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
46
CHANGELOG.md
46
CHANGELOG.md
@@ -1,5 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## [1.4.2](https://github.com/thomiceli/opengist/compare/v1.4.1...v1.4.2) - 2023-07-17
|
||||
### Added
|
||||
- External url to HTML links & redirects (#75)
|
||||
- Make unlisted gists not SEO crawlable (#78)
|
||||
- Warning message on OAuth unlink (#79)
|
||||
|
||||
### Changed
|
||||
- Redirect to `/all` when not logged in (#76)
|
||||
- Removed Dev Docker image (#80)
|
||||
|
||||
## [1.4.1](https://github.com/thomiceli/opengist/compare/v1.4.0...v1.4.1) - 2023-06-25
|
||||
### ⚠️ Docker users ⚠️
|
||||
Opengist Docker volume has been changed from `/root/.opengist` to `/opengist`, do not forget to update your
|
||||
`docker-compose.yml` file or any other Docker related configuration.
|
||||
|
||||
Please make a backup of your Opengist data directory before updating.
|
||||
|
||||
### Fixed
|
||||
- Git message remote: `warning: unable to access '/root/.config/git/attributes': Permission denied` (#71)
|
||||
|
||||
## [1.4.0](https://github.com/thomiceli/opengist/compare/v1.3.0...v1.4.0) - 2023-06-23
|
||||
### ⚠️ Docker users ⚠️
|
||||
Opengist Docker volume has been changed from `/root/.opengist` to `/opengist`, do not forget to update your
|
||||
`docker-compose.yml` file or any other Docker related configuration.
|
||||
|
||||
Please make a backup of your Opengist data directory before updating.
|
||||
|
||||
### Added
|
||||
- Search gists, browse users snippets, likes and forks (#68)
|
||||
- SQLite WAL journal mode by default (#54)
|
||||
- Change SQLite journal mode via configuration (#54)
|
||||
- Configuration via environment variables (#50)
|
||||
- Docker dev image (#56)
|
||||
- Choose Docker container/volumes owner via UID/GID (#63)
|
||||
|
||||
### Changed
|
||||
- Docker volume changed from `/root/.opengist` to `/opengist` (#63)
|
||||
- `DEV` environment variable renamed to `OG_DEV` (#64)
|
||||
- Use `npx` in Makefile instead of `./node_modules/.bin` (#66)
|
||||
- DEPRECATED: `OG_CONFIG` environment variable (#64)
|
||||
|
||||
### Fixed
|
||||
- Gitea URL joins (#43, #61)
|
||||
- Dark mode flickering (#44)
|
||||
- Typos (#42)
|
||||
|
||||
## [1.3.0](https://github.com/thomiceli/opengist/compare/v1.2.0...v1.3.0) - 2023-05-27
|
||||
### Added
|
||||
- Disable login form via admin panel
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@@ -21,10 +21,11 @@ COPY . .
|
||||
RUN make
|
||||
|
||||
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.17 as run
|
||||
|
||||
RUN apk update && \
|
||||
apk add --no-cache \
|
||||
shadow \
|
||||
openssl \
|
||||
openssh \
|
||||
curl \
|
||||
@@ -36,10 +37,14 @@ RUN apk update && \
|
||||
musl-dev \
|
||||
libstdc++
|
||||
|
||||
WORKDIR /opengist
|
||||
RUN addgroup -S opengist && \
|
||||
adduser -S -G opengist -H -s /bin/ash -g 'Opengist User' opengist
|
||||
|
||||
COPY --from=build /opengist/opengist .
|
||||
WORKDIR /app/opengist
|
||||
|
||||
COPY --from=build --chown=opengist:opengist /opengist/opengist .
|
||||
COPY --from=build --chown=opengist:opengist /opengist/docker ./docker
|
||||
|
||||
EXPOSE 6157 2222
|
||||
VOLUME /root/.opengist
|
||||
CMD ["./opengist"]
|
||||
VOLUME /opengist
|
||||
ENTRYPOINT ["./docker/entrypoint.sh"]
|
||||
|
||||
6
Makefile
6
Makefile
@@ -13,7 +13,7 @@ install:
|
||||
|
||||
build_frontend:
|
||||
@echo "Building frontend assets..."
|
||||
./node_modules/.bin/vite build
|
||||
npx vite build
|
||||
|
||||
build_backend:
|
||||
@echo "Building Opengist binary..."
|
||||
@@ -27,11 +27,11 @@ build_docker:
|
||||
|
||||
watch_frontend:
|
||||
@echo "Building frontend assets..."
|
||||
./node_modules/.bin/vite dev --port 16157
|
||||
npx vite dev --port 16157
|
||||
|
||||
watch_backend:
|
||||
@echo "Building Opengist binary..."
|
||||
DEV=1 ./node_modules/.bin/nodemon --watch '**/*' -e html,yml,go,js --signal SIGTERM --exec 'go run . --config config.yml'
|
||||
OG_DEV=1 npx nodemon --watch '**/*' -e html,yml,go,js --signal SIGTERM --exec 'go run . --config config.yml'
|
||||
|
||||
watch:
|
||||
@bash ./watch.sh
|
||||
|
||||
98
README.md
98
README.md
@@ -3,6 +3,7 @@
|
||||

|
||||

|
||||

|
||||
[](https://goreportcard.com/report/github.com/thomiceli/opengist)
|
||||
|
||||
A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomice.li).
|
||||
|
||||
@@ -11,6 +12,8 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic
|
||||
* [With Docker](#with-docker)
|
||||
* [From source](#from-source)
|
||||
* [Configuration](#configuration)
|
||||
* [Via YAML file](#configuration-via-yaml-file)
|
||||
* [Via Environment Variables](#configuration-via-environment-variables)
|
||||
* [Administration](#administration)
|
||||
* [Use Nginx as a reverse proxy](#use-nginx-as-a-reverse-proxy)
|
||||
* [Use Fail2ban](#use-fail2ban)
|
||||
@@ -24,7 +27,7 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic
|
||||
* Revisions history
|
||||
* Syntax highlighting ; markdown & CSV support
|
||||
* Like / Fork snippets
|
||||
* Search for all snippets or for certain users snippets
|
||||
* Search for snippets ; browse users snippets, likes and forks
|
||||
* Editor with indentation mode & size ; drag and drop files
|
||||
* Download raw files or as a ZIP archive
|
||||
* OAuth2 login with GitHub and Gitea
|
||||
@@ -40,9 +43,10 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic
|
||||
|
||||
#### Todo
|
||||
|
||||
- [ ] Tests
|
||||
- [ ] Search for snippets
|
||||
- [ ] Translation
|
||||
- [ ] Code/text search
|
||||
- [ ] Embed snippets
|
||||
- [ ] Tests
|
||||
- [ ] Filesystem/Redis support for user sessions
|
||||
- [ ] Have a cool logo
|
||||
|
||||
@@ -50,10 +54,10 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic
|
||||
|
||||
### With Docker
|
||||
|
||||
A Docker [image](https://github.com/users/thomiceli/packages/container/package/opengist), available for each release, can be pulled
|
||||
Docker [images](https://github.com/thomiceli/opengist/pkgs/container/opengist) are available for each release :
|
||||
|
||||
```
|
||||
docker pull ghcr.io/thomiceli/opengist:1
|
||||
```shell
|
||||
docker pull ghcr.io/thomiceli/opengist:1.4
|
||||
```
|
||||
|
||||
It can be used in a `docker-compose.yml` file :
|
||||
@@ -67,17 +71,25 @@ version: "3"
|
||||
|
||||
services:
|
||||
opengist:
|
||||
image: ghcr.io/thomiceli/opengist:1
|
||||
image: ghcr.io/thomiceli/opengist:1.4
|
||||
container_name: opengist
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6157:6157" # HTTP port
|
||||
- "2222:2222" # SSH port, can be removed if you don't use SSH
|
||||
volumes:
|
||||
- "$HOME/.opengist:/root/.opengist"
|
||||
- "$HOME/.opengist:/opengist"
|
||||
```
|
||||
|
||||
You can define which user/group should run the container and own the files by setting the `UID` and `GID` environment variables :
|
||||
|
||||
```yml
|
||||
services:
|
||||
opengist:
|
||||
# ...
|
||||
environment:
|
||||
CONFIG: |
|
||||
log-level: info
|
||||
UID: 1001
|
||||
GID: 1001
|
||||
```
|
||||
|
||||
### From source
|
||||
@@ -95,29 +107,65 @@ Opengist is now running on port 6157, you can browse http://localhost:6157
|
||||
|
||||
## Configuration
|
||||
|
||||
Opengist can be configured using YAML. The full configuration file is [config.yml](config.yml), each default key/value
|
||||
pair can be overridden.
|
||||
Opengist provides flexible configuration options through either a YAML file and/or environment variables.
|
||||
You would only need to specify the configuration options you want to change — for any config option left untouched, Opengist will simply apply the default values.
|
||||
|
||||
### With docker
|
||||
<details>
|
||||
<summary>Configuration option list</summary>
|
||||
|
||||
Add a `CONFIG` environment variable in the `docker-compose.yml` file to the `opengist` service :
|
||||
| YAML Config Key | Environment Variable | Default value | Description |
|
||||
|-----------------------|--------------------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| log-level | OG_LOG_LEVEL | `warn` | Set the log level to one of the following: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`. |
|
||||
| external-url | OG_EXTERNAL_URL | none | Public URL for the Git HTTP/SSH connection. If not set, uses the URL from the request. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| http.port | OG_HTTP_PORT | `6157` | The port on which the HTTP server should listen. |
|
||||
| http.git-enabled | OG_HTTP_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via HTTP. (`true` or `false`) |
|
||||
| http.tls-enabled | OG_HTTP_TLS_ENABLED | `false` | Enable or disable TLS for the HTTP server. (`true` or `false`) |
|
||||
| http.cert-file | OG_HTTP_CERT_FILE | none | Path to the TLS certificate file if TLS is enabled. |
|
||||
| http.key-file | OG_HTTP_KEY_FILE | none | Path to the TLS key file if TLS is enabled. |
|
||||
| ssh.git-enabled | OG_SSH_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via SSH. (`true` or `false`) |
|
||||
| ssh.host | OG_SSH_HOST | `0.0.0.0` | The host on which the SSH server should bind. |
|
||||
| ssh.port | OG_SSH_PORT | `2222` | The port on which the SSH server should listen. |
|
||||
| ssh.external-domain | OG_SSH_EXTERNAL_DOMAIN | none | Public domain for the Git SSH connection, if it has to be different from the HTTP one. If not set, uses the URL from the request. |
|
||||
| ssh.keygen-executable | OG_SSH_KEYGEN_EXECUTABLE | `ssh-keygen` | Path to the SSH key generation executable. |
|
||||
| github.client-key | OG_GITHUB_CLIENT_KEY | none | The client key for the GitHub OAuth application. |
|
||||
| github.secret | OG_GITHUB_SECRET | none | The secret for the GitHub OAuth application. |
|
||||
| gitea.client-key | OG_GITEA_CLIENT_KEY | none | The client key for the Gitea OAuth application. |
|
||||
| gitea.secret | OG_GITEA_SECRET | none | The secret for the Gitea OAuth application. |
|
||||
| gitea.url | OG_GITEA_URL | `https://gitea.com/` | The URL of the Gitea instance. |
|
||||
|
||||
```diff
|
||||
environment:
|
||||
CONFIG: |
|
||||
log-level: info
|
||||
ssh.git-enabled: false
|
||||
# ...
|
||||
```
|
||||
</details>
|
||||
|
||||
### With binary
|
||||
### Configuration via YAML file
|
||||
|
||||
Create a `config.yml` file (you can reuse this [one](config.yml)) and run Opengist binary with the `--config` flag :
|
||||
The configuration file must be specified when launching the application, using the `--config` flag followed by the path to your YAML file.
|
||||
|
||||
```shell
|
||||
./opengist --config /path/to/config.yml
|
||||
```
|
||||
|
||||
You can start by copying and/or modifying the provided [config.yml](config.yml) file.
|
||||
|
||||
### Configuration via Environment Variables
|
||||
|
||||
Usage with Docker Compose :
|
||||
|
||||
```yml
|
||||
services:
|
||||
opengist:
|
||||
# ...
|
||||
environment:
|
||||
OG_LOG_LEVEL: "info"
|
||||
# etc.
|
||||
```
|
||||
Usage via command line :
|
||||
|
||||
```shell
|
||||
OG_LOG_LEVEL=info ./opengist
|
||||
```
|
||||
|
||||
## Administration
|
||||
|
||||
@@ -141,7 +189,7 @@ server {
|
||||
|
||||
Then run :
|
||||
```shell
|
||||
service nginx restart
|
||||
service nginx restart
|
||||
```
|
||||
|
||||
### Use Fail2ban
|
||||
@@ -171,7 +219,7 @@ port = anyport
|
||||
|
||||
Then run
|
||||
```shell
|
||||
service fail2ban restart
|
||||
service fail2ban restart
|
||||
```
|
||||
|
||||
## Configure OAuth
|
||||
|
||||
@@ -11,6 +11,10 @@ opengist-home:
|
||||
# Name of the SQLite database file. Default: opengist.db
|
||||
db-filename: opengist.db
|
||||
|
||||
# Set the journal mode for SQLite. Default: WAL
|
||||
# See https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||
sqlite.journal-mode: WAL
|
||||
|
||||
|
||||
# HTTP server configuration
|
||||
# Host to bind to. Default: 0.0.0.0
|
||||
|
||||
11
docker/entrypoint.sh
Executable file
11
docker/entrypoint.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
export USER=opengist
|
||||
UID=${UID:-1000}
|
||||
GID=${GID:-1000}
|
||||
groupmod -o -g "$GID" $USER
|
||||
usermod -o -u "$UID" $USER
|
||||
|
||||
chown -R "$USER:$USER" /opengist
|
||||
|
||||
exec su $USER -c "OG_OPENGIST_HOME=/opengist /app/opengist/opengist"
|
||||
@@ -2,46 +2,52 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/utils"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var OpengistVersion = "1.3.0"
|
||||
var OpengistVersion = "1.4.2"
|
||||
|
||||
var C *config
|
||||
|
||||
// Not using nested structs because the library
|
||||
// doesn't support dot notation in this case sadly
|
||||
type config struct {
|
||||
LogLevel string `yaml:"log-level"`
|
||||
ExternalUrl string `yaml:"external-url"`
|
||||
OpengistHome string `yaml:"opengist-home"`
|
||||
DBFilename string `yaml:"db-filename"`
|
||||
LogLevel string `yaml:"log-level" env:"OG_LOG_LEVEL"`
|
||||
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"`
|
||||
|
||||
HttpHost string `yaml:"http.host"`
|
||||
HttpPort string `yaml:"http.port"`
|
||||
HttpGit bool `yaml:"http.git-enabled"`
|
||||
HttpTLSEnabled bool `yaml:"http.tls-enabled"`
|
||||
HttpCertFile string `yaml:"http.cert-file"`
|
||||
HttpKeyFile string `yaml:"http.key-file"`
|
||||
SqliteJournalMode string `yaml:"sqlite.journal-mode" env:"OG_SQLITE_JOURNAL_MODE"`
|
||||
|
||||
SshGit bool `yaml:"ssh.git-enabled"`
|
||||
SshHost string `yaml:"ssh.host"`
|
||||
SshPort string `yaml:"ssh.port"`
|
||||
SshExternalDomain string `yaml:"ssh.external-domain"`
|
||||
SshKeygen string `yaml:"ssh.keygen-executable"`
|
||||
HttpHost string `yaml:"http.host" env:"OG_HTTP_HOST"`
|
||||
HttpPort string `yaml:"http.port" env:"OG_HTTP_PORT"`
|
||||
HttpGit bool `yaml:"http.git-enabled" env:"OG_HTTP_GIT_ENABLED"`
|
||||
HttpTLSEnabled bool `yaml:"http.tls-enabled" env:"OG_HTTP_TLS_ENABLED"`
|
||||
HttpCertFile string `yaml:"http.cert-file" env:"OG_HTTP_CERT_FILE"`
|
||||
HttpKeyFile string `yaml:"http.key-file" env:"OG_HTTP_KEY_FILE"`
|
||||
|
||||
GithubClientKey string `yaml:"github.client-key"`
|
||||
GithubSecret string `yaml:"github.secret"`
|
||||
SshGit bool `yaml:"ssh.git-enabled" env:"OG_SSH_GIT_ENABLED"`
|
||||
SshHost string `yaml:"ssh.host" env:"OG_SSH_HOST"`
|
||||
SshPort string `yaml:"ssh.port" env:"OG_SSH_PORT"`
|
||||
SshExternalDomain string `yaml:"ssh.external-domain" env:"OG_SSH_EXTERNAL_DOMAIN"`
|
||||
SshKeygen string `yaml:"ssh.keygen-executable" env:"OG_SSH_KEYGEN_EXECUTABLE"`
|
||||
|
||||
GiteaClientKey string `yaml:"gitea.client-key"`
|
||||
GiteaSecret string `yaml:"gitea.secret"`
|
||||
GiteaUrl string `yaml:"gitea.url"`
|
||||
GithubClientKey string `yaml:"github.client-key" env:"OG_GITHUB_CLIENT_KEY"`
|
||||
GithubSecret string `yaml:"github.secret" env:"OG_GITHUB_SECRET"`
|
||||
|
||||
GiteaClientKey string `yaml:"gitea.client-key" env:"OG_GITEA_CLIENT_KEY"`
|
||||
GiteaSecret string `yaml:"gitea.secret" env:"OG_GITEA_SECRET"`
|
||||
GiteaUrl string `yaml:"gitea.url" env:"OG_GITEA_URL"`
|
||||
}
|
||||
|
||||
func configWithDefaults() (*config, error) {
|
||||
@@ -55,6 +61,8 @@ func configWithDefaults() (*config, error) {
|
||||
c.OpengistHome = filepath.Join(homeDir, ".opengist")
|
||||
c.DBFilename = "opengist.db"
|
||||
|
||||
c.SqliteJournalMode = "WAL"
|
||||
|
||||
c.HttpHost = "0.0.0.0"
|
||||
c.HttpPort = "6157"
|
||||
c.HttpGit = true
|
||||
@@ -77,37 +85,16 @@ func InitConfig(configPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if configPath != "" {
|
||||
absolutePath, _ := filepath.Abs(configPath)
|
||||
absolutePath = filepath.Clean(absolutePath)
|
||||
file, err := os.Open(absolutePath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
fmt.Println("No YML config file found at " + absolutePath)
|
||||
} else {
|
||||
fmt.Println("Using config file: " + absolutePath)
|
||||
|
||||
// Override default values with values from config.yml
|
||||
d := yaml.NewDecoder(file)
|
||||
if err = d.Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
}
|
||||
} else {
|
||||
fmt.Println("No config file specified. Using default values.")
|
||||
if err = loadConfigFromYaml(c, configPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Override default values with environment variables (as yaml)
|
||||
configEnv := os.Getenv("CONFIG")
|
||||
if configEnv != "" {
|
||||
fmt.Println("Using config from environment variable: CONFIG")
|
||||
d := yaml.NewDecoder(strings.NewReader(configEnv))
|
||||
if err = d.Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = loadConfigFromEnv(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = checks(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
C = c
|
||||
@@ -132,6 +119,10 @@ func InitLog() {
|
||||
|
||||
multi := zerolog.MultiLevelWriter(zerolog.NewConsoleWriter(), file)
|
||||
log.Logger = zerolog.New(multi).Level(level).With().Timestamp().Logger()
|
||||
|
||||
if !utils.SliceContains([]string{"trace", "debug", "info", "warn", "error", "fatal", "panic"}, strings.ToLower(C.LogLevel)) {
|
||||
log.Warn().Msg("Invalid log level: " + C.LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckGitVersion(version string) (bool, error) {
|
||||
@@ -159,3 +150,92 @@ func GetHomeDir() string {
|
||||
absolutePath, _ := filepath.Abs(C.OpengistHome)
|
||||
return filepath.Clean(absolutePath)
|
||||
}
|
||||
|
||||
func loadConfigFromYaml(c *config, configPath string) error {
|
||||
if configPath != "" {
|
||||
absolutePath, _ := filepath.Abs(configPath)
|
||||
absolutePath = filepath.Clean(absolutePath)
|
||||
file, err := os.Open(absolutePath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
fmt.Println("No YAML config file found at " + absolutePath)
|
||||
} else {
|
||||
fmt.Println("Using YAML config file: " + absolutePath)
|
||||
|
||||
// Override default values with values from config.yml
|
||||
d := yaml.NewDecoder(file)
|
||||
if err = d.Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
}
|
||||
} else {
|
||||
fmt.Println("No YAML config file specified.")
|
||||
}
|
||||
|
||||
// Override default values with environment variables (as yaml)
|
||||
configEnv := os.Getenv("CONFIG")
|
||||
if configEnv != "" {
|
||||
fmt.Println("Using config from environment variable: CONFIG")
|
||||
fmt.Println("!! This method of setting the config is deprecated and will be removed in a future version of Opengist")
|
||||
d := yaml.NewDecoder(strings.NewReader(configEnv))
|
||||
if err := d.Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConfigFromEnv(c *config) error {
|
||||
v := reflect.ValueOf(c).Elem()
|
||||
var envVars []string
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
tag := v.Type().Field(i).Tag.Get("env")
|
||||
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
envValue := os.Getenv(strings.ToUpper(tag))
|
||||
if envValue == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch v.Field(i).Kind() {
|
||||
case reflect.String:
|
||||
v.Field(i).SetString(envValue)
|
||||
case reflect.Bool:
|
||||
boolVal, err := strconv.ParseBool(envValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Field(i).SetBool(boolVal)
|
||||
}
|
||||
|
||||
envVars = append(envVars, tag)
|
||||
}
|
||||
|
||||
if len(envVars) > 0 {
|
||||
fmt.Println("Using environment variables config: " + strings.Join(envVars, ", "))
|
||||
} else {
|
||||
fmt.Println("No environment variables config specified.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checks(c *config) error {
|
||||
if _, err := url.Parse(c.ExternalUrl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := url.Parse(c.GiteaUrl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,23 +3,40 @@ package models
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
"github.com/thomiceli/opengist/internal/utils"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var db *gorm.DB
|
||||
|
||||
func Setup(dbpath string) error {
|
||||
func Setup(dbPath string) error {
|
||||
var err error
|
||||
journalMode := strings.ToUpper(config.C.SqliteJournalMode)
|
||||
|
||||
if db, err = gorm.Open(sqlite.Open(dbpath+"?_fk=true"), &gorm.Config{
|
||||
if !utils.SliceContains([]string{"DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"}, journalMode) {
|
||||
log.Warn().Msg("Invalid SQLite journal mode: " + journalMode)
|
||||
}
|
||||
|
||||
if db, err = gorm.Open(sqlite.Open(dbPath+"?_fk=true&_journal_mode="+journalMode), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = db.AutoMigrate(&User{}, &SSHKey{}, &Gist{}, &AdminSetting{}); err != nil {
|
||||
if err = db.SetupJoinTable(&Gist{}, "Likes", &Like{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = db.SetupJoinTable(&User{}, "Liked", &Like{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = db.AutoMigrate(&User{}, &Gist{}, &SSHKey{}, &AdminSetting{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ type Gist struct {
|
||||
ForkedID uint
|
||||
}
|
||||
|
||||
type Like struct {
|
||||
UserID uint `gorm:"primaryKey"`
|
||||
GistID uint `gorm:"primaryKey"`
|
||||
CreatedAt int64
|
||||
}
|
||||
|
||||
func (gist *Gist) BeforeDelete(tx *gorm.DB) error {
|
||||
// Decrement fork counter if the gist was forked
|
||||
err := tx.Model(&Gist{}).
|
||||
@@ -80,11 +86,11 @@ func GetAllGists(offset int) ([]*Gist, error) {
|
||||
return gists, err
|
||||
}
|
||||
|
||||
func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
|
||||
func GetAllGistsFromSearch(currentUserId uint, query string, offset int, sort string, order string) ([]*Gist, error) {
|
||||
var gists []*Gist
|
||||
err := db.Preload("User").Preload("Forked.User").
|
||||
Where("users.username = ? and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", fromUser, currentUserId).
|
||||
Joins("join users on gists.user_id = users.id").
|
||||
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
|
||||
Where("gists.title like ? or gists.description like ?", "%"+query+"%", "%"+query+"%").
|
||||
Limit(11).
|
||||
Offset(offset * 10).
|
||||
Order("gists." + sort + "_at " + order).
|
||||
@@ -93,6 +99,74 @@ func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort s
|
||||
return gists, err
|
||||
}
|
||||
|
||||
func gistsFromUserStatement(fromUserId uint, currentUserId uint) *gorm.DB {
|
||||
return db.Preload("User").Preload("Forked.User").
|
||||
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
|
||||
Where("users.id = ?", fromUserId).
|
||||
Joins("join users on gists.user_id = users.id")
|
||||
}
|
||||
|
||||
func GetAllGistsFromUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
|
||||
var gists []*Gist
|
||||
err := gistsFromUserStatement(fromUserId, currentUserId).Limit(11).
|
||||
Offset(offset * 10).
|
||||
Order("gists." + sort + "_at " + order).
|
||||
Find(&gists).Error
|
||||
|
||||
return gists, err
|
||||
}
|
||||
|
||||
func CountAllGistsFromUser(fromUserId uint, currentUserId uint) (int64, error) {
|
||||
var count int64
|
||||
err := gistsFromUserStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func likedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
|
||||
return db.Preload("User").Preload("Forked.User").
|
||||
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
|
||||
Where("likes.user_id = ?", fromUserId).
|
||||
Joins("join likes on gists.id = likes.gist_id").
|
||||
Joins("join users on likes.user_id = users.id")
|
||||
}
|
||||
|
||||
func GetAllGistsLikedByUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
|
||||
var gists []*Gist
|
||||
err := likedStatement(fromUserId, currentUserId).Limit(11).
|
||||
Offset(offset * 10).
|
||||
Order("gists." + sort + "_at " + order).
|
||||
Find(&gists).Error
|
||||
return gists, err
|
||||
}
|
||||
|
||||
func CountAllGistsLikedByUser(fromUserId uint, currentUserId uint) (int64, error) {
|
||||
var count int64
|
||||
err := likedStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func forkedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
|
||||
return db.Preload("User").Preload("Forked.User").
|
||||
Where("gists.forked_id is not null and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
|
||||
Where("gists.user_id = ?", fromUserId).
|
||||
Joins("join users on gists.user_id = users.id")
|
||||
}
|
||||
|
||||
func GetAllGistsForkedByUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
|
||||
var gists []*Gist
|
||||
err := forkedStatement(fromUserId, currentUserId).Limit(11).
|
||||
Offset(offset * 10).
|
||||
Order("gists." + sort + "_at " + order).
|
||||
Find(&gists).Error
|
||||
return gists, err
|
||||
}
|
||||
|
||||
func CountAllGistsForkedByUser(fromUserId uint, currentUserId uint) (int64, error) {
|
||||
var count int64
|
||||
err := forkedStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func GetAllGistsRows() ([]*Gist, error) {
|
||||
var gists []*Gist
|
||||
err := db.Table("gists").
|
||||
|
||||
10
internal/utils/slice.go
Normal file
10
internal/utils/slice.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package utils
|
||||
|
||||
func SliceContains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -185,15 +185,15 @@ func adminSyncReposFromDB(ctx echo.Context) error {
|
||||
return redirect(ctx, "/admin-panel")
|
||||
}
|
||||
|
||||
func adminSettings(ctx echo.Context) error {
|
||||
setData(ctx, "title", "Admin Settings")
|
||||
setData(ctx, "htmlTitle", "Admin Settings - Admin panel")
|
||||
setData(ctx, "adminHeaderPage", "settings")
|
||||
func adminConfig(ctx echo.Context) error {
|
||||
setData(ctx, "title", "Configuration")
|
||||
setData(ctx, "htmlTitle", "Configuration - Admin panel")
|
||||
setData(ctx, "adminHeaderPage", "config")
|
||||
|
||||
return html(ctx, "admin_settings.html")
|
||||
return html(ctx, "admin_config.html")
|
||||
}
|
||||
|
||||
func adminSetSetting(ctx echo.Context) error {
|
||||
func adminSetConfig(ctx echo.Context) error {
|
||||
key := ctx.FormValue("key")
|
||||
value := ctx.FormValue("value")
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/gothic"
|
||||
@@ -17,9 +22,6 @@ import (
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"gorm.io/gorm"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var title = cases.Title(language.English)
|
||||
@@ -45,7 +47,7 @@ func processRegister(ctx echo.Context) error {
|
||||
|
||||
sess := getSession(ctx)
|
||||
|
||||
var dto = new(models.UserDTO)
|
||||
dto := new(models.UserDTO)
|
||||
if err := ctx.Bind(dto); err != nil {
|
||||
return errorRes(400, "Cannot bind data", err)
|
||||
}
|
||||
@@ -205,7 +207,7 @@ func oauthCallback(ctx echo.Context) error {
|
||||
case "github":
|
||||
resp, err = http.Get("https://github.com/" + user.NickName + ".keys")
|
||||
case "gitea":
|
||||
resp, err = http.Get(trimGiteaUrl() + "/" + user.NickName + ".keys")
|
||||
resp, err = http.Get(urlJoin(config.C.GiteaUrl, user.NickName+".keys"))
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@@ -252,8 +254,6 @@ func oauth(ctx echo.Context) error {
|
||||
httpProtocol = "https"
|
||||
}
|
||||
|
||||
giteaUrl := trimGiteaUrl()
|
||||
|
||||
var opengistUrl string
|
||||
if config.C.ExternalUrl != "" {
|
||||
opengistUrl = config.C.ExternalUrl
|
||||
@@ -267,7 +267,8 @@ func oauth(ctx echo.Context) error {
|
||||
github.New(
|
||||
config.C.GithubClientKey,
|
||||
config.C.GithubSecret,
|
||||
opengistUrl+"/oauth/github/callback"),
|
||||
urlJoin(opengistUrl, "/oauth/github/callback"),
|
||||
),
|
||||
)
|
||||
|
||||
case "gitea":
|
||||
@@ -275,10 +276,11 @@ func oauth(ctx echo.Context) error {
|
||||
gitea.NewCustomisedURL(
|
||||
config.C.GiteaClientKey,
|
||||
config.C.GiteaSecret,
|
||||
opengistUrl+"/oauth/gitea/callback",
|
||||
giteaUrl+"/login/oauth/authorize",
|
||||
giteaUrl+"/login/oauth/access_token",
|
||||
giteaUrl+"/api/v1/user"),
|
||||
urlJoin(opengistUrl, "/oauth/gitea/callback"),
|
||||
urlJoin(config.C.GiteaUrl, "/login/oauth/authorize"),
|
||||
urlJoin(config.C.GiteaUrl, "/login/oauth/access_token"),
|
||||
urlJoin(config.C.GiteaUrl, "/api/v1/user"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -325,23 +327,21 @@ func logout(ctx echo.Context) error {
|
||||
return redirect(ctx, "/all")
|
||||
}
|
||||
|
||||
func trimGiteaUrl() string {
|
||||
giteaUrl := config.C.GiteaUrl
|
||||
// remove trailing slash
|
||||
if giteaUrl[len(giteaUrl)-1] == '/' {
|
||||
giteaUrl = giteaUrl[:len(giteaUrl)-1]
|
||||
func urlJoin(base string, elem ...string) string {
|
||||
joined, err := url.JoinPath(base, elem...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot join url")
|
||||
}
|
||||
|
||||
return giteaUrl
|
||||
return joined
|
||||
}
|
||||
|
||||
func getAvatarUrlFromProvider(provider string, identifier string) string {
|
||||
fmt.Println("getAvatarUrlFromProvider", provider, identifier)
|
||||
switch provider {
|
||||
case "github":
|
||||
return "https://avatars.githubusercontent.com/u/" + identifier + "?v=4"
|
||||
case "gitea":
|
||||
resp, err := http.Get("https://gitea.com/api/v1/users/" + identifier)
|
||||
resp, err := http.Get(urlJoin(config.C.GiteaUrl, "/api/v1/users/", identifier))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get user from Gitea")
|
||||
return ""
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
"html/template"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -79,16 +80,20 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
setData(ctx, "hasLiked", hasLiked)
|
||||
}
|
||||
|
||||
if gist.Private {
|
||||
setData(ctx, "NoIndex", true)
|
||||
}
|
||||
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func allGists(ctx echo.Context) error {
|
||||
var err error
|
||||
var urlPage string
|
||||
|
||||
fromUserStr := ctx.Param("user")
|
||||
|
||||
userLogged := getUserLogged(ctx)
|
||||
|
||||
pageInt := getPage(ctx)
|
||||
|
||||
sort := "created"
|
||||
@@ -114,14 +119,38 @@ func allGists(ctx echo.Context) error {
|
||||
} else {
|
||||
currentUserId = 0
|
||||
}
|
||||
|
||||
if fromUserStr == "" {
|
||||
setData(ctx, "htmlTitle", "All gists")
|
||||
fromUserStr = "all"
|
||||
gists, err = models.GetAllGistsForCurrentUser(currentUserId, pageInt-1, sort, order)
|
||||
urlctx := ctx.Request().URL.Path
|
||||
if strings.HasSuffix(urlctx, "search") {
|
||||
setData(ctx, "htmlTitle", "Search results")
|
||||
setData(ctx, "mode", "search")
|
||||
setData(ctx, "searchQuery", ctx.QueryParam("q"))
|
||||
setData(ctx, "searchQueryUrl", template.URL("&q="+ctx.QueryParam("q")))
|
||||
urlPage = "search"
|
||||
gists, err = models.GetAllGistsFromSearch(currentUserId, ctx.QueryParam("q"), pageInt-1, sort, order)
|
||||
} else if strings.HasSuffix(urlctx, "all") {
|
||||
setData(ctx, "htmlTitle", "All gists")
|
||||
setData(ctx, "mode", "all")
|
||||
urlPage = "all"
|
||||
gists, err = models.GetAllGistsForCurrentUser(currentUserId, pageInt-1, sort, order)
|
||||
}
|
||||
} else {
|
||||
setData(ctx, "htmlTitle", "All gists from "+fromUserStr)
|
||||
liked := false
|
||||
forked := false
|
||||
|
||||
liked, err = regexp.MatchString(`/[^/]*/liked`, ctx.Request().URL.Path)
|
||||
if err != nil {
|
||||
return errorRes(500, "Error matching regexp", err)
|
||||
}
|
||||
|
||||
forked, err = regexp.MatchString(`/[^/]*/forked`, ctx.Request().URL.Path)
|
||||
if err != nil {
|
||||
return errorRes(500, "Error matching regexp", err)
|
||||
}
|
||||
|
||||
var fromUser *models.User
|
||||
|
||||
fromUser, err = models.GetUserByUsername(fromUserStr)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -131,7 +160,40 @@ func allGists(ctx echo.Context) error {
|
||||
}
|
||||
setData(ctx, "fromUser", fromUser)
|
||||
|
||||
gists, err = models.GetAllGistsFromUser(fromUserStr, currentUserId, pageInt-1, sort, order)
|
||||
if countFromUser, err := models.CountAllGistsFromUser(fromUser.ID, currentUserId); err != nil {
|
||||
return errorRes(500, "Error counting gists", err)
|
||||
} else {
|
||||
setData(ctx, "countFromUser", countFromUser)
|
||||
}
|
||||
|
||||
if countLiked, err := models.CountAllGistsLikedByUser(fromUser.ID, currentUserId); err != nil {
|
||||
return errorRes(500, "Error counting liked gists", err)
|
||||
} else {
|
||||
setData(ctx, "countLiked", countLiked)
|
||||
}
|
||||
|
||||
if countForked, err := models.CountAllGistsForkedByUser(fromUser.ID, currentUserId); err != nil {
|
||||
return errorRes(500, "Error counting forked gists", err)
|
||||
} else {
|
||||
setData(ctx, "countForked", countForked)
|
||||
}
|
||||
|
||||
if liked {
|
||||
urlPage = fromUserStr + "/liked"
|
||||
setData(ctx, "htmlTitle", "All gists liked by "+fromUserStr)
|
||||
setData(ctx, "mode", "liked")
|
||||
gists, err = models.GetAllGistsLikedByUser(fromUser.ID, currentUserId, pageInt-1, sort, order)
|
||||
} else if forked {
|
||||
urlPage = fromUserStr + "/forked"
|
||||
setData(ctx, "htmlTitle", "All gists forked by "+fromUserStr)
|
||||
setData(ctx, "mode", "forked")
|
||||
gists, err = models.GetAllGistsForkedByUser(fromUser.ID, currentUserId, pageInt-1, sort, order)
|
||||
} else {
|
||||
urlPage = fromUserStr
|
||||
setData(ctx, "htmlTitle", "All gists from "+fromUserStr)
|
||||
setData(ctx, "mode", "fromUser")
|
||||
gists, err = models.GetAllGistsFromUser(fromUser.ID, currentUserId, pageInt-1, sort, order)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -142,6 +204,7 @@ func allGists(ctx echo.Context) error {
|
||||
return errorRes(404, "Page not found", nil)
|
||||
}
|
||||
|
||||
setData(ctx, "urlPage", urlPage)
|
||||
return html(ctx, "all.html")
|
||||
}
|
||||
|
||||
@@ -318,7 +381,7 @@ func processCreate(ctx echo.Context) error {
|
||||
}
|
||||
|
||||
if err = gist.AddAndCommitFiles(&dto.Files); err != nil {
|
||||
return errorRes(500, "Error adding and commiting files", err)
|
||||
return errorRes(500, "Error adding and committing files", err)
|
||||
}
|
||||
|
||||
if isCreate {
|
||||
@@ -527,7 +590,7 @@ func likes(ctx echo.Context) error {
|
||||
return errorRes(404, "Page not found", nil)
|
||||
}
|
||||
|
||||
setData(ctx, "htmlTitle", "Likes for "+gist.Title)
|
||||
setData(ctx, "htmlTitle", "Like for "+gist.Title)
|
||||
setData(ctx, "revision", "HEAD")
|
||||
return html(ctx, "likes.html")
|
||||
}
|
||||
|
||||
@@ -151,10 +151,9 @@ func infoRefs(ctx echo.Context) error {
|
||||
gist := getData(ctx, "gist").(*models.Gist)
|
||||
|
||||
serviceType := ctx.QueryParam("service")
|
||||
if !strings.HasPrefix(serviceType, "git-") {
|
||||
service = ""
|
||||
if strings.HasPrefix(serviceType, "git-") {
|
||||
service = strings.TrimPrefix(serviceType, "git-")
|
||||
}
|
||||
service = strings.TrimPrefix(serviceType, "git-")
|
||||
|
||||
if service != "upload-pack" && service != "receive-pack" {
|
||||
if err := gist.UpdateServerInfo(); err != nil {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var dev = os.Getenv("DEV") == "1"
|
||||
var dev = os.Getenv("OG_DEV") == "1"
|
||||
var store *sessions.CookieStore
|
||||
var re = regexp.MustCompile("[^a-z0-9]+")
|
||||
var fm = template.FuncMap{
|
||||
@@ -85,7 +85,7 @@ var fm = template.FuncMap{
|
||||
if dev {
|
||||
return "http://localhost:16157/" + jsfile
|
||||
}
|
||||
return "/" + manifestEntries[jsfile].File
|
||||
return config.C.ExternalUrl + "/" + manifestEntries[jsfile].File
|
||||
},
|
||||
"defaultAvatar": defaultAvatar,
|
||||
}
|
||||
@@ -193,12 +193,15 @@ func Start() {
|
||||
g2.POST("/gists/:gist/delete", adminGistDelete)
|
||||
g2.POST("/sync-fs", adminSyncReposFromFS)
|
||||
g2.POST("/sync-db", adminSyncReposFromDB)
|
||||
g2.GET("/settings", adminSettings)
|
||||
g2.PUT("/set-setting", adminSetSetting)
|
||||
g2.GET("/configuration", adminConfig)
|
||||
g2.PUT("/set-config", adminSetConfig)
|
||||
}
|
||||
|
||||
g1.GET("/all", allGists, checkRequireLogin)
|
||||
g1.GET("/search", allGists, checkRequireLogin)
|
||||
g1.GET("/:user", allGists, checkRequireLogin)
|
||||
g1.GET("/:user/liked", allGists, checkRequireLogin)
|
||||
g1.GET("/:user/forked", allGists, checkRequireLogin)
|
||||
|
||||
g3 := g1.Group("/:user/:gistname")
|
||||
{
|
||||
@@ -253,6 +256,8 @@ func dataInit(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return errorRes(500, "Cannot read settings from database", err)
|
||||
}
|
||||
|
||||
setData(ctx, "c", config.C)
|
||||
|
||||
setData(ctx, "githubOauth", config.C.GithubClientKey != "" && config.C.GithubSecret != "")
|
||||
setData(ctx, "giteaOauth", config.C.GiteaClientKey != "" && config.C.GiteaSecret != "")
|
||||
|
||||
@@ -318,7 +323,7 @@ func logged(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
if user != nil {
|
||||
return next(ctx)
|
||||
}
|
||||
return redirect(ctx, "/login")
|
||||
return redirect(ctx, "/all")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,5 +379,5 @@ func defaultAvatar() string {
|
||||
if dev {
|
||||
return "http://localhost:16157/default.png"
|
||||
}
|
||||
return "/" + manifestEntries["default.png"].File
|
||||
return config.C.ExternalUrl + "/" + manifestEntries["default.png"].File
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
"github.com/thomiceli/opengist/internal/models"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"html/template"
|
||||
@@ -44,7 +45,7 @@ func htmlWithCode(ctx echo.Context, code int, template string) error {
|
||||
}
|
||||
|
||||
func redirect(ctx echo.Context, location string) error {
|
||||
return ctx.Redirect(302, location)
|
||||
return ctx.Redirect(302, config.C.ExternalUrl+location)
|
||||
}
|
||||
|
||||
func plainText(ctx echo.Context, code int, message string) error {
|
||||
@@ -166,7 +167,7 @@ func validateReservedKeywords(fl validator.FieldLevel) bool {
|
||||
name := fl.Field().String()
|
||||
|
||||
restrictedNames := map[string]struct{}{}
|
||||
for _, restrictedName := range []string{"assets", "register", "login", "logout", "config", "admin-panel", "all"} {
|
||||
for _, restrictedName := range []string{"assets", "register", "login", "logout", "settings", "admin-panel", "all", "search"} {
|
||||
restrictedNames[restrictedName] = struct{}{}
|
||||
}
|
||||
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -31,7 +31,7 @@
|
||||
"sass": "^1.62.1",
|
||||
"sugarss": "^4.0.1",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"vite": "^4.2.1"
|
||||
"vite": "^4.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -4819,9 +4819,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.2.1.tgz",
|
||||
"integrity": "sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.2.3.tgz",
|
||||
"integrity": "sha512-kLU+m2q0Y434Y1kCy3TchefAdtFso0ILi0dLyFV8Us3InXTU11H/B5ZTqCKIQHzSKNxVG/yEx813EA9f1imQ9A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.17.5",
|
||||
@@ -8651,9 +8651,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.2.1.tgz",
|
||||
"integrity": "sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.2.3.tgz",
|
||||
"integrity": "sha512-kLU+m2q0Y434Y1kCy3TchefAdtFso0ILi0dLyFV8Us3InXTU11H/B5ZTqCKIQHzSKNxVG/yEx813EA9f1imQ9A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.17.5",
|
||||
|
||||
@@ -31,6 +31,6 @@
|
||||
"sass": "^1.62.1",
|
||||
"sugarss": "^4.0.1",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"vite": "^4.2.1"
|
||||
"vite": "^4.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const setSetting = (key: string, value: string) => {
|
||||
data.append('key', key);
|
||||
data.append('value', value);
|
||||
data.append('_csrf', ((document.getElementsByName('_csrf')[0] as HTMLInputElement).value));
|
||||
return fetch('/admin-panel/set-setting', {
|
||||
return fetch('/admin-panel/set-config', {
|
||||
method: 'PUT',
|
||||
credentials: 'same-origin',
|
||||
body: data,
|
||||
|
||||
@@ -6,26 +6,10 @@ import moment from 'moment';
|
||||
import md from 'markdown-it';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const themeMenu = document.getElementById('theme-menu')!;
|
||||
|
||||
const checkTheme = () => {
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
|
||||
checkTheme()
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)')
|
||||
.addEventListener('change',({ matches }) => {
|
||||
checkTheme()
|
||||
}
|
||||
)
|
||||
|
||||
document.getElementById('light-mode')!.onclick = (e) => {
|
||||
e.stopPropagation()
|
||||
localStorage.theme = 'light';
|
||||
@@ -51,6 +35,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
themeMenu.classList.toggle('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('user-btn')?.addEventListener("click" , (e) => {
|
||||
document.getElementById('user-menu').classList.toggle('hidden');
|
||||
})
|
||||
|
||||
document.querySelectorAll('.moment-timestamp').forEach((e: HTMLElement) => {
|
||||
e.title = moment.unix(parseInt(e.innerHTML)).format('LLLL');
|
||||
e.innerHTML = moment.unix(parseInt(e.innerHTML)).fromNow();
|
||||
@@ -81,9 +69,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return '<pre class="hljs"><code>' +
|
||||
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
|
||||
hljs.highlight(str, {language: lang, ignoreIllegals: true}).value +
|
||||
'</code></pre>';
|
||||
} catch (__) {}
|
||||
} catch (__) {
|
||||
}
|
||||
}
|
||||
|
||||
return '<pre class="hljs"><code>' + md().utils.escapeHtml(str) + '</code></pre>';
|
||||
|
||||
12
public/style.css
vendored
12
public/style.css
vendored
@@ -140,3 +140,15 @@ table.csv-table thead tr th {
|
||||
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;
|
||||
}
|
||||
|
||||
10
templates/base/admin_header.html
vendored
10
templates/base/admin_header.html
vendored
@@ -9,14 +9,14 @@
|
||||
<div class="mb-4">
|
||||
<div class="">
|
||||
<nav class="flex space-x-4" aria-label="Tabs">
|
||||
<a href="/admin-panel" class="{{ if eq .adminHeaderPage "index" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
<a href="{{ $.c.ExternalUrl }}/admin-panel" class="{{ if eq .adminHeaderPage "index" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}">General</a>
|
||||
<a href="/admin-panel/users" class="{{ if eq .adminHeaderPage "users" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
<a href="{{ $.c.ExternalUrl }}/admin-panel/users" class="{{ if eq .adminHeaderPage "users" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">Users</a>
|
||||
<a href="/admin-panel/gists" class="{{ if eq .adminHeaderPage "gists" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
<a href="{{ $.c.ExternalUrl }}/admin-panel/gists" class="{{ if eq .adminHeaderPage "gists" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">Gists</a>
|
||||
<a href="/admin-panel/settings" class="{{ if eq .adminHeaderPage "settings" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">Admin settings</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/admin-panel/configuration" class="{{ if eq .adminHeaderPage "config" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
|
||||
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">Configuration</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
142
templates/base/base_header.html
vendored
142
templates/base/base_header.html
vendored
@@ -3,6 +3,28 @@
|
||||
<html lang="en" class="h-full">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
{{ if .NoIndex }}
|
||||
<meta name="robots" content="noindex, follow">
|
||||
{{ end }}
|
||||
|
||||
<script>
|
||||
const checkTheme = () => {
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
|
||||
checkTheme()
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)')
|
||||
.addEventListener('change', ({matches}) => {
|
||||
checkTheme()
|
||||
}
|
||||
)
|
||||
|
||||
</script>
|
||||
<link rel="icon" type="image/svg+xml" href="{{ asset "favicon.svg" }}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="{{ asset "main.css" }}" />
|
||||
@@ -35,7 +57,7 @@
|
||||
|
||||
<div class="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
|
||||
<div class="flex-shrink-0 flex items-center">
|
||||
<a href="/">
|
||||
<a href="{{ $.c.ExternalUrl }}/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 flex text-primary-400" 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" />
|
||||
</svg>
|
||||
@@ -43,41 +65,93 @@
|
||||
</div>
|
||||
<div class="hidden sm:block sm:ml-6">
|
||||
<div class="flex space-x-4">
|
||||
<a href="/all" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">All</a>
|
||||
<a href="/" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">New</a>
|
||||
{{ if .userLogged }}
|
||||
<a href="/{{ .userLogged.Username }}" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">My gists</a>
|
||||
{{ end }}
|
||||
<a href="{{ $.c.ExternalUrl }}/all" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">All</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">New</a>
|
||||
<div class="flex flex-1 items-center justify-center px-2 lg:ml-6 lg:justify-end">
|
||||
<div class="w-full max-w-lg lg:max-w-xs">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<div class="relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<form action="/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 type="submit" hidden="hidden">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
|
||||
{{ if .userLogged }}
|
||||
{{ if .userLogged.IsAdmin }}
|
||||
<a href="/admin-panel" class="hidden sm:block text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium" aria-current="page">Admin</a>
|
||||
{{ end }}
|
||||
<a href="/settings" class="hidden sm:block text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium" aria-current="page">Settings</a>
|
||||
|
||||
<a href="/logout" id="logged-button" class="inline-flex text-slate-700 dark:text-slate-300 hover:bg-rose-500 hover:text-white dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||
<p class="mr-1 username">{{ .userLogged.Username }}</p>
|
||||
<p class="mr-1 logout hidden">Logout</p>
|
||||
<span class="sr-only">User</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
<div id="user-btn" class="hidden sm:flex items-center ml-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md px-3 py-2">
|
||||
<div class="inline-flex">
|
||||
<p class="hidden sm:block text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white rounded-md text-sm font-medium mr-2">{{ .userLogged.Username }}</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="h-5 w-5 inline-block">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden relative sm:inline-block text-left">
|
||||
<div id="user-menu" class="hidden w-32 font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-600 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none">
|
||||
<div class="py-1" role="none">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .userLogged.Username }}" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
|
||||
</svg>
|
||||
My gists
|
||||
</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .userLogged.Username }}/liked" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
|
||||
<path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
|
||||
</svg>
|
||||
Liked
|
||||
</a>
|
||||
</div>
|
||||
{{ if .userLogged.IsAdmin }}
|
||||
<div class="py-1" role="none">
|
||||
<a href="{{ $.c.ExternalUrl }}/admin-panel" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
|
||||
</svg>
|
||||
Admin
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="py-1" role="none">
|
||||
<a href="{{ $.c.ExternalUrl }}/settings" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
Settings
|
||||
</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/logout" class="dark:text-rose-400 text-rose-500 group flex items-center px-3 py-1.5 text-sm w-full hover:text-rose-600 dark:hover:text-rose-500" role="menuitem" tabindex="-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 dark:text-rose-400 text-rose-500 group-hover:text-rose-600 dark:group-hover:text-rose-500">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
|
||||
</svg>
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{ else }}
|
||||
{{ if not .DisableSignup }}
|
||||
<a href="/register" class="inline-flex text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||
<a href="{{ $.c.ExternalUrl }}/register" class="inline-flex text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||
<p class="text-slate-700 dark:text-slate-300 mr-1">Register</p>
|
||||
</a>
|
||||
{{ end }}
|
||||
<a href="/login" class="inline-flex text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||
<a href="{{ $.c.ExternalUrl }}/login" class="inline-flex text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||
<p class="text-slate-700 dark:text-slate-300 mr-1">Login</p>
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
<div class="ml-2 border-l-1 border-gray-200 dark:border-gray-600 rounded-md"><br /></div>
|
||||
<div class="hidden sm:block ml-2 border-l-1 border-gray-200 dark:border-gray-600 rounded-md"><br /></div>
|
||||
<div id="theme-btn" class="hidden sm:flex items-center ml-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md px-3 py-2">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5 text-primary-500 dark:hidden">
|
||||
@@ -88,7 +162,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="hidden relative sm:inline-block text-left">
|
||||
<div id="theme-menu" class="hidden font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-900 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none">
|
||||
<div id="theme-menu" class="hidden font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-600 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none">
|
||||
<div class="py-1" role="none">
|
||||
<!-- Active: "bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900", Not Active: "text-gray-300 dark:text-gray-700" -->
|
||||
<button id="light-mode" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
|
||||
@@ -120,15 +194,29 @@
|
||||
|
||||
<!-- Mobile menu -->
|
||||
<div class="sm:hidden hidden" id="mobile-menu">
|
||||
<div class="mx-2">
|
||||
<label for="searchmobile" class="sr-only">Search</label>
|
||||
<div class="relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<form action="/search" method="GET">
|
||||
<input id="searchmobile" 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 type="submit" hidden="hidden">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-2 pt-2 pb-3 space-y-1">
|
||||
<a href="/all" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">All</a>
|
||||
<a href="/" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">New</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/all" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">All</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">New</a>
|
||||
{{ if .userLogged }}
|
||||
<a href="/{{ .userLogged.Username }}" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">My gists</a>
|
||||
<a href="/settings" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">Settings</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .userLogged.Username }}" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">My gists</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/settings" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">Settings</a>
|
||||
|
||||
{{ if .userLogged.IsAdmin }}
|
||||
<a href="/admin-panel" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">Admin</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/admin-panel" class="text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white block px-3 py-2 rounded-md text-base font-medium">Admin</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
26
templates/base/gist_header.html
vendored
26
templates/base/gist_header.html
vendored
@@ -4,7 +4,7 @@
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold leading-tight break-all">
|
||||
<a href="/{{ .gist.User.Username }}">{{ .gist.User.Username }}</a> <span class="text-slate-700 dark:text-slate-300">/</span> <a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}">{{ .gist.Title }}</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}">{{ .gist.User.Username }}</a> <span class="text-slate-700 dark:text-slate-300">/</span> <a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}">{{ .gist.Title }}</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="lg:flex-row flex py-2 lg:py-0 lg:ml-auto">
|
||||
@@ -24,7 +24,7 @@
|
||||
Unlike
|
||||
{{ end }}
|
||||
</button>
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/likes" class="text-slate-700 dark:text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}/likes" class="text-slate-700 dark:text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||
{{ .gist.NbLikes }}
|
||||
</a>
|
||||
</form>
|
||||
@@ -37,38 +37,38 @@
|
||||
</svg>
|
||||
Fork
|
||||
</button>
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/forks" class="text-slate-700 dark:text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}/forks" class="text-slate-700 dark:text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||
{{ .gist.NbForks }}
|
||||
</a>
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<div class="lg:flex-row flex lg:py-0 lg:ml-auto flex items-center">
|
||||
<a href="/login" type="submit" class="ml-auto focus-within:z-10 text-slate-700 dark:text-slate-300 relative 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-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||
<a href="{{ $.c.ExternalUrl }}/login" type="submit" class="ml-auto focus-within:z-10 text-slate-700 dark:text-slate-300 relative 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-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||
<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 mr-2">
|
||||
<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>
|
||||
Like
|
||||
</a>
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/likes" class="text-slate-700 dark:text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}/likes" class="text-slate-700 dark:text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||
{{ .gist.NbLikes }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="ml-2 flex items-center">
|
||||
<a href="/login" type="submit" class="ml-auto focus-within:z-10 text-slate-700 dark:text-slate-300 relative 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-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||
<a href="{{ $.c.ExternalUrl }}/login" type="submit" class="ml-auto focus-within:z-10 text-slate-700 dark:text-slate-300 relative 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-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||
<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 mr-2">
|
||||
<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>
|
||||
Fork
|
||||
</a>
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/forks" class="text-slate-700 dark:text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}/forks" class="text-slate-700 dark:text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||
{{ .gist.NbForks }}
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .userLogged }}{{ if eq .gist.User.Username .userLogged.Username }}
|
||||
<div class="ml-2 flex items-center">
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/edit" class="relative inline-flex items-center space-x-2 rounded-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}/edit" class="relative inline-flex items-center space-x-2 rounded-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||
</svg>
|
||||
@@ -89,7 +89,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ if .gist.Forked }}
|
||||
<p class="mt-1 max-w-2xl text-sm text-slate-500">Forked from <a href="/{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Uuid }}">{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Title }}</a></p>
|
||||
<p class="mt-1 max-w-2xl text-sm text-slate-500">Forked from <a href="{{ $.c.ExternalUrl }}/{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Uuid }}">{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Title }}</a></p>
|
||||
{{ end }}
|
||||
<p class="mt-1 max-w-2xl text-sm text-slate-500">Last active <span class="moment-timestamp"> {{ .gist.UpdatedAt }} </span>
|
||||
{{ if .gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> Unlisted </span>{{ end }}
|
||||
@@ -110,18 +110,18 @@
|
||||
<div class="hidden sm:block">
|
||||
<div class="border-b flex border-gray-200 dark:border-gray-700">
|
||||
<nav class="-mb-px flex-auto space-x-4" aria-label="Tabs">
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}" class="inline-flex items-center text-slate-700 dark:text-slate-300 {{ if eq .page "code"}}border-slate-500 dark:border-slate-300 {{else}}border-transparent hover:border-gray-700 dark:hover:border-gray-200{{end}} hover:text-slate-700 dark:hover:text-slate-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm" aria-current="page">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}" class="inline-flex items-center text-slate-700 dark:text-slate-300 {{ if eq .page "code"}}border-slate-500 dark:border-slate-300 {{else}}border-transparent hover:border-gray-700 dark:hover:border-gray-200{{end}} hover:text-slate-700 dark:hover:text-slate-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm" aria-current="page">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-1">
|
||||
<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>
|
||||
Code
|
||||
</a>
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/revisions" class="inline-flex items-center text-slate-700 dark:text-slate-300 {{ if eq .page "revisions"}}border-slate-500 dark:border-slate-300 {{else}}border-transparent hover:border-gray-700 dark:hover:border-gray-200{{end}} hover:text-slate-700 dark:hover:text-slate-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}/revisions" class="inline-flex items-center text-slate-700 dark:text-slate-300 {{ if eq .page "revisions"}}border-slate-500 dark:border-slate-300 {{else}}border-transparent hover:border-gray-700 dark:hover:border-gray-200{{end}} hover:text-slate-700 dark:hover:text-slate-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-1">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
|
||||
</svg>
|
||||
Revisions
|
||||
<span class="inline-flex items-center ml-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> {{ if .nbCommits }}{{ .nbCommits }}{{else}}0{{ end }} </span>
|
||||
<span class="inline-flex items-center ml-2 px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> {{ if .nbCommits }}{{ .nbCommits }}{{else}}0{{ end }} </span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="float-right inline-flex items-center space-x-2">
|
||||
@@ -164,7 +164,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/archive/{{ .revision }}" class="whitespace-nowrap text-slate-700 dark:text-slate-300 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium 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">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}/archive/{{ .revision }}" class="whitespace-nowrap text-slate-700 dark:text-slate-300 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium 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">
|
||||
Download ZIP</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
4
templates/base/pagination.html
vendored
4
templates/base/pagination.html
vendored
@@ -1,7 +1,7 @@
|
||||
{{ define "pagination" }}
|
||||
<div class="flex justify-center space-x-2">
|
||||
{{ if .prevPage }}
|
||||
<a href="/{{ .urlPage }}?page={{ .prevPage }}{{ .urlParams }}" class="relative inline-flex items-center space-x-2 rounded-md border border-white dark:border-gray-900 bg-white dark:bg-gray-900 px-2 py-1.5 font-medium text-slate-700 dark:text-slate-300 hover:border-gray-200 dark:hover:border-gray-400 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 text-sm leading-4">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .urlPage }}?page={{ .prevPage }}{{ .urlParams }}" class="relative inline-flex items-center space-x-2 rounded-md border border-white dark:border-gray-900 bg-white dark:bg-gray-900 px-2 py-1.5 font-medium text-slate-700 dark:text-slate-300 hover:border-gray-200 dark:hover:border-gray-400 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 text-sm leading-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-1 w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
@@ -15,7 +15,7 @@
|
||||
{{ .prevLabel }}</span>
|
||||
{{ end }}
|
||||
{{ if .nextPage }}
|
||||
<a href="/{{ .urlPage }}?page={{ .nextPage }}{{ .urlParams }}" class="relative inline-flex items-center space-x-2 rounded-md border border-white dark:border-gray-900 bg-white dark:bg-gray-900 px-2 py-1.5 font-medium text-slate-700 dark:text-slate-300 hover:border-gray-200 dark:hover:border-gray-400 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 text-sm leading-4">{{ .nextLabel }}
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .urlPage }}?page={{ .nextPage }}{{ .urlParams }}" class="relative inline-flex items-center space-x-2 rounded-md border border-white dark:border-gray-900 bg-white dark:bg-gray-900 px-2 py-1.5 font-medium text-slate-700 dark:text-slate-300 hover:border-gray-200 dark:hover:border-gray-400 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 text-sm leading-4">{{ .nextLabel }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="ml-1 w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
||||
|
||||
117
templates/pages/admin_config.html
vendored
Normal file
117
templates/pages/admin_config.html
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
{{ template "header" .}}
|
||||
{{ template "admin_header" .}}
|
||||
|
||||
<div class="grid gap-4 grid-cols-1 md:grid-cols-2">
|
||||
<div class="p-6 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-700">
|
||||
<p class="italic text-xs text-gray-400 dark:text-gray-400 mb-4">This configuration can be <a target="_blank" href="https://github.com/thomiceli/opengist#configuration">overridden</a> by a YAML config file and/or environment variables.</p>
|
||||
<dl class="dl-config">
|
||||
<div class="relative col-span-3">
|
||||
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="bg-gray-50 dark:bg-gray-800 px-2 text-sm text-slate-700 dark:text-slate-300 font-bold">General</span>
|
||||
</div>
|
||||
</div>
|
||||
<dt>Log level</dt><dd>{{ .c.LogLevel }}</dd>
|
||||
<dt>External URL</dt><dd>{{ .c.ExternalUrl }}</dd>
|
||||
<dt>Opengist home</dt><dd>{{ .c.OpengistHome }}</dd>
|
||||
<dt>DB filename</dt><dd>{{ .c.DBFilename }}</dd>
|
||||
<dt>SQLite Journal Mode</dt><dd>{{ .c.SqliteJournalMode }}</dd>
|
||||
<div class="relative col-span-3 mt-4">
|
||||
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="bg-gray-50 dark:bg-gray-800 px-2 text-sm text-slate-700 dark:text-slate-300 font-bold">HTTP</span>
|
||||
</div>
|
||||
</div>
|
||||
<dt>HTTP host</dt><dd>{{ .c.HttpHost }}</dd>
|
||||
<dt>HTTP port</dt><dd>{{ .c.HttpPort }}</dd>
|
||||
<dt>HTTP Git enabled</dt><dd>{{ .c.HttpGit }}</dd>
|
||||
<dt>HTTP TLS enabled</dt><dd>{{ .c.HttpTLSEnabled }}</dd>
|
||||
<dt>HTTP Cert file</dt><dd>{{ .c.HttpCertFile }}</dd>
|
||||
<dt>HTTP Key file</dt><dd>{{ .c.HttpKeyFile }}</dd>
|
||||
<div class="relative col-span-3 mt-4">
|
||||
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="bg-gray-50 dark:bg-gray-800 px-2 text-sm text-slate-700 dark:text-slate-300 font-bold">SSH</span>
|
||||
</div>
|
||||
</div>
|
||||
<dt>SSH Git enabled</dt><dd>{{ .c.SshGit }}</dd>
|
||||
<dt>SSH host</dt><dd>{{ .c.SshHost }}</dd>
|
||||
<dt>SSH port</dt><dd>{{ .c.SshPort }}</dd>
|
||||
<dt>SSH external domain</dt><dd>{{ .c.SshExternalDomain }}</dd>
|
||||
<dt>SSH Keygen</dt><dd>{{ .c.SshKeygen }}</dd>
|
||||
<div class="relative col-span-3 mt-4">
|
||||
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="bg-gray-50 dark:bg-gray-800 px-2 text-sm text-slate-700 dark:text-slate-300 font-bold">OAuth</span>
|
||||
</div>
|
||||
</div>
|
||||
<dt>Github Client key</dt><dd>{{ .c.GithubClientKey }}</dd>
|
||||
<dt>Github Secret</dt><dd>{{ .c.GithubSecret }}</dd>
|
||||
<dt>Gitea client Key</dt><dd>{{ .c.GiteaClientKey }}</dd>
|
||||
<dt>Gitea Secret</dt><dd>{{ .c.GiteaSecret }}</dd>
|
||||
<dt>Gitea URL</dt><dd>{{ .c.GiteaUrl }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div>
|
||||
<ul role="list" class="divide-y divide-slate-300 dark:divide-gray-200 px-4 py-2 sm:px-6 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-700">
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<span class="text-sm font-medium leading-6 text-slate-700 dark:text-slate-300">Disable signup</span>
|
||||
<span class="text-sm text-gray-400 dark:text-gray-400">Forbid the creation of new accounts.</span>
|
||||
</span>
|
||||
<button type="button" id="disable-signup" data-bool="{{ .DisableSignup }}" class="toggle-button {{ if .DisableSignup }}bg-primary-600{{else}}bg-gray-300 dark:bg-gray-400{{end}} relative inline-flex h-6 w-11 ml-4 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description">
|
||||
<span aria-hidden="true" class="{{ if .DisableSignup }}translate-x-5{{else}}translate-x-0{{end}} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<span class="text-sm font-medium leading-6 text-slate-700 dark:text-slate-300">Require login</span>
|
||||
<span class="text-sm text-gray-400 dark:text-gray-400">Enforce users to be logged in to see gists.</span>
|
||||
</span>
|
||||
<button type="button" id="require-login" data-bool="{{ .RequireLogin }}" class="toggle-button {{ if .RequireLogin }}bg-primary-600{{else}}bg-gray-300 dark:bg-gray-400{{end}} relative inline-flex h-6 w-11 ml-4 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description">
|
||||
<span aria-hidden="true" class="{{ if .RequireLogin }}translate-x-5{{else}}translate-x-0{{end}} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<span class="text-sm font-medium leading-6 text-slate-700 dark:text-slate-300">Disable login form</span>
|
||||
<span class="text-sm text-gray-400 dark:text-gray-400">Forbid logging in via the login form to force using OAuth providers instead.</span>
|
||||
</span>
|
||||
<button type="button" id="disable-login-form" data-bool="{{ .DisableLoginForm }}" class="toggle-button {{ if .DisableLoginForm }}bg-primary-600{{else}}bg-gray-300 dark:bg-gray-400{{end}} relative inline-flex h-6 w-11 ml-4 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description">
|
||||
<span aria-hidden="true" class="{{ if .DisableLoginForm }}translate-x-5{{else}}translate-x-0{{end}} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<span class="text-sm font-medium leading-6 text-slate-700 dark:text-slate-300">Disable Gravatar</span>
|
||||
<span class="text-sm text-gray-400 dark:text-gray-400">Disable the usage of Gravatar as an avatar provider.</span>
|
||||
</span>
|
||||
<button type="button" id="disable-gravatar" data-bool="{{ .DisableGravatar }}" class="toggle-button {{ if .DisableGravatar }}bg-primary-600{{else}}bg-gray-300 dark:bg-gray-400{{end}} relative inline-flex h-6 w-11 ml-4 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description">
|
||||
<span aria-hidden="true" class="{{ if .DisableGravatar }}translate-x-5{{else}}translate-x-0{{end}} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{{ .csrfHtml }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="{{ asset "admin.ts" }}"></script>
|
||||
|
||||
{{ template "admin_footer" .}}
|
||||
{{ template "footer" .}}
|
||||
4
templates/pages/admin_gists.html
vendored
4
templates/pages/admin_gists.html
vendored
@@ -21,8 +21,8 @@
|
||||
{{ range $gist := .data }}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-slate-700 dark:text-slate-300 sm:pl-0">{{ $gist.ID }}</td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300"><a href="/{{ $gist.User.Username }}/{{ $gist.Uuid }}">{{ $gist.Title }}</a></td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300"><a href="/{{ $gist.User.Username }}">{{ $gist.User.Username }}</a></td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300"><a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}/{{ $gist.Uuid }}">{{ $gist.Title }}</a></td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300"><a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}">{{ $gist.User.Username }}</a></td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300">{{ $gist.Private }}</td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300">{{ $gist.NbFiles }}</td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300">{{ $gist.NbLikes }}</td>
|
||||
|
||||
58
templates/pages/admin_settings.html
vendored
58
templates/pages/admin_settings.html
vendored
@@ -1,58 +0,0 @@
|
||||
{{ template "header" .}}
|
||||
{{ template "admin_header" .}}
|
||||
|
||||
<div class="mx-auto max-w-lg px-4 py-2 sm:px-6 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-700">
|
||||
|
||||
<ul role="list" class="divide-y divide-slate-300 dark:divide-gray-200">
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<span class="text-sm font-medium leading-6 text-slate-700 dark:text-slate-300">Disable signup</span>
|
||||
<span class="text-sm text-gray-400 dark:text-gray-400">Forbid the creation of new accounts.</span>
|
||||
</span>
|
||||
<button type="button" id="disable-signup" data-bool="{{ .DisableSignup }}" class="toggle-button {{ if .DisableSignup }}bg-primary-600{{else}}bg-gray-300 dark:bg-gray-400{{end}} relative inline-flex h-6 w-11 ml-4 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description">
|
||||
<span aria-hidden="true" class="{{ if .DisableSignup }}translate-x-5{{else}}translate-x-0{{end}} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<span class="text-sm font-medium leading-6 text-slate-700 dark:text-slate-300">Require login</span>
|
||||
<span class="text-sm text-gray-400 dark:text-gray-400">Enforce users to be logged in to see gists.</span>
|
||||
</span>
|
||||
<button type="button" id="require-login" data-bool="{{ .RequireLogin }}" class="toggle-button {{ if .RequireLogin }}bg-primary-600{{else}}bg-gray-300 dark:bg-gray-400{{end}} relative inline-flex h-6 w-11 ml-4 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description">
|
||||
<span aria-hidden="true" class="{{ if .RequireLogin }}translate-x-5{{else}}translate-x-0{{end}} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<span class="text-sm font-medium leading-6 text-slate-700 dark:text-slate-300">Disable login form</span>
|
||||
<span class="text-sm text-gray-400 dark:text-gray-400">Forbid logging in via the login form to force using OAuth providers instead.</span>
|
||||
</span>
|
||||
<button type="button" id="disable-login-form" data-bool="{{ .DisableLoginForm }}" class="toggle-button {{ if .DisableLoginForm }}bg-primary-600{{else}}bg-gray-300 dark:bg-gray-400{{end}} relative inline-flex h-6 w-11 ml-4 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description">
|
||||
<span aria-hidden="true" class="{{ if .DisableLoginForm }}translate-x-5{{else}}translate-x-0{{end}} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<span class="text-sm font-medium leading-6 text-slate-700 dark:text-slate-300">Disable Gravatar</span>
|
||||
<span class="text-sm text-gray-400 dark:text-gray-400">Disable the usage of Gravatar as an avatar provider.</span>
|
||||
</span>
|
||||
<button type="button" id="disable-gravatar" data-bool="{{ .DisableGravatar }}" class="toggle-button {{ if .DisableGravatar }}bg-primary-600{{else}}bg-gray-300 dark:bg-gray-400{{end}} relative inline-flex h-6 w-11 ml-4 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description">
|
||||
<span aria-hidden="true" class="{{ if .DisableGravatar }}translate-x-5{{else}}translate-x-0{{end}} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{{ .csrfHtml }}
|
||||
</div>
|
||||
|
||||
<script type="module" src="{{ asset "admin.ts" }}"></script>
|
||||
|
||||
{{ template "admin_footer" .}}
|
||||
{{ template "footer" .}}
|
||||
2
templates/pages/admin_users.html
vendored
2
templates/pages/admin_users.html
vendored
@@ -17,7 +17,7 @@
|
||||
{{ range $user := .data }}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-slate-700 dark:text-slate-300 sm:pl-0">{{ $user.ID }}</td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300"><a href="/{{ $user.Username }}">{{ $user.Username }}</a></td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300"><a href="{{ $.c.ExternalUrl }}/{{ $user.Username }}">{{ $user.Username }}</a></td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-sm text-slate-700 dark:text-slate-300"><span class="moment-timestamp-date">{{ $user.CreatedAt }}</span></td>
|
||||
<td class="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
|
||||
<form action="/admin-panel/users/{{ $user.ID }}/delete" method="POST" onsubmit="return confirm('Do you want to delete this user ?')">
|
||||
|
||||
144
templates/pages/all.html
vendored
144
templates/pages/all.html
vendored
@@ -1,56 +1,108 @@
|
||||
{{ template "header" .}}
|
||||
<div class="py-10">
|
||||
<header class="pb-4 flex ">
|
||||
<div class="flex-auto">
|
||||
{{if .fromUser}}
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<img class="h-12 w-12 rounded-md mr-2 border border-gray-200 dark:border-gray-700" src="{{ avatarUrl .fromUser .DisableGravatar }}" alt="">
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold leading-tight">{{.fromUser.Username}}</h1>
|
||||
<p class="text-sm text-slate-500">Joined <span class="moment-timestamp">{{.fromUser.CreatedAt}}</span></p>
|
||||
<header class="pb-4 ">
|
||||
<div class="flex">
|
||||
<div class="flex-auto">
|
||||
{{if .fromUser}}
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<img class="h-12 w-12 rounded-md mr-2 border border-gray-200 dark:border-gray-700" src="{{ avatarUrl .fromUser .DisableGravatar }}" alt="">
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold leading-tight">{{.fromUser.Username}}</h1>
|
||||
<p class="text-sm text-slate-500">Joined <span class="moment-timestamp">{{.fromUser.CreatedAt}}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
{{ if eq .mode "all" }}
|
||||
<h1 class="text-2xl font-bold leading-tight">All gists</h1>
|
||||
{{ else if eq .mode "search" }}
|
||||
<h1 class="text-2xl font-bold leading-tight">Search results</h1>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<h1 class="text-2xl font-bold leading-tight">All gists</h1>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="float-right">
|
||||
<div class="relative inline-block text-left">
|
||||
<div>
|
||||
<button type="button" class="whitespace-nowrap inline-flex text-slate-700 dark:text-slate-300 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-2 focus:ring-primary-500 focus:border-primary-500 leading-3" id="sort-gists-button">
|
||||
<span class="text-gray-700 dark:text-gray-300">Sort : <span class="text-slate-700 dark:text-slate-300">{{.order}} {{.sort}}</span></span>
|
||||
<svg class="-mr-1 ml-2 h-3 w-3" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="sort-gists-dropdown" class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-300 dark:divide-gray-700 rounded-md rounded border border-gray-300 dark:border-gray-600 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="" role="none">
|
||||
<a href="/{{if .fromUser}}{{.fromUser.Username}}{{else}}all{{end}}?sort=created&order=desc" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-t-md" role="menuitem">
|
||||
Recently created
|
||||
</a>
|
||||
<div class="align-middle inline-flex items-center">
|
||||
<div class="relative text-left">
|
||||
<div>
|
||||
<button type="button" class="whitespace-nowrap inline-flex text-slate-700 dark:text-slate-300 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-2 focus:ring-primary-500 focus:border-primary-500 leading-3" id="sort-gists-button">
|
||||
<span class="text-gray-700 dark:text-gray-300">Sort : <span class="text-slate-700 dark:text-slate-300">{{.order}} {{.sort}}</span></span>
|
||||
<svg class="-mr-1 ml-2 h-3 w-3" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<a href="/{{if .fromUser}}{{.fromUser.Username}}{{else}}all{{end}}?sort=created&order=asc" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500" role="menuitem">
|
||||
Least recently created
|
||||
</a>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<a href="/{{if .fromUser}}{{.fromUser.Username}}{{else}}all{{end}}?sort=updated&order=desc" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500" role="menuitem">
|
||||
Recently updated
|
||||
</a>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<a href="/{{if .fromUser}}{{.fromUser.Username}}{{else}}all{{end}}?sort=updated&order=asc" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-b-md" role="menuitem">
|
||||
Least recently updated
|
||||
</a>
|
||||
<div id="sort-gists-dropdown" class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-200 dark:divide-gray-700 rounded-md rounded border border-gray-200 dark:border-gray-600 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="" role="none">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .urlPage }}?sort=created&order=desc{{.searchQueryUrl}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-t-md" role="menuitem">
|
||||
Recently created
|
||||
</a>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .urlPage }}?sort=created&order=asc{{.searchQueryUrl}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500" role="menuitem">
|
||||
Least recently created
|
||||
</a>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .urlPage }}?sort=updated&order=desc{{.searchQueryUrl}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500" role="menuitem">
|
||||
Recently updated
|
||||
</a>
|
||||
</div>
|
||||
<div class="" role="none">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .urlPage }}?sort=updated&order=asc{{.searchQueryUrl}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-b-md" role="menuitem">
|
||||
Least recently updated
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{ if and (ne .mode "all") (ne .mode "search") }}
|
||||
<div class="mt-4">
|
||||
<div class="sm:hidden">
|
||||
<label for="tabs" class="sr-only">Select a tab</label>
|
||||
<select id="gist-tabs" name="tabs" class="block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-primary-500 focus:outline-none focus:ring-primary-500 sm:text-sm dark:bg-gray-800 dark:border-gray-700">
|
||||
<option {{if eq .mode "fromUser"}}selected {{end}}data-url="/{{ .fromUser.Username }}">All gists ({{ .countFromUser }})</option>
|
||||
{{ if ne .countLiked 0 }}<option {{if eq .mode "liked"}}selected {{end}}data-url="/{{ .fromUser.Username }}/liked">Liked ({{ .countLiked }})</option>{{end}}
|
||||
{{ if ne .countForked 0 }}<option {{if eq .mode "forked"}}selected {{end}}data-url="/{{ .fromUser.Username }}/forked">Forked ({{ .countForked }})</option>{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="hidden sm:block">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 flex">
|
||||
<div class="flex-auto">
|
||||
<nav class="-mb-px flex space-x-6" aria-label="Tabs">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .fromUser.Username }}" class="{{if eq .mode "fromUser"}}border-primary-500 font-bold {{else}}border-transparent hover:border-gray-200 hover:text-gray-700{{end}} text-slate-700 dark:text-slate-300 inline-flex items-center whitespace-nowrap border-b-2 py-2 px-1 text-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-1">
|
||||
<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>
|
||||
All gists
|
||||
<span class="bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-slate-300 ml-2 hidden rounded-full py-0.5 px-2.5 text-xs font-medium md:inline-block">{{ .countFromUser }}</span>
|
||||
</a>
|
||||
{{ if ne .countLiked 0 }}
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .fromUser.Username }}/liked" class="{{if eq .mode "liked"}}border-primary-500 font-bold {{else}}border-transparent hover:border-gray-200 hover:text-gray-700{{end}} text-slate-700 dark:text-slate-300 inline-flex items-center whitespace-nowrap border-b-2 py-2 px-1 text-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6 mr-1">
|
||||
<path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
|
||||
</svg>
|
||||
Liked
|
||||
<span class="bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-slate-300 ml-2 hidden rounded-full py-0.5 px-2.5 text-xs font-medium md:inline-block">{{ .countLiked }}</span>
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ if ne .countForked 0 }}
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .fromUser.Username }}/forked" class="{{if eq .mode "forked"}}border-primary-500 font-bold {{else}}border-transparent hover:border-gray-200 hover:text-gray-700{{end}} text-slate-700 dark:text-slate-300 inline-flex items-center whitespace-nowrap border-b-2 py-2 px-1 text-sm" aria-current="page">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-1">
|
||||
<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>
|
||||
Forked
|
||||
<span class="bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-slate-300 ml-2 hidden rounded-full py-0.5 px-2.5 text-xs font-medium md:inline-block">{{ .countForked }}</span>
|
||||
</a>
|
||||
{{ end }}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</header>
|
||||
<main>
|
||||
<div>
|
||||
@@ -59,7 +111,7 @@
|
||||
<div class="mb-8">
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<h4 class="text-md leading-tight break-all py-1 flex-auto">
|
||||
<a href="/{{ $gist.User.Username }}">{{ $gist.User.Username }}</a> <span class="text-slate-700 dark:text-slate-300">/</span> <a class="font-bold" href="/{{ $gist.User.Username }}/{{ $gist.Uuid }}">{{ $gist.Title }}</a>
|
||||
<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.Uuid }}">{{ $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">
|
||||
@@ -84,10 +136,10 @@
|
||||
|
||||
</div>
|
||||
<h5 class="text-sm text-slate-500 pb-1">Last active <span class="moment-timestamp">{{ $gist.UpdatedAt }}</span>
|
||||
{{ if $gist.Forked }} • Forked from <a href="/{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Uuid }}">{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Title }}</a> {{ end }}
|
||||
{{ if $gist.Forked }} • Forked from <a href="{{ $.c.ExternalUrl }}/{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Uuid }}">{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Title }}</a> {{ end }}
|
||||
{{ if $gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> Unlisted </span>{{ end }}</h5>
|
||||
<h5 class="text-xs text-slate-700 dark:text-slate-300 py-1">{{ $gist.Description }}</h5>
|
||||
<a href="/{{ $gist.User.Username }}/{{ $gist.Uuid }}" class="text-slate-700 dark:text-slate-300">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}/{{ $gist.Uuid }}" class="text-slate-700 dark:text-slate-300">
|
||||
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 overflow-auto hover:border-primary-600">
|
||||
<div class="code overflow-auto">
|
||||
{{ if isMarkdown $gist.PreviewFilename }}
|
||||
|
||||
12
templates/pages/auth_form.html
vendored
12
templates/pages/auth_form.html
vendored
@@ -20,14 +20,14 @@
|
||||
<div>
|
||||
<label for="username" class="block text-sm font-medium text-slate-700 dark:text-slate-300"> Username </label>
|
||||
<div class="mt-1">
|
||||
<input id="username" name="username" type="text" required class="bg-gray-50 dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
<input id="username" name="username" type="text" required class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<label for="password" class="block text-sm font-medium text-slate-700 dark:text-slate-300"> Password </label>
|
||||
<div class="mt-1">
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" required class="bg-gray-50 dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" required class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
{{ if eq .title "Login" }}
|
||||
@@ -36,7 +36,7 @@
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">Login</button>
|
||||
</div>
|
||||
{{ if not .DisableSignup }}
|
||||
<span class="float-right text-sm py-2 underline"><a href="/register">Register instead →</a></span>
|
||||
<span class="float-right text-sm py-2 underline"><a href="{{ $.c.ExternalUrl }}/register">Register instead →</a></span>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
@@ -44,7 +44,7 @@
|
||||
<div class="flex-auto">
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">Register</button>
|
||||
</div>
|
||||
<span class="float-right text-sm py-2 underline"><a href="/login">Login instead →</a></span>
|
||||
<span class="float-right text-sm py-2 underline"><a href="{{ $.c.ExternalUrl }}/login">Login instead →</a></span>
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -62,12 +62,12 @@
|
||||
{{ end }}
|
||||
<div>
|
||||
{{ if .githubOauth }}
|
||||
<a href="/oauth/github" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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">
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/github" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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">
|
||||
Continue with GitHub account
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ if .giteaOauth }}
|
||||
<a href="/oauth/gitea" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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">
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitea" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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">
|
||||
Continue with Gitea account
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
2
templates/pages/edit.html
vendored
2
templates/pages/edit.html
vendored
@@ -93,7 +93,7 @@
|
||||
|
||||
<div class="flex">
|
||||
<button type="button" id="add-file" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-gray-700 dark:text-white bg-gray-100 dark:bg-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">Add file</button>
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}" type="submit" name="private" value="1" class="ml-auto inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm bg-gray-100 dark:bg-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 text-rose-600 dark:text-rose-400 hover:text-rose-700">Cancel</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ .gist.User.Username }}/{{ .gist.Uuid }}" type="submit" name="private" value="1" class="ml-auto inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm bg-gray-100 dark:bg-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 text-rose-600 dark:text-rose-400 hover:text-rose-700">Cancel</a>
|
||||
<button type="submit" name="private" value="0" class="ml-2 inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">Save</button>
|
||||
</div>
|
||||
{{ .csrfHtml }}
|
||||
|
||||
6
templates/pages/forks.html
vendored
6
templates/pages/forks.html
vendored
@@ -7,15 +7,15 @@
|
||||
<ul role="list" class="divide-y divide-gray-300 dark:divide-gray-700">
|
||||
{{ range $gist := .forks }}
|
||||
<li class="flex py-4">
|
||||
<a href="/{{ $gist.User.Username }}">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}">
|
||||
<img class="h-12 w-12 rounded-md mr-2 border border-gray-200 dark:border-gray-700" src="{{ avatarUrl $gist.User $.DisableGravatar }}" alt="">
|
||||
</a>
|
||||
<div>
|
||||
<a href="/{{ $gist.User.Username }}" class="text-sm font-medium text-slate-700 dark:text-slate-300">{{ $gist.User.Username }}</a>
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}" class="text-sm font-medium text-slate-700 dark:text-slate-300">{{ $gist.User.Username }}</a>
|
||||
<p class="text-sm text-slate-500">Forked <span class="moment-timestamp">{{ $gist.CreatedAt }}</span></p>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<a class="ml-auto text-slate-700 dark:text-slate-300 relative inline-flex items-center space-x-2 rounded-md border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-200 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" href="/{{ $gist.User.Username }}/{{ $gist.Uuid }}">
|
||||
<a class="ml-auto text-slate-700 dark:text-slate-300 relative inline-flex items-center space-x-2 rounded-md border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-200 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" href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}/{{ $gist.Uuid }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="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>
|
||||
|
||||
6
templates/pages/gist.html
vendored
6
templates/pages/gist.html
vendored
@@ -10,15 +10,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 flex 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" />
|
||||
</svg>
|
||||
<span class="flex-auto ml-2 text-sm text-slate-700 dark:text-slate-300 filename" id="file-{{ slug $file.Filename }}"><a href="#file-{{ slug $file.Filename }}" class="text-slate-700 dark:text-slate-300 hover:text-black dark:hover:text-white">{{ $file.Filename }}</a></span>
|
||||
<span class="flex-auto ml-2 text-sm text-slate-700 dark:text-slate-300 filename" id="file-{{ slug $file.Filename }}"><a href="{{ $.c.ExternalUrl }}#file-{{ slug $file.Filename }}" class="text-slate-700 dark:text-slate-300 hover:text-black dark:hover:text-white">{{ $file.Filename }}</a></span>
|
||||
|
||||
<button class="float-right mx-2 px-2.5 py-0.5 leading-4 rounded-md text-xs font-medium bg-gray-100 dark:bg-gray-600 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-slate-700 dark:hover:text-slate-300 select-none copy-gist-btn"> Copy </button>
|
||||
<a href="/{{ $.gist.User.Username }}/{{ $.gist.Uuid }}/raw/{{ $.commit }}/{{$file.Filename}}" class="text-slate-700 dark:text-slate-300 float-right mr-2 px-2.5 py-0.5 leading-4 rounded-md text-xs font-medium bg-gray-100 dark:bg-gray-600 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-slate-700 dark:hover:text-slate-300 select-none"> Raw </a>
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ $.gist.User.Username }}/{{ $.gist.Uuid }}/raw/{{ $.commit }}/{{$file.Filename}}" class="text-slate-700 dark:text-slate-300 float-right mr-2 px-2.5 py-0.5 leading-4 rounded-md text-xs font-medium bg-gray-100 dark:bg-gray-600 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-slate-700 dark:hover:text-slate-300 select-none"> Raw </a>
|
||||
<div class="hidden gist-content">{{ $file.Content }}</div>
|
||||
</div>
|
||||
{{ if $file.Truncated }}
|
||||
<div class="text-sm px-4 py-1.5 border-t-1 border-gray-200 dark:border-gray-700">
|
||||
This file has been truncated. <a href="/{{ $.gist.User.Username }}/{{ $.gist.Uuid }}/raw/{{ $.commit }}/{{$file.Filename}}">View the full file.</a>
|
||||
This file has been truncated. <a href="{{ $.c.ExternalUrl }}/{{ $.gist.User.Username }}/{{ $.gist.Uuid }}/raw/{{ $.commit }}/{{$file.Filename}}">View the full file.</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if and (not $csv) (isCsv $file.Filename) }}
|
||||
|
||||
2
templates/pages/likes.html
vendored
2
templates/pages/likes.html
vendored
@@ -7,7 +7,7 @@
|
||||
<div class="relative flex items-center space-x-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-6 py-5 shadow-sm focus-within:ring-1 focus-within:border-primary-500 focus-within:ring-primary-500 hover:border-gray-600 dark:hover:border-gray-400">
|
||||
<div class="min-w-0 flex">
|
||||
<img class="h-12 w-12 rounded-md mr-2 border border-gray-200 dark:border-gray-700" src="{{ avatarUrl $user $.DisableGravatar }}" alt="">
|
||||
<a href="/{{ $user.Username }}" class="focus:outline-none">
|
||||
<a href="{{ $.c.ExternalUrl }}/{{ $user.Username }}" class="focus:outline-none">
|
||||
<span class="absolute inset-0" aria-hidden="true"></span>
|
||||
<p class="text-sm font-medium text-slate-700 dark:text-slate-300 align-middle">{{ $user.Username }}</p>
|
||||
</a>
|
||||
|
||||
2
templates/pages/revisions.html
vendored
2
templates/pages/revisions.html
vendored
@@ -12,7 +12,7 @@
|
||||
</svg>
|
||||
{{ $user := (index $.emails $commit.AuthorEmail) }}
|
||||
<img class="h-5 w-5 rounded-full inline" src="{{if $user }}{{ avatarUrl $user $.DisableGravatar }}{{else}}{{defaultAvatar}}{{end}}" alt="" />
|
||||
<span class="font-bold">{{if $user}}<a href="/{{$user.Username}}" class="text-slate-300 hover:text-slate-300 hover:underline">{{ $commit.AuthorName }}</a>{{else}}{{ $commit.AuthorName }}{{end}}</span> revised this gist <span class="moment-timestamp font-bold">{{ $commit.Timestamp }}</span>. <a href="/{{ $.gist.User.Username }}/{{ $.gist.Uuid }}/rev/{{ $commit.Hash }}">Go to revision</a></h3>
|
||||
<span class="font-bold">{{if $user}}<a href="{{ $.c.ExternalUrl }}/{{$user.Username}}" class="text-slate-300 hover:text-slate-300 hover:underline">{{ $commit.AuthorName }}</a>{{else}}{{ $commit.AuthorName }}{{end}}</span> revised this gist <span class="moment-timestamp font-bold">{{ $commit.Timestamp }}</span>. <a href="{{ $.c.ExternalUrl }}/{{ $.gist.User.Username }}/{{ $.gist.Uuid }}/rev/{{ $commit.Hash }}">Go to revision</a></h3>
|
||||
{{ if ne $commit.Changed "" }}
|
||||
<p class="text-sm float-right py-2">
|
||||
<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 inline-flex">
|
||||
|
||||
10
templates/pages/settings.html
vendored
10
templates/pages/settings.html
vendored
@@ -37,11 +37,12 @@
|
||||
|
||||
{{ if .githubOauth }}
|
||||
{{ if .userLogged.GithubID }}
|
||||
<a href="/oauth/github" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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">
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/github" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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"
|
||||
onclick="return confirm('Are you sure you want to unlink your GitHub account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
Unlink GitHub account
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="/oauth/github" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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">
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/github" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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">
|
||||
Link GitHub account
|
||||
</a>
|
||||
{{ end }}
|
||||
@@ -49,11 +50,12 @@
|
||||
|
||||
{{ if .giteaOauth }}
|
||||
{{ if .userLogged.GiteaID }}
|
||||
<a href="/oauth/gitea" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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-200 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">
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitea" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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-200 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"
|
||||
onclick="return confirm('Are you sure you want to unlink your Gitea account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
Unlink Gitea account
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="/oauth/gitea" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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-200 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">
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/gitea" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} 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-200 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">
|
||||
Link Gitea account
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
Reference in New Issue
Block a user