mirror of
https://github.com/JustKato/FreePad.git
synced 2026-03-13 07:49:46 +02:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aea10baffd | |||
| 42eb5add65 | |||
| 53066025f0 | |||
| 23dd69e060 | |||
| 0bc4942924 | |||
| 23fa17b840 | |||
| 5414dab201 | |||
| 8735837cb4 | |||
| 7552cd7cd3 | |||
|
|
74af4d1554 | ||
| 916bcae961 | |||
| 53b35745cd | |||
| 725bc4222b |
@@ -18,3 +18,7 @@ DEV_MODE=0
|
||||
|
||||
# Maximum file storage age in minutes, set to -1 to disable
|
||||
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
|
||||
27
Changelog.md
Normal file
27
Changelog.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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"
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM alpine
|
||||
|
||||
LABEL version="1.0.0"
|
||||
LABEL version="1.3.0"
|
||||
|
||||
# Copy the distribution files
|
||||
COPY ./dist /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://ko-fi.com/justkato)
|
||||

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

|
||||
|
||||
# **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 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 "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/templates 2> /dev/null || true
|
||||
rm dist/.env 2> /dev/null || true
|
||||
@@ -10,6 +10,10 @@ rm dist/.env 2> /dev/null || true
|
||||
# Build
|
||||
echo "Building executable"
|
||||
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"
|
||||
cp -r ./templates ./dist/templates
|
||||
|
||||
1
go.mod
1
go.mod
@@ -6,4 +6,5 @@ require (
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/joho/godotenv v1.4.0
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
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/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
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.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/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/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/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/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
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/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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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.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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
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-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-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-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-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-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.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-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/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.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.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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
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.3.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
|
||||
}
|
||||
|
||||
fmt.Println("[Task::Cleanup]: Task registered")
|
||||
for range time.Tick(time.Minute * 5) {
|
||||
objects.CleanupPosts(cleanupInterval)
|
||||
}
|
||||
// Run all handlers
|
||||
go cleanupHandler(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
|
||||
}
|
||||
|
||||
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 {
|
||||
// Lookup if the maximum pad size variable exists.
|
||||
maxPadSize, exists := os.LookupEnv("MAXIMUM_PAD_SIZE")
|
||||
|
||||
@@ -1,21 +1,157 @@
|
||||
package objects
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
Name string `json:"name"`
|
||||
LastModified string `json:"last_modified"`
|
||||
Content string `json:"content"`
|
||||
Views uint32 `json:"views"`
|
||||
}
|
||||
|
||||
// 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(""), 0777)
|
||||
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) 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
|
||||
}
|
||||
|
||||
// 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, 0777)
|
||||
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 {
|
||||
|
||||
baseStoragePath, exists := os.LookupEnv("PAD_STORAGE_PATH")
|
||||
@@ -38,6 +174,7 @@ func getStorageDirectory() string {
|
||||
return baseStoragePath
|
||||
}
|
||||
|
||||
// Get a post from the file system
|
||||
func GetPost(fileName string) Post {
|
||||
// Get the base storage directory and make sure it exists
|
||||
storageDir := getStorageDirectory()
|
||||
@@ -45,9 +182,13 @@ func GetPost(fileName string) Post {
|
||||
// Generate the file path
|
||||
filePath := fmt.Sprintf("%s%s", storageDir, fileName)
|
||||
|
||||
// Get the post views and add 1 to them
|
||||
postViews := AddViewToPost(fileName)
|
||||
|
||||
p := Post{
|
||||
Name: fileName,
|
||||
Content: "",
|
||||
Views: postViews,
|
||||
LastModified: "Never Before",
|
||||
}
|
||||
|
||||
@@ -74,6 +215,7 @@ func GetPost(fileName string) Post {
|
||||
return p
|
||||
}
|
||||
|
||||
// Write a post to the file system
|
||||
func WritePost(p Post) error {
|
||||
|
||||
maximumPadSize := helper.GetMaximumPadSize()
|
||||
@@ -91,6 +233,8 @@ func WritePost(p Post) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Actually close the file
|
||||
defer f.Close()
|
||||
|
||||
// Write the contnets
|
||||
_, err = f.WriteString(p.Content)
|
||||
@@ -105,6 +249,7 @@ func WritePost(p Post) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup all of the older posts based on the environment settings
|
||||
func CleanupPosts(age int) {
|
||||
// Initialize the files buffer
|
||||
var files []string
|
||||
@@ -155,5 +300,4 @@ func CleanupPosts(age int) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
@@ -23,6 +24,13 @@ func HomeRoutes(router *gin.Engine) {
|
||||
// Get the post we are looking for.
|
||||
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
|
||||
maximumPadSize := helper.GetMaximumPadSize()
|
||||
|
||||
@@ -40,6 +48,7 @@ func HomeRoutes(router *gin.Engine) {
|
||||
"post_content": post.Content,
|
||||
"maximumPadSize": maximumPadSize,
|
||||
"last_modified": post.LastModified,
|
||||
"views": post.Views,
|
||||
"domain_base": helper.GetDomainBase(),
|
||||
})
|
||||
})
|
||||
@@ -59,6 +68,7 @@ func HomeRoutes(router *gin.Engine) {
|
||||
p := objects.Post{
|
||||
Name: postName,
|
||||
Content: postContent,
|
||||
Views: 0, // This can just be ignored
|
||||
LastModified: time.Now().Format("02/01/2006 03:04:05 PM"),
|
||||
}
|
||||
|
||||
|
||||
15
main.go
15
main.go
@@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/JustKato/FreePad/lib/controllers"
|
||||
"github.com/JustKato/FreePad/lib/objects"
|
||||
"github.com/JustKato/FreePad/lib/routes"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/joho/godotenv"
|
||||
@@ -22,15 +24,28 @@ func main() {
|
||||
// Run the 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
|
||||
router := gin.Default()
|
||||
|
||||
// Apply the FreePad Headers
|
||||
controllers.ApplyHeaders(router)
|
||||
|
||||
// Read HTML Templates
|
||||
router.LoadHTMLGlob("templates/**/*.html")
|
||||
|
||||
// Load in static path
|
||||
router.Static("/static", "static/")
|
||||
|
||||
// Implement the rate limiter
|
||||
controllers.DoRateLimit(router)
|
||||
|
||||
// Add Routes
|
||||
routes.HomeRoutes(router)
|
||||
|
||||
|
||||
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 |
@@ -166,6 +166,24 @@ function saveLocalArchive() {
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
// Open the modal
|
||||
MicroModal.show(`qrmodal`)
|
||||
}
|
||||
|
||||
document.addEventListener(`DOMContentLoaded`, e => {
|
||||
|
||||
{ // Textarea Focusing
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{{ template "inc/header.html" .}}
|
||||
|
||||
<style>
|
||||
<link rel="stylesheet" href="/static/css/qr-style.css">
|
||||
|
||||
<style>
|
||||
#pad-content {
|
||||
height: 16rem;
|
||||
}
|
||||
@@ -13,7 +14,6 @@
|
||||
.dropdown-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -27,21 +27,26 @@
|
||||
<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">
|
||||
<img src="/static/img/logo_transparent.png" alt="Logo" style="max-width: 50%; margin: 0 auto;"
|
||||
class="mx-auto">
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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-status" class="my-4 row">
|
||||
<div class="col-md-12 col-lg-4 col-xl-4" title="Status">
|
||||
<div class="input-group">
|
||||
<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">
|
||||
<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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
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>
|
||||
</span>
|
||||
<input type="text" class="form-control" readonly value="Loaded" id="loading_status">
|
||||
@@ -51,20 +56,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="input-group">
|
||||
<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">
|
||||
<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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
class="bi bi-eye" viewBox="0 0 16 16">
|
||||
<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>
|
||||
</span>
|
||||
<input type="text" class="form-control" readonly value="1">
|
||||
<input type="text" class="form-control" readonly value="{{.views}}">
|
||||
</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="input-group">
|
||||
<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">
|
||||
<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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
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>
|
||||
</span>
|
||||
<input type="text" class="form-control" id="last_modified_" readonly value="{{.last_modified}}">
|
||||
@@ -75,44 +87,63 @@
|
||||
|
||||
<div id="pad-options" class="row">
|
||||
<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()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||
<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>
|
||||
<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>
|
||||
<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-qr-code" viewBox="0 0 16 16">
|
||||
<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>
|
||||
Refresh Pad
|
||||
Get QR
|
||||
</button>
|
||||
</div>
|
||||
<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();">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" 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>
|
||||
<button type="button" class="btn btn-secondary btn-md w-100"
|
||||
title="Download the contents into a text file" onclick="window.pad.downloadPadContents();">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
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>
|
||||
Download Pad
|
||||
</button>
|
||||
</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="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">
|
||||
<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"/>
|
||||
<button type="button" class="btn btn-secondary btn-md w-100 dropdown-toggle"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<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>
|
||||
Archive Pad
|
||||
</button>
|
||||
<ul class="dropdown-menu w-100" id="archives-selection">
|
||||
<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">
|
||||
<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>
|
||||
<span class="archive-date">
|
||||
Archive Current
|
||||
</span>
|
||||
</li>
|
||||
<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">
|
||||
<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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
class="bi bi-file-earmark-text" viewBox="0 0 16 16">
|
||||
<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>
|
||||
<span class="archive-date">
|
||||
DATE
|
||||
@@ -134,6 +165,27 @@
|
||||
|
||||
</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" .}}
|
||||
</body>
|
||||
|
||||
@@ -142,10 +194,17 @@
|
||||
<script src="/static/js/pad-scripts.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="https://cdn.rawgit.com/davidshimjs/qrcodejs/gh-pages/qrcode.min.js"></script>
|
||||
<script src="https://unpkg.com/micromodal/dist/micromodal.min.js"></script>
|
||||
|
||||
<script>
|
||||
window.pad = new Pad({{.title }}, {{.last_modified }});
|
||||
|
||||
document.addEventListener(`DOMContentLoaded`, e => {
|
||||
// Initialize the micromodal library
|
||||
MicroModal.init();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user