mirror of
https://github.com/JustKato/FreePad.git
synced 2026-03-13 15:59:46 +02:00
Compare commits
35 Commits
main
...
feature/pa
| Author | SHA1 | Date | |
|---|---|---|---|
| 400fd23b3e | |||
| bf1d032e68 | |||
| faff1ab527 | |||
| d056a4d429 | |||
| b710d24a2d | |||
| c3c9aacac3 | |||
| d949b3decb | |||
| 662dad90b7 | |||
| 1585d3b158 | |||
| 1d50efe3c6 | |||
| 0f5a352fc6 | |||
| 6a8f4f81e5 | |||
| 781b4bcf80 | |||
| f748adf132 | |||
| 4bfad3ef40 | |||
| 11658b4b5e | |||
| 685c6ae15f | |||
| 97102b98b3 | |||
| 22657cc111 | |||
| 3dc09cae64 | |||
| 70b671c0be | |||
| 6177dcecb8 | |||
| aea10baffd | |||
| 42eb5add65 | |||
| 53066025f0 | |||
| 23dd69e060 | |||
| 0bc4942924 | |||
| 23fa17b840 | |||
| 5414dab201 | |||
| 8735837cb4 | |||
| 7552cd7cd3 | |||
|
|
74af4d1554 | ||
| 916bcae961 | |||
| 53b35745cd | |||
| 725bc4222b |
@@ -18,3 +18,11 @@ DEV_MODE=0
|
|||||||
|
|
||||||
# Maximum file storage age in minutes, set to -1 to disable
|
# Maximum file storage age in minutes, set to -1 to disable
|
||||||
CLEANUP_MAX_AGE=43200 # Default is a month
|
CLEANUP_MAX_AGE=43200 # Default is a month
|
||||||
|
|
||||||
|
# Maximum pad file lenght, this is in characters, a character is one byte.
|
||||||
|
# Default: 524288 ( 500kb )
|
||||||
|
MAXIMUM_PAD_SIZE=524288
|
||||||
|
|
||||||
|
# Your admin access token
|
||||||
|
# If the value is not defined the admin interface will not be available
|
||||||
|
# ADMIN_TOKEN=SUPER_SECRET_ADMIN_TOKEN
|
||||||
30
Changelog.md
Normal file
30
Changelog.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# 1.4.0 🖌
|
||||||
|
Syntax highlight has been implemented, as well as a couple security concerns being patched. Thank you to everyone that has helped me out with those
|
||||||
|
|
||||||
|
# 1.3.0 👀
|
||||||
|
Implemented a views system, now everyone can see how many times a pad has been accessed, an auto-save has also been added for those views to file in the `data` dir.
|
||||||
|
|
||||||
|
# 1.2.0 🍥
|
||||||
|
QR Code generation has been implemented, the refresh button has been removed for the sake of keepign things simple and symmetrical. This will generate a QR code on the javascript side.
|
||||||
|
|
||||||
|
I have created this feature mainly for people that are trying to quickly transfer information from their computer to their phone extremely quickly, simply just type your Pad, click on the `QR Code` button and then pull your phone's camera out and get instant access, no longer typing the URL or sending the URL through some messaging app and wastring time.
|
||||||
|
|
||||||
|
# 1.1.1 🗒
|
||||||
|
The freepad version has been added as a header to the response
|
||||||
|
|
||||||
|
# 1.1.0 🛑
|
||||||
|
Implemented a rate-limiting system, quite primitive and basic implementation on my part since it's looking at all requests not just the POST requests, this can be bad news if someone is using the service a ton and won't truly protect from floods as it's ip-based but should offer a level of security better than none.
|
||||||
|
|
||||||
|
# 1.0.0 🖥
|
||||||
|
Initial release of FreePad, this included all of the basic functionality such as:
|
||||||
|
- Homepage
|
||||||
|
- Generating Pads
|
||||||
|
- Real-Time Saving
|
||||||
|
- Download Functionality
|
||||||
|
- Archive Functionality
|
||||||
|
- Refresh Button
|
||||||
|
- Dark/Light Theme Toggles
|
||||||
|
- New look/logo
|
||||||
|
|
||||||
|
# 0.9 🥶
|
||||||
|
Old version of FreePad which depended on a database for storing data, this was later dropped as it was pointless since we are only storing temporary data, so we moved to "v2"
|
||||||
26
Dockerfile
26
Dockerfile
@@ -1,9 +1,27 @@
|
|||||||
FROM alpine
|
# Importing golang 1.18 to use as a builder for our source
|
||||||
|
FROM golang:1.18 as builder
|
||||||
|
|
||||||
LABEL version="1.0.0"
|
# Use the /src directory as a workdir
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
# Copy the distribution files
|
# Copy the src to /src
|
||||||
COPY ./dist /app
|
COPY . ./
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Build the executable
|
||||||
|
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o freepad .
|
||||||
|
|
||||||
|
# Import alpine linux as a base
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
LABEL version="1.4.0"
|
||||||
|
|
||||||
|
# Copy the files from the builder to the new image
|
||||||
|
COPY --from=builder /src/freepad /app/freepad
|
||||||
|
COPY --from=builder /src/templates /app/templates
|
||||||
|
COPY --from=builder /src/static /app/static
|
||||||
|
|
||||||
# Make /app the work directory
|
# Make /app the work directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
21
LICENSE
21
LICENSE
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Kato Twofold
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
64
README.md
64
README.md
@@ -5,8 +5,8 @@ Quickly create "pads" and share with others
|
|||||||
[](https://hub.docker.com/r/justkato/freepad)
|
[](https://hub.docker.com/r/justkato/freepad)
|
||||||
[](https://ko-fi.com/justkato)
|
[](https://ko-fi.com/justkato)
|
||||||

|

|
||||||
|
|
||||||
[](https://pad.justkato.me/)
|
[](https://pad.justkato.me/)
|
||||||

|
|
||||||
|
|
||||||
# **FreePad**
|
# **FreePad**
|
||||||
|
|
||||||
@@ -31,3 +31,65 @@ The `.env` file contains all of the available options and you should use it to c
|
|||||||
If you need any help with any setting you can always open an issue over on github and get help from me.
|
If you need any help with any setting you can always open an issue over on github and get help from me.
|
||||||
|
|
||||||
If you are barely getting started with hosting your own services, or even Sys admin stuff in general or writing code my suggestion is to just copy `.env` and leave it as is until you get it running with the defaults running fine, afterwards you can play with it a little and who knows, maybe even get to learn something!
|
If you are barely getting started with hosting your own services, or even Sys admin stuff in general or writing code my suggestion is to just copy `.env` and leave it as is until you get it running with the defaults running fine, afterwards you can play with it a little and who knows, maybe even get to learn something!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Docker `(Recommended)`
|
||||||
|
```bash
|
||||||
|
# Get into a directory to run this
|
||||||
|
mkdir ~/freepad && cd freepad
|
||||||
|
|
||||||
|
# Copy the latest .env and docker-compose.example.yaml files
|
||||||
|
wget -O docker-compose.yaml https://raw.githubusercontent.com/JustKato/FreePad/master/docker-compose.example.yaml
|
||||||
|
wget -O .env https://raw.githubusercontent.com/JustKato/FreePad/master/.env.example
|
||||||
|
|
||||||
|
# Edit your docker-compose.yaml and change the DOMAIN_BASE environment variable
|
||||||
|
vim docker-compose.yaml
|
||||||
|
|
||||||
|
# Edit your .env file and change the variables to your liking
|
||||||
|
vim .env
|
||||||
|
|
||||||
|
# Run the container
|
||||||
|
docker-compose up
|
||||||
|
# Run the container in the background
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
## Distribution
|
||||||
|
[Downloads here](https://github.com/JustKato/FreePad/releases)
|
||||||
|
```bash
|
||||||
|
# Get into a directory to run this
|
||||||
|
mkdir ~/freepad && cd freepad
|
||||||
|
|
||||||
|
# Get the latest distribution from https://github.com/JustKato/FreePad/releases
|
||||||
|
wget -O release.zip https://github.com/JustKato/FreePad/releases/download/main/...
|
||||||
|
|
||||||
|
# Unzip the release
|
||||||
|
unzip release.zip
|
||||||
|
|
||||||
|
# Edit the .env file
|
||||||
|
vim ./.env
|
||||||
|
|
||||||
|
# Run the program
|
||||||
|
./freepad
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
```bash
|
||||||
|
# Clone th erepo
|
||||||
|
git clone https://github.com/JustKato/FreePad.git ~/freepad
|
||||||
|
|
||||||
|
# Get into the directory
|
||||||
|
cd ~/freepad
|
||||||
|
|
||||||
|
# Build using the script
|
||||||
|
./build.sh
|
||||||
|
|
||||||
|
# Copy the environment variable
|
||||||
|
cp .env dist/
|
||||||
|
|
||||||
|
# Go into the program's directory
|
||||||
|
cd dist
|
||||||
|
|
||||||
|
# Run the program
|
||||||
|
./freepad
|
||||||
|
```
|
||||||
6
build.sh
6
build.sh
@@ -2,7 +2,7 @@
|
|||||||
echo "Building FreePad...\n";
|
echo "Building FreePad...\n";
|
||||||
|
|
||||||
echo "Removing old build file...";
|
echo "Removing old build file...";
|
||||||
rm dist/freepad 2> /dev/null || true
|
rm dist/freepad* 2> /dev/null || true
|
||||||
rm -r dist/static 2> /dev/null || true
|
rm -r dist/static 2> /dev/null || true
|
||||||
rm -r dist/templates 2> /dev/null || true
|
rm -r dist/templates 2> /dev/null || true
|
||||||
rm dist/.env 2> /dev/null || true
|
rm dist/.env 2> /dev/null || true
|
||||||
@@ -10,6 +10,10 @@ rm dist/.env 2> /dev/null || true
|
|||||||
# Build
|
# Build
|
||||||
echo "Building executable"
|
echo "Building executable"
|
||||||
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o ./dist/freepad .
|
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o ./dist/freepad .
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o ./dist/freepad-arm64 .
|
||||||
|
CGO_ENABLED=0 GOOS=windows go build -a -installsuffix cgo -o ./dist/freepad.exe .
|
||||||
|
CGO_ENABLED=0 GOOS=darwin go build -a -installsuffix cgo -o ./dist/freepad-darwin .
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -installsuffix cgo -o ./dist/freepad-darwin-64 .
|
||||||
|
|
||||||
echo "Copying templates"
|
echo "Copying templates"
|
||||||
cp -r ./templates ./dist/templates
|
cp -r ./templates ./dist/templates
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
freepad:
|
freepad:
|
||||||
# Uncomment the bellow to use the production docker image from the docker repository
|
# Uncomment the bellow to use the production docker image from the docker repository
|
||||||
# image:
|
image: justkato/freepad
|
||||||
# Comment the build line if you are just looking to use a docker-compose file
|
# Comment the build line if you are just looking to use a docker-compose file
|
||||||
build: .
|
# build: .
|
||||||
# I don't recommend changing the 8080 as there would be no reason to,
|
# I don't recommend changing the 8080 as there would be no reason to,
|
||||||
# simply change the 3113 port to anything you would like for the container to listen on
|
# simply change the 3113 port to anything you would like for the container to listen on
|
||||||
ports:
|
ports:
|
||||||
- 3113:8080
|
- 8080:8080
|
||||||
# This will read from your .env variables, in that file you will find the documentation as well
|
# This will read from your .env variables, in that file you will find the documentation as well
|
||||||
environment:
|
environment:
|
||||||
- DOMAIN_BASE
|
- DOMAIN_BASE
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -6,4 +6,5 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.7.7
|
github.com/gin-gonic/gin v1.7.7
|
||||||
github.com/joho/godotenv v1.4.0
|
github.com/joho/godotenv v1.4.0
|
||||||
github.com/mrz1836/go-sanitize v1.1.5
|
github.com/mrz1836/go-sanitize v1.1.5
|
||||||
|
github.com/ulule/limiter/v3 v3.10.0
|
||||||
)
|
)
|
||||||
|
|||||||
100
go.sum
100
go.sum
@@ -1,6 +1,11 @@
|
|||||||
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||||
@@ -13,13 +18,32 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
|
|||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
@@ -30,32 +54,100 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD
|
|||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mrz1836/go-sanitize v1.1.5 h1:LOywG3ijK/B/D9ik3hsniyIzA1JVZlM2wmp3Q/CBk88=
|
github.com/mrz1836/go-sanitize v1.1.5 h1:LOywG3ijK/B/D9ik3hsniyIzA1JVZlM2wmp3Q/CBk88=
|
||||||
github.com/mrz1836/go-sanitize v1.1.5/go.mod h1:HnnbbJTcBhbr770WyRL4SA95I4FFOnGg/RTLJybsuN8=
|
github.com/mrz1836/go-sanitize v1.1.5/go.mod h1:HnnbbJTcBhbr770WyRL4SA95I4FFOnGg/RTLJybsuN8=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/ulule/limiter/v3 v3.10.0 h1:C9mx3tgxYnt4pUYKWktZf7aEOVPbRYxR+onNFjQTEp0=
|
||||||
|
github.com/ulule/limiter/v3 v3.10.0/go.mod h1:NqPA/r8QfP7O11iC+95X6gcWJPtRWjKrtOUw07BTvoo=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||||
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||||
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
62
lib/controllers/controllers_admin.go
Normal file
62
lib/controllers/controllers_admin.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/JustKato/FreePad/lib/helper"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminMiddleware(router *gin.RouterGroup) {
|
||||||
|
|
||||||
|
// Handl
|
||||||
|
router.Use(func(ctx *gin.Context) {
|
||||||
|
|
||||||
|
// Check which route we are accessing
|
||||||
|
fmt.Println(`Accesing: `, ctx.Request.RequestURI)
|
||||||
|
|
||||||
|
// Check if the request is other than the login request
|
||||||
|
if ctx.Request.RequestURI != "/admin/login" {
|
||||||
|
// Check if the user is logged-in
|
||||||
|
|
||||||
|
fmt.Println(`Checking if admin`)
|
||||||
|
|
||||||
|
if !IsAdmin(ctx) {
|
||||||
|
// Not an admin, redirect to homepage
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, "/")
|
||||||
|
ctx.Abort()
|
||||||
|
|
||||||
|
fmt.Println(`Not an admin!`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAdmin(ctx *gin.Context) bool {
|
||||||
|
adminToken, err := ctx.Cookie("admin_token")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the real token
|
||||||
|
sha512Hasher := sha512.New()
|
||||||
|
sha512Hasher.Write([]byte(helper.GetAdminToken()))
|
||||||
|
hashHexToken := sha512Hasher.Sum(nil)
|
||||||
|
trueToken := hex.EncodeToString(hashHexToken)
|
||||||
|
|
||||||
|
// Check if the user's admin token matches the token
|
||||||
|
if adminToken != "" && adminToken == trueToken {
|
||||||
|
// Yep, it's the admin!
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Definitely not an admin
|
||||||
|
return false
|
||||||
|
}
|
||||||
15
lib/controllers/controllers_header.go
Normal file
15
lib/controllers/controllers_header.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
func ApplyHeaders(router *gin.Engine) {
|
||||||
|
|
||||||
|
router.Use(func(ctx *gin.Context) {
|
||||||
|
// Apply the header
|
||||||
|
ctx.Header("FreePad-Version", "1.4.0")
|
||||||
|
|
||||||
|
// Move on
|
||||||
|
ctx.Next()
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
34
lib/controllers/controllers_ratelimiter.go
Normal file
34
lib/controllers/controllers_ratelimiter.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/JustKato/FreePad/lib/helper"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/ulule/limiter/v3"
|
||||||
|
"github.com/ulule/limiter/v3/drivers/store/memory"
|
||||||
|
|
||||||
|
mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Apply the rate limiter to the gin Engine
|
||||||
|
func DoRateLimit(router *gin.Engine) {
|
||||||
|
|
||||||
|
// Initialize the rate limiter
|
||||||
|
rate := limiter.Rate{
|
||||||
|
Period: 5 * time.Minute,
|
||||||
|
Limit: int64(helper.GetApiBanLimit()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the memory storage
|
||||||
|
store := memory.NewStore()
|
||||||
|
|
||||||
|
// Initialize the limiter instance
|
||||||
|
instance := limiter.New(store, rate)
|
||||||
|
|
||||||
|
// Create the gin middleware
|
||||||
|
middleWare := mgin.NewMiddleware(instance)
|
||||||
|
|
||||||
|
// use the middleware in gin
|
||||||
|
router.Use(middleWare)
|
||||||
|
}
|
||||||
@@ -28,9 +28,23 @@ func TaskManager() {
|
|||||||
cleanupInterval = 1
|
cleanupInterval = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("[Task::Cleanup]: Task registered")
|
// Run all handlers
|
||||||
for range time.Tick(time.Minute * 5) {
|
go cleanupHandler(cleanupInterval)
|
||||||
objects.CleanupPosts(cleanupInterval)
|
go savePostHandler()
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func savePostHandler() {
|
||||||
|
// Save the views cache
|
||||||
|
fmt.Println("[Task::Save]: File save registered")
|
||||||
|
for range time.NewTicker(time.Minute * 1).C {
|
||||||
|
objects.SavePostViewsCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupHandler(cleanupInterval int) {
|
||||||
|
fmt.Println("[Task::Cleanup]: Cleanup task registered")
|
||||||
|
for range time.NewTicker(time.Minute * 5).C {
|
||||||
|
objects.CleanupPosts(cleanupInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,25 @@ func GetDomainBase() string {
|
|||||||
return domainBase
|
return domainBase
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetApiBanLimit() int {
|
||||||
|
banLimit, exists := os.LookupEnv("API_BAN_LIMIT")
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
os.Setenv("API_BAN_LIMIT", "300")
|
||||||
|
banLimit = "300"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try and convert the string into an integer
|
||||||
|
rez, err := strconv.Atoi(banLimit)
|
||||||
|
// Check if the conversion has failed
|
||||||
|
if err != nil {
|
||||||
|
// Simply return the default
|
||||||
|
return 300
|
||||||
|
}
|
||||||
|
|
||||||
|
return rez
|
||||||
|
}
|
||||||
|
|
||||||
func GetMaximumPadSize() int {
|
func GetMaximumPadSize() int {
|
||||||
// Lookup if the maximum pad size variable exists.
|
// Lookup if the maximum pad size variable exists.
|
||||||
maxPadSize, exists := os.LookupEnv("MAXIMUM_PAD_SIZE")
|
maxPadSize, exists := os.LookupEnv("MAXIMUM_PAD_SIZE")
|
||||||
@@ -53,3 +72,18 @@ func GetCacheMapLimit() int {
|
|||||||
|
|
||||||
return rez
|
return rez
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the admin token used to authenticate as an admin
|
||||||
|
func GetAdminToken() string {
|
||||||
|
// Get the admin login from the environment
|
||||||
|
adminToken, exists := os.LookupEnv("ADMIN_TOKEN")
|
||||||
|
|
||||||
|
// Check if the admin token was defined
|
||||||
|
if !exists {
|
||||||
|
// The admin token was not defined, disable admin logins
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the admin token
|
||||||
|
return adminToken
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,21 +1,166 @@
|
|||||||
package objects
|
package objects
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/JustKato/FreePad/lib/helper"
|
"github.com/JustKato/FreePad/lib/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Initialize the views cache
|
||||||
|
var ViewsCache map[string]uint32 = make(map[string]uint32)
|
||||||
|
|
||||||
|
// Mutex lock for the ViewsCache
|
||||||
|
var viewersLock sync.Mutex
|
||||||
|
|
||||||
type Post struct {
|
type Post struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
LastModified string `json:"last_modified"`
|
LastModified string `json:"last_modified"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
Views uint32 `json:"views"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Post) Delete() error {
|
||||||
|
filePath := path.Join(getStorageDirectory(), p.Name)
|
||||||
|
|
||||||
|
// Remove the file and return the result
|
||||||
|
return os.Remove(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the path to the views JSON
|
||||||
|
func getViewsFilePath() (string, error) {
|
||||||
|
// Get the path to the storage then append the const name for the storage file
|
||||||
|
filePath := path.Join(getStorageDirectory(), "views_storage.json")
|
||||||
|
|
||||||
|
// Check if the file exists
|
||||||
|
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
// Create the file
|
||||||
|
err := os.WriteFile(filePath, []byte(""), 0640)
|
||||||
|
if err != nil {
|
||||||
|
return ``, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the file path
|
||||||
|
return filePath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the views cache from file
|
||||||
|
func LoadViewsCache() error {
|
||||||
|
// Get the views file path
|
||||||
|
viewsFilePath, err := getViewsFilePath()
|
||||||
|
if err != nil {
|
||||||
|
// This is now a problem for the upstairs
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the contents of the file as a map
|
||||||
|
f, err := os.ReadFile(viewsFilePath)
|
||||||
|
if err != nil {
|
||||||
|
// This is now a problem for the upstairs
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the contents are valid
|
||||||
|
if len(f) <= 0 && len(string(f)) <= 0 {
|
||||||
|
// The file is completely empty!
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedData map[string]uint32
|
||||||
|
|
||||||
|
// Parse the data
|
||||||
|
err = json.Unmarshal(f, &parsedData)
|
||||||
|
if err != nil {
|
||||||
|
// This is now a problem for the function caller :D
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
storageDir := getStorageDirectory()
|
||||||
|
// Loop through all of the mapped files
|
||||||
|
for fileName := range parsedData {
|
||||||
|
// Grab the path to the file
|
||||||
|
// TODO: Create a generic function that checks if a post exists by name to use here adn in the GetPost method.
|
||||||
|
filePath := path.Join(storageDir, fileName)
|
||||||
|
// Check if the file exists
|
||||||
|
if _, err := os.Stat(filePath); !os.IsNotExist(err) {
|
||||||
|
// Looks like the file does not exist anymore, remove it from the map
|
||||||
|
delete(parsedData, fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the current cache
|
||||||
|
ViewsCache = parsedData
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddViewToPost(postName string, incrementViews bool) uint32 {
|
||||||
|
// Lock the viewers mapping
|
||||||
|
viewersLock.Lock()
|
||||||
|
|
||||||
|
// Check if the map has any value set to this elem
|
||||||
|
if _, ok := ViewsCache[postName]; !ok {
|
||||||
|
// Set the map value
|
||||||
|
ViewsCache[postName] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if incrementViews {
|
||||||
|
// Add to the counter
|
||||||
|
ViewsCache[postName]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock
|
||||||
|
viewersLock.Unlock()
|
||||||
|
|
||||||
|
// Return the value
|
||||||
|
return ViewsCache[postName]
|
||||||
|
}
|
||||||
|
|
||||||
|
func SavePostViewsCache() error {
|
||||||
|
|
||||||
|
data, err := json.Marshal(ViewsCache)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
viewsFilePath, err := getViewsFilePath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(viewsFilePath, os.O_WRONLY|os.O_CREATE, 0640)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Actually close the file
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Delete all past content
|
||||||
|
err = f.Truncate(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset pointer
|
||||||
|
_, err = f.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the json into storage
|
||||||
|
_, err = f.Write(data)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the path to the storage directory
|
||||||
func getStorageDirectory() string {
|
func getStorageDirectory() string {
|
||||||
|
|
||||||
baseStoragePath, exists := os.LookupEnv("PAD_STORAGE_PATH")
|
baseStoragePath, exists := os.LookupEnv("PAD_STORAGE_PATH")
|
||||||
@@ -26,7 +171,7 @@ func getStorageDirectory() string {
|
|||||||
// Check if the base storage path exists
|
// Check if the base storage path exists
|
||||||
if _, err := os.Stat(baseStoragePath); os.IsNotExist(err) {
|
if _, err := os.Stat(baseStoragePath); os.IsNotExist(err) {
|
||||||
// Looks like the base storage path was NOT set, create the dir
|
// Looks like the base storage path was NOT set, create the dir
|
||||||
err = os.Mkdir(baseStoragePath, 0777)
|
err = os.Mkdir(baseStoragePath, 0640)
|
||||||
// Check for errors
|
// Check for errors
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// No way this sends an error unless it goes horribly wrong.
|
// No way this sends an error unless it goes horribly wrong.
|
||||||
@@ -38,16 +183,21 @@ func getStorageDirectory() string {
|
|||||||
return baseStoragePath
|
return baseStoragePath
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPost(fileName string) Post {
|
// Get a post from the file system
|
||||||
|
func GetPost(fileName string, incrementViews bool) Post {
|
||||||
// Get the base storage directory and make sure it exists
|
// Get the base storage directory and make sure it exists
|
||||||
storageDir := getStorageDirectory()
|
storageDir := getStorageDirectory()
|
||||||
|
|
||||||
// Generate the file path
|
// Generate the file path
|
||||||
filePath := fmt.Sprintf("%s%s", storageDir, fileName)
|
filePath := fmt.Sprintf("%s%s", storageDir, fileName)
|
||||||
|
|
||||||
|
// Get the post views and add 1 to them
|
||||||
|
postViews := AddViewToPost(fileName, incrementViews)
|
||||||
|
|
||||||
p := Post{
|
p := Post{
|
||||||
Name: fileName,
|
Name: fileName,
|
||||||
Content: "",
|
Content: "",
|
||||||
|
Views: postViews,
|
||||||
LastModified: "Never Before",
|
LastModified: "Never Before",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +224,7 @@ func GetPost(fileName string) Post {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write a post to the file system
|
||||||
func WritePost(p Post) error {
|
func WritePost(p Post) error {
|
||||||
|
|
||||||
maximumPadSize := helper.GetMaximumPadSize()
|
maximumPadSize := helper.GetMaximumPadSize()
|
||||||
@@ -98,13 +249,10 @@ func WritePost(p Post) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := f.Close(); err != nil {
|
return f.Close()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup all of the older posts based on the environment settings
|
||||||
func CleanupPosts(age int) {
|
func CleanupPosts(age int) {
|
||||||
// Initialize the files buffer
|
// Initialize the files buffer
|
||||||
var files []string
|
var files []string
|
||||||
@@ -155,5 +303,31 @@ func CleanupPosts(age int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllPosts() []Post {
|
||||||
|
// Initialize the list of posts
|
||||||
|
postList := []Post{}
|
||||||
|
|
||||||
|
// Get the posts storage directory
|
||||||
|
storageDir := getStorageDirectory()
|
||||||
|
|
||||||
|
// Read the directory listing
|
||||||
|
files, err := os.ReadDir(storageDir)
|
||||||
|
// Check if thereh as been an issues with reading the directory contents
|
||||||
|
if err != nil {
|
||||||
|
// Log the error
|
||||||
|
fmt.Println("Error::GetAllPosts:", err)
|
||||||
|
// Return an empty list to have a clean fallback
|
||||||
|
return []Post{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all of the files
|
||||||
|
for _, v := range files {
|
||||||
|
// Process the file into a pad
|
||||||
|
postList = append(postList, GetPost(v.Name(), false))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the post list
|
||||||
|
return postList
|
||||||
}
|
}
|
||||||
|
|||||||
95
lib/routes/routes_admin.go
Normal file
95
lib/routes/routes_admin.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/JustKato/FreePad/lib/controllers"
|
||||||
|
"github.com/JustKato/FreePad/lib/helper"
|
||||||
|
"github.com/JustKato/FreePad/lib/objects"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"crypto/sha512"
|
||||||
|
)
|
||||||
|
|
||||||
|
var adminLoginToken string = ""
|
||||||
|
|
||||||
|
func AdminRoutes(router *gin.RouterGroup) {
|
||||||
|
|
||||||
|
adminLoginToken = helper.GetAdminToken()
|
||||||
|
|
||||||
|
// Apply the admin middleware for identification
|
||||||
|
controllers.AdminMiddleware(router)
|
||||||
|
|
||||||
|
// Admin login route
|
||||||
|
router.GET("/login", func(ctx *gin.Context) {
|
||||||
|
ctx.HTML(200, "admin_login.html", gin.H{
|
||||||
|
"title": "Login Login",
|
||||||
|
"domain_base": helper.GetDomainBase(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/login", func(ctx *gin.Context) {
|
||||||
|
|
||||||
|
// Get the value of the admin token
|
||||||
|
adminToken := ctx.PostForm("admin-token")
|
||||||
|
|
||||||
|
// Check if the input admin token matches our admin token
|
||||||
|
if adminLoginToken != "" && adminLoginToken == adminToken {
|
||||||
|
|
||||||
|
sha512Hasher := sha512.New()
|
||||||
|
sha512Hasher.Write([]byte(adminToken))
|
||||||
|
|
||||||
|
// Set the cookie to be an admin
|
||||||
|
hashHexToken := sha512Hasher.Sum(nil)
|
||||||
|
hashToken := hex.EncodeToString(hashHexToken)
|
||||||
|
|
||||||
|
// Set the cookie
|
||||||
|
ctx.SetCookie("admin_token", hashToken, 60*60, "/", helper.GetDomainBase(), true, true)
|
||||||
|
|
||||||
|
ctx.Request.Method = "GET"
|
||||||
|
|
||||||
|
// Redirect the user to the admin page
|
||||||
|
ctx.Redirect(http.StatusFound, "/admin/view")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
ctx.Request.Method = "GET"
|
||||||
|
|
||||||
|
// Redirect the user to the admin page
|
||||||
|
ctx.Redirect(http.StatusFound, "/admin/login?fail")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET("/delete/:padname", func(ctx *gin.Context) {
|
||||||
|
// Get the pad name that we bout' to delete
|
||||||
|
padName := ctx.Param("padname")
|
||||||
|
|
||||||
|
// Try and get the pad, check if valid
|
||||||
|
pad := objects.GetPost(padName, false)
|
||||||
|
|
||||||
|
// Delete the pad
|
||||||
|
err := pad.Delete()
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
// Redirect the user to the admin page
|
||||||
|
ctx.Redirect(http.StatusFound, "/admin/view")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Admin view route
|
||||||
|
router.GET("/view", func(ctx *gin.Context) {
|
||||||
|
|
||||||
|
// Get all of the pads as a listing
|
||||||
|
padList := objects.GetAllPosts()
|
||||||
|
|
||||||
|
ctx.HTML(200, "admin_view.html", gin.H{
|
||||||
|
"title": "Admin",
|
||||||
|
"padList": padList,
|
||||||
|
"domain_base": helper.GetDomainBase(),
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -23,6 +24,13 @@ func HomeRoutes(router *gin.Engine) {
|
|||||||
// Get the post we are looking for.
|
// Get the post we are looking for.
|
||||||
postName := c.Param("post")
|
postName := c.Param("post")
|
||||||
|
|
||||||
|
if postName == `views_storage.json` {
|
||||||
|
// Redirect the user to the homepage as this is a reserved keyword
|
||||||
|
c.Redirect(http.StatusPermanentRedirect, "/")
|
||||||
|
// Do not proceed further
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get the maximum pad size, so that we may notify the client-side to match server-side
|
// Get the maximum pad size, so that we may notify the client-side to match server-side
|
||||||
maximumPadSize := helper.GetMaximumPadSize()
|
maximumPadSize := helper.GetMaximumPadSize()
|
||||||
|
|
||||||
@@ -31,15 +39,16 @@ func HomeRoutes(router *gin.Engine) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
postName = newPostName
|
postName = newPostName
|
||||||
}
|
}
|
||||||
postName = sanitize.AlphaNumeric(postName, true)
|
postName = sanitize.XSS(sanitize.SingleLine(postName))
|
||||||
|
|
||||||
post := objects.GetPost(postName)
|
post := objects.GetPost(postName, true)
|
||||||
|
|
||||||
c.HTML(200, "page.html", gin.H{
|
c.HTML(200, "page.html", gin.H{
|
||||||
"title": postName,
|
"title": postName,
|
||||||
"post_content": post.Content,
|
"post_content": post.Content,
|
||||||
"maximumPadSize": maximumPadSize,
|
"maximumPadSize": maximumPadSize,
|
||||||
"last_modified": post.LastModified,
|
"last_modified": post.LastModified,
|
||||||
|
"views": post.Views,
|
||||||
"domain_base": helper.GetDomainBase(),
|
"domain_base": helper.GetDomainBase(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -54,11 +63,12 @@ func HomeRoutes(router *gin.Engine) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
postName = newPostName
|
postName = newPostName
|
||||||
}
|
}
|
||||||
postName = sanitize.AlphaNumeric(postName, true)
|
postName = sanitize.XSS(sanitize.SingleLine(postName))
|
||||||
|
|
||||||
p := objects.Post{
|
p := objects.Post{
|
||||||
Name: postName,
|
Name: postName,
|
||||||
Content: postContent,
|
Content: postContent,
|
||||||
|
Views: 0, // This can just be ignored
|
||||||
LastModified: time.Now().Format("02/01/2006 03:04:05 PM"),
|
LastModified: time.Now().Format("02/01/2006 03:04:05 PM"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
main.go
18
main.go
@@ -1,9 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/JustKato/FreePad/lib/controllers"
|
"github.com/JustKato/FreePad/lib/controllers"
|
||||||
|
"github.com/JustKato/FreePad/lib/objects"
|
||||||
"github.com/JustKato/FreePad/lib/routes"
|
"github.com/JustKato/FreePad/lib/routes"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
@@ -22,15 +24,31 @@ func main() {
|
|||||||
// Run the TaskManager
|
// Run the TaskManager
|
||||||
go controllers.TaskManager()
|
go controllers.TaskManager()
|
||||||
|
|
||||||
|
// Load in the views data from storage
|
||||||
|
err := objects.LoadViewsCache()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to load views from cache")
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the router
|
// Initialize the router
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
|
// Apply the FreePad Headers
|
||||||
|
controllers.ApplyHeaders(router)
|
||||||
|
|
||||||
// Read HTML Templates
|
// Read HTML Templates
|
||||||
router.LoadHTMLGlob("templates/**/*.html")
|
router.LoadHTMLGlob("templates/**/*.html")
|
||||||
|
|
||||||
// Load in static path
|
// Load in static path
|
||||||
router.Static("/static", "static/")
|
router.Static("/static", "static/")
|
||||||
|
|
||||||
|
// Implement the rate limiter
|
||||||
|
controllers.DoRateLimit(router)
|
||||||
|
|
||||||
|
// Admin Routing
|
||||||
|
routes.AdminRoutes(router.Group("/admin"))
|
||||||
|
|
||||||
// Add Routes
|
// Add Routes
|
||||||
routes.HomeRoutes(router)
|
routes.HomeRoutes(router)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300&display=swap');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--color-border-default: #444c56;
|
--color-border-default: #444c56;
|
||||||
--color-fg-default: #adbac7;
|
--color-fg-default: #adbac7;
|
||||||
@@ -35,6 +37,48 @@ main#main-card {
|
|||||||
right: .5rem;
|
right: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pad-content, #textarea-preview {
|
||||||
|
tab-size: 2;
|
||||||
|
|
||||||
|
font-family: 'Roboto Mono', monospace !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pad-content-area {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-content-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-only-content .edit-content-text {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-only-content .view-content-text {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pad-content-toggler {
|
||||||
|
position: absolute;
|
||||||
|
top: .5rem;
|
||||||
|
right: .5rem;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#textarea-preview {
|
||||||
|
max-height: calc(17rem + 30vh);
|
||||||
|
min-height: 17rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
textarea:focus,
|
textarea:focus,
|
||||||
input[type="text"]:focus,
|
input[type="text"]:focus,
|
||||||
@@ -56,3 +100,27 @@ input[type="color"]:focus,
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== Scrollbar CSS ===== */
|
||||||
|
/* Firefox */
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #3b3b3b #ffffff00;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome, Edge, and Safari */
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: #ffffff00;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #3b3b3b;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #ffffff00;
|
||||||
|
}
|
||||||
174
static/css/qr-style.css
Normal file
174
static/css/qr-style.css
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
/**************************\
|
||||||
|
Basic Modal Styles
|
||||||
|
\**************************/
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__container {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 30px;
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 100vh;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__title {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.25;
|
||||||
|
color: #555273;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__close {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__header .modal__close:before {
|
||||||
|
content: "\2715";
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__content {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: rgba(0, 0, 0, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__btn {
|
||||||
|
font-size: .875rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
padding-top: .5rem;
|
||||||
|
padding-bottom: .5rem;
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
color: rgba(0, 0, 0, .8);
|
||||||
|
border-radius: .25rem;
|
||||||
|
border-style: none;
|
||||||
|
border-width: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
text-transform: none;
|
||||||
|
overflow: visible;
|
||||||
|
line-height: 1.15;
|
||||||
|
margin: 0;
|
||||||
|
will-change: transform;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
transform: translateZ(0);
|
||||||
|
transition: -webkit-transform .25s ease-out;
|
||||||
|
transition: transform .25s ease-out;
|
||||||
|
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__btn:focus,
|
||||||
|
.modal__btn:hover {
|
||||||
|
-webkit-transform: scale(1.05);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__btn-primary {
|
||||||
|
background-color: #555273;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**************************\
|
||||||
|
Demo Animation Style
|
||||||
|
\**************************/
|
||||||
|
@keyframes mmfadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mmfadeOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mmslideIn {
|
||||||
|
from {
|
||||||
|
transform: translateY(15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mmslideOut {
|
||||||
|
from {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(-10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal-slide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal-slide.is-open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal-slide[aria-hidden="false"] .modal__overlay {
|
||||||
|
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal-slide[aria-hidden="false"] .modal__container {
|
||||||
|
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal-slide[aria-hidden="true"] .modal__overlay {
|
||||||
|
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal-slide[aria-hidden="true"] .modal__container {
|
||||||
|
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal-slide .modal__container,
|
||||||
|
.micromodal-slide .modal__overlay {
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
BIN
static/img/banner_how_to_run.png
Normal file
BIN
static/img/banner_how_to_run.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -3,12 +3,12 @@ function sendMyData(el) {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
// Check if the writing watch was sending something already
|
// Check if the writing watch was sending something already
|
||||||
if ( !!window.writingWatch ) {
|
if (!!window.writingWatch) {
|
||||||
// Clear old timeout
|
// Clear old timeout
|
||||||
clearTimeout(window.writingWatch);
|
clearTimeout(window.writingWatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( el.value.length > maximumPadSize ) {
|
if (el.value.length > maximumPadSize) {
|
||||||
let err = new Error(`Your Pad is too big! Please keep it limited to ${maximumPadSize} characters!`);
|
let err = new Error(`Your Pad is too big! Please keep it limited to ${maximumPadSize} characters!`);
|
||||||
alert(err);
|
alert(err);
|
||||||
throw err;
|
throw err;
|
||||||
@@ -16,6 +16,13 @@ function sendMyData(el) {
|
|||||||
|
|
||||||
el.setAttribute(`readonly`, `1`);
|
el.setAttribute(`readonly`, `1`);
|
||||||
|
|
||||||
|
const textareaPreview = document.getElementById(`textarea-preview`)
|
||||||
|
if (!!textareaPreview) {
|
||||||
|
textareaPreview.textContent = el.value;
|
||||||
|
|
||||||
|
hljs.highlightElement(document.getElementById(`textarea-preview`));
|
||||||
|
}
|
||||||
|
|
||||||
formData.set("content", el.value);
|
formData.set("content", el.value);
|
||||||
|
|
||||||
updateStatus(`Attempting to save...`, `text-warning`);
|
updateStatus(`Attempting to save...`, `text-warning`);
|
||||||
@@ -24,22 +31,22 @@ function sendMyData(el) {
|
|||||||
body: formData,
|
body: formData,
|
||||||
method: "post",
|
method: "post",
|
||||||
})
|
})
|
||||||
.then( resp => {
|
.then(resp => {
|
||||||
resp.json()
|
resp.json()
|
||||||
.then( e => {
|
.then(e => {
|
||||||
document.getElementById(`last_modified_`).value = e.pad.last_modified;
|
document.getElementById(`last_modified_`).value = e.pad.last_modified;
|
||||||
updateStatus(`Succesfully Saved`, `text-success`);
|
updateStatus(`Succesfully Saved`, `text-success`);
|
||||||
})
|
})
|
||||||
.catch( err => {
|
.catch(err => {
|
||||||
updateStatus(`Failed to Save`, `text-danger`);
|
updateStatus(`Failed to Save`, `text-danger`);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch( err => {
|
.catch(err => {
|
||||||
updateStatus(`Failed to Save`, `text-danger`);
|
updateStatus(`Failed to Save`, `text-danger`);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
})
|
})
|
||||||
.finally( () => {
|
.finally(() => {
|
||||||
el.removeAttribute(`readonly`);
|
el.removeAttribute(`readonly`);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -47,13 +54,13 @@ function sendMyData(el) {
|
|||||||
function toggleWritingWatch(el) {
|
function toggleWritingWatch(el) {
|
||||||
|
|
||||||
// Check if the writing watch was sending something already
|
// Check if the writing watch was sending something already
|
||||||
if ( !!window.writingWatch ) {
|
if (!!window.writingWatch) {
|
||||||
// Clear old timeout
|
// Clear old timeout
|
||||||
clearTimeout(window.writingWatch);
|
clearTimeout(window.writingWatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a timeout for the action
|
// Set a timeout for the action
|
||||||
window.writingWatch = setTimeout( () => {
|
window.writingWatch = setTimeout(() => {
|
||||||
// Send out the data
|
// Send out the data
|
||||||
sendMyData(el)
|
sendMyData(el)
|
||||||
}, 750)
|
}, 750)
|
||||||
@@ -74,7 +81,7 @@ function getLocalArchives() {
|
|||||||
let a = localStorage.getItem(`${padTitle}_archives`);
|
let a = localStorage.getItem(`${padTitle}_archives`);
|
||||||
|
|
||||||
// Check if we had anything in storage for the archives
|
// Check if we had anything in storage for the archives
|
||||||
if ( a == null ) {
|
if (a == null) {
|
||||||
// There were nothing in storage
|
// There were nothing in storage
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -82,7 +89,7 @@ function getLocalArchives() {
|
|||||||
try {
|
try {
|
||||||
// Try and parse the json
|
// Try and parse the json
|
||||||
a = JSON.parse(a);
|
a = JSON.parse(a);
|
||||||
} catch ( err ) {
|
} catch (err) {
|
||||||
// Return null of the fail
|
// Return null of the fail
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -93,7 +100,7 @@ function getLocalArchives() {
|
|||||||
function storeArchives(archives) {
|
function storeArchives(archives) {
|
||||||
|
|
||||||
// Check if the provided list is an array
|
// Check if the provided list is an array
|
||||||
if ( !Array.isArray(archives) ) return;
|
if (!Array.isArray(archives)) return;
|
||||||
|
|
||||||
// Set the current archives
|
// Set the current archives
|
||||||
localStorage.setItem(`${padTitle}_archives`, JSON.stringify(archives));
|
localStorage.setItem(`${padTitle}_archives`, JSON.stringify(archives));
|
||||||
@@ -105,13 +112,13 @@ function renderArchivesSelection() {
|
|||||||
const archivesSelection = document.getElementById(`archives-selection`);
|
const archivesSelection = document.getElementById(`archives-selection`);
|
||||||
const rowTemplate = document.getElementById(`archive-selection-example`);
|
const rowTemplate = document.getElementById(`archive-selection-example`);
|
||||||
// Clear any old optiosn
|
// Clear any old optiosn
|
||||||
archivesSelection.querySelectorAll(`.dropdown-item:not(#do-archive-button):not(#archive-selection-example)`).forEach( el => {
|
archivesSelection.querySelectorAll(`.dropdown-item:not(#do-archive-button):not(#archive-selection-example)`).forEach(el => {
|
||||||
// Remove the element
|
// Remove the element
|
||||||
el.remove();
|
el.remove();
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get the current list of available archives
|
// Get the current list of available archives
|
||||||
for ( let a of getLocalArchives() ) {
|
for (let a of getLocalArchives()) {
|
||||||
// Clone the template row
|
// Clone the template row
|
||||||
const row = rowTemplate.cloneNode(true);
|
const row = rowTemplate.cloneNode(true);
|
||||||
|
|
||||||
@@ -130,7 +137,7 @@ function renderArchivesSelection() {
|
|||||||
|
|
||||||
let resp = confirm("Load contents of pad from memory? This will overwrite the current pad for everyone.");
|
let resp = confirm("Load contents of pad from memory? This will overwrite the current pad for everyone.");
|
||||||
|
|
||||||
if ( !!resp ) {
|
if (!!resp) {
|
||||||
document.getElementById(`pad-content`).value = a.content;
|
document.getElementById(`pad-content`).value = a.content;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -142,7 +149,7 @@ function renderArchivesSelection() {
|
|||||||
function saveLocalArchive() {
|
function saveLocalArchive() {
|
||||||
let resp = confirm("Save a local copy of the current Pad?");
|
let resp = confirm("Save a local copy of the current Pad?");
|
||||||
|
|
||||||
if ( !resp ) {
|
if (!resp) {
|
||||||
// Do not
|
// Do not
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -166,11 +173,53 @@ function saveLocalArchive() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener(`DOMContentLoaded`, e => {
|
function generateQRCode() {
|
||||||
|
var qrcodeContainer = document.getElementById(`qrcode`)
|
||||||
|
// Remove old contents
|
||||||
|
qrcodeContainer.innerHTML = "";
|
||||||
|
// Add new qr
|
||||||
|
new QRCode(qrcodeContainer, {
|
||||||
|
text: window.location.toString(),
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
colorDark: "#555273",
|
||||||
|
colorLight: "#ffffff",
|
||||||
|
correctLevel: QRCode.CorrectLevel.H
|
||||||
|
});
|
||||||
|
|
||||||
{ // Textarea Focusing
|
// Open the modal
|
||||||
|
MicroModal.show(`qrmodal`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTextareaPreview() {
|
||||||
|
setTextareaPreview(!document.getElementById(`pad-content-toggler`).classList.contains(`read-only`))
|
||||||
|
}
|
||||||
|
|
||||||
|
// t == true - Read Only
|
||||||
|
// t == false - Edit mode
|
||||||
|
function setTextareaPreview(t = true) {
|
||||||
|
const prev = document.getElementById(`textarea-preview`)
|
||||||
const textarea = document.getElementById(`pad-content`);
|
const textarea = document.getElementById(`pad-content`);
|
||||||
|
const toggler = document.getElementById(`pad-content-toggler`);
|
||||||
|
const padContentArea = document.getElementById(`pad-content-area`);
|
||||||
|
|
||||||
|
if (t) {
|
||||||
|
// Toggle read only
|
||||||
|
prev.classList.remove(`hidden`)
|
||||||
|
toggler.classList.add(`read-only`);
|
||||||
|
|
||||||
|
padContentArea.classList.add(`read-only-content`);
|
||||||
|
|
||||||
|
textarea.classList.add(`hidden`);
|
||||||
|
} else {
|
||||||
|
// Toggle edit mode
|
||||||
|
prev.classList.add(`hidden`)
|
||||||
|
toggler.classList.remove(`read-only`);
|
||||||
|
|
||||||
|
padContentArea.classList.remove(`read-only-content`);
|
||||||
|
|
||||||
|
|
||||||
|
textarea.classList.remove(`hidden`);
|
||||||
// Focus
|
// Focus
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
// Scroll
|
// Scroll
|
||||||
@@ -179,6 +228,38 @@ document.addEventListener(`DOMContentLoaded`, e => {
|
|||||||
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener(`DOMContentLoaded`, e => {
|
||||||
|
|
||||||
|
{ // Textarea Handling
|
||||||
|
const textarea = document.getElementById(`pad-content`);
|
||||||
|
setTextareaPreview(!!textarea.value);
|
||||||
|
|
||||||
|
// Make sure tabs are taken into consideration
|
||||||
|
textarea.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key == 'Tab') {
|
||||||
|
e.preventDefault();
|
||||||
|
const start = this.selectionStart;
|
||||||
|
const end = this.selectionEnd;
|
||||||
|
|
||||||
|
// set textarea value to: text before caret + tab + text after caret
|
||||||
|
this.value = this.value.substring(0, start) +
|
||||||
|
"\t" + this.value.substring(end);
|
||||||
|
|
||||||
|
// put caret at right position again
|
||||||
|
this.selectionStart =
|
||||||
|
this.selectionEnd = start + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try { // highlights
|
||||||
|
hljs.highlightElement(document.getElementById(`textarea-preview`));
|
||||||
|
} catch ( err ) {
|
||||||
|
console.err(err);
|
||||||
|
}
|
||||||
|
|
||||||
{ // Archives
|
{ // Archives
|
||||||
renderArchivesSelection()
|
renderArchivesSelection()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,14 @@ class Pad {
|
|||||||
// Create a new blob of the contents of the pad
|
// Create a new blob of the contents of the pad
|
||||||
var blob = new Blob([ document.getElementById(`pad-content`).value ], { type: "text/plain;charset=utf-8" });
|
var blob = new Blob([ document.getElementById(`pad-content`).value ], { type: "text/plain;charset=utf-8" });
|
||||||
|
|
||||||
|
let downloadFileName = this.title;
|
||||||
|
if ( !this.title.includes(`.`) ) {
|
||||||
|
// Append a default file format
|
||||||
|
downloadFileName += `.txt`;
|
||||||
|
}
|
||||||
|
|
||||||
// Save the blob as
|
// Save the blob as
|
||||||
saveAs(blob, `${this.title}.txt`);
|
saveAs(blob, `${downloadFileName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
12
static/vendor/bootstrap/bootstrap-nightshade.min.css
vendored
Normal file
12
static/vendor/bootstrap/bootstrap-nightshade.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
static/vendor/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
7
static/vendor/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
static/vendor/bootstrap/darkmode.min.js
vendored
Normal file
6
static/vendor/bootstrap/darkmode.min.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap-Dark-5 v1.1.3 (https://vinorodrigues.github.io/bootstrap-dark-5/)
|
||||||
|
* Copyright 2021 Vino Rodrigues
|
||||||
|
* Licensed under MIT (https://github.com/vinorodrigues/bootstrap-dark-5/blob/main/LICENSE.md)
|
||||||
|
*/
|
||||||
|
"use strict";class DarkMode{constructor(){this._hasGDPRConsent=!1,this.cookieExpiry=365,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",(function(){DarkMode.onDOMContentLoaded()})):DarkMode.onDOMContentLoaded()}get inDarkMode(){return DarkMode.getColorScheme()==DarkMode.VALUE_DARK}set inDarkMode(e){this.setDarkMode(e,!1)}get hasGDPRConsent(){return this._hasGDPRConsent}set hasGDPRConsent(e){if(this._hasGDPRConsent=e,e){const e=DarkMode.readCookie(DarkMode.DATA_KEY);e&&(DarkMode.saveCookie(DarkMode.DATA_KEY,"",-1),localStorage.setItem(DarkMode.DATA_KEY,e))}else{const e=localStorage.getItem(DarkMode.DATA_KEY);e&&(localStorage.removeItem(DarkMode.DATA_KEY),DarkMode.saveCookie(DarkMode.DATA_KEY,e))}}get documentRoot(){return document.getElementsByTagName("html")[0]}static saveCookie(e,o="",t=365){let a="";if(t){const e=new Date;e.setTime(e.getTime()+24*t*60*60*1e3),a="; expires="+e.toUTCString()}document.cookie=e+"="+o+a+"; SameSite=Strict; path=/"}saveValue(e,o,t=this.cookieExpiry){this.hasGDPRConsent?DarkMode.saveCookie(e,o,t):localStorage.setItem(e,o)}static readCookie(e){const o=e+"=",t=document.cookie.split(";");for(let e=0;e<t.length;e++){const a=t[e].trim();if(a.startsWith(o))return a.substring(o.length)}return""}readValue(e){if(this.hasGDPRConsent)return DarkMode.readCookie(e);{const o=localStorage.getItem(e);return o||""}}eraseValue(e){this.hasGDPRConsent?this.saveValue(e,"",-1):localStorage.removeItem(e)}getSavedColorScheme(){const e=this.readValue(DarkMode.DATA_KEY);return e||""}getPreferedColorScheme(){return window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?DarkMode.VALUE_DARK:window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches?DarkMode.VALUE_LIGHT:""}setDarkMode(e,o=!0){const t=document.querySelectorAll("[data-"+DarkMode.DATA_SELECTOR+"]");if(0==t.length)e?(this.documentRoot.classList.remove(DarkMode.CLASS_NAME_LIGHT),this.documentRoot.classList.add(DarkMode.CLASS_NAME_DARK)):(this.documentRoot.classList.remove(DarkMode.CLASS_NAME_DARK),this.documentRoot.classList.add(DarkMode.CLASS_NAME_LIGHT));else for(let o=0;o<t.length;o++)t[o].setAttribute("data-"+DarkMode.DATA_SELECTOR,e?DarkMode.VALUE_DARK:DarkMode.VALUE_LIGHT);o&&this.saveValue(DarkMode.DATA_KEY,e?DarkMode.VALUE_DARK:DarkMode.VALUE_LIGHT)}toggleDarkMode(e=!0){let o;const t=document.querySelector("[data-"+DarkMode.DATA_SELECTOR+"]");o=t?t.getAttribute("data-"+DarkMode.DATA_SELECTOR)==DarkMode.VALUE_DARK:this.documentRoot.classList.contains(DarkMode.CLASS_NAME_DARK),this.setDarkMode(!o,e)}resetDarkMode(){this.eraseValue(DarkMode.DATA_KEY);const e=this.getPreferedColorScheme();if(e)this.setDarkMode(e==DarkMode.VALUE_DARK,!1);else{const e=document.querySelectorAll("[data-"+DarkMode.DATA_SELECTOR+"]");if(0==e.length)this.documentRoot.classList.remove(DarkMode.CLASS_NAME_LIGHT),this.documentRoot.classList.remove(DarkMode.CLASS_NAME_DARK);else for(let o=0;o<e.length;o++)e[o].setAttribute("data-"+DarkMode.DATA_SELECTOR,"")}}static getColorScheme(){const e=document.querySelector("[data-"+DarkMode.DATA_SELECTOR+"]");if(e){const o=e.getAttribute("data-"+DarkMode.DATA_SELECTOR);return o==DarkMode.VALUE_DARK||o==DarkMode.VALUE_LIGHT?o:""}return darkmode.documentRoot.classList.contains(DarkMode.CLASS_NAME_DARK)?DarkMode.VALUE_DARK:darkmode.documentRoot.classList.contains(DarkMode.CLASS_NAME_LIGHT)?DarkMode.VALUE_LIGHT:""}static updatePreferedColorSchemeEvent(){let e=darkmode.getSavedColorScheme();e||(e=darkmode.getPreferedColorScheme(),e&&darkmode.setDarkMode(e==DarkMode.VALUE_DARK,!1))}static onDOMContentLoaded(){let e=darkmode.readValue(DarkMode.DATA_KEY);e||(e=DarkMode.getColorScheme(),e||(e=darkmode.getPreferedColorScheme()));const o=e==DarkMode.VALUE_DARK;darkmode.setDarkMode(o,!1),window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",(function(){DarkMode.updatePreferedColorSchemeEvent()}))}}DarkMode.DATA_KEY="bs.prefers-color-scheme",DarkMode.DATA_SELECTOR="bs-color-scheme",DarkMode.VALUE_LIGHT="light",DarkMode.VALUE_DARK="dark",DarkMode.CLASS_NAME_LIGHT="light",DarkMode.CLASS_NAME_DARK="dark";const darkmode=new DarkMode;
|
||||||
1173
static/vendor/hljs/highlight.min.js
vendored
Normal file
1173
static/vendor/hljs/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
98
static/vendor/hljs/theme.css
vendored
Normal file
98
static/vendor/hljs/theme.css
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*!
|
||||||
|
Theme: a11y-dark
|
||||||
|
Author: @ericwbailey
|
||||||
|
Maintainer: @ericwbailey
|
||||||
|
|
||||||
|
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
|
||||||
|
*/
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
background: #2b2b2b;
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comment */
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #d4d0ab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Red */
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-tag,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-deletion {
|
||||||
|
color: #ffa07a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Orange */
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-params,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-link {
|
||||||
|
color: #f5ab35;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Yellow */
|
||||||
|
.hljs-attribute {
|
||||||
|
color: #ffd700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Green */
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-addition {
|
||||||
|
color: #abe338;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blue */
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-section {
|
||||||
|
color: #00e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Purple */
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
color: #dcc6e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (-ms-high-contrast: active) {
|
||||||
|
.hljs-addition,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-link,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-params,
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-quote {
|
||||||
|
color: highlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
static/vendor/micromodal/micromodal.min.js
vendored
Normal file
1
static/vendor/micromodal/micromodal.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/vendor/qrcodejs/qrcode.min.js
vendored
Normal file
1
static/vendor/qrcodejs/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
{{ define "inc/footer.html"}}
|
{{ define "inc/footer.html"}}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/js/darkmode.min.js"></script>
|
<script src="/static/vendor/bootstrap/darkmode.min.js"></script>
|
||||||
<script src="/static/js/main.js"></script>
|
<script src="/static/js/main.js"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-nightshade.min.css" rel="stylesheet">
|
<link href="/static/vendor/bootstrap/bootstrap-nightshade.min.css" rel="stylesheet">
|
||||||
<!-- Love https://vinorodrigues.github.io/bootstrap-dark-5/ -->
|
<!-- Love https://vinorodrigues.github.io/bootstrap-dark-5/ -->
|
||||||
<link rel="stylesheet" href="/static/css/main.css">
|
<link rel="stylesheet" href="/static/css/main.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
42
templates/pages/admin_login.html
Normal file
42
templates/pages/admin_login.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{{ template "inc/header.html" .}}
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<main id="main-card" class="container rounded mt-5 shadow-sm">
|
||||||
|
<div class="p-3">
|
||||||
|
|
||||||
|
<a href="/" class="logo-container w-100 d-flex mb-4">
|
||||||
|
<img src="/static/img/logo_transparent.png" alt="Logo" style="max-width: 50%; margin: 0 auto;" class="mx-auto">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="form-group my-4">
|
||||||
|
<form class="search-action input-group" method="post" action="/admin/login">
|
||||||
|
<input autocomplete="off" type="password" class="form-control form-control-lg" name="admin-token" placeholder="Your Admin token" aria-label="Your Admin token" aria-describedby="admin-token-button" id="admin-token">
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="submit" id="admin-token-button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24 " height="24 " fill="currentColor" class="bi bi-box-arrow-in-right" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M6 3.5a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 0-1 0v2A1.5 1.5 0 0 0 6.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-8A1.5 1.5 0 0 0 5 3.5v2a.5.5 0 0 0 1 0v-2z"/>
|
||||||
|
<path fill-rule="evenodd" d="M11.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H1.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
<small class="text-muted">Access the admin interface for FreePad, this can only be done through the Admin Token.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="text-muted py-5 border-top text-center">
|
||||||
|
<p class="mb-1">
|
||||||
|
FreePad by <a href="https://justkato.me/">©Kato Twofold</a>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
FreePad is freely available over on our <a href="https://github.com/JustKato/FreePad">GitHub</a>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{ template "inc/theme-toggle.html" .}}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
{{ template "inc/footer.html" .}}
|
||||||
94
templates/pages/admin_view.html
Normal file
94
templates/pages/admin_view.html
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
{{ template "inc/header.html" .}}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.pad-instance {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pad-list {
|
||||||
|
max-height: 30rem;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-name {
|
||||||
|
max-width: 30%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<main id="main-card" class="container rounded mt-5 shadow-sm">
|
||||||
|
<div class="p-3">
|
||||||
|
|
||||||
|
<a href="/" class="logo-container w-100 d-flex mb-4">
|
||||||
|
<img src="/static/img/logo_transparent.png" alt="Logo" style="max-width: 50%; margin: 0 auto;" class="mx-auto">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="form-group my-4 border-top p-3 border">
|
||||||
|
|
||||||
|
<div class="pad-instance my-2 border-bottom">
|
||||||
|
<div class="pad-name col-5">
|
||||||
|
Pad Name
|
||||||
|
</div>
|
||||||
|
<div class="pad-last-view col-1">
|
||||||
|
Views
|
||||||
|
</div>
|
||||||
|
<div class="pad-last-modified col-4">
|
||||||
|
Create Date
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
Actions
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="pad-list" >
|
||||||
|
{{ range $indx, $element := .padList }}
|
||||||
|
|
||||||
|
<div class="pad-instance my-2">
|
||||||
|
<div class="pad-name col-5">
|
||||||
|
<a href="/{{ $element.Name }}">
|
||||||
|
{{ $element.Name }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="pad-last-view col-1">
|
||||||
|
{{ $element.Views }}
|
||||||
|
</div>
|
||||||
|
<div class="pad-last-modified col-4">
|
||||||
|
{{ $element.LastModified }}
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<div onclick="doDelete({{ $element.Name }})" class="btn btn-danger">
|
||||||
|
Delete
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{ template "inc/theme-toggle.html" .}}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function doDelete(id) {
|
||||||
|
// Confirm deletion
|
||||||
|
if ( confirm("Confirm pad deletion?") ) {
|
||||||
|
// Do delete
|
||||||
|
window.location.href = `/admin/delete/${id}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ template "inc/footer.html" .}}
|
||||||
@@ -46,6 +46,22 @@
|
|||||||
just write it in the box above and get to the right page, write anything in
|
just write it in the box above and get to the right page, write anything in
|
||||||
and access the same address on any other device to get your info!
|
and access the same address on any other device to get your info!
|
||||||
</p>
|
</p>
|
||||||
|
<small>A couple hints:</small>
|
||||||
|
<p>
|
||||||
|
Pads take into consideration file extensions, use <code>.json</code>,
|
||||||
|
<code>.js</code>, <code>.cpp</code>, <code>.txt</code>, etc... to help
|
||||||
|
parse your type of file
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The archival feature helps you store information on your local machine! Save your
|
||||||
|
pads and you can always come back and rewrite them exactly as they have been
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
All pads can be publicly edited, so if you choose some common name
|
||||||
|
and someone elses accesses the link they can completely remove/edit
|
||||||
|
what you wrote, not to mention seein that information, so refrain
|
||||||
|
from sharing important data here.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{{ template "inc/header.html" .}}
|
{{ template "inc/header.html" .}}
|
||||||
|
|
||||||
<style>
|
<link rel="stylesheet" href="/static/css/qr-style.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
#pad-content {
|
#pad-content {
|
||||||
height: 16rem;
|
height: 16rem;
|
||||||
}
|
}
|
||||||
@@ -13,35 +14,59 @@
|
|||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var maximumPadSize = Number({{.maximumPadSize}});
|
var maximumPadSize = Number({{.maximumPadSize }});
|
||||||
var padTitle = {{.title}};
|
var padTitle = {{.title }};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/static/vendor/hljs/theme.css">
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<main id="main-card" class="container rounded mt-5 shadow-sm">
|
<main id="main-card" class="container rounded mt-5 shadow-sm">
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
|
|
||||||
<a href="/" class="logo-container w-100 d-flex mb-4">
|
<a href="/" class="logo-container w-100 d-flex mb-4">
|
||||||
<img src="/static/img/logo_transparent.png" alt="Logo" style="max-width: 50%; margin: 0 auto;" class="mx-auto">
|
<img src="/static/img/logo_transparent.png" alt="Logo" style="max-width: 50%; margin: 0 auto;"
|
||||||
|
class="mx-auto">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="mb-4">{{.title}}</h2>
|
<h2 class="mb-4">{{.title}}</h2>
|
||||||
|
|
||||||
<textarea maxlength="{{.maximumPadSize}}" name="pad-content" id="pad-content" onchange="sendMyData(this)" onkeydown="updateStatus(`Not Saved`, `text-warning`); toggleWritingWatch(this)" class="form-control">{{.post_content}}</textarea>
|
<div id="pad-content-area">
|
||||||
|
<div class="btn-sm btn" id="pad-content-toggler" onclick="toggleTextareaPreview()">
|
||||||
|
<span class="edit-content-text" title="Edit Content">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">
|
||||||
|
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="view-content-text" title="ReadOnly">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eyeglasses" viewBox="0 0 16 16" style="margin-top: 1rem;">
|
||||||
|
<path d="M4 6a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm2.625.547a3 3 0 0 0-5.584.953H.5a.5.5 0 0 0 0 1h.541A3 3 0 0 0 7 8a1 1 0 0 1 2 0 3 3 0 0 0 5.959.5h.541a.5.5 0 0 0 0-1h-.541a3 3 0 0 0-5.584-.953A1.993 1.993 0 0 0 8 6c-.532 0-1.016.208-1.375.547zM14 8a2 2 0 1 1-4 0 2 2 0 0 1 4 0z"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre><code id="textarea-preview" class="form-control hidden">{{.post_content}}</code></pre>
|
||||||
|
|
||||||
|
<textarea maxlength="{{.maximumPadSize}}" name="pad-content" id="pad-content" onchange="sendMyData(this)"
|
||||||
|
onkeydown="updateStatus(`Not Saved`, `text-warning`); toggleWritingWatch(this)"
|
||||||
|
class="form-control hidden">{{.post_content}}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="pad-status" class="my-4 row">
|
<div id="pad-status" class="my-4 row">
|
||||||
<div class="col-md-12 col-lg-4 col-xl-4" title="Status">
|
<div class="col-md-12 col-lg-4 col-xl-4" title="Status">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-reception-3" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
<path d="M0 11.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2zm4-3a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-5zm4-3a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-8zm4 8a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/>
|
class="bi bi-reception-3" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M0 11.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2zm4-3a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-5zm4-3a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-8zm4 8a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<input type="text" class="form-control" readonly value="Loaded" id="loading_status">
|
<input type="text" class="form-control" readonly value="Loaded" id="loading_status">
|
||||||
@@ -51,20 +76,27 @@
|
|||||||
<div class="col-md-12 col-lg-4 col-xl-4 mt-4 mt-lg-0 mt-xl-0" title="Current Viewers">
|
<div class="col-md-12 col-lg-4 col-xl-4 mt-4 mt-lg-0 mt-xl-0" title="Current Viewers">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"></path>
|
class="bi bi-eye" viewBox="0 0 16 16">
|
||||||
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"></path>
|
<path
|
||||||
|
d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z">
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z">
|
||||||
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<input type="text" class="form-control" readonly value="1">
|
<input type="text" class="form-control" readonly value="{{.views}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-12 col-lg-4 col-xl-4 mt-4 mt-lg-0 mt-xl-0" title="Last Modified">
|
<div class="col-md-12 col-lg-4 col-xl-4 mt-4 mt-lg-0 mt-xl-0" title="Last Modified">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-hourglass-split" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
<path d="M2.5 15a.5.5 0 1 1 0-1h1v-1a4.5 4.5 0 0 1 2.557-4.06c.29-.139.443-.377.443-.59v-.7c0-.213-.154-.451-.443-.59A4.5 4.5 0 0 1 3.5 3V2h-1a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-1v1a4.5 4.5 0 0 1-2.557 4.06c-.29.139-.443.377-.443.59v.7c0 .213.154.451.443.59A4.5 4.5 0 0 1 12.5 13v1h1a.5.5 0 0 1 0 1h-11zm2-13v1c0 .537.12 1.045.337 1.5h6.326c.216-.455.337-.963.337-1.5V2h-7zm3 6.35c0 .701-.478 1.236-1.011 1.492A3.5 3.5 0 0 0 4.5 13s.866-1.299 3-1.48V8.35zm1 0v3.17c2.134.181 3 1.48 3 1.48a3.5 3.5 0 0 0-1.989-3.158C8.978 9.586 8.5 9.052 8.5 8.351z"/>
|
class="bi bi-hourglass-split" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M2.5 15a.5.5 0 1 1 0-1h1v-1a4.5 4.5 0 0 1 2.557-4.06c.29-.139.443-.377.443-.59v-.7c0-.213-.154-.451-.443-.59A4.5 4.5 0 0 1 3.5 3V2h-1a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-1v1a4.5 4.5 0 0 1-2.557 4.06c-.29.139-.443.377-.443.59v.7c0 .213.154.451.443.59A4.5 4.5 0 0 1 12.5 13v1h1a.5.5 0 0 1 0 1h-11zm2-13v1c0 .537.12 1.045.337 1.5h6.326c.216-.455.337-.963.337-1.5V2h-7zm3 6.35c0 .701-.478 1.236-1.011 1.492A3.5 3.5 0 0 0 4.5 13s.866-1.299 3-1.48V8.35zm1 0v3.17c2.134.181 3 1.48 3 1.48a3.5 3.5 0 0 0-1.989-3.158C8.978 9.586 8.5 9.052 8.5 8.351z" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<input type="text" class="form-control" id="last_modified_" readonly value="{{.last_modified}}">
|
<input type="text" class="form-control" id="last_modified_" readonly value="{{.last_modified}}">
|
||||||
@@ -75,44 +107,63 @@
|
|||||||
|
|
||||||
<div id="pad-options" class="row">
|
<div id="pad-options" class="row">
|
||||||
<div class="col-md-12 col-lg-4 col-xl-4">
|
<div class="col-md-12 col-lg-4 col-xl-4">
|
||||||
<button type="button" class="btn btn-secondary btn-md w-100" title="Refresh the contents of the pad" onclick="window.location.reload()">
|
<button type="button" class="btn btn-secondary btn-md w-100" title="Generate a quick QR code of the current page to easily send to other devices, such as your phone." onclick="generateQRCode()">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"></path>
|
class="bi bi-qr-code" viewBox="0 0 16 16">
|
||||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"></path>
|
<path d="M2 2h2v2H2V2Z" />
|
||||||
|
<path d="M6 0v6H0V0h6ZM5 1H1v4h4V1ZM4 12H2v2h2v-2Z" />
|
||||||
|
<path d="M6 10v6H0v-6h6Zm-5 1v4h4v-4H1Zm11-9h2v2h-2V2Z" />
|
||||||
|
<path
|
||||||
|
d="M10 0v6h6V0h-6Zm5 1v4h-4V1h4ZM8 1V0h1v2H8v2H7V1h1Zm0 5V4h1v2H8ZM6 8V7h1V6h1v2h1V7h5v1h-4v1H7V8H6Zm0 0v1H2V8H1v1H0V7h3v1h3Zm10 1h-1V7h1v2Zm-1 0h-1v2h2v-1h-1V9Zm-4 0h2v1h-1v1h-1V9Zm2 3v-1h-1v1h-1v1H9v1h3v-2h1Zm0 0h3v1h-2v1h-1v-2Zm-4-1v1h1v-2H7v1h2Z" />
|
||||||
|
<path d="M7 12h1v3h4v1H7v-4Zm9 2v2h-3v-1h2v-1h1Z" />
|
||||||
</svg>
|
</svg>
|
||||||
Refresh Pad
|
Get QR
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12 col-lg-4 col-xl-4 mt-4 mt-lg-0 mt-xl-0">
|
<div class="col-md-12 col-lg-4 col-xl-4 mt-4 mt-lg-0 mt-xl-0">
|
||||||
<button type="button" class="btn btn-secondary btn-md w-100" title="Download the contents into a text file" onclick="window.pad.downloadPadContents();">
|
<button type="button" class="btn btn-secondary btn-md w-100"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cloud-download" viewBox="0 0 16 16">
|
title="Download the contents into a text file" onclick="window.pad.downloadPadContents();">
|
||||||
<path d="M4.406 1.342A5.53 5.53 0 0 1 8 0c2.69 0 4.923 2 5.166 4.579C14.758 4.804 16 6.137 16 7.773 16 9.569 14.502 11 12.687 11H10a.5.5 0 0 1 0-1h2.688C13.979 10 15 8.988 15 7.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 2.825 10.328 1 8 1a4.53 4.53 0 0 0-2.941 1.1c-.757.652-1.153 1.438-1.153 2.055v.448l-.445.049C2.064 4.805 1 5.952 1 7.318 1 8.785 2.23 10 3.781 10H6a.5.5 0 0 1 0 1H3.781C1.708 11 0 9.366 0 7.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383z"></path>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
<path d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708l3 3z"></path>
|
class="bi bi-cloud-download" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M4.406 1.342A5.53 5.53 0 0 1 8 0c2.69 0 4.923 2 5.166 4.579C14.758 4.804 16 6.137 16 7.773 16 9.569 14.502 11 12.687 11H10a.5.5 0 0 1 0-1h2.688C13.979 10 15 8.988 15 7.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 2.825 10.328 1 8 1a4.53 4.53 0 0 0-2.941 1.1c-.757.652-1.153 1.438-1.153 2.055v.448l-.445.049C2.064 4.805 1 5.952 1 7.318 1 8.785 2.23 10 3.781 10H6a.5.5 0 0 1 0 1H3.781C1.708 11 0 9.366 0 7.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383z">
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708l3 3z">
|
||||||
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
Download Pad
|
Download Pad
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12 col-lg-4 col-xl-4 mt-4 mt-lg-0 mt-xl-0" title="Archive the current state of the pad">
|
<div class="col-md-12 col-lg-4 col-xl-4 mt-4 mt-lg-0 mt-xl-0" title="Archive the current state of the pad">
|
||||||
<div class="btn-group w-100" role="group">
|
<div class="btn-group w-100" role="group">
|
||||||
<button type="button" class="btn btn-secondary btn-md w-100 dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<button type="button" class="btn btn-secondary btn-md w-100 dropdown-toggle"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16">
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
|
class="bi bi-archive" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z" />
|
||||||
</svg>
|
</svg>
|
||||||
Archive Pad
|
Archive Pad
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu w-100" id="archives-selection">
|
<ul class="dropdown-menu w-100" id="archives-selection">
|
||||||
<li class="dropdown-item" onclick="saveLocalArchive()" id="do-archive-button">
|
<li class="dropdown-item" onclick="saveLocalArchive()" id="do-archive-button">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
<path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/>
|
class="bi bi-archive" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="archive-date">
|
<span class="archive-date">
|
||||||
Archive Current
|
Archive Current
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item archive-selection" id="archive-selection-example">
|
<li class="dropdown-item archive-selection" id="archive-selection-example">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-text" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/>
|
class="bi bi-file-earmark-text" viewBox="0 0 16 16">
|
||||||
<path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
<path
|
||||||
|
d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z" />
|
||||||
|
<path
|
||||||
|
d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="archive-date">
|
<span class="archive-date">
|
||||||
DATE
|
DATE
|
||||||
@@ -134,17 +185,46 @@
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- START::QRCODE_MODAL -->
|
||||||
|
<div class="modal micromodal-slide" id="qrmodal" aria-hidden="true">
|
||||||
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
|
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="qrmodal-title">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title" id="qrmodal-title">
|
||||||
|
QRCode
|
||||||
|
</h2>
|
||||||
|
<button class="modal__close" aria-label="Close modal" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<main class="modal__content" id="qrmodal-content">
|
||||||
|
<div id="qrcode"></div>
|
||||||
|
</main>
|
||||||
|
<footer class="modal__footer">
|
||||||
|
<button class="modal__btn" data-micromodal-close aria-label="Close this dialog window">Close</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END::QRCODE_MODAL -->
|
||||||
|
|
||||||
{{ template "inc/theme-toggle.html" .}}
|
{{ template "inc/theme-toggle.html" .}}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script src="/static/js/fileSaver.js"></script>
|
<script src="/static/js/fileSaver.js"></script>
|
||||||
<script src="/static/js/pad.js"></script>
|
<script src="/static/js/pad.js"></script>
|
||||||
<script src="/static/js/pad-scripts.js"></script>
|
<script src="/static/js/pad-scripts.js"></script>
|
||||||
|
<script src="/static/vendor/hljs/highlight.min.js"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
<script src="/static/vendor/bootstrap/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/static/vendor/qrcodejs/qrcode.min.js"></script>
|
||||||
|
<script src="/static/vendor/micromodal/micromodal.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.pad = new Pad({{.title}}, {{.last_modified}});
|
window.pad = new Pad({{.title }}, {{.last_modified }});
|
||||||
|
|
||||||
|
document.addEventListener(`DOMContentLoaded`, e => {
|
||||||
|
// Initialize the micromodal library
|
||||||
|
MicroModal.init();
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user